Multi-level logging (#185)
Closes #93 Summary of changes: - Adds the [uber-go-/zap](https://github.com/uber-go/zap) library for leveled logging (69040996e0bcbd2f211d0d8d576147027e928e3b) - A new CLI arg `--log-level` which can take the values `debug` `info` `warn` `error` `fatal` `panic`. The default is `info`, and the existing `--quiet` flag is treated as `--log-level warn` - All `helm.exec` calls are preceded by a one-line summary. The current information on the full helm command line arguments `exec: helm exec ... ` is only output at `debug` level. This means sensitive command line arguments which may include passwords and `--set` arguments should be hidden by default.
This commit is contained in:
		
							parent
							
								
									35732f3a93
								
							
						
					
					
						commit
						06a1d245ae
					
				|  | @ -42,6 +42,31 @@ | ||||||
|   packages = ["."] |   packages = ["."] | ||||||
|   revision = "6011f165dc288c72abd8acd7722f837c5c64198d" |   revision = "6011f165dc288c72abd8acd7722f837c5c64198d" | ||||||
| 
 | 
 | ||||||
|  | [[projects]] | ||||||
|  |   name = "go.uber.org/atomic" | ||||||
|  |   packages = ["."] | ||||||
|  |   revision = "1ea20fb1cbb1cc08cbd0d913a96dead89aa18289" | ||||||
|  |   version = "v1.3.2" | ||||||
|  | 
 | ||||||
|  | [[projects]] | ||||||
|  |   name = "go.uber.org/multierr" | ||||||
|  |   packages = ["."] | ||||||
|  |   revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a" | ||||||
|  |   version = "v1.1.0" | ||||||
|  | 
 | ||||||
|  | [[projects]] | ||||||
|  |   name = "go.uber.org/zap" | ||||||
|  |   packages = [ | ||||||
|  |     ".", | ||||||
|  |     "buffer", | ||||||
|  |     "internal/bufferpool", | ||||||
|  |     "internal/color", | ||||||
|  |     "internal/exit", | ||||||
|  |     "zapcore" | ||||||
|  |   ] | ||||||
|  |   revision = "eeedf312bc6c57391d84767a4cd413f02a917974" | ||||||
|  |   version = "v1.8.0" | ||||||
|  | 
 | ||||||
| [[projects]] | [[projects]] | ||||||
|   branch = "master" |   branch = "master" | ||||||
|   name = "golang.org/x/crypto" |   name = "golang.org/x/crypto" | ||||||
|  | @ -60,6 +85,6 @@ | ||||||
| [solve-meta] | [solve-meta] | ||||||
|   analyzer-name = "dep" |   analyzer-name = "dep" | ||||||
|   analyzer-version = 1 |   analyzer-version = 1 | ||||||
|   inputs-digest = "2f414b2f5156dc8144a90cdbbb9685d5b8af323024f986bb52e66601f02f3c0f" |   inputs-digest = "57e868f6ae57c81a07ee682742f3b71bf5c7956311a3bb8ea76459677fc104c7" | ||||||
|   solver-name = "gps-cdcl" |   solver-name = "gps-cdcl" | ||||||
|   solver-version = 1 |   solver-version = 1 | ||||||
|  |  | ||||||
|  | @ -1,9 +1,11 @@ | ||||||
| package helmexec | package helmexec | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" |  | ||||||
| 	"io" | 	"io" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap" | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
|  | @ -13,16 +15,33 @@ const ( | ||||||
| type execer struct { | type execer struct { | ||||||
| 	helmBinary  string | 	helmBinary  string | ||||||
| 	runner      Runner | 	runner      Runner | ||||||
| 	writer      io.Writer | 	logger      *zap.SugaredLogger | ||||||
| 	kubeContext string | 	kubeContext string | ||||||
| 	extra       []string | 	extra       []string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger { | ||||||
|  | 	var cfg zapcore.EncoderConfig | ||||||
|  | 	cfg.MessageKey = "message" | ||||||
|  | 	out := zapcore.AddSync(writer) | ||||||
|  | 	var level zapcore.Level | ||||||
|  | 	err := level.Set(logLevel) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	core := zapcore.NewCore( | ||||||
|  | 		zapcore.NewConsoleEncoder(cfg), | ||||||
|  | 		out, | ||||||
|  | 		level, | ||||||
|  | 	) | ||||||
|  | 	return zap.New(core).Sugar() | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // New for running helm commands
 | // New for running helm commands
 | ||||||
| func New(writer io.Writer, kubeContext string) *execer { | func New(logger *zap.SugaredLogger, kubeContext string) *execer { | ||||||
| 	return &execer{ | 	return &execer{ | ||||||
| 		helmBinary:  command, | 		helmBinary:  command, | ||||||
| 		writer:      writer, | 		logger:      logger, | ||||||
| 		kubeContext: kubeContext, | 		kubeContext: kubeContext, | ||||||
| 		runner:      &ShellRunner{}, | 		runner:      &ShellRunner{}, | ||||||
| 	} | 	} | ||||||
|  | @ -45,68 +64,77 @@ func (helm *execer) AddRepo(name, repository, certfile, keyfile, username, passw | ||||||
| 	if username != "" && password != "" { | 	if username != "" && password != "" { | ||||||
| 		args = append(args, "--username", username, "--password", password) | 		args = append(args, "--username", username, "--password", password) | ||||||
| 	} | 	} | ||||||
|  | 	helm.logger.Infof("Adding repo %v %v", name, repository) | ||||||
| 	out, err := helm.exec(args...) | 	out, err := helm.exec(args...) | ||||||
| 	helm.write(out) | 	helm.write(out) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helm *execer) UpdateRepo() error { | func (helm *execer) UpdateRepo() error { | ||||||
|  | 	helm.logger.Info("Updating repo") | ||||||
| 	out, err := helm.exec("repo", "update") | 	out, err := helm.exec("repo", "update") | ||||||
| 	helm.write(out) | 	helm.write(out) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helm *execer) UpdateDeps(chart string) error { | func (helm *execer) UpdateDeps(chart string) error { | ||||||
|  | 	helm.logger.Infof("Updating dependency %v", chart) | ||||||
| 	out, err := helm.exec("dependency", "update", chart) | 	out, err := helm.exec("dependency", "update", chart) | ||||||
| 	helm.write(out) | 	helm.write(out) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helm *execer) SyncRelease(name, chart string, flags ...string) error { | func (helm *execer) SyncRelease(name, chart string, flags ...string) error { | ||||||
|  | 	helm.logger.Infof("Upgrading %v", chart) | ||||||
| 	out, err := helm.exec(append([]string{"upgrade", "--install", "--reset-values", name, chart}, flags...)...) | 	out, err := helm.exec(append([]string{"upgrade", "--install", "--reset-values", name, chart}, flags...)...) | ||||||
| 	helm.write(out) | 	helm.write(out) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helm *execer) ReleaseStatus(name string) error { | func (helm *execer) ReleaseStatus(name string) error { | ||||||
|  | 	helm.logger.Infof("Getting status %v", name) | ||||||
| 	out, err := helm.exec(append([]string{"status", name})...) | 	out, err := helm.exec(append([]string{"status", name})...) | ||||||
| 	if helm.writer != nil { | 	helm.write(out) | ||||||
| 		helm.writer.Write(out) |  | ||||||
| 	} |  | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helm *execer) DecryptSecret(name string) (string, error) { | func (helm *execer) DecryptSecret(name string) (string, error) { | ||||||
|  | 	helm.logger.Infof("Decrypting secret %v", name) | ||||||
| 	out, err := helm.exec(append([]string{"secrets", "dec", name})...) | 	out, err := helm.exec(append([]string{"secrets", "dec", name})...) | ||||||
| 	helm.write(out) | 	helm.write(out) | ||||||
| 	return name + ".dec", err | 	return name + ".dec", err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helm *execer) DiffRelease(name, chart string, flags ...string) error { | func (helm *execer) DiffRelease(name, chart string, flags ...string) error { | ||||||
|  | 	helm.logger.Infof("Comparing %v %v", name, chart) | ||||||
| 	out, err := helm.exec(append([]string{"diff", "upgrade", "--allow-unreleased", name, chart}, flags...)...) | 	out, err := helm.exec(append([]string{"diff", "upgrade", "--allow-unreleased", name, chart}, flags...)...) | ||||||
| 	helm.write(out) | 	helm.write(out) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helm *execer) Lint(chart string, flags ...string) error { | func (helm *execer) Lint(chart string, flags ...string) error { | ||||||
|  | 	helm.logger.Infof("Linting %v", chart) | ||||||
| 	out, err := helm.exec(append([]string{"lint", chart}, flags...)...) | 	out, err := helm.exec(append([]string{"lint", chart}, flags...)...) | ||||||
| 	helm.write(out) | 	helm.write(out) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helm *execer) Fetch(chart string, flags ...string) error { | func (helm *execer) Fetch(chart string, flags ...string) error { | ||||||
|  | 	helm.logger.Infof("Fetching %v", chart) | ||||||
| 	out, err := helm.exec(append([]string{"fetch", chart}, flags...)...) | 	out, err := helm.exec(append([]string{"fetch", chart}, flags...)...) | ||||||
| 	helm.write(out) | 	helm.write(out) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helm *execer) DeleteRelease(name string, flags ...string) error { | func (helm *execer) DeleteRelease(name string, flags ...string) error { | ||||||
|  | 	helm.logger.Infof("Deleting %v", name) | ||||||
| 	out, err := helm.exec(append([]string{"delete", name}, flags...)...) | 	out, err := helm.exec(append([]string{"delete", name}, flags...)...) | ||||||
| 	helm.write(out) | 	helm.write(out) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helm *execer) TestRelease(name string, flags ...string) error { | func (helm *execer) TestRelease(name string, flags ...string) error { | ||||||
|  | 	helm.logger.Infof("Testing %v", name) | ||||||
| 	out, err := helm.exec(append([]string{"test", name}, flags...)...) | 	out, err := helm.exec(append([]string{"test", name}, flags...)...) | ||||||
| 	helm.write(out) | 	helm.write(out) | ||||||
| 	return err | 	return err | ||||||
|  | @ -120,12 +148,12 @@ func (helm *execer) exec(args ...string) ([]byte, error) { | ||||||
| 	if helm.kubeContext != "" { | 	if helm.kubeContext != "" { | ||||||
| 		cmdargs = append(cmdargs, "--kube-context", helm.kubeContext) | 		cmdargs = append(cmdargs, "--kube-context", helm.kubeContext) | ||||||
| 	} | 	} | ||||||
| 	helm.write([]byte(fmt.Sprintf("exec: %s %s\n", helm.helmBinary, strings.Join(cmdargs, " ")))) | 	helm.logger.Debugf("exec: %s %s", helm.helmBinary, strings.Join(cmdargs, " ")) | ||||||
| 	return helm.runner.Execute(helm.helmBinary, cmdargs) | 	return helm.runner.Execute(helm.helmBinary, cmdargs) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helm *execer) write(out []byte) { | func (helm *execer) write(out []byte) { | ||||||
| 	if helm.writer != nil { | 	if len(out) > 0 { | ||||||
| 		helm.writer.Write(out) | 		helm.logger.Infof("%s", out) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,9 +2,11 @@ package helmexec | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"io" | 	"os" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Mocking the command-line runner
 | // Mocking the command-line runner
 | ||||||
|  | @ -18,8 +20,8 @@ func (mock *mockRunner) Execute(cmd string, args []string) ([]byte, error) { | ||||||
| 	return []byte{}, nil | 	return []byte{}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func MockExecer(writer io.Writer, kubeContext string) *execer { | func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer { | ||||||
| 	execer := New(writer, kubeContext) | 	execer := New(logger, kubeContext) | ||||||
| 	execer.runner = &mockRunner{} | 	execer.runner = &mockRunner{} | ||||||
| 	return execer | 	return execer | ||||||
| } | } | ||||||
|  | @ -28,7 +30,8 @@ func MockExecer(writer io.Writer, kubeContext string) *execer { | ||||||
| 
 | 
 | ||||||
| func TestNewHelmExec(t *testing.T) { | func TestNewHelmExec(t *testing.T) { | ||||||
| 	buffer := bytes.NewBufferString("something") | 	buffer := bytes.NewBufferString("something") | ||||||
| 	helm := New(buffer, "dev") | 	logger := NewLogger(buffer, "debug") | ||||||
|  | 	helm := New(logger, "dev") | ||||||
| 	if helm.kubeContext != "dev" { | 	if helm.kubeContext != "dev" { | ||||||
| 		t.Error("helmexec.New() - kubeContext") | 		t.Error("helmexec.New() - kubeContext") | ||||||
| 	} | 	} | ||||||
|  | @ -41,7 +44,7 @@ func TestNewHelmExec(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Test_SetExtraArgs(t *testing.T) { | func Test_SetExtraArgs(t *testing.T) { | ||||||
| 	helm := New(new(bytes.Buffer), "dev") | 	helm := New(NewLogger(os.Stdout, "info"), "dev") | ||||||
| 	helm.SetExtraArgs() | 	helm.SetExtraArgs() | ||||||
| 	if len(helm.extra) != 0 { | 	if len(helm.extra) != 0 { | ||||||
| 		t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field") | 		t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field") | ||||||
|  | @ -57,7 +60,7 @@ func Test_SetExtraArgs(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Test_SetHelmBinary(t *testing.T) { | func Test_SetHelmBinary(t *testing.T) { | ||||||
| 	helm := New(new(bytes.Buffer), "dev") | 	helm := New(NewLogger(os.Stdout, "info"), "dev") | ||||||
| 	if helm.helmBinary != "helm" { | 	if helm.helmBinary != "helm" { | ||||||
| 		t.Error("helmexec.command - default command is not helm") | 		t.Error("helmexec.command - default command is not helm") | ||||||
| 	} | 	} | ||||||
|  | @ -69,23 +72,30 @@ func Test_SetHelmBinary(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func Test_AddRepo(t *testing.T) { | func Test_AddRepo(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "dev") | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "dev") | ||||||
| 	helm.AddRepo("myRepo", "https://repo.example.com/", "cert.pem", "key.pem", "", "") | 	helm.AddRepo("myRepo", "https://repo.example.com/", "cert.pem", "key.pem", "", "") | ||||||
| 	expected := "exec: helm repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem --kube-context dev\n" | 	expected := `Adding repo myRepo https://repo.example.com/
 | ||||||
|  | exec: helm repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem --kube-context dev
 | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	buffer.Reset() | 	buffer.Reset() | ||||||
| 	helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "") | 	helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "") | ||||||
| 	expected = "exec: helm repo add myRepo https://repo.example.com/ --kube-context dev\n" | 	expected = `Adding repo myRepo https://repo.example.com/
 | ||||||
|  | exec: helm repo add myRepo https://repo.example.com/ --kube-context dev
 | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	buffer.Reset() | 	buffer.Reset() | ||||||
| 	helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "example_user", "example_password") | 	helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "example_user", "example_password") | ||||||
| 	expected = "exec: helm repo add myRepo https://repo.example.com/ --username example_user --password example_password --kube-context dev\n" | 	expected = `Adding repo myRepo https://repo.example.com/
 | ||||||
|  | exec: helm repo add myRepo https://repo.example.com/ --username example_user --password example_password --kube-context dev
 | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
|  | @ -93,9 +103,12 @@ func Test_AddRepo(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func Test_UpdateRepo(t *testing.T) { | func Test_UpdateRepo(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "dev") | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "dev") | ||||||
| 	helm.UpdateRepo() | 	helm.UpdateRepo() | ||||||
| 	expected := "exec: helm repo update --kube-context dev\n" | 	expected := `Updating repo | ||||||
|  | exec: helm repo update --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.UpdateRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.UpdateRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
|  | @ -103,16 +116,21 @@ func Test_UpdateRepo(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func Test_SyncRelease(t *testing.T) { | func Test_SyncRelease(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "dev") | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "dev") | ||||||
| 	helm.SyncRelease("release", "chart", "--timeout 10", "--wait") | 	helm.SyncRelease("release", "chart", "--timeout 10", "--wait") | ||||||
| 	expected := "exec: helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev\n" | 	expected := `Upgrading chart | ||||||
|  | exec: helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	buffer.Reset() | 	buffer.Reset() | ||||||
| 	helm.SyncRelease("release", "chart") | 	helm.SyncRelease("release", "chart") | ||||||
| 	expected = "exec: helm upgrade --install --reset-values release chart --kube-context dev\n" | 	expected = `Upgrading chart | ||||||
|  | exec: helm upgrade --install --reset-values release chart --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
|  | @ -120,17 +138,22 @@ func Test_SyncRelease(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func Test_UpdateDeps(t *testing.T) { | func Test_UpdateDeps(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "dev") | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "dev") | ||||||
| 	helm.UpdateDeps("./chart/foo") | 	helm.UpdateDeps("./chart/foo") | ||||||
| 	expected := "exec: helm dependency update ./chart/foo --kube-context dev\n" | 	expected := `Updating dependency ./chart/foo | ||||||
|  | exec: helm dependency update ./chart/foo --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.UpdateDeps()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	buffer.Reset() | 	buffer.Reset() | ||||||
| 	helm.SetExtraArgs("--verify") | 	helm.SetExtraArgs("--verify") | ||||||
| 	helm.UpdateDeps("./chart/foo") | 	helm.UpdateDeps("./chart/foo") | ||||||
| 	expected = "exec: helm dependency update ./chart/foo --verify --kube-context dev\n" | 	expected = `Updating dependency ./chart/foo | ||||||
|  | exec: helm dependency update ./chart/foo --verify --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
|  | @ -138,9 +161,12 @@ func Test_UpdateDeps(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func Test_DecryptSecret(t *testing.T) { | func Test_DecryptSecret(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "dev") | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "dev") | ||||||
| 	helm.DecryptSecret("secretName") | 	helm.DecryptSecret("secretName") | ||||||
| 	expected := "exec: helm secrets dec secretName --kube-context dev\n" | 	expected := `Decrypting secret secretName | ||||||
|  | exec: helm secrets dec secretName --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.DecryptSecret()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.DecryptSecret()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
|  | @ -148,16 +174,21 @@ func Test_DecryptSecret(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func Test_DiffRelease(t *testing.T) { | func Test_DiffRelease(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "dev") | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "dev") | ||||||
| 	helm.DiffRelease("release", "chart", "--timeout 10", "--wait") | 	helm.DiffRelease("release", "chart", "--timeout 10", "--wait") | ||||||
| 	expected := "exec: helm diff upgrade --allow-unreleased release chart --timeout 10 --wait --kube-context dev\n" | 	expected := `Comparing release chart | ||||||
|  | exec: helm diff upgrade --allow-unreleased release chart --timeout 10 --wait --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	buffer.Reset() | 	buffer.Reset() | ||||||
| 	helm.DiffRelease("release", "chart") | 	helm.DiffRelease("release", "chart") | ||||||
| 	expected = "exec: helm diff upgrade --allow-unreleased release chart --kube-context dev\n" | 	expected = `Comparing release chart | ||||||
|  | exec: helm diff upgrade --allow-unreleased release chart --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
|  | @ -165,18 +196,24 @@ func Test_DiffRelease(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func Test_DeleteRelease(t *testing.T) { | func Test_DeleteRelease(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "dev") | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "dev") | ||||||
| 	helm.DeleteRelease("release") | 	helm.DeleteRelease("release") | ||||||
| 	expected := "exec: helm delete release --kube-context dev\n" | 	expected := `Deleting release | ||||||
|  | exec: helm delete release --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.DeleteRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.DeleteRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| func Test_DeleteRelease_Flags(t *testing.T) { | func Test_DeleteRelease_Flags(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "dev") | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "dev") | ||||||
| 	helm.DeleteRelease("release", "--purge") | 	helm.DeleteRelease("release", "--purge") | ||||||
| 	expected := "exec: helm delete release --purge --kube-context dev\n" | 	expected := `Deleting release | ||||||
|  | exec: helm delete release --purge --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.DeleteRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.DeleteRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
|  | @ -184,18 +221,24 @@ func Test_DeleteRelease_Flags(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func Test_TestRelease(t *testing.T) { | func Test_TestRelease(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "dev") | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "dev") | ||||||
| 	helm.TestRelease("release") | 	helm.TestRelease("release") | ||||||
| 	expected := "exec: helm test release --kube-context dev\n" | 	expected := `Testing release | ||||||
|  | exec: helm test release --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.TestRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.TestRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| func Test_TestRelease_Flags(t *testing.T) { | func Test_TestRelease_Flags(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "dev") | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "dev") | ||||||
| 	helm.TestRelease("release", "--cleanup", "--timeout", "60") | 	helm.TestRelease("release", "--cleanup", "--timeout", "60") | ||||||
| 	expected := "exec: helm test release --cleanup --timeout 60 --kube-context dev\n" | 	expected := `Testing release | ||||||
|  | exec: helm test release --cleanup --timeout 60 --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.TestRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.TestRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
|  | @ -203,9 +246,12 @@ func Test_TestRelease_Flags(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func Test_ReleaseStatus(t *testing.T) { | func Test_ReleaseStatus(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "dev") | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "dev") | ||||||
| 	helm.ReleaseStatus("myRelease") | 	helm.ReleaseStatus("myRelease") | ||||||
| 	expected := "exec: helm status myRelease --kube-context dev\n" | 	expected := `Getting status myRelease | ||||||
|  | exec: helm status myRelease --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.ReleaseStatus()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.ReleaseStatus()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
|  | @ -213,21 +259,22 @@ func Test_ReleaseStatus(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func Test_exec(t *testing.T) { | func Test_exec(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "") | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "") | ||||||
| 	helm.exec("version") | 	helm.exec("version") | ||||||
| 	expected := "exec: helm version\n" | 	expected := "exec: helm version\n" | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	helm = MockExecer(nil, "dev") | 	helm = MockExecer(logger, "dev") | ||||||
| 	ret, _ := helm.exec("diff") | 	ret, _ := helm.exec("diff") | ||||||
| 	if len(ret) != 0 { | 	if len(ret) != 0 { | ||||||
| 		t.Error("helmexec.exec() - expected empty return value") | 		t.Error("helmexec.exec() - expected empty return value") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	buffer.Reset() | 	buffer.Reset() | ||||||
| 	helm = MockExecer(&buffer, "dev") | 	helm = MockExecer(logger, "dev") | ||||||
| 	helm.exec("diff", "release", "chart", "--timeout 10", "--wait") | 	helm.exec("diff", "release", "chart", "--timeout 10", "--wait") | ||||||
| 	expected = "exec: helm diff release chart --timeout 10 --wait --kube-context dev\n" | 	expected = "exec: helm diff release chart --timeout 10 --wait --kube-context dev\n" | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
|  | @ -250,7 +297,7 @@ func Test_exec(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	buffer.Reset() | 	buffer.Reset() | ||||||
| 	helm = MockExecer(&buffer, "") | 	helm = MockExecer(logger, "") | ||||||
| 	helm.SetHelmBinary("overwritten") | 	helm.SetHelmBinary("overwritten") | ||||||
| 	helm.exec("version") | 	helm.exec("version") | ||||||
| 	expected = "exec: overwritten version\n" | 	expected = "exec: overwritten version\n" | ||||||
|  | @ -261,9 +308,12 @@ func Test_exec(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func Test_Lint(t *testing.T) { | func Test_Lint(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "dev") | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "dev") | ||||||
| 	helm.Lint("path/to/chart", "--values", "file.yml") | 	helm.Lint("path/to/chart", "--values", "file.yml") | ||||||
| 	expected := "exec: helm lint path/to/chart --values file.yml --kube-context dev\n" | 	expected := `Linting path/to/chart | ||||||
|  | exec: helm lint path/to/chart --values file.yml --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.Lint()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.Lint()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
|  | @ -271,10 +321,35 @@ func Test_Lint(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func Test_Fetch(t *testing.T) { | func Test_Fetch(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "dev") | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "dev") | ||||||
| 	helm.Fetch("chart", "--version", "1.2.3", "--untar", "--untardir", "/tmp/dir") | 	helm.Fetch("chart", "--version", "1.2.3", "--untar", "--untardir", "/tmp/dir") | ||||||
| 	expected := "exec: helm fetch chart --version 1.2.3 --untar --untardir /tmp/dir --kube-context dev\n" | 	expected := `Fetching chart | ||||||
|  | exec: helm fetch chart --version 1.2.3 --untar --untardir /tmp/dir --kube-context dev | ||||||
|  | ` | ||||||
| 	if buffer.String() != expected { | 	if buffer.String() != expected { | ||||||
| 		t.Errorf("helmexec.Lint()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.Lint()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | var logLevelTests = map[string]string{ | ||||||
|  | 	"debug": `Adding repo myRepo https://repo.example.com/
 | ||||||
|  | exec: helm repo add myRepo https://repo.example.com/ --username example_user --password example_password
 | ||||||
|  | `, | ||||||
|  | 	"info": `Adding repo myRepo https://repo.example.com/
 | ||||||
|  | `, | ||||||
|  | 	"warn": ``, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func Test_LogLevels(t *testing.T) { | ||||||
|  | 	var buffer bytes.Buffer | ||||||
|  | 	for logLevel, expected := range logLevelTests { | ||||||
|  | 		buffer.Reset() | ||||||
|  | 		logger := NewLogger(&buffer, logLevel) | ||||||
|  | 		helm := MockExecer(logger, "") | ||||||
|  | 		helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "example_user", "example_password") | ||||||
|  | 		if buffer.String() != expected { | ||||||
|  | 			t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										45
									
								
								main.go
								
								
								
								
							
							
						
						
									
										45
									
								
								main.go
								
								
								
								
							|  | @ -2,18 +2,20 @@ package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" |  | ||||||
| 	"log" | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/signal" | 	"os/signal" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"syscall" | 	"syscall" | ||||||
| 
 | 
 | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"sort" | ||||||
|  | 
 | ||||||
| 	"github.com/roboll/helmfile/helmexec" | 	"github.com/roboll/helmfile/helmexec" | ||||||
| 	"github.com/roboll/helmfile/state" | 	"github.com/roboll/helmfile/state" | ||||||
| 	"github.com/urfave/cli" | 	"github.com/urfave/cli" | ||||||
| 	"path/filepath" | 	"go.uber.org/zap" | ||||||
| 	"sort" | 	"go.uber.org/zap/zapcore" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
|  | @ -24,6 +26,28 @@ const ( | ||||||
| 
 | 
 | ||||||
| var Version string | var Version string | ||||||
| 
 | 
 | ||||||
|  | func configure_logging(c *cli.Context) error { | ||||||
|  | 	// Valid levels:
 | ||||||
|  | 	// https://github.com/uber-go/zap/blob/7e7e266a8dbce911a49554b945538c5b950196b8/zapcore/level.go#L126
 | ||||||
|  | 	logLevel := c.GlobalString("log-level") | ||||||
|  | 	if c.GlobalBool("quiet") { | ||||||
|  | 		logLevel = "warn" | ||||||
|  | 	} | ||||||
|  | 	var level zapcore.Level | ||||||
|  | 	err := level.Set(logLevel) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	logger := helmexec.NewLogger(os.Stdout, logLevel) | ||||||
|  | 	if c.App.Metadata == nil { | ||||||
|  | 		// Auto-initialised in 1.19.0
 | ||||||
|  | 		// https://github.com/urfave/cli/blob/master/CHANGELOG.md#1190---2016-11-19
 | ||||||
|  | 		c.App.Metadata = make(map[string]interface{}) | ||||||
|  | 	} | ||||||
|  | 	c.App.Metadata["logger"] = logger | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func main() { | func main() { | ||||||
| 
 | 
 | ||||||
| 	app := cli.NewApp() | 	app := cli.NewApp() | ||||||
|  | @ -42,12 +66,16 @@ func main() { | ||||||
| 		}, | 		}, | ||||||
| 		cli.BoolFlag{ | 		cli.BoolFlag{ | ||||||
| 			Name:  "quiet, q", | 			Name:  "quiet, q", | ||||||
| 			Usage: "silence output", | 			Usage: "Silence output. Equivalent to log-level warn", | ||||||
| 		}, | 		}, | ||||||
| 		cli.StringFlag{ | 		cli.StringFlag{ | ||||||
| 			Name:  "kube-context", | 			Name:  "kube-context", | ||||||
| 			Usage: "Set kubectl context. Uses current context by default", | 			Usage: "Set kubectl context. Uses current context by default", | ||||||
| 		}, | 		}, | ||||||
|  | 		cli.StringFlag{ | ||||||
|  | 			Name:  "log-level", | ||||||
|  | 			Usage: "Set log level, default info", | ||||||
|  | 		}, | ||||||
| 		cli.StringFlag{ | 		cli.StringFlag{ | ||||||
| 			Name:  "namespace, n", | 			Name:  "namespace, n", | ||||||
| 			Usage: "Set namespace. Uses the namespace set in the context by default", | 			Usage: "Set namespace. Uses the namespace set in the context by default", | ||||||
|  | @ -61,6 +89,7 @@ func main() { | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	app.Before = configure_logging | ||||||
| 	app.Commands = []cli.Command{ | 	app.Commands = []cli.Command{ | ||||||
| 		{ | 		{ | ||||||
| 			Name:  "repos", | 			Name:  "repos", | ||||||
|  | @ -422,7 +451,6 @@ func directoryExistsAt(path string) bool { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func loadDesiredStateFromFile(c *cli.Context, file string) (*state.HelmState, helmexec.Interface, error) { | func loadDesiredStateFromFile(c *cli.Context, file string) (*state.HelmState, helmexec.Interface, error) { | ||||||
| 	quiet := c.GlobalBool("quiet") |  | ||||||
| 	kubeContext := c.GlobalString("kube-context") | 	kubeContext := c.GlobalString("kube-context") | ||||||
| 	namespace := c.GlobalString("namespace") | 	namespace := c.GlobalString("namespace") | ||||||
| 	labels := c.GlobalStringSlice("selector") | 	labels := c.GlobalStringSlice("selector") | ||||||
|  | @ -455,10 +483,6 @@ func loadDesiredStateFromFile(c *cli.Context, file string) (*state.HelmState, he | ||||||
| 			os.Exit(1) | 			os.Exit(1) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	var writer io.Writer |  | ||||||
| 	if !quiet { |  | ||||||
| 		writer = os.Stdout |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	sigs := make(chan os.Signal, 1) | 	sigs := make(chan os.Signal, 1) | ||||||
| 	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) | 	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) | ||||||
|  | @ -469,7 +493,8 @@ func loadDesiredStateFromFile(c *cli.Context, file string) (*state.HelmState, he | ||||||
| 		clean(st, errs) | 		clean(st, errs) | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
| 	return st, helmexec.New(writer, kubeContext), nil | 	logger := c.App.Metadata["logger"].(*zap.SugaredLogger) | ||||||
|  | 	return st, helmexec.New(logger, kubeContext), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func clean(state *state.HelmState, errs []error) error { | func clean(state *state.HelmState, errs []error) error { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,15 @@ | ||||||
|  | coverage: | ||||||
|  |   range: 80..100 | ||||||
|  |   round: down | ||||||
|  |   precision: 2 | ||||||
|  | 
 | ||||||
|  |   status: | ||||||
|  |     project:                   # measuring the overall project coverage | ||||||
|  |       default:                 # context, you can create multiple ones with custom titles | ||||||
|  |         enabled: yes           # must be yes|true to enable this status | ||||||
|  |         target: 100            # specify the target coverage for each commit status | ||||||
|  |                                #   option: "auto" (must increase from parent commit or pull request base) | ||||||
|  |                                #   option: "X%" a static target percentage to hit | ||||||
|  |         if_not_found: success  # if parent is not found report status as success, error, or failure | ||||||
|  |         if_ci_failed: error    # if ci fails report status as success, error, or failure | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,11 @@ | ||||||
|  | .DS_Store | ||||||
|  | /vendor | ||||||
|  | /cover | ||||||
|  | cover.out | ||||||
|  | lint.log | ||||||
|  | 
 | ||||||
|  | # Binaries | ||||||
|  | *.test | ||||||
|  | 
 | ||||||
|  | # Profiling output | ||||||
|  | *.prof | ||||||
|  | @ -0,0 +1,23 @@ | ||||||
|  | sudo: false | ||||||
|  | language: go | ||||||
|  | go_import_path: go.uber.org/atomic | ||||||
|  | 
 | ||||||
|  | go: | ||||||
|  |   - 1.7 | ||||||
|  |   - 1.8 | ||||||
|  |   - 1.9 | ||||||
|  | 
 | ||||||
|  | cache: | ||||||
|  |   directories: | ||||||
|  |     - vendor | ||||||
|  | 
 | ||||||
|  | install: | ||||||
|  |   - make install_ci | ||||||
|  | 
 | ||||||
|  | script: | ||||||
|  |   - make test_ci | ||||||
|  |   - scripts/test-ubergo.sh | ||||||
|  |   - make lint | ||||||
|  | 
 | ||||||
|  | after_success: | ||||||
|  |   - bash <(curl -s https://codecov.io/bash) | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | Copyright (c) 2016 Uber Technologies, Inc. | ||||||
|  | 
 | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | in the Software without restriction, including without limitation the rights | ||||||
|  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | copies of the Software, and to permit persons to whom the Software is | ||||||
|  | furnished to do so, subject to the following conditions: | ||||||
|  | 
 | ||||||
|  | The above copyright notice and this permission notice shall be included in | ||||||
|  | all copies or substantial portions of the Software. | ||||||
|  | 
 | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  | THE SOFTWARE. | ||||||
|  | @ -0,0 +1,64 @@ | ||||||
|  | PACKAGES := $(shell glide nv) | ||||||
|  | # Many Go tools take file globs or directories as arguments instead of packages.
 | ||||||
|  | PACKAGE_FILES ?= *.go | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # The linting tools evolve with each Go version, so run them only on the latest
 | ||||||
|  | # stable release.
 | ||||||
|  | GO_VERSION := $(shell go version | cut -d " " -f 3) | ||||||
|  | GO_MINOR_VERSION := $(word 2,$(subst ., ,$(GO_VERSION))) | ||||||
|  | LINTABLE_MINOR_VERSIONS := 7 8 | ||||||
|  | ifneq ($(filter $(LINTABLE_MINOR_VERSIONS),$(GO_MINOR_VERSION)),) | ||||||
|  | SHOULD_LINT := true | ||||||
|  | endif | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export GO15VENDOREXPERIMENT=1 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .PHONY: build | ||||||
|  | build: | ||||||
|  | 	go build -i $(PACKAGES) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .PHONY: install | ||||||
|  | install: | ||||||
|  | 	glide --version || go get github.com/Masterminds/glide | ||||||
|  | 	glide install | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .PHONY: test | ||||||
|  | test: | ||||||
|  | 	go test -cover -race $(PACKAGES) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .PHONY: install_ci | ||||||
|  | install_ci: install | ||||||
|  | 	go get github.com/wadey/gocovmerge | ||||||
|  | 	go get github.com/mattn/goveralls | ||||||
|  | 	go get golang.org/x/tools/cmd/cover | ||||||
|  | ifdef SHOULD_LINT | ||||||
|  | 	go get github.com/golang/lint/golint | ||||||
|  | endif | ||||||
|  | 
 | ||||||
|  | .PHONY: lint | ||||||
|  | lint: | ||||||
|  | ifdef SHOULD_LINT | ||||||
|  | 	@rm -rf lint.log | ||||||
|  | 	@echo "Checking formatting..." | ||||||
|  | 	@gofmt -d -s $(PACKAGE_FILES) 2>&1 | tee lint.log | ||||||
|  | 	@echo "Checking vet..." | ||||||
|  | 	@$(foreach dir,$(PACKAGE_FILES),go tool vet $(dir) 2>&1 | tee -a lint.log;) | ||||||
|  | 	@echo "Checking lint..." | ||||||
|  | 	@$(foreach dir,$(PKGS),golint $(dir) 2>&1 | tee -a lint.log;) | ||||||
|  | 	@echo "Checking for unresolved FIXMEs..." | ||||||
|  | 	@git grep -i fixme | grep -v -e vendor -e Makefile | tee -a lint.log | ||||||
|  | 	@[ ! -s lint.log ] | ||||||
|  | else | ||||||
|  | 	@echo "Skipping linters on" $(GO_VERSION) | ||||||
|  | endif | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .PHONY: test_ci | ||||||
|  | test_ci: install_ci build | ||||||
|  | 	./scripts/cover.sh $(shell go list $(PACKAGES)) | ||||||
|  | @ -0,0 +1,36 @@ | ||||||
|  | # atomic [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![Go Report Card][reportcard-img]][reportcard] | ||||||
|  | 
 | ||||||
|  | Simple wrappers for primitive types to enforce atomic access. | ||||||
|  | 
 | ||||||
|  | ## Installation | ||||||
|  | `go get -u go.uber.org/atomic` | ||||||
|  | 
 | ||||||
|  | ## Usage | ||||||
|  | The standard library's `sync/atomic` is powerful, but it's easy to forget which | ||||||
|  | variables must be accessed atomically. `go.uber.org/atomic` preserves all the | ||||||
|  | functionality of the standard library, but wraps the primitive types to | ||||||
|  | provide a safer, more convenient API. | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | var atom atomic.Uint32 | ||||||
|  | atom.Store(42) | ||||||
|  | atom.Sub(2) | ||||||
|  | atom.CAS(40, 11) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | See the [documentation][doc] for a complete API specification. | ||||||
|  | 
 | ||||||
|  | ## Development Status | ||||||
|  | Stable. | ||||||
|  | 
 | ||||||
|  | <hr> | ||||||
|  | Released under the [MIT License](LICENSE.txt). | ||||||
|  | 
 | ||||||
|  | [doc-img]: https://godoc.org/github.com/uber-go/atomic?status.svg | ||||||
|  | [doc]: https://godoc.org/go.uber.org/atomic | ||||||
|  | [ci-img]: https://travis-ci.org/uber-go/atomic.svg?branch=master | ||||||
|  | [ci]: https://travis-ci.org/uber-go/atomic | ||||||
|  | [cov-img]: https://codecov.io/gh/uber-go/atomic/branch/master/graph/badge.svg | ||||||
|  | [cov]: https://codecov.io/gh/uber-go/atomic | ||||||
|  | [reportcard-img]: https://goreportcard.com/badge/go.uber.org/atomic | ||||||
|  | [reportcard]: https://goreportcard.com/report/go.uber.org/atomic | ||||||
|  | @ -0,0 +1,351 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | // Package atomic provides simple wrappers around numerics to enforce atomic
 | ||||||
|  | // access.
 | ||||||
|  | package atomic | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"math" | ||||||
|  | 	"sync/atomic" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Int32 is an atomic wrapper around an int32.
 | ||||||
|  | type Int32 struct{ v int32 } | ||||||
|  | 
 | ||||||
|  | // NewInt32 creates an Int32.
 | ||||||
|  | func NewInt32(i int32) *Int32 { | ||||||
|  | 	return &Int32{i} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Load atomically loads the wrapped value.
 | ||||||
|  | func (i *Int32) Load() int32 { | ||||||
|  | 	return atomic.LoadInt32(&i.v) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Add atomically adds to the wrapped int32 and returns the new value.
 | ||||||
|  | func (i *Int32) Add(n int32) int32 { | ||||||
|  | 	return atomic.AddInt32(&i.v, n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Sub atomically subtracts from the wrapped int32 and returns the new value.
 | ||||||
|  | func (i *Int32) Sub(n int32) int32 { | ||||||
|  | 	return atomic.AddInt32(&i.v, -n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Inc atomically increments the wrapped int32 and returns the new value.
 | ||||||
|  | func (i *Int32) Inc() int32 { | ||||||
|  | 	return i.Add(1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Dec atomically decrements the wrapped int32 and returns the new value.
 | ||||||
|  | func (i *Int32) Dec() int32 { | ||||||
|  | 	return i.Sub(1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CAS is an atomic compare-and-swap.
 | ||||||
|  | func (i *Int32) CAS(old, new int32) bool { | ||||||
|  | 	return atomic.CompareAndSwapInt32(&i.v, old, new) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Store atomically stores the passed value.
 | ||||||
|  | func (i *Int32) Store(n int32) { | ||||||
|  | 	atomic.StoreInt32(&i.v, n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Swap atomically swaps the wrapped int32 and returns the old value.
 | ||||||
|  | func (i *Int32) Swap(n int32) int32 { | ||||||
|  | 	return atomic.SwapInt32(&i.v, n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Int64 is an atomic wrapper around an int64.
 | ||||||
|  | type Int64 struct{ v int64 } | ||||||
|  | 
 | ||||||
|  | // NewInt64 creates an Int64.
 | ||||||
|  | func NewInt64(i int64) *Int64 { | ||||||
|  | 	return &Int64{i} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Load atomically loads the wrapped value.
 | ||||||
|  | func (i *Int64) Load() int64 { | ||||||
|  | 	return atomic.LoadInt64(&i.v) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Add atomically adds to the wrapped int64 and returns the new value.
 | ||||||
|  | func (i *Int64) Add(n int64) int64 { | ||||||
|  | 	return atomic.AddInt64(&i.v, n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Sub atomically subtracts from the wrapped int64 and returns the new value.
 | ||||||
|  | func (i *Int64) Sub(n int64) int64 { | ||||||
|  | 	return atomic.AddInt64(&i.v, -n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Inc atomically increments the wrapped int64 and returns the new value.
 | ||||||
|  | func (i *Int64) Inc() int64 { | ||||||
|  | 	return i.Add(1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Dec atomically decrements the wrapped int64 and returns the new value.
 | ||||||
|  | func (i *Int64) Dec() int64 { | ||||||
|  | 	return i.Sub(1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CAS is an atomic compare-and-swap.
 | ||||||
|  | func (i *Int64) CAS(old, new int64) bool { | ||||||
|  | 	return atomic.CompareAndSwapInt64(&i.v, old, new) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Store atomically stores the passed value.
 | ||||||
|  | func (i *Int64) Store(n int64) { | ||||||
|  | 	atomic.StoreInt64(&i.v, n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Swap atomically swaps the wrapped int64 and returns the old value.
 | ||||||
|  | func (i *Int64) Swap(n int64) int64 { | ||||||
|  | 	return atomic.SwapInt64(&i.v, n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Uint32 is an atomic wrapper around an uint32.
 | ||||||
|  | type Uint32 struct{ v uint32 } | ||||||
|  | 
 | ||||||
|  | // NewUint32 creates a Uint32.
 | ||||||
|  | func NewUint32(i uint32) *Uint32 { | ||||||
|  | 	return &Uint32{i} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Load atomically loads the wrapped value.
 | ||||||
|  | func (i *Uint32) Load() uint32 { | ||||||
|  | 	return atomic.LoadUint32(&i.v) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Add atomically adds to the wrapped uint32 and returns the new value.
 | ||||||
|  | func (i *Uint32) Add(n uint32) uint32 { | ||||||
|  | 	return atomic.AddUint32(&i.v, n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Sub atomically subtracts from the wrapped uint32 and returns the new value.
 | ||||||
|  | func (i *Uint32) Sub(n uint32) uint32 { | ||||||
|  | 	return atomic.AddUint32(&i.v, ^(n - 1)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Inc atomically increments the wrapped uint32 and returns the new value.
 | ||||||
|  | func (i *Uint32) Inc() uint32 { | ||||||
|  | 	return i.Add(1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Dec atomically decrements the wrapped int32 and returns the new value.
 | ||||||
|  | func (i *Uint32) Dec() uint32 { | ||||||
|  | 	return i.Sub(1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CAS is an atomic compare-and-swap.
 | ||||||
|  | func (i *Uint32) CAS(old, new uint32) bool { | ||||||
|  | 	return atomic.CompareAndSwapUint32(&i.v, old, new) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Store atomically stores the passed value.
 | ||||||
|  | func (i *Uint32) Store(n uint32) { | ||||||
|  | 	atomic.StoreUint32(&i.v, n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Swap atomically swaps the wrapped uint32 and returns the old value.
 | ||||||
|  | func (i *Uint32) Swap(n uint32) uint32 { | ||||||
|  | 	return atomic.SwapUint32(&i.v, n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Uint64 is an atomic wrapper around a uint64.
 | ||||||
|  | type Uint64 struct{ v uint64 } | ||||||
|  | 
 | ||||||
|  | // NewUint64 creates a Uint64.
 | ||||||
|  | func NewUint64(i uint64) *Uint64 { | ||||||
|  | 	return &Uint64{i} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Load atomically loads the wrapped value.
 | ||||||
|  | func (i *Uint64) Load() uint64 { | ||||||
|  | 	return atomic.LoadUint64(&i.v) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Add atomically adds to the wrapped uint64 and returns the new value.
 | ||||||
|  | func (i *Uint64) Add(n uint64) uint64 { | ||||||
|  | 	return atomic.AddUint64(&i.v, n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Sub atomically subtracts from the wrapped uint64 and returns the new value.
 | ||||||
|  | func (i *Uint64) Sub(n uint64) uint64 { | ||||||
|  | 	return atomic.AddUint64(&i.v, ^(n - 1)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Inc atomically increments the wrapped uint64 and returns the new value.
 | ||||||
|  | func (i *Uint64) Inc() uint64 { | ||||||
|  | 	return i.Add(1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Dec atomically decrements the wrapped uint64 and returns the new value.
 | ||||||
|  | func (i *Uint64) Dec() uint64 { | ||||||
|  | 	return i.Sub(1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CAS is an atomic compare-and-swap.
 | ||||||
|  | func (i *Uint64) CAS(old, new uint64) bool { | ||||||
|  | 	return atomic.CompareAndSwapUint64(&i.v, old, new) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Store atomically stores the passed value.
 | ||||||
|  | func (i *Uint64) Store(n uint64) { | ||||||
|  | 	atomic.StoreUint64(&i.v, n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Swap atomically swaps the wrapped uint64 and returns the old value.
 | ||||||
|  | func (i *Uint64) Swap(n uint64) uint64 { | ||||||
|  | 	return atomic.SwapUint64(&i.v, n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Bool is an atomic Boolean.
 | ||||||
|  | type Bool struct{ v uint32 } | ||||||
|  | 
 | ||||||
|  | // NewBool creates a Bool.
 | ||||||
|  | func NewBool(initial bool) *Bool { | ||||||
|  | 	return &Bool{boolToInt(initial)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Load atomically loads the Boolean.
 | ||||||
|  | func (b *Bool) Load() bool { | ||||||
|  | 	return truthy(atomic.LoadUint32(&b.v)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CAS is an atomic compare-and-swap.
 | ||||||
|  | func (b *Bool) CAS(old, new bool) bool { | ||||||
|  | 	return atomic.CompareAndSwapUint32(&b.v, boolToInt(old), boolToInt(new)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Store atomically stores the passed value.
 | ||||||
|  | func (b *Bool) Store(new bool) { | ||||||
|  | 	atomic.StoreUint32(&b.v, boolToInt(new)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Swap sets the given value and returns the previous value.
 | ||||||
|  | func (b *Bool) Swap(new bool) bool { | ||||||
|  | 	return truthy(atomic.SwapUint32(&b.v, boolToInt(new))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Toggle atomically negates the Boolean and returns the previous value.
 | ||||||
|  | func (b *Bool) Toggle() bool { | ||||||
|  | 	return truthy(atomic.AddUint32(&b.v, 1) - 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func truthy(n uint32) bool { | ||||||
|  | 	return n&1 == 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func boolToInt(b bool) uint32 { | ||||||
|  | 	if b { | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Float64 is an atomic wrapper around float64.
 | ||||||
|  | type Float64 struct { | ||||||
|  | 	v uint64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewFloat64 creates a Float64.
 | ||||||
|  | func NewFloat64(f float64) *Float64 { | ||||||
|  | 	return &Float64{math.Float64bits(f)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Load atomically loads the wrapped value.
 | ||||||
|  | func (f *Float64) Load() float64 { | ||||||
|  | 	return math.Float64frombits(atomic.LoadUint64(&f.v)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Store atomically stores the passed value.
 | ||||||
|  | func (f *Float64) Store(s float64) { | ||||||
|  | 	atomic.StoreUint64(&f.v, math.Float64bits(s)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Add atomically adds to the wrapped float64 and returns the new value.
 | ||||||
|  | func (f *Float64) Add(s float64) float64 { | ||||||
|  | 	for { | ||||||
|  | 		old := f.Load() | ||||||
|  | 		new := old + s | ||||||
|  | 		if f.CAS(old, new) { | ||||||
|  | 			return new | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Sub atomically subtracts from the wrapped float64 and returns the new value.
 | ||||||
|  | func (f *Float64) Sub(s float64) float64 { | ||||||
|  | 	return f.Add(-s) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CAS is an atomic compare-and-swap.
 | ||||||
|  | func (f *Float64) CAS(old, new float64) bool { | ||||||
|  | 	return atomic.CompareAndSwapUint64(&f.v, math.Float64bits(old), math.Float64bits(new)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Duration is an atomic wrapper around time.Duration
 | ||||||
|  | // https://godoc.org/time#Duration
 | ||||||
|  | type Duration struct { | ||||||
|  | 	v Int64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewDuration creates a Duration.
 | ||||||
|  | func NewDuration(d time.Duration) *Duration { | ||||||
|  | 	return &Duration{v: *NewInt64(int64(d))} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Load atomically loads the wrapped value.
 | ||||||
|  | func (d *Duration) Load() time.Duration { | ||||||
|  | 	return time.Duration(d.v.Load()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Store atomically stores the passed value.
 | ||||||
|  | func (d *Duration) Store(n time.Duration) { | ||||||
|  | 	d.v.Store(int64(n)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Add atomically adds to the wrapped time.Duration and returns the new value.
 | ||||||
|  | func (d *Duration) Add(n time.Duration) time.Duration { | ||||||
|  | 	return time.Duration(d.v.Add(int64(n))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Sub atomically subtracts from the wrapped time.Duration and returns the new value.
 | ||||||
|  | func (d *Duration) Sub(n time.Duration) time.Duration { | ||||||
|  | 	return time.Duration(d.v.Sub(int64(n))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Swap atomically swaps the wrapped time.Duration and returns the old value.
 | ||||||
|  | func (d *Duration) Swap(n time.Duration) time.Duration { | ||||||
|  | 	return time.Duration(d.v.Swap(int64(n))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CAS is an atomic compare-and-swap.
 | ||||||
|  | func (d *Duration) CAS(old, new time.Duration) bool { | ||||||
|  | 	return d.v.CAS(int64(old), int64(new)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Value shadows the type of the same name from sync/atomic
 | ||||||
|  | // https://godoc.org/sync/atomic#Value
 | ||||||
|  | type Value struct{ atomic.Value } | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | hash: f14d51408e3e0e4f73b34e4039484c78059cd7fc5f4996fdd73db20dc8d24f53 | ||||||
|  | updated: 2016-10-27T00:10:51.16960137-07:00 | ||||||
|  | imports: [] | ||||||
|  | testImports: | ||||||
|  | - name: github.com/davecgh/go-spew | ||||||
|  |   version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d | ||||||
|  |   subpackages: | ||||||
|  |   - spew | ||||||
|  | - name: github.com/pmezard/go-difflib | ||||||
|  |   version: d8ed2627bdf02c080bf22230dbb337003b7aba2d | ||||||
|  |   subpackages: | ||||||
|  |   - difflib | ||||||
|  | - name: github.com/stretchr/testify | ||||||
|  |   version: d77da356e56a7428ad25149ca77381849a6a5232 | ||||||
|  |   subpackages: | ||||||
|  |   - assert | ||||||
|  |   - require | ||||||
|  | @ -0,0 +1,6 @@ | ||||||
|  | package: go.uber.org/atomic | ||||||
|  | testImport: | ||||||
|  | - package: github.com/stretchr/testify | ||||||
|  |   subpackages: | ||||||
|  |   - assert | ||||||
|  |   - require | ||||||
|  | @ -0,0 +1,49 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package atomic | ||||||
|  | 
 | ||||||
|  | // String is an atomic type-safe wrapper around Value for strings.
 | ||||||
|  | type String struct{ v Value } | ||||||
|  | 
 | ||||||
|  | // NewString creates a String.
 | ||||||
|  | func NewString(str string) *String { | ||||||
|  | 	s := &String{} | ||||||
|  | 	if str != "" { | ||||||
|  | 		s.Store(str) | ||||||
|  | 	} | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Load atomically loads the wrapped string.
 | ||||||
|  | func (s *String) Load() string { | ||||||
|  | 	v := s.v.Load() | ||||||
|  | 	if v == nil { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return v.(string) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Store atomically stores the passed string.
 | ||||||
|  | // Note: Converting the string to an interface{} to store in the Value
 | ||||||
|  | // requires an allocation.
 | ||||||
|  | func (s *String) Store(str string) { | ||||||
|  | 	s.v.Store(str) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,15 @@ | ||||||
|  | coverage: | ||||||
|  |   range: 80..100 | ||||||
|  |   round: down | ||||||
|  |   precision: 2 | ||||||
|  | 
 | ||||||
|  |   status: | ||||||
|  |     project:                   # measuring the overall project coverage | ||||||
|  |       default:                 # context, you can create multiple ones with custom titles | ||||||
|  |         enabled: yes           # must be yes|true to enable this status | ||||||
|  |         target: 100            # specify the target coverage for each commit status | ||||||
|  |                                #   option: "auto" (must increase from parent commit or pull request base) | ||||||
|  |                                #   option: "X%" a static target percentage to hit | ||||||
|  |         if_not_found: success  # if parent is not found report status as success, error, or failure | ||||||
|  |         if_ci_failed: error    # if ci fails report status as success, error, or failure | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | /vendor | ||||||
|  | @ -0,0 +1,33 @@ | ||||||
|  | sudo: false | ||||||
|  | language: go | ||||||
|  | go_import_path: go.uber.org/multierr | ||||||
|  | 
 | ||||||
|  | env: | ||||||
|  |   global: | ||||||
|  |     - GO15VENDOREXPERIMENT=1 | ||||||
|  | 
 | ||||||
|  | go: | ||||||
|  |   - 1.7 | ||||||
|  |   - 1.8 | ||||||
|  |   - tip | ||||||
|  | 
 | ||||||
|  | cache: | ||||||
|  |   directories: | ||||||
|  |     - vendor | ||||||
|  | 
 | ||||||
|  | before_install: | ||||||
|  | - go version | ||||||
|  | 
 | ||||||
|  | install: | ||||||
|  | - | | ||||||
|  |   set -e | ||||||
|  |   make install_ci | ||||||
|  | 
 | ||||||
|  | script: | ||||||
|  | - | | ||||||
|  |   set -e | ||||||
|  |   make lint | ||||||
|  |   make test_ci | ||||||
|  | 
 | ||||||
|  | after_success: | ||||||
|  | - bash <(curl -s https://codecov.io/bash) | ||||||
|  | @ -0,0 +1,28 @@ | ||||||
|  | Releases | ||||||
|  | ======== | ||||||
|  | 
 | ||||||
|  | v1.1.0 (2017-06-30) | ||||||
|  | =================== | ||||||
|  | 
 | ||||||
|  | -   Added an `Errors(error) []error` function to extract the underlying list of | ||||||
|  |     errors for a multierr error. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | v1.0.0 (2017-05-31) | ||||||
|  | =================== | ||||||
|  | 
 | ||||||
|  | No changes since v0.2.0. This release is committing to making no breaking | ||||||
|  | changes to the current API in the 1.X series. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | v0.2.0 (2017-04-11) | ||||||
|  | =================== | ||||||
|  | 
 | ||||||
|  | -   Repeatedly appending to the same error is now faster due to fewer | ||||||
|  |     allocations. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | v0.1.0 (2017-31-03) | ||||||
|  | =================== | ||||||
|  | 
 | ||||||
|  | -   Initial release | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | Copyright (c) 2017 Uber Technologies, Inc. | ||||||
|  | 
 | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | in the Software without restriction, including without limitation the rights | ||||||
|  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | copies of the Software, and to permit persons to whom the Software is | ||||||
|  | furnished to do so, subject to the following conditions: | ||||||
|  | 
 | ||||||
|  | The above copyright notice and this permission notice shall be included in | ||||||
|  | all copies or substantial portions of the Software. | ||||||
|  | 
 | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  | THE SOFTWARE. | ||||||
|  | @ -0,0 +1,74 @@ | ||||||
|  | export GO15VENDOREXPERIMENT=1 | ||||||
|  | 
 | ||||||
|  | PACKAGES := $(shell glide nv) | ||||||
|  | 
 | ||||||
|  | GO_FILES := $(shell \
 | ||||||
|  | 	find . '(' -path '*/.*' -o -path './vendor' ')' -prune \
 | ||||||
|  | 	-o -name '*.go' -print | cut -b3-) | ||||||
|  | 
 | ||||||
|  | .PHONY: install | ||||||
|  | install: | ||||||
|  | 	glide --version || go get github.com/Masterminds/glide | ||||||
|  | 	glide install | ||||||
|  | 
 | ||||||
|  | .PHONY: build | ||||||
|  | build: | ||||||
|  | 	go build -i $(PACKAGES) | ||||||
|  | 
 | ||||||
|  | .PHONY: test | ||||||
|  | test: | ||||||
|  | 	go test -cover -race $(PACKAGES) | ||||||
|  | 
 | ||||||
|  | .PHONY: gofmt | ||||||
|  | gofmt: | ||||||
|  | 	$(eval FMT_LOG := $(shell mktemp -t gofmt.XXXXX)) | ||||||
|  | 	@gofmt -e -s -l $(GO_FILES) > $(FMT_LOG) || true | ||||||
|  | 	@[ ! -s "$(FMT_LOG)" ] || (echo "gofmt failed:" | cat - $(FMT_LOG) && false) | ||||||
|  | 
 | ||||||
|  | .PHONY: govet | ||||||
|  | govet: | ||||||
|  | 	$(eval VET_LOG := $(shell mktemp -t govet.XXXXX)) | ||||||
|  | 	@go vet $(PACKAGES) 2>&1 \
 | ||||||
|  | 		| grep -v '^exit status' > $(VET_LOG) || true | ||||||
|  | 	@[ ! -s "$(VET_LOG)" ] || (echo "govet failed:" | cat - $(VET_LOG) && false) | ||||||
|  | 
 | ||||||
|  | .PHONY: golint | ||||||
|  | golint: | ||||||
|  | 	@go get github.com/golang/lint/golint | ||||||
|  | 	$(eval LINT_LOG := $(shell mktemp -t golint.XXXXX)) | ||||||
|  | 	@cat /dev/null > $(LINT_LOG) | ||||||
|  | 	@$(foreach pkg, $(PACKAGES), golint $(pkg) >> $(LINT_LOG) || true;) | ||||||
|  | 	@[ ! -s "$(LINT_LOG)" ] || (echo "golint failed:" | cat - $(LINT_LOG) && false) | ||||||
|  | 
 | ||||||
|  | .PHONY: staticcheck | ||||||
|  | staticcheck: | ||||||
|  | 	@go get honnef.co/go/tools/cmd/staticcheck | ||||||
|  | 	$(eval STATICCHECK_LOG := $(shell mktemp -t staticcheck.XXXXX)) | ||||||
|  | 	@staticcheck $(PACKAGES) 2>&1 > $(STATICCHECK_LOG) || true | ||||||
|  | 	@[ ! -s "$(STATICCHECK_LOG)" ] || (echo "staticcheck failed:" | cat - $(STATICCHECK_LOG) && false) | ||||||
|  | 
 | ||||||
|  | .PHONY: lint | ||||||
|  | lint: gofmt govet golint staticcheck | ||||||
|  | 
 | ||||||
|  | .PHONY: cover | ||||||
|  | cover: | ||||||
|  | 	./scripts/cover.sh $(shell go list $(PACKAGES)) | ||||||
|  | 	go tool cover -html=cover.out -o cover.html | ||||||
|  | 
 | ||||||
|  | update-license: | ||||||
|  | 	@go get go.uber.org/tools/update-license | ||||||
|  | 	@update-license \
 | ||||||
|  | 		$(shell go list -json $(PACKAGES) | \
 | ||||||
|  | 			jq -r '.Dir + "/" + (.GoFiles | .[])') | ||||||
|  | 
 | ||||||
|  | ##############################################################################
 | ||||||
|  | 
 | ||||||
|  | .PHONY: install_ci | ||||||
|  | install_ci: install | ||||||
|  | 	go get github.com/wadey/gocovmerge | ||||||
|  | 	go get github.com/mattn/goveralls | ||||||
|  | 	go get golang.org/x/tools/cmd/cover | ||||||
|  | 
 | ||||||
|  | .PHONY: test_ci | ||||||
|  | test_ci: install_ci | ||||||
|  | 	./scripts/cover.sh $(shell go list $(PACKAGES)) | ||||||
|  | @ -0,0 +1,23 @@ | ||||||
|  | # multierr [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] | ||||||
|  | 
 | ||||||
|  | `multierr` allows combining one or more Go `error`s together. | ||||||
|  | 
 | ||||||
|  | ## Installation | ||||||
|  | 
 | ||||||
|  |     go get -u go.uber.org/multierr | ||||||
|  | 
 | ||||||
|  | ## Status | ||||||
|  | 
 | ||||||
|  | Stable: No breaking changes will be made before 2.0. | ||||||
|  | 
 | ||||||
|  | ------------------------------------------------------------------------------- | ||||||
|  | 
 | ||||||
|  | Released under the [MIT License]. | ||||||
|  | 
 | ||||||
|  | [MIT License]: LICENSE.txt | ||||||
|  | [doc-img]: https://godoc.org/go.uber.org/multierr?status.svg | ||||||
|  | [doc]: https://godoc.org/go.uber.org/multierr | ||||||
|  | [ci-img]: https://travis-ci.org/uber-go/multierr.svg?branch=master | ||||||
|  | [cov-img]: https://codecov.io/gh/uber-go/multierr/branch/master/graph/badge.svg | ||||||
|  | [ci]: https://travis-ci.org/uber-go/multierr | ||||||
|  | [cov]: https://codecov.io/gh/uber-go/multierr | ||||||
|  | @ -0,0 +1,401 @@ | ||||||
|  | // Copyright (c) 2017 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | // Package multierr allows combining one or more errors together.
 | ||||||
|  | //
 | ||||||
|  | // Overview
 | ||||||
|  | //
 | ||||||
|  | // Errors can be combined with the use of the Combine function.
 | ||||||
|  | //
 | ||||||
|  | // 	multierr.Combine(
 | ||||||
|  | // 		reader.Close(),
 | ||||||
|  | // 		writer.Close(),
 | ||||||
|  | // 		conn.Close(),
 | ||||||
|  | // 	)
 | ||||||
|  | //
 | ||||||
|  | // If only two errors are being combined, the Append function may be used
 | ||||||
|  | // instead.
 | ||||||
|  | //
 | ||||||
|  | // 	err = multierr.Combine(reader.Close(), writer.Close())
 | ||||||
|  | //
 | ||||||
|  | // This makes it possible to record resource cleanup failures from deferred
 | ||||||
|  | // blocks with the help of named return values.
 | ||||||
|  | //
 | ||||||
|  | // 	func sendRequest(req Request) (err error) {
 | ||||||
|  | // 		conn, err := openConnection()
 | ||||||
|  | // 		if err != nil {
 | ||||||
|  | // 			return err
 | ||||||
|  | // 		}
 | ||||||
|  | // 		defer func() {
 | ||||||
|  | // 			err = multierr.Append(err, conn.Close())
 | ||||||
|  | // 		}()
 | ||||||
|  | // 		// ...
 | ||||||
|  | // 	}
 | ||||||
|  | //
 | ||||||
|  | // The underlying list of errors for a returned error object may be retrieved
 | ||||||
|  | // with the Errors function.
 | ||||||
|  | //
 | ||||||
|  | // 	errors := multierr.Errors(err)
 | ||||||
|  | // 	if len(errors) > 0 {
 | ||||||
|  | // 		fmt.Println("The following errors occurred:")
 | ||||||
|  | // 	}
 | ||||||
|  | //
 | ||||||
|  | // Advanced Usage
 | ||||||
|  | //
 | ||||||
|  | // Errors returned by Combine and Append MAY implement the following
 | ||||||
|  | // interface.
 | ||||||
|  | //
 | ||||||
|  | // 	type errorGroup interface {
 | ||||||
|  | // 		// Returns a slice containing the underlying list of errors.
 | ||||||
|  | // 		//
 | ||||||
|  | // 		// This slice MUST NOT be modified by the caller.
 | ||||||
|  | // 		Errors() []error
 | ||||||
|  | // 	}
 | ||||||
|  | //
 | ||||||
|  | // Note that if you need access to list of errors behind a multierr error, you
 | ||||||
|  | // should prefer using the Errors function. That said, if you need cheap
 | ||||||
|  | // read-only access to the underlying errors slice, you can attempt to cast
 | ||||||
|  | // the error to this interface. You MUST handle the failure case gracefully
 | ||||||
|  | // because errors returned by Combine and Append are not guaranteed to
 | ||||||
|  | // implement this interface.
 | ||||||
|  | //
 | ||||||
|  | // 	var errors []error
 | ||||||
|  | // 	group, ok := err.(errorGroup)
 | ||||||
|  | // 	if ok {
 | ||||||
|  | // 		errors = group.Errors()
 | ||||||
|  | // 	} else {
 | ||||||
|  | // 		errors = []error{err}
 | ||||||
|  | // 	}
 | ||||||
|  | package multierr // import "go.uber.org/multierr"
 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/atomic" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	// Separator for single-line error messages.
 | ||||||
|  | 	_singlelineSeparator = []byte("; ") | ||||||
|  | 
 | ||||||
|  | 	_newline = []byte("\n") | ||||||
|  | 
 | ||||||
|  | 	// Prefix for multi-line messages
 | ||||||
|  | 	_multilinePrefix = []byte("the following errors occurred:") | ||||||
|  | 
 | ||||||
|  | 	// Prefix for the first and following lines of an item in a list of
 | ||||||
|  | 	// multi-line error messages.
 | ||||||
|  | 	//
 | ||||||
|  | 	// For example, if a single item is:
 | ||||||
|  | 	//
 | ||||||
|  | 	// 	foo
 | ||||||
|  | 	// 	bar
 | ||||||
|  | 	//
 | ||||||
|  | 	// It will become,
 | ||||||
|  | 	//
 | ||||||
|  | 	// 	 -  foo
 | ||||||
|  | 	// 	    bar
 | ||||||
|  | 	_multilineSeparator = []byte("\n -  ") | ||||||
|  | 	_multilineIndent    = []byte("    ") | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // _bufferPool is a pool of bytes.Buffers.
 | ||||||
|  | var _bufferPool = sync.Pool{ | ||||||
|  | 	New: func() interface{} { | ||||||
|  | 		return &bytes.Buffer{} | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type errorGroup interface { | ||||||
|  | 	Errors() []error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Errors returns a slice containing zero or more errors that the supplied
 | ||||||
|  | // error is composed of. If the error is nil, the returned slice is empty.
 | ||||||
|  | //
 | ||||||
|  | // 	err := multierr.Append(r.Close(), w.Close())
 | ||||||
|  | // 	errors := multierr.Errors(err)
 | ||||||
|  | //
 | ||||||
|  | // If the error is not composed of other errors, the returned slice contains
 | ||||||
|  | // just the error that was passed in.
 | ||||||
|  | //
 | ||||||
|  | // Callers of this function are free to modify the returned slice.
 | ||||||
|  | func Errors(err error) []error { | ||||||
|  | 	if err == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Note that we're casting to multiError, not errorGroup. Our contract is
 | ||||||
|  | 	// that returned errors MAY implement errorGroup. Errors, however, only
 | ||||||
|  | 	// has special behavior for multierr-specific error objects.
 | ||||||
|  | 	//
 | ||||||
|  | 	// This behavior can be expanded in the future but I think it's prudent to
 | ||||||
|  | 	// start with as little as possible in terms of contract and possibility
 | ||||||
|  | 	// of misuse.
 | ||||||
|  | 	eg, ok := err.(*multiError) | ||||||
|  | 	if !ok { | ||||||
|  | 		return []error{err} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	errors := eg.Errors() | ||||||
|  | 	result := make([]error, len(errors)) | ||||||
|  | 	copy(result, errors) | ||||||
|  | 	return result | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // multiError is an error that holds one or more errors.
 | ||||||
|  | //
 | ||||||
|  | // An instance of this is guaranteed to be non-empty and flattened. That is,
 | ||||||
|  | // none of the errors inside multiError are other multiErrors.
 | ||||||
|  | //
 | ||||||
|  | // multiError formats to a semi-colon delimited list of error messages with
 | ||||||
|  | // %v and with a more readable multi-line format with %+v.
 | ||||||
|  | type multiError struct { | ||||||
|  | 	copyNeeded atomic.Bool | ||||||
|  | 	errors     []error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ errorGroup = (*multiError)(nil) | ||||||
|  | 
 | ||||||
|  | // Errors returns the list of underlying errors.
 | ||||||
|  | //
 | ||||||
|  | // This slice MUST NOT be modified.
 | ||||||
|  | func (merr *multiError) Errors() []error { | ||||||
|  | 	if merr == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return merr.errors | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (merr *multiError) Error() string { | ||||||
|  | 	if merr == nil { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	buff := _bufferPool.Get().(*bytes.Buffer) | ||||||
|  | 	buff.Reset() | ||||||
|  | 
 | ||||||
|  | 	merr.writeSingleline(buff) | ||||||
|  | 
 | ||||||
|  | 	result := buff.String() | ||||||
|  | 	_bufferPool.Put(buff) | ||||||
|  | 	return result | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (merr *multiError) Format(f fmt.State, c rune) { | ||||||
|  | 	if c == 'v' && f.Flag('+') { | ||||||
|  | 		merr.writeMultiline(f) | ||||||
|  | 	} else { | ||||||
|  | 		merr.writeSingleline(f) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (merr *multiError) writeSingleline(w io.Writer) { | ||||||
|  | 	first := true | ||||||
|  | 	for _, item := range merr.errors { | ||||||
|  | 		if first { | ||||||
|  | 			first = false | ||||||
|  | 		} else { | ||||||
|  | 			w.Write(_singlelineSeparator) | ||||||
|  | 		} | ||||||
|  | 		io.WriteString(w, item.Error()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (merr *multiError) writeMultiline(w io.Writer) { | ||||||
|  | 	w.Write(_multilinePrefix) | ||||||
|  | 	for _, item := range merr.errors { | ||||||
|  | 		w.Write(_multilineSeparator) | ||||||
|  | 		writePrefixLine(w, _multilineIndent, fmt.Sprintf("%+v", item)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Writes s to the writer with the given prefix added before each line after
 | ||||||
|  | // the first.
 | ||||||
|  | func writePrefixLine(w io.Writer, prefix []byte, s string) { | ||||||
|  | 	first := true | ||||||
|  | 	for len(s) > 0 { | ||||||
|  | 		if first { | ||||||
|  | 			first = false | ||||||
|  | 		} else { | ||||||
|  | 			w.Write(prefix) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		idx := strings.IndexByte(s, '\n') | ||||||
|  | 		if idx < 0 { | ||||||
|  | 			idx = len(s) - 1 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		io.WriteString(w, s[:idx+1]) | ||||||
|  | 		s = s[idx+1:] | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type inspectResult struct { | ||||||
|  | 	// Number of top-level non-nil errors
 | ||||||
|  | 	Count int | ||||||
|  | 
 | ||||||
|  | 	// Total number of errors including multiErrors
 | ||||||
|  | 	Capacity int | ||||||
|  | 
 | ||||||
|  | 	// Index of the first non-nil error in the list. Value is meaningless if
 | ||||||
|  | 	// Count is zero.
 | ||||||
|  | 	FirstErrorIdx int | ||||||
|  | 
 | ||||||
|  | 	// Whether the list contains at least one multiError
 | ||||||
|  | 	ContainsMultiError bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Inspects the given slice of errors so that we can efficiently allocate
 | ||||||
|  | // space for it.
 | ||||||
|  | func inspect(errors []error) (res inspectResult) { | ||||||
|  | 	first := true | ||||||
|  | 	for i, err := range errors { | ||||||
|  | 		if err == nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		res.Count++ | ||||||
|  | 		if first { | ||||||
|  | 			first = false | ||||||
|  | 			res.FirstErrorIdx = i | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if merr, ok := err.(*multiError); ok { | ||||||
|  | 			res.Capacity += len(merr.errors) | ||||||
|  | 			res.ContainsMultiError = true | ||||||
|  | 		} else { | ||||||
|  | 			res.Capacity++ | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // fromSlice converts the given list of errors into a single error.
 | ||||||
|  | func fromSlice(errors []error) error { | ||||||
|  | 	res := inspect(errors) | ||||||
|  | 	switch res.Count { | ||||||
|  | 	case 0: | ||||||
|  | 		return nil | ||||||
|  | 	case 1: | ||||||
|  | 		// only one non-nil entry
 | ||||||
|  | 		return errors[res.FirstErrorIdx] | ||||||
|  | 	case len(errors): | ||||||
|  | 		if !res.ContainsMultiError { | ||||||
|  | 			// already flat
 | ||||||
|  | 			return &multiError{errors: errors} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	nonNilErrs := make([]error, 0, res.Capacity) | ||||||
|  | 	for _, err := range errors[res.FirstErrorIdx:] { | ||||||
|  | 		if err == nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if nested, ok := err.(*multiError); ok { | ||||||
|  | 			nonNilErrs = append(nonNilErrs, nested.errors...) | ||||||
|  | 		} else { | ||||||
|  | 			nonNilErrs = append(nonNilErrs, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &multiError{errors: nonNilErrs} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Combine combines the passed errors into a single error.
 | ||||||
|  | //
 | ||||||
|  | // If zero arguments were passed or if all items are nil, a nil error is
 | ||||||
|  | // returned.
 | ||||||
|  | //
 | ||||||
|  | // 	Combine(nil, nil)  // == nil
 | ||||||
|  | //
 | ||||||
|  | // If only a single error was passed, it is returned as-is.
 | ||||||
|  | //
 | ||||||
|  | // 	Combine(err)  // == err
 | ||||||
|  | //
 | ||||||
|  | // Combine skips over nil arguments so this function may be used to combine
 | ||||||
|  | // together errors from operations that fail independently of each other.
 | ||||||
|  | //
 | ||||||
|  | // 	multierr.Combine(
 | ||||||
|  | // 		reader.Close(),
 | ||||||
|  | // 		writer.Close(),
 | ||||||
|  | // 		pipe.Close(),
 | ||||||
|  | // 	)
 | ||||||
|  | //
 | ||||||
|  | // If any of the passed errors is a multierr error, it will be flattened along
 | ||||||
|  | // with the other errors.
 | ||||||
|  | //
 | ||||||
|  | // 	multierr.Combine(multierr.Combine(err1, err2), err3)
 | ||||||
|  | // 	// is the same as
 | ||||||
|  | // 	multierr.Combine(err1, err2, err3)
 | ||||||
|  | //
 | ||||||
|  | // The returned error formats into a readable multi-line error message if
 | ||||||
|  | // formatted with %+v.
 | ||||||
|  | //
 | ||||||
|  | // 	fmt.Sprintf("%+v", multierr.Combine(err1, err2))
 | ||||||
|  | func Combine(errors ...error) error { | ||||||
|  | 	return fromSlice(errors) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Append appends the given errors together. Either value may be nil.
 | ||||||
|  | //
 | ||||||
|  | // This function is a specialization of Combine for the common case where
 | ||||||
|  | // there are only two errors.
 | ||||||
|  | //
 | ||||||
|  | // 	err = multierr.Append(reader.Close(), writer.Close())
 | ||||||
|  | //
 | ||||||
|  | // The following pattern may also be used to record failure of deferred
 | ||||||
|  | // operations without losing information about the original error.
 | ||||||
|  | //
 | ||||||
|  | // 	func doSomething(..) (err error) {
 | ||||||
|  | // 		f := acquireResource()
 | ||||||
|  | // 		defer func() {
 | ||||||
|  | // 			err = multierr.Append(err, f.Close())
 | ||||||
|  | // 		}()
 | ||||||
|  | func Append(left error, right error) error { | ||||||
|  | 	switch { | ||||||
|  | 	case left == nil: | ||||||
|  | 		return right | ||||||
|  | 	case right == nil: | ||||||
|  | 		return left | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if _, ok := right.(*multiError); !ok { | ||||||
|  | 		if l, ok := left.(*multiError); ok && !l.copyNeeded.Swap(true) { | ||||||
|  | 			// Common case where the error on the left is constantly being
 | ||||||
|  | 			// appended to.
 | ||||||
|  | 			errs := append(l.errors, right) | ||||||
|  | 			return &multiError{errors: errs} | ||||||
|  | 		} else if !ok { | ||||||
|  | 			// Both errors are single errors.
 | ||||||
|  | 			return &multiError{errors: []error{left, right}} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Either right or both, left and right, are multiErrors. Rely on usual
 | ||||||
|  | 	// expensive logic.
 | ||||||
|  | 	errors := [2]error{left, right} | ||||||
|  | 	return fromSlice(errors[0:]) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | hash: b53b5e9a84b9cb3cc4b2d0499e23da2feca1eec318ce9bb717ecf35bf24bf221 | ||||||
|  | updated: 2017-04-10T13:34:45.671678062-07:00 | ||||||
|  | imports: | ||||||
|  | - name: go.uber.org/atomic | ||||||
|  |   version: 3b8db5e93c4c02efbc313e17b2e796b0914a01fb | ||||||
|  | testImports: | ||||||
|  | - name: github.com/davecgh/go-spew | ||||||
|  |   version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 | ||||||
|  |   subpackages: | ||||||
|  |   - spew | ||||||
|  | - name: github.com/pmezard/go-difflib | ||||||
|  |   version: d8ed2627bdf02c080bf22230dbb337003b7aba2d | ||||||
|  |   subpackages: | ||||||
|  |   - difflib | ||||||
|  | - name: github.com/stretchr/testify | ||||||
|  |   version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 | ||||||
|  |   subpackages: | ||||||
|  |   - assert | ||||||
|  |   - require | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | package: go.uber.org/multierr | ||||||
|  | import: | ||||||
|  | - package: go.uber.org/atomic | ||||||
|  |   version: ^1 | ||||||
|  | testImport: | ||||||
|  | - package: github.com/stretchr/testify | ||||||
|  |   subpackages: | ||||||
|  |   - assert | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | coverage: | ||||||
|  |   range: 80..100 | ||||||
|  |   round: down | ||||||
|  |   precision: 2 | ||||||
|  | 
 | ||||||
|  |   status: | ||||||
|  |     project:                   # measuring the overall project coverage | ||||||
|  |       default:                 # context, you can create multiple ones with custom titles | ||||||
|  |         enabled: yes           # must be yes|true to enable this status | ||||||
|  |         target: 95%            # specify the target coverage for each commit status | ||||||
|  |                                #   option: "auto" (must increase from parent commit or pull request base) | ||||||
|  |                                #   option: "X%" a static target percentage to hit | ||||||
|  |         if_not_found: success  # if parent is not found report status as success, error, or failure | ||||||
|  |         if_ci_failed: error    # if ci fails report status as success, error, or failure | ||||||
|  | ignore: | ||||||
|  |   - internal/readme/readme.go | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,28 @@ | ||||||
|  | # Compiled Object files, Static and Dynamic libs (Shared Objects) | ||||||
|  | *.o | ||||||
|  | *.a | ||||||
|  | *.so | ||||||
|  | 
 | ||||||
|  | # Folders | ||||||
|  | _obj | ||||||
|  | _test | ||||||
|  | vendor | ||||||
|  | 
 | ||||||
|  | # Architecture specific extensions/prefixes | ||||||
|  | *.[568vq] | ||||||
|  | [568vq].out | ||||||
|  | 
 | ||||||
|  | *.cgo1.go | ||||||
|  | *.cgo2.c | ||||||
|  | _cgo_defun.c | ||||||
|  | _cgo_gotypes.go | ||||||
|  | _cgo_export.* | ||||||
|  | 
 | ||||||
|  | _testmain.go | ||||||
|  | 
 | ||||||
|  | *.exe | ||||||
|  | *.test | ||||||
|  | *.prof | ||||||
|  | *.pprof | ||||||
|  | *.out | ||||||
|  | *.log | ||||||
|  | @ -0,0 +1,108 @@ | ||||||
|  | # :zap: zap [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] | ||||||
|  | 
 | ||||||
|  | Blazing fast, structured, leveled logging in Go. | ||||||
|  | 
 | ||||||
|  | ## Installation | ||||||
|  | 
 | ||||||
|  | `go get -u go.uber.org/zap` | ||||||
|  | 
 | ||||||
|  | Note that zap only supports the two most recent minor versions of Go. | ||||||
|  | 
 | ||||||
|  | ## Quick Start | ||||||
|  | 
 | ||||||
|  | In contexts where performance is nice, but not critical, use the | ||||||
|  | `SugaredLogger`. It's 4-10x faster than than other structured logging | ||||||
|  | packages and includes both structured and `printf`-style APIs. | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | logger, _ := zap.NewProduction() | ||||||
|  | defer logger.Sync() // flushes buffer, if any | ||||||
|  | sugar := logger.Sugar() | ||||||
|  | sugar.Infow("failed to fetch URL", | ||||||
|  |   // Structured context as loosely typed key-value pairs. | ||||||
|  |   "url", url, | ||||||
|  |   "attempt", 3, | ||||||
|  |   "backoff", time.Second, | ||||||
|  | ) | ||||||
|  | sugar.Infof("Failed to fetch URL: %s", url) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | When performance and type safety are critical, use the `Logger`. It's even | ||||||
|  | faster than the `SugaredLogger` and allocates far less, but it only supports | ||||||
|  | structured logging. | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | logger, _ := zap.NewProduction() | ||||||
|  | defer logger.Sync() | ||||||
|  | logger.Info("failed to fetch URL", | ||||||
|  |   // Structured context as strongly typed Field values. | ||||||
|  |   zap.String("url", url), | ||||||
|  |   zap.Int("attempt", 3), | ||||||
|  |   zap.Duration("backoff", time.Second), | ||||||
|  | ) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | See the [documentation][doc] and [FAQ](FAQ.md) for more details. | ||||||
|  | 
 | ||||||
|  | ## Performance | ||||||
|  | 
 | ||||||
|  | For applications that log in the hot path, reflection-based serialization and | ||||||
|  | string formatting are prohibitively expensive — they're CPU-intensive | ||||||
|  | and make many small allocations. Put differently, using `encoding/json` and | ||||||
|  | `fmt.Fprintf` to log tons of `interface{}`s makes your application slow. | ||||||
|  | 
 | ||||||
|  | Zap takes a different approach. It includes a reflection-free, zero-allocation | ||||||
|  | JSON encoder, and the base `Logger` strives to avoid serialization overhead | ||||||
|  | and allocations wherever possible. By building the high-level `SugaredLogger` | ||||||
|  | on that foundation, zap lets users *choose* when they need to count every | ||||||
|  | allocation and when they'd prefer a more familiar, loosely typed API. | ||||||
|  | 
 | ||||||
|  | As measured by its own [benchmarking suite][], not only is zap more performant | ||||||
|  | than comparable structured logging packages — it's also faster than the | ||||||
|  | standard library. Like all benchmarks, take these with a grain of salt.<sup | ||||||
|  | id="anchor-versions">[1](#footnote-versions)</sup> | ||||||
|  | 
 | ||||||
|  | Log a message and 10 fields: | ||||||
|  | 
 | ||||||
|  | {{.BenchmarkAddingFields}} | ||||||
|  | 
 | ||||||
|  | Log a message with a logger that already has 10 fields of context: | ||||||
|  | 
 | ||||||
|  | {{.BenchmarkAccumulatedContext}} | ||||||
|  | 
 | ||||||
|  | Log a static string, without any context or `printf`-style templating: | ||||||
|  | 
 | ||||||
|  | {{.BenchmarkWithoutFields}} | ||||||
|  | 
 | ||||||
|  | ## Development Status: Stable | ||||||
|  | 
 | ||||||
|  | All APIs are finalized, and no breaking changes will be made in the 1.x series | ||||||
|  | of releases. Users of semver-aware dependency management systems should pin | ||||||
|  | zap to `^1`. | ||||||
|  | 
 | ||||||
|  | ## Contributing | ||||||
|  | 
 | ||||||
|  | We encourage and support an active, healthy community of contributors — | ||||||
|  | including you! Details are in the [contribution guide](CONTRIBUTING.md) and | ||||||
|  | the [code of conduct](CODE_OF_CONDUCT.md). The zap maintainers keep an eye on | ||||||
|  | issues and pull requests, but you can also report any negative conduct to | ||||||
|  | oss-conduct@uber.com. That email list is a private, safe space; even the zap | ||||||
|  | maintainers don't have access, so don't hesitate to hold us to a high | ||||||
|  | standard. | ||||||
|  | 
 | ||||||
|  | <hr> | ||||||
|  | 
 | ||||||
|  | Released under the [MIT License](LICENSE.txt). | ||||||
|  | 
 | ||||||
|  | <sup id="footnote-versions">1</sup> In particular, keep in mind that we may be | ||||||
|  | benchmarking against slightly older versions of other packages. Versions are | ||||||
|  | pinned in zap's [glide.lock][] file. [↩](#anchor-versions) | ||||||
|  | 
 | ||||||
|  | [doc-img]: https://godoc.org/go.uber.org/zap?status.svg | ||||||
|  | [doc]: https://godoc.org/go.uber.org/zap | ||||||
|  | [ci-img]: https://travis-ci.org/uber-go/zap.svg?branch=master | ||||||
|  | [ci]: https://travis-ci.org/uber-go/zap | ||||||
|  | [cov-img]: https://codecov.io/gh/uber-go/zap/branch/master/graph/badge.svg | ||||||
|  | [cov]: https://codecov.io/gh/uber-go/zap | ||||||
|  | [benchmarking suite]: https://github.com/uber-go/zap/tree/master/benchmarks | ||||||
|  | [glide.lock]: https://github.com/uber-go/zap/blob/master/glide.lock | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | language: go | ||||||
|  | sudo: false | ||||||
|  | go: | ||||||
|  |   - "1.9" | ||||||
|  |   - "1.10" | ||||||
|  | go_import_path: go.uber.org/zap | ||||||
|  | env: | ||||||
|  |   global: | ||||||
|  |     - TEST_TIMEOUT_SCALE=10 | ||||||
|  | cache: | ||||||
|  |   directories: | ||||||
|  |     - vendor | ||||||
|  | install: | ||||||
|  |   - make dependencies | ||||||
|  | script: | ||||||
|  |   - make lint | ||||||
|  |   - make test | ||||||
|  |   - make bench | ||||||
|  | after_success: | ||||||
|  |   - make cover | ||||||
|  |   - bash <(curl -s https://codecov.io/bash) | ||||||
|  | @ -0,0 +1,286 @@ | ||||||
|  | # Changelog | ||||||
|  | 
 | ||||||
|  | ## v1.8.0 (13 Apr 2018) | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | * [#508][]: Make log level configurable when redirecting the standard | ||||||
|  |   library's logger. | ||||||
|  | * [#518][]: Add a logger that writes to a `*testing.TB`. | ||||||
|  | * [#577][]: Add a top-level alias for `zapcore.Field` to clean up GoDoc. | ||||||
|  | 
 | ||||||
|  | Bugfixes: | ||||||
|  | * [#574][]: Add a missing import comment to `go.uber.org/zap/buffer`. | ||||||
|  | 
 | ||||||
|  | Thanks to @DiSiqueira and @djui for their contributions to this release. | ||||||
|  | 
 | ||||||
|  | ## v1.7.1 (25 Sep 2017) | ||||||
|  | 
 | ||||||
|  | Bugfixes: | ||||||
|  | * [#504][]: Store strings when using AddByteString with the map encoder. | ||||||
|  | 
 | ||||||
|  | ## v1.7.0 (21 Sep 2017) | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | 
 | ||||||
|  | * [#487][]: Add `NewStdLogAt`, which extends `NewStdLog` by allowing the user | ||||||
|  |   to specify the level of the logged messages. | ||||||
|  | 
 | ||||||
|  | ## v1.6.0 (30 Aug 2017) | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | 
 | ||||||
|  | * [#491][]: Omit zap stack frames from stacktraces. | ||||||
|  | * [#490][]: Add a `ContextMap` method to observer logs for simpler | ||||||
|  |   field validation in tests. | ||||||
|  | 
 | ||||||
|  | ## v1.5.0 (22 Jul 2017) | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | 
 | ||||||
|  | * [#460][] and [#470][]: Support errors produced by `go.uber.org/multierr`. | ||||||
|  | * [#465][]: Support user-supplied encoders for logger names. | ||||||
|  | 
 | ||||||
|  | Bugfixes: | ||||||
|  | 
 | ||||||
|  | * [#477][]: Fix a bug that incorrectly truncated deep stacktraces. | ||||||
|  | 
 | ||||||
|  | Thanks to @richard-tunein and @pavius for their contributions to this release. | ||||||
|  | 
 | ||||||
|  | ## v1.4.1 (08 Jun 2017) | ||||||
|  | 
 | ||||||
|  | This release fixes two bugs. | ||||||
|  | 
 | ||||||
|  | Bugfixes: | ||||||
|  | 
 | ||||||
|  | * [#435][]: Support a variety of case conventions when unmarshaling levels. | ||||||
|  | * [#444][]: Fix a panic in the observer. | ||||||
|  | 
 | ||||||
|  | ## v1.4.0 (12 May 2017) | ||||||
|  | 
 | ||||||
|  | This release adds a few small features and is fully backward-compatible. | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | 
 | ||||||
|  | * [#424][]: Add a `LineEnding` field to `EncoderConfig`, allowing users to | ||||||
|  |   override the Unix-style default. | ||||||
|  | * [#425][]: Preserve time zones when logging times. | ||||||
|  | * [#431][]: Make `zap.AtomicLevel` implement `fmt.Stringer`, which makes a | ||||||
|  |   variety of operations a bit simpler. | ||||||
|  | 
 | ||||||
|  | ## v1.3.0 (25 Apr 2017) | ||||||
|  | 
 | ||||||
|  | This release adds an enhancement to zap's testing helpers as well as the | ||||||
|  | ability to marshal an AtomicLevel. It is fully backward-compatible. | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | 
 | ||||||
|  | * [#415][]: Add a substring-filtering helper to zap's observer. This is | ||||||
|  |   particularly useful when testing the `SugaredLogger`. | ||||||
|  | * [#416][]: Make `AtomicLevel` implement `encoding.TextMarshaler`. | ||||||
|  | 
 | ||||||
|  | ## v1.2.0 (13 Apr 2017) | ||||||
|  | 
 | ||||||
|  | This release adds a gRPC compatibility wrapper. It is fully backward-compatible. | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | 
 | ||||||
|  | * [#402][]: Add a `zapgrpc` package that wraps zap's Logger and implements | ||||||
|  |   `grpclog.Logger`. | ||||||
|  | 
 | ||||||
|  | ## v1.1.0 (31 Mar 2017) | ||||||
|  | 
 | ||||||
|  | This release fixes two bugs and adds some enhancements to zap's testing helpers. | ||||||
|  | It is fully backward-compatible. | ||||||
|  | 
 | ||||||
|  | Bugfixes: | ||||||
|  | 
 | ||||||
|  | * [#385][]: Fix caller path trimming on Windows. | ||||||
|  | * [#396][]: Fix a panic when attempting to use non-existent directories with | ||||||
|  |   zap's configuration struct. | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | 
 | ||||||
|  | * [#386][]: Add filtering helpers to zaptest's observing logger. | ||||||
|  | 
 | ||||||
|  | Thanks to @moitias for contributing to this release. | ||||||
|  | 
 | ||||||
|  | ## v1.0.0 (14 Mar 2017) | ||||||
|  | 
 | ||||||
|  | This is zap's first stable release. All exported APIs are now final, and no | ||||||
|  | further breaking changes will be made in the 1.x release series. Anyone using a | ||||||
|  | semver-aware dependency manager should now pin to `^1`. | ||||||
|  | 
 | ||||||
|  | Breaking changes: | ||||||
|  | 
 | ||||||
|  | * [#366][]: Add byte-oriented APIs to encoders to log UTF-8 encoded text without | ||||||
|  |   casting from `[]byte` to `string`. | ||||||
|  | * [#364][]: To support buffering outputs, add `Sync` methods to `zapcore.Core`, | ||||||
|  |   `zap.Logger`, and `zap.SugaredLogger`. | ||||||
|  | * [#371][]: Rename the `testutils` package to `zaptest`, which is less likely to | ||||||
|  |   clash with other testing helpers. | ||||||
|  | 
 | ||||||
|  | Bugfixes: | ||||||
|  | 
 | ||||||
|  | * [#362][]: Make the ISO8601 time formatters fixed-width, which is friendlier | ||||||
|  |   for tab-separated console output. | ||||||
|  | * [#369][]: Remove the automatic locks in `zapcore.NewCore`, which allows zap to | ||||||
|  |   work with concurrency-safe `WriteSyncer` implementations. | ||||||
|  | * [#347][]: Stop reporting errors when trying to `fsync` standard out on Linux | ||||||
|  |   systems. | ||||||
|  | * [#373][]: Report the correct caller from zap's standard library | ||||||
|  |   interoperability wrappers. | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | 
 | ||||||
|  | * [#348][]: Add a registry allowing third-party encodings to work with zap's | ||||||
|  |   built-in `Config`. | ||||||
|  | * [#327][]: Make the representation of logger callers configurable (like times, | ||||||
|  |   levels, and durations). | ||||||
|  | * [#376][]: Allow third-party encoders to use their own buffer pools, which | ||||||
|  |   removes the last performance advantage that zap's encoders have over plugins. | ||||||
|  | * [#346][]: Add `CombineWriteSyncers`, a convenience function to tee multiple | ||||||
|  |   `WriteSyncer`s and lock the result. | ||||||
|  | * [#365][]: Make zap's stacktraces compatible with mid-stack inlining (coming in | ||||||
|  |   Go 1.9). | ||||||
|  | * [#372][]: Export zap's observing logger as `zaptest/observer`. This makes it | ||||||
|  |   easier for particularly punctilious users to unit test their application's | ||||||
|  |   logging. | ||||||
|  | 
 | ||||||
|  | Thanks to @suyash, @htrendev, @flisky, @Ulexus, and @skipor for their | ||||||
|  | contributions to this release. | ||||||
|  | 
 | ||||||
|  | ## v1.0.0-rc.3 (7 Mar 2017) | ||||||
|  | 
 | ||||||
|  | This is the third release candidate for zap's stable release. There are no | ||||||
|  | breaking changes. | ||||||
|  | 
 | ||||||
|  | Bugfixes: | ||||||
|  | 
 | ||||||
|  | * [#339][]: Byte slices passed to `zap.Any` are now correctly treated as binary blobs | ||||||
|  |   rather than `[]uint8`. | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | 
 | ||||||
|  | * [#307][]: Users can opt into colored output for log levels. | ||||||
|  | * [#353][]: In addition to hijacking the output of the standard library's | ||||||
|  |   package-global logging functions, users can now construct a zap-backed | ||||||
|  |   `log.Logger` instance. | ||||||
|  | * [#311][]: Frames from common runtime functions and some of zap's internal | ||||||
|  |   machinery are now omitted from stacktraces. | ||||||
|  | 
 | ||||||
|  | Thanks to @ansel1 and @suyash for their contributions to this release. | ||||||
|  | 
 | ||||||
|  | ## v1.0.0-rc.2 (21 Feb 2017) | ||||||
|  | 
 | ||||||
|  | This is the second release candidate for zap's stable release. It includes two | ||||||
|  | breaking changes. | ||||||
|  | 
 | ||||||
|  | Breaking changes: | ||||||
|  | 
 | ||||||
|  | * [#316][]: Zap's global loggers are now fully concurrency-safe | ||||||
|  |   (previously, users had to ensure that `ReplaceGlobals` was called before the | ||||||
|  |   loggers were in use). However, they must now be accessed via the `L()` and | ||||||
|  |   `S()` functions. Users can update their projects with | ||||||
|  | 
 | ||||||
|  |   ``` | ||||||
|  |   gofmt -r "zap.L -> zap.L()" -w . | ||||||
|  |   gofmt -r "zap.S -> zap.S()" -w . | ||||||
|  |   ``` | ||||||
|  | * [#309][] and [#317][]: RC1 was mistakenly shipped with invalid | ||||||
|  |   JSON and YAML struct tags on all config structs. This release fixes the tags | ||||||
|  |   and adds static analysis to prevent similar bugs in the future. | ||||||
|  | 
 | ||||||
|  | Bugfixes: | ||||||
|  | 
 | ||||||
|  | * [#321][]: Redirecting the standard library's `log` output now | ||||||
|  |   correctly reports the logger's caller. | ||||||
|  | 
 | ||||||
|  | Enhancements: | ||||||
|  | 
 | ||||||
|  | * [#325][] and [#333][]: Zap now transparently supports non-standard, rich | ||||||
|  |   errors like those produced by `github.com/pkg/errors`. | ||||||
|  | * [#326][]: Though `New(nil)` continues to return a no-op logger, `NewNop()` is | ||||||
|  |   now preferred. Users can update their projects with `gofmt -r 'zap.New(nil) -> | ||||||
|  |   zap.NewNop()' -w .`. | ||||||
|  | * [#300][]: Incorrectly importing zap as `github.com/uber-go/zap` now returns a | ||||||
|  |   more informative error. | ||||||
|  | 
 | ||||||
|  | Thanks to @skipor and @chapsuk for their contributions to this release. | ||||||
|  | 
 | ||||||
|  | ## v1.0.0-rc.1 (14 Feb 2017) | ||||||
|  | 
 | ||||||
|  | This is the first release candidate for zap's stable release. There are multiple | ||||||
|  | breaking changes and improvements from the pre-release version. Most notably: | ||||||
|  | 
 | ||||||
|  | * **Zap's import path is now "go.uber.org/zap"** — all users will | ||||||
|  |   need to update their code. | ||||||
|  | * User-facing types and functions remain in the `zap` package. Code relevant | ||||||
|  |   largely to extension authors is now in the `zapcore` package. | ||||||
|  | * The `zapcore.Core` type makes it easy for third-party packages to use zap's | ||||||
|  |   internals but provide a different user-facing API. | ||||||
|  | * `Logger` is now a concrete type instead of an interface. | ||||||
|  | * A less verbose (though slower) logging API is included by default. | ||||||
|  | * Package-global loggers `L` and `S` are included. | ||||||
|  | * A human-friendly console encoder is included. | ||||||
|  | * A declarative config struct allows common logger configurations to be managed | ||||||
|  |   as configuration instead of code. | ||||||
|  | * Sampling is more accurate, and doesn't depend on the standard library's shared | ||||||
|  |   timer heap. | ||||||
|  | 
 | ||||||
|  | ## v0.1.0-beta.1 (6 Feb 2017) | ||||||
|  | 
 | ||||||
|  | This is a minor version, tagged to allow users to pin to the pre-1.0 APIs and | ||||||
|  | upgrade at their leisure. Since this is the first tagged release, there are no | ||||||
|  | backward compatibility concerns and all functionality is new. | ||||||
|  | 
 | ||||||
|  | Early zap adopters should pin to the 0.1.x minor version until they're ready to | ||||||
|  | upgrade to the upcoming stable release. | ||||||
|  | 
 | ||||||
|  | [#316]: https://github.com/uber-go/zap/pull/316 | ||||||
|  | [#309]: https://github.com/uber-go/zap/pull/309 | ||||||
|  | [#317]: https://github.com/uber-go/zap/pull/317 | ||||||
|  | [#321]: https://github.com/uber-go/zap/pull/321 | ||||||
|  | [#325]: https://github.com/uber-go/zap/pull/325 | ||||||
|  | [#333]: https://github.com/uber-go/zap/pull/333 | ||||||
|  | [#326]: https://github.com/uber-go/zap/pull/326 | ||||||
|  | [#300]: https://github.com/uber-go/zap/pull/300 | ||||||
|  | [#339]: https://github.com/uber-go/zap/pull/339 | ||||||
|  | [#307]: https://github.com/uber-go/zap/pull/307 | ||||||
|  | [#353]: https://github.com/uber-go/zap/pull/353 | ||||||
|  | [#311]: https://github.com/uber-go/zap/pull/311 | ||||||
|  | [#366]: https://github.com/uber-go/zap/pull/366 | ||||||
|  | [#364]: https://github.com/uber-go/zap/pull/364 | ||||||
|  | [#371]: https://github.com/uber-go/zap/pull/371 | ||||||
|  | [#362]: https://github.com/uber-go/zap/pull/362 | ||||||
|  | [#369]: https://github.com/uber-go/zap/pull/369 | ||||||
|  | [#347]: https://github.com/uber-go/zap/pull/347 | ||||||
|  | [#373]: https://github.com/uber-go/zap/pull/373 | ||||||
|  | [#348]: https://github.com/uber-go/zap/pull/348 | ||||||
|  | [#327]: https://github.com/uber-go/zap/pull/327 | ||||||
|  | [#376]: https://github.com/uber-go/zap/pull/376 | ||||||
|  | [#346]: https://github.com/uber-go/zap/pull/346 | ||||||
|  | [#365]: https://github.com/uber-go/zap/pull/365 | ||||||
|  | [#372]: https://github.com/uber-go/zap/pull/372 | ||||||
|  | [#385]: https://github.com/uber-go/zap/pull/385 | ||||||
|  | [#396]: https://github.com/uber-go/zap/pull/396 | ||||||
|  | [#386]: https://github.com/uber-go/zap/pull/386 | ||||||
|  | [#402]: https://github.com/uber-go/zap/pull/402 | ||||||
|  | [#415]: https://github.com/uber-go/zap/pull/415 | ||||||
|  | [#416]: https://github.com/uber-go/zap/pull/416 | ||||||
|  | [#424]: https://github.com/uber-go/zap/pull/424 | ||||||
|  | [#425]: https://github.com/uber-go/zap/pull/425 | ||||||
|  | [#431]: https://github.com/uber-go/zap/pull/431 | ||||||
|  | [#435]: https://github.com/uber-go/zap/pull/435 | ||||||
|  | [#444]: https://github.com/uber-go/zap/pull/444 | ||||||
|  | [#477]: https://github.com/uber-go/zap/pull/477 | ||||||
|  | [#465]: https://github.com/uber-go/zap/pull/465 | ||||||
|  | [#460]: https://github.com/uber-go/zap/pull/460 | ||||||
|  | [#470]: https://github.com/uber-go/zap/pull/470 | ||||||
|  | [#487]: https://github.com/uber-go/zap/pull/487 | ||||||
|  | [#490]: https://github.com/uber-go/zap/pull/490 | ||||||
|  | [#491]: https://github.com/uber-go/zap/pull/491 | ||||||
|  | [#504]: https://github.com/uber-go/zap/pull/504 | ||||||
|  | [#508]: https://github.com/uber-go/zap/pull/508 | ||||||
|  | [#518]: https://github.com/uber-go/zap/pull/518 | ||||||
|  | [#577]: https://github.com/uber-go/zap/pull/577 | ||||||
|  | [#574]: https://github.com/uber-go/zap/pull/574 | ||||||
|  | @ -0,0 +1,75 @@ | ||||||
|  | # Contributor Covenant Code of Conduct | ||||||
|  | 
 | ||||||
|  | ## Our Pledge | ||||||
|  | 
 | ||||||
|  | In the interest of fostering an open and welcoming environment, we as | ||||||
|  | contributors and maintainers pledge to making participation in our project and | ||||||
|  | our community a harassment-free experience for everyone, regardless of age, | ||||||
|  | body size, disability, ethnicity, gender identity and expression, level of | ||||||
|  | experience, nationality, personal appearance, race, religion, or sexual | ||||||
|  | identity and orientation. | ||||||
|  | 
 | ||||||
|  | ## Our Standards | ||||||
|  | 
 | ||||||
|  | Examples of behavior that contributes to creating a positive environment | ||||||
|  | include: | ||||||
|  | 
 | ||||||
|  | * Using welcoming and inclusive language | ||||||
|  | * Being respectful of differing viewpoints and experiences | ||||||
|  | * Gracefully accepting constructive criticism | ||||||
|  | * Focusing on what is best for the community | ||||||
|  | * Showing empathy towards other community members | ||||||
|  | 
 | ||||||
|  | Examples of unacceptable behavior by participants include: | ||||||
|  | 
 | ||||||
|  | * The use of sexualized language or imagery and unwelcome sexual attention or | ||||||
|  |   advances | ||||||
|  | * Trolling, insulting/derogatory comments, and personal or political attacks | ||||||
|  | * Public or private harassment | ||||||
|  | * Publishing others' private information, such as a physical or electronic | ||||||
|  |   address, without explicit permission | ||||||
|  | * Other conduct which could reasonably be considered inappropriate in a | ||||||
|  |   professional setting | ||||||
|  | 
 | ||||||
|  | ## Our Responsibilities | ||||||
|  | 
 | ||||||
|  | Project maintainers are responsible for clarifying the standards of acceptable | ||||||
|  | behavior and are expected to take appropriate and fair corrective action in | ||||||
|  | response to any instances of unacceptable behavior. | ||||||
|  | 
 | ||||||
|  | Project maintainers have the right and responsibility to remove, edit, or | ||||||
|  | reject comments, commits, code, wiki edits, issues, and other contributions | ||||||
|  | that are not aligned to this Code of Conduct, or to ban temporarily or | ||||||
|  | permanently any contributor for other behaviors that they deem inappropriate, | ||||||
|  | threatening, offensive, or harmful. | ||||||
|  | 
 | ||||||
|  | ## Scope | ||||||
|  | 
 | ||||||
|  | This Code of Conduct applies both within project spaces and in public spaces | ||||||
|  | when an individual is representing the project or its community. Examples of | ||||||
|  | representing a project or community include using an official project e-mail | ||||||
|  | address, posting via an official social media account, or acting as an | ||||||
|  | appointed representative at an online or offline event. Representation of a | ||||||
|  | project may be further defined and clarified by project maintainers. | ||||||
|  | 
 | ||||||
|  | ## Enforcement | ||||||
|  | 
 | ||||||
|  | Instances of abusive, harassing, or otherwise unacceptable behavior may be | ||||||
|  | reported by contacting the project team at oss-conduct@uber.com. The project | ||||||
|  | team will review and investigate all complaints, and will respond in a way | ||||||
|  | that it deems appropriate to the circumstances. The project team is obligated | ||||||
|  | to maintain confidentiality with regard to the reporter of an incident. | ||||||
|  | Further details of specific enforcement policies may be posted separately. | ||||||
|  | 
 | ||||||
|  | Project maintainers who do not follow or enforce the Code of Conduct in good | ||||||
|  | faith may face temporary or permanent repercussions as determined by other | ||||||
|  | members of the project's leadership. | ||||||
|  | 
 | ||||||
|  | ## Attribution | ||||||
|  | 
 | ||||||
|  | This Code of Conduct is adapted from the [Contributor Covenant][homepage], | ||||||
|  | version 1.4, available at | ||||||
|  | [http://contributor-covenant.org/version/1/4][version]. | ||||||
|  | 
 | ||||||
|  | [homepage]: http://contributor-covenant.org | ||||||
|  | [version]: http://contributor-covenant.org/version/1/4/ | ||||||
|  | @ -0,0 +1,81 @@ | ||||||
|  | # Contributing | ||||||
|  | 
 | ||||||
|  | We'd love your help making zap the very best structured logging library in Go! | ||||||
|  | 
 | ||||||
|  | If you'd like to add new exported APIs, please [open an issue][open-issue] | ||||||
|  | describing your proposal — discussing API changes ahead of time makes | ||||||
|  | pull request review much smoother. In your issue, pull request, and any other | ||||||
|  | communications, please remember to treat your fellow contributors with | ||||||
|  | respect! We take our [code of conduct](CODE_OF_CONDUCT.md) seriously. | ||||||
|  | 
 | ||||||
|  | Note that you'll need to sign [Uber's Contributor License Agreement][cla] | ||||||
|  | before we can accept any of your contributions. If necessary, a bot will remind | ||||||
|  | you to accept the CLA when you open your pull request. | ||||||
|  | 
 | ||||||
|  | ## Setup | ||||||
|  | 
 | ||||||
|  | [Fork][fork], then clone the repository: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | mkdir -p $GOPATH/src/go.uber.org | ||||||
|  | cd $GOPATH/src/go.uber.org | ||||||
|  | git clone git@github.com:your_github_username/zap.git | ||||||
|  | cd zap | ||||||
|  | git remote add upstream https://github.com/uber-go/zap.git | ||||||
|  | git fetch upstream | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Install zap's dependencies: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | make dependencies | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Make sure that the tests and the linters pass: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | make test | ||||||
|  | make lint | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | If you're not using the minor version of Go specified in the Makefile's | ||||||
|  | `LINTABLE_MINOR_VERSIONS` variable, `make lint` doesn't do anything. This is | ||||||
|  | fine, but it means that you'll only discover lint failures after you open your | ||||||
|  | pull request. | ||||||
|  | 
 | ||||||
|  | ## Making Changes | ||||||
|  | 
 | ||||||
|  | Start by creating a new branch for your changes: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | cd $GOPATH/src/go.uber.org/zap | ||||||
|  | git checkout master | ||||||
|  | git fetch upstream | ||||||
|  | git rebase upstream/master | ||||||
|  | git checkout -b cool_new_feature | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Make your changes, then ensure that `make lint` and `make test` still pass. If | ||||||
|  | you're satisfied with your changes, push them to your fork. | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | git push origin cool_new_feature | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Then use the GitHub UI to open a pull request. | ||||||
|  | 
 | ||||||
|  | At this point, you're waiting on us to review your changes. We *try* to respond | ||||||
|  | to issues and pull requests within a few business days, and we may suggest some | ||||||
|  | improvements or alternatives. Once your changes are approved, one of the | ||||||
|  | project maintainers will merge them. | ||||||
|  | 
 | ||||||
|  | We're much more likely to approve your changes if you: | ||||||
|  | 
 | ||||||
|  | * Add tests for new functionality. | ||||||
|  | * Write a [good commit message][commit-message]. | ||||||
|  | * Maintain backward compatibility. | ||||||
|  | 
 | ||||||
|  | [fork]: https://github.com/uber-go/zap/fork | ||||||
|  | [open-issue]: https://github.com/uber-go/zap/issues/new | ||||||
|  | [cla]: https://cla-assistant.io/uber-go/zap | ||||||
|  | [commit-message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html | ||||||
|  | @ -0,0 +1,154 @@ | ||||||
|  | # Frequently Asked Questions | ||||||
|  | 
 | ||||||
|  | ## Design | ||||||
|  | 
 | ||||||
|  | ### Why spend so much effort on logger performance? | ||||||
|  | 
 | ||||||
|  | Of course, most applications won't notice the impact of a slow logger: they | ||||||
|  | already take tens or hundreds of milliseconds for each operation, so an extra | ||||||
|  | millisecond doesn't matter. | ||||||
|  | 
 | ||||||
|  | On the other hand, why *not* make structured logging fast? The `SugaredLogger` | ||||||
|  | isn't any harder to use than other logging packages, and the `Logger` makes | ||||||
|  | structured logging possible in performance-sensitive contexts. Across a fleet | ||||||
|  | of Go microservices, making each application even slightly more efficient adds | ||||||
|  | up quickly. | ||||||
|  | 
 | ||||||
|  | ### Why aren't `Logger` and `SugaredLogger` interfaces? | ||||||
|  | 
 | ||||||
|  | Unlike the familiar `io.Writer` and `http.Handler`, `Logger` and | ||||||
|  | `SugaredLogger` interfaces would include *many* methods. As [Rob Pike points | ||||||
|  | out][go-proverbs], "The bigger the interface, the weaker the abstraction." | ||||||
|  | Interfaces are also rigid — *any* change requires releasing a new major | ||||||
|  | version, since it breaks all third-party implementations. | ||||||
|  | 
 | ||||||
|  | Making the `Logger` and `SugaredLogger` concrete types doesn't sacrifice much | ||||||
|  | abstraction, and it lets us add methods without introducing breaking changes. | ||||||
|  | Your applications should define and depend upon an interface that includes | ||||||
|  | just the methods you use. | ||||||
|  | 
 | ||||||
|  | ### Why sample application logs? | ||||||
|  | 
 | ||||||
|  | Applications often experience runs of errors, either because of a bug or | ||||||
|  | because of a misbehaving user. Logging errors is usually a good idea, but it | ||||||
|  | can easily make this bad situation worse: not only is your application coping | ||||||
|  | with a flood of errors, it's also spending extra CPU cycles and I/O logging | ||||||
|  | those errors. Since writes are typically serialized, logging limits throughput | ||||||
|  | when you need it most. | ||||||
|  | 
 | ||||||
|  | Sampling fixes this problem by dropping repetitive log entries. Under normal | ||||||
|  | conditions, your application writes out every entry. When similar entries are | ||||||
|  | logged hundreds or thousands of times each second, though, zap begins dropping | ||||||
|  | duplicates to preserve throughput. | ||||||
|  | 
 | ||||||
|  | ### Why do the structured logging APIs take a message in addition to fields? | ||||||
|  | 
 | ||||||
|  | Subjectively, we find it helpful to accompany structured context with a brief | ||||||
|  | description. This isn't critical during development, but it makes debugging | ||||||
|  | and operating unfamiliar systems much easier. | ||||||
|  | 
 | ||||||
|  | More concretely, zap's sampling algorithm uses the message to identify | ||||||
|  | duplicate entries. In our experience, this is a practical middle ground | ||||||
|  | between random sampling (which often drops the exact entry that you need while | ||||||
|  | debugging) and hashing the complete entry (which is prohibitively expensive). | ||||||
|  | 
 | ||||||
|  | ### Why include package-global loggers? | ||||||
|  | 
 | ||||||
|  | Since so many other logging packages include a global logger, many | ||||||
|  | applications aren't designed to accept loggers as explicit parameters. | ||||||
|  | Changing function signatures is often a breaking change, so zap includes | ||||||
|  | global loggers to simplify migration. | ||||||
|  | 
 | ||||||
|  | Avoid them where possible. | ||||||
|  | 
 | ||||||
|  | ### Why include dedicated Panic and Fatal log levels? | ||||||
|  | 
 | ||||||
|  | In general, application code should handle errors gracefully instead of using | ||||||
|  | `panic` or `os.Exit`. However, every rule has exceptions, and it's common to | ||||||
|  | crash when an error is truly unrecoverable. To avoid losing any information | ||||||
|  | — especially the reason for the crash — the logger must flush any | ||||||
|  | buffered entries before the process exits. | ||||||
|  | 
 | ||||||
|  | Zap makes this easy by offering `Panic` and `Fatal` logging methods that | ||||||
|  | automatically flush before exiting. Of course, this doesn't guarantee that | ||||||
|  | logs will never be lost, but it eliminates a common error. | ||||||
|  | 
 | ||||||
|  | See the discussion in uber-go/zap#207 for more details. | ||||||
|  | 
 | ||||||
|  | ### What's `DPanic`? | ||||||
|  | 
 | ||||||
|  | `DPanic` stands for "panic in development." In development, it logs at | ||||||
|  | `PanicLevel`; otherwise, it logs at `ErrorLevel`. `DPanic` makes it easier to | ||||||
|  | catch errors that are theoretically possible, but shouldn't actually happen, | ||||||
|  | *without* crashing in production. | ||||||
|  | 
 | ||||||
|  | If you've ever written code like this, you need `DPanic`: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | if err != nil { | ||||||
|  |   panic(fmt.Sprintf("shouldn't ever get here: %v", err)) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Installation | ||||||
|  | 
 | ||||||
|  | ### What does the error `expects import "go.uber.org/zap"` mean? | ||||||
|  | 
 | ||||||
|  | Either zap was installed incorrectly or you're referencing the wrong package | ||||||
|  | name in your code. | ||||||
|  | 
 | ||||||
|  | Zap's source code happens to be hosted on GitHub, but the [import | ||||||
|  | path][import-path] is `go.uber.org/zap`. This gives us, the project | ||||||
|  | maintainers, the freedom to move the source code if necessary. However, it | ||||||
|  | means that you need to take a little care when installing and using the | ||||||
|  | package. | ||||||
|  | 
 | ||||||
|  | If you follow two simple rules, everything should work: install zap with `go | ||||||
|  | get -u go.uber.org/zap`, and always import it in your code with `import | ||||||
|  | "go.uber.org/zap"`. Your code shouldn't contain *any* references to | ||||||
|  | `github.com/uber-go/zap`. | ||||||
|  | 
 | ||||||
|  | ## Usage | ||||||
|  | 
 | ||||||
|  | ### Does zap support log rotation? | ||||||
|  | 
 | ||||||
|  | Zap doesn't natively support rotating log files, since we prefer to leave this | ||||||
|  | to an external program like `logrotate`. | ||||||
|  | 
 | ||||||
|  | However, it's easy to integrate a log rotation package like | ||||||
|  | [`gopkg.in/natefinch/lumberjack.v2`][lumberjack] as a `zapcore.WriteSyncer`. | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | // lumberjack.Logger is already safe for concurrent use, so we don't need to | ||||||
|  | // lock it. | ||||||
|  | w := zapcore.AddSync(&lumberjack.Logger{ | ||||||
|  |   Filename:   "/var/log/myapp/foo.log", | ||||||
|  |   MaxSize:    500, // megabytes | ||||||
|  |   MaxBackups: 3, | ||||||
|  |   MaxAge:     28, // days | ||||||
|  | }) | ||||||
|  | core := zapcore.NewCore( | ||||||
|  |   zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), | ||||||
|  |   w, | ||||||
|  |   zap.InfoLevel, | ||||||
|  | ) | ||||||
|  | logger := zap.New(core) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Extensions | ||||||
|  | 
 | ||||||
|  | We'd love to support every logging need within zap itself, but we're only | ||||||
|  | familiar with a handful of log ingestion systems, flag-parsing packages, and | ||||||
|  | the like. Rather than merging code that we can't effectively debug and | ||||||
|  | support, we'd rather grow an ecosystem of zap extensions. | ||||||
|  | 
 | ||||||
|  | We're aware of the following extensions, but haven't used them ourselves: | ||||||
|  | 
 | ||||||
|  | | Package | Integration | | ||||||
|  | | --- | --- | | ||||||
|  | | `github.com/tchap/zapext` | Sentry, syslog | | ||||||
|  | | `github.com/fgrosse/zaptest` | Ginkgo | | ||||||
|  | 
 | ||||||
|  | [go-proverbs]: https://go-proverbs.github.io/ | ||||||
|  | [import-path]: https://golang.org/cmd/go/#hdr-Remote_import_paths | ||||||
|  | [lumberjack]: https://godoc.org/gopkg.in/natefinch/lumberjack.v2 | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | Copyright (c) 2016-2017 Uber Technologies, Inc. | ||||||
|  | 
 | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | in the Software without restriction, including without limitation the rights | ||||||
|  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | copies of the Software, and to permit persons to whom the Software is | ||||||
|  | furnished to do so, subject to the following conditions: | ||||||
|  | 
 | ||||||
|  | The above copyright notice and this permission notice shall be included in | ||||||
|  | all copies or substantial portions of the Software. | ||||||
|  | 
 | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  | THE SOFTWARE. | ||||||
|  | @ -0,0 +1,76 @@ | ||||||
|  | export GO15VENDOREXPERIMENT=1 | ||||||
|  | 
 | ||||||
|  | BENCH_FLAGS ?= -cpuprofile=cpu.pprof -memprofile=mem.pprof -benchmem | ||||||
|  | PKGS ?= $(shell glide novendor) | ||||||
|  | # Many Go tools take file globs or directories as arguments instead of packages.
 | ||||||
|  | PKG_FILES ?= *.go zapcore benchmarks buffer zapgrpc zaptest zaptest/observer internal/bufferpool internal/exit internal/color internal/ztest | ||||||
|  | 
 | ||||||
|  | # The linting tools evolve with each Go version, so run them only on the latest
 | ||||||
|  | # stable release.
 | ||||||
|  | GO_VERSION := $(shell go version | cut -d " " -f 3) | ||||||
|  | GO_MINOR_VERSION := $(word 2,$(subst ., ,$(GO_VERSION))) | ||||||
|  | LINTABLE_MINOR_VERSIONS := 10 | ||||||
|  | ifneq ($(filter $(LINTABLE_MINOR_VERSIONS),$(GO_MINOR_VERSION)),) | ||||||
|  | SHOULD_LINT := true | ||||||
|  | endif | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .PHONY: all | ||||||
|  | all: lint test | ||||||
|  | 
 | ||||||
|  | .PHONY: dependencies | ||||||
|  | dependencies: | ||||||
|  | 	@echo "Installing Glide and locked dependencies..." | ||||||
|  | 	glide --version || go get -u -f github.com/Masterminds/glide | ||||||
|  | 	glide install | ||||||
|  | 	@echo "Installing test dependencies..." | ||||||
|  | 	go install ./vendor/github.com/axw/gocov/gocov | ||||||
|  | 	go install ./vendor/github.com/mattn/goveralls | ||||||
|  | ifdef SHOULD_LINT | ||||||
|  | 	@echo "Installing golint..." | ||||||
|  | 	go install ./vendor/github.com/golang/lint/golint | ||||||
|  | else | ||||||
|  | 	@echo "Not installing golint, since we don't expect to lint on" $(GO_VERSION) | ||||||
|  | endif | ||||||
|  | 
 | ||||||
|  | # Disable printf-like invocation checking due to testify.assert.Error()
 | ||||||
|  | VET_RULES := -printf=false | ||||||
|  | 
 | ||||||
|  | .PHONY: lint | ||||||
|  | lint: | ||||||
|  | ifdef SHOULD_LINT | ||||||
|  | 	@rm -rf lint.log | ||||||
|  | 	@echo "Checking formatting..." | ||||||
|  | 	@gofmt -d -s $(PKG_FILES) 2>&1 | tee lint.log | ||||||
|  | 	@echo "Installing test dependencies for vet..." | ||||||
|  | 	@go test -i $(PKGS) | ||||||
|  | 	@echo "Checking vet..." | ||||||
|  | 	@$(foreach dir,$(PKG_FILES),go tool vet $(VET_RULES) $(dir) 2>&1 | tee -a lint.log;) | ||||||
|  | 	@echo "Checking lint..." | ||||||
|  | 	@$(foreach dir,$(PKGS),golint $(dir) 2>&1 | tee -a lint.log;) | ||||||
|  | 	@echo "Checking for unresolved FIXMEs..." | ||||||
|  | 	@git grep -i fixme | grep -v -e vendor -e Makefile | tee -a lint.log | ||||||
|  | 	@echo "Checking for license headers..." | ||||||
|  | 	@./check_license.sh | tee -a lint.log | ||||||
|  | 	@[ ! -s lint.log ] | ||||||
|  | else | ||||||
|  | 	@echo "Skipping linters on" $(GO_VERSION) | ||||||
|  | endif | ||||||
|  | 
 | ||||||
|  | .PHONY: test | ||||||
|  | test: | ||||||
|  | 	go test -race $(PKGS) | ||||||
|  | 
 | ||||||
|  | .PHONY: cover | ||||||
|  | cover: | ||||||
|  | 	./scripts/cover.sh $(PKGS) | ||||||
|  | 
 | ||||||
|  | .PHONY: bench | ||||||
|  | BENCH ?= . | ||||||
|  | bench: | ||||||
|  | 	@$(foreach pkg,$(PKGS),go test -bench=$(BENCH) -run="^$$" $(BENCH_FLAGS) $(pkg);) | ||||||
|  | 
 | ||||||
|  | .PHONY: updatereadme | ||||||
|  | updatereadme: | ||||||
|  | 	rm -f README.md | ||||||
|  | 	cat .readme.tmpl | go run internal/readme/readme.go > README.md | ||||||
|  | @ -0,0 +1,136 @@ | ||||||
|  | # :zap: zap [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] | ||||||
|  | 
 | ||||||
|  | Blazing fast, structured, leveled logging in Go. | ||||||
|  | 
 | ||||||
|  | ## Installation | ||||||
|  | 
 | ||||||
|  | `go get -u go.uber.org/zap` | ||||||
|  | 
 | ||||||
|  | Note that zap only supports the two most recent minor versions of Go. | ||||||
|  | 
 | ||||||
|  | ## Quick Start | ||||||
|  | 
 | ||||||
|  | In contexts where performance is nice, but not critical, use the | ||||||
|  | `SugaredLogger`. It's 4-10x faster than than other structured logging | ||||||
|  | packages and includes both structured and `printf`-style APIs. | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | logger, _ := zap.NewProduction() | ||||||
|  | defer logger.Sync() // flushes buffer, if any | ||||||
|  | sugar := logger.Sugar() | ||||||
|  | sugar.Infow("failed to fetch URL", | ||||||
|  |   // Structured context as loosely typed key-value pairs. | ||||||
|  |   "url", url, | ||||||
|  |   "attempt", 3, | ||||||
|  |   "backoff", time.Second, | ||||||
|  | ) | ||||||
|  | sugar.Infof("Failed to fetch URL: %s", url) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | When performance and type safety are critical, use the `Logger`. It's even | ||||||
|  | faster than the `SugaredLogger` and allocates far less, but it only supports | ||||||
|  | structured logging. | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | logger, _ := zap.NewProduction() | ||||||
|  | defer logger.Sync() | ||||||
|  | logger.Info("failed to fetch URL", | ||||||
|  |   // Structured context as strongly typed Field values. | ||||||
|  |   zap.String("url", url), | ||||||
|  |   zap.Int("attempt", 3), | ||||||
|  |   zap.Duration("backoff", time.Second), | ||||||
|  | ) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | See the [documentation][doc] and [FAQ](FAQ.md) for more details. | ||||||
|  | 
 | ||||||
|  | ## Performance | ||||||
|  | 
 | ||||||
|  | For applications that log in the hot path, reflection-based serialization and | ||||||
|  | string formatting are prohibitively expensive — they're CPU-intensive | ||||||
|  | and make many small allocations. Put differently, using `encoding/json` and | ||||||
|  | `fmt.Fprintf` to log tons of `interface{}`s makes your application slow. | ||||||
|  | 
 | ||||||
|  | Zap takes a different approach. It includes a reflection-free, zero-allocation | ||||||
|  | JSON encoder, and the base `Logger` strives to avoid serialization overhead | ||||||
|  | and allocations wherever possible. By building the high-level `SugaredLogger` | ||||||
|  | on that foundation, zap lets users *choose* when they need to count every | ||||||
|  | allocation and when they'd prefer a more familiar, loosely typed API. | ||||||
|  | 
 | ||||||
|  | As measured by its own [benchmarking suite][], not only is zap more performant | ||||||
|  | than comparable structured logging packages — it's also faster than the | ||||||
|  | standard library. Like all benchmarks, take these with a grain of salt.<sup | ||||||
|  | id="anchor-versions">[1](#footnote-versions)</sup> | ||||||
|  | 
 | ||||||
|  | Log a message and 10 fields: | ||||||
|  | 
 | ||||||
|  | | Package | Time | Objects Allocated | | ||||||
|  | | :--- | :---: | :---: | | ||||||
|  | | :zap: zap | 3131 ns/op | 5 allocs/op | | ||||||
|  | | :zap: zap (sugared) | 4173 ns/op | 21 allocs/op | | ||||||
|  | | zerolog | 16154 ns/op | 90 allocs/op | | ||||||
|  | | lion | 16341 ns/op | 111 allocs/op | | ||||||
|  | | go-kit | 17049 ns/op | 126 allocs/op | | ||||||
|  | | logrus | 23662 ns/op | 142 allocs/op | | ||||||
|  | | log15 | 36351 ns/op | 149 allocs/op | | ||||||
|  | | apex/log | 42530 ns/op | 126 allocs/op | | ||||||
|  | 
 | ||||||
|  | Log a message with a logger that already has 10 fields of context: | ||||||
|  | 
 | ||||||
|  | | Package | Time | Objects Allocated | | ||||||
|  | | :--- | :---: | :---: | | ||||||
|  | | :zap: zap | 380 ns/op | 0 allocs/op | | ||||||
|  | | :zap: zap (sugared) | 564 ns/op | 2 allocs/op | | ||||||
|  | | zerolog | 321 ns/op | 0 allocs/op | | ||||||
|  | | lion | 7092 ns/op | 39 allocs/op | | ||||||
|  | | go-kit | 20226 ns/op | 115 allocs/op | | ||||||
|  | | logrus | 22312 ns/op | 130 allocs/op | | ||||||
|  | | log15 | 28788 ns/op | 79 allocs/op | | ||||||
|  | | apex/log | 42063 ns/op | 115 allocs/op | | ||||||
|  | 
 | ||||||
|  | Log a static string, without any context or `printf`-style templating: | ||||||
|  | 
 | ||||||
|  | | Package | Time | Objects Allocated | | ||||||
|  | | :--- | :---: | :---: | | ||||||
|  | | :zap: zap | 361 ns/op | 0 allocs/op | | ||||||
|  | | :zap: zap (sugared) | 534 ns/op | 2 allocs/op | | ||||||
|  | | zerolog | 323 ns/op | 0 allocs/op | | ||||||
|  | | standard library | 575 ns/op | 2 allocs/op | | ||||||
|  | | go-kit | 922 ns/op | 13 allocs/op | | ||||||
|  | | lion | 1413 ns/op | 10 allocs/op | | ||||||
|  | | logrus | 2291 ns/op | 27 allocs/op | | ||||||
|  | | apex/log | 3690 ns/op | 11 allocs/op | | ||||||
|  | | log15 | 5954 ns/op | 26 allocs/op | | ||||||
|  | 
 | ||||||
|  | ## Development Status: Stable | ||||||
|  | 
 | ||||||
|  | All APIs are finalized, and no breaking changes will be made in the 1.x series | ||||||
|  | of releases. Users of semver-aware dependency management systems should pin | ||||||
|  | zap to `^1`. | ||||||
|  | 
 | ||||||
|  | ## Contributing | ||||||
|  | 
 | ||||||
|  | We encourage and support an active, healthy community of contributors — | ||||||
|  | including you! Details are in the [contribution guide](CONTRIBUTING.md) and | ||||||
|  | the [code of conduct](CODE_OF_CONDUCT.md). The zap maintainers keep an eye on | ||||||
|  | issues and pull requests, but you can also report any negative conduct to | ||||||
|  | oss-conduct@uber.com. That email list is a private, safe space; even the zap | ||||||
|  | maintainers don't have access, so don't hesitate to hold us to a high | ||||||
|  | standard. | ||||||
|  | 
 | ||||||
|  | <hr> | ||||||
|  | 
 | ||||||
|  | Released under the [MIT License](LICENSE.txt). | ||||||
|  | 
 | ||||||
|  | <sup id="footnote-versions">1</sup> In particular, keep in mind that we may be | ||||||
|  | benchmarking against slightly older versions of other packages. Versions are | ||||||
|  | pinned in zap's [glide.lock][] file. [↩](#anchor-versions) | ||||||
|  | 
 | ||||||
|  | [doc-img]: https://godoc.org/go.uber.org/zap?status.svg | ||||||
|  | [doc]: https://godoc.org/go.uber.org/zap | ||||||
|  | [ci-img]: https://travis-ci.org/uber-go/zap.svg?branch=master | ||||||
|  | [ci]: https://travis-ci.org/uber-go/zap | ||||||
|  | [cov-img]: https://codecov.io/gh/uber-go/zap/branch/master/graph/badge.svg | ||||||
|  | [cov]: https://codecov.io/gh/uber-go/zap | ||||||
|  | [benchmarking suite]: https://github.com/uber-go/zap/tree/master/benchmarks | ||||||
|  | [glide.lock]: https://github.com/uber-go/zap/blob/master/glide.lock | ||||||
|  | @ -0,0 +1,320 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Array constructs a field with the given key and ArrayMarshaler. It provides
 | ||||||
|  | // a flexible, but still type-safe and efficient, way to add array-like types
 | ||||||
|  | // to the logging context. The struct's MarshalLogArray method is called lazily.
 | ||||||
|  | func Array(key string, val zapcore.ArrayMarshaler) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.ArrayMarshalerType, Interface: val} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Bools constructs a field that carries a slice of bools.
 | ||||||
|  | func Bools(key string, bs []bool) Field { | ||||||
|  | 	return Array(key, bools(bs)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ByteStrings constructs a field that carries a slice of []byte, each of which
 | ||||||
|  | // must be UTF-8 encoded text.
 | ||||||
|  | func ByteStrings(key string, bss [][]byte) Field { | ||||||
|  | 	return Array(key, byteStringsArray(bss)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Complex128s constructs a field that carries a slice of complex numbers.
 | ||||||
|  | func Complex128s(key string, nums []complex128) Field { | ||||||
|  | 	return Array(key, complex128s(nums)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Complex64s constructs a field that carries a slice of complex numbers.
 | ||||||
|  | func Complex64s(key string, nums []complex64) Field { | ||||||
|  | 	return Array(key, complex64s(nums)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Durations constructs a field that carries a slice of time.Durations.
 | ||||||
|  | func Durations(key string, ds []time.Duration) Field { | ||||||
|  | 	return Array(key, durations(ds)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Float64s constructs a field that carries a slice of floats.
 | ||||||
|  | func Float64s(key string, nums []float64) Field { | ||||||
|  | 	return Array(key, float64s(nums)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Float32s constructs a field that carries a slice of floats.
 | ||||||
|  | func Float32s(key string, nums []float32) Field { | ||||||
|  | 	return Array(key, float32s(nums)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Ints constructs a field that carries a slice of integers.
 | ||||||
|  | func Ints(key string, nums []int) Field { | ||||||
|  | 	return Array(key, ints(nums)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Int64s constructs a field that carries a slice of integers.
 | ||||||
|  | func Int64s(key string, nums []int64) Field { | ||||||
|  | 	return Array(key, int64s(nums)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Int32s constructs a field that carries a slice of integers.
 | ||||||
|  | func Int32s(key string, nums []int32) Field { | ||||||
|  | 	return Array(key, int32s(nums)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Int16s constructs a field that carries a slice of integers.
 | ||||||
|  | func Int16s(key string, nums []int16) Field { | ||||||
|  | 	return Array(key, int16s(nums)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Int8s constructs a field that carries a slice of integers.
 | ||||||
|  | func Int8s(key string, nums []int8) Field { | ||||||
|  | 	return Array(key, int8s(nums)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Strings constructs a field that carries a slice of strings.
 | ||||||
|  | func Strings(key string, ss []string) Field { | ||||||
|  | 	return Array(key, stringArray(ss)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Times constructs a field that carries a slice of time.Times.
 | ||||||
|  | func Times(key string, ts []time.Time) Field { | ||||||
|  | 	return Array(key, times(ts)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Uints constructs a field that carries a slice of unsigned integers.
 | ||||||
|  | func Uints(key string, nums []uint) Field { | ||||||
|  | 	return Array(key, uints(nums)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Uint64s constructs a field that carries a slice of unsigned integers.
 | ||||||
|  | func Uint64s(key string, nums []uint64) Field { | ||||||
|  | 	return Array(key, uint64s(nums)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Uint32s constructs a field that carries a slice of unsigned integers.
 | ||||||
|  | func Uint32s(key string, nums []uint32) Field { | ||||||
|  | 	return Array(key, uint32s(nums)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Uint16s constructs a field that carries a slice of unsigned integers.
 | ||||||
|  | func Uint16s(key string, nums []uint16) Field { | ||||||
|  | 	return Array(key, uint16s(nums)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Uint8s constructs a field that carries a slice of unsigned integers.
 | ||||||
|  | func Uint8s(key string, nums []uint8) Field { | ||||||
|  | 	return Array(key, uint8s(nums)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Uintptrs constructs a field that carries a slice of pointer addresses.
 | ||||||
|  | func Uintptrs(key string, us []uintptr) Field { | ||||||
|  | 	return Array(key, uintptrs(us)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Errors constructs a field that carries a slice of errors.
 | ||||||
|  | func Errors(key string, errs []error) Field { | ||||||
|  | 	return Array(key, errArray(errs)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type bools []bool | ||||||
|  | 
 | ||||||
|  | func (bs bools) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range bs { | ||||||
|  | 		arr.AppendBool(bs[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type byteStringsArray [][]byte | ||||||
|  | 
 | ||||||
|  | func (bss byteStringsArray) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range bss { | ||||||
|  | 		arr.AppendByteString(bss[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type complex128s []complex128 | ||||||
|  | 
 | ||||||
|  | func (nums complex128s) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendComplex128(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type complex64s []complex64 | ||||||
|  | 
 | ||||||
|  | func (nums complex64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendComplex64(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type durations []time.Duration | ||||||
|  | 
 | ||||||
|  | func (ds durations) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range ds { | ||||||
|  | 		arr.AppendDuration(ds[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type float64s []float64 | ||||||
|  | 
 | ||||||
|  | func (nums float64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendFloat64(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type float32s []float32 | ||||||
|  | 
 | ||||||
|  | func (nums float32s) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendFloat32(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ints []int | ||||||
|  | 
 | ||||||
|  | func (nums ints) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendInt(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type int64s []int64 | ||||||
|  | 
 | ||||||
|  | func (nums int64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendInt64(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type int32s []int32 | ||||||
|  | 
 | ||||||
|  | func (nums int32s) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendInt32(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type int16s []int16 | ||||||
|  | 
 | ||||||
|  | func (nums int16s) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendInt16(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type int8s []int8 | ||||||
|  | 
 | ||||||
|  | func (nums int8s) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendInt8(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type stringArray []string | ||||||
|  | 
 | ||||||
|  | func (ss stringArray) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range ss { | ||||||
|  | 		arr.AppendString(ss[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type times []time.Time | ||||||
|  | 
 | ||||||
|  | func (ts times) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range ts { | ||||||
|  | 		arr.AppendTime(ts[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type uints []uint | ||||||
|  | 
 | ||||||
|  | func (nums uints) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendUint(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type uint64s []uint64 | ||||||
|  | 
 | ||||||
|  | func (nums uint64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendUint64(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type uint32s []uint32 | ||||||
|  | 
 | ||||||
|  | func (nums uint32s) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendUint32(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type uint16s []uint16 | ||||||
|  | 
 | ||||||
|  | func (nums uint16s) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendUint16(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type uint8s []uint8 | ||||||
|  | 
 | ||||||
|  | func (nums uint8s) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendUint8(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type uintptrs []uintptr | ||||||
|  | 
 | ||||||
|  | func (nums uintptrs) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range nums { | ||||||
|  | 		arr.AppendUintptr(nums[i]) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,106 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | // Package buffer provides a thin wrapper around a byte slice. Unlike the
 | ||||||
|  | // standard library's bytes.Buffer, it supports a portion of the strconv
 | ||||||
|  | // package's zero-allocation formatters.
 | ||||||
|  | package buffer // import "go.uber.org/zap/buffer"
 | ||||||
|  | 
 | ||||||
|  | import "strconv" | ||||||
|  | 
 | ||||||
|  | const _size = 1024 // by default, create 1 KiB buffers
 | ||||||
|  | 
 | ||||||
|  | // Buffer is a thin wrapper around a byte slice. It's intended to be pooled, so
 | ||||||
|  | // the only way to construct one is via a Pool.
 | ||||||
|  | type Buffer struct { | ||||||
|  | 	bs   []byte | ||||||
|  | 	pool Pool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AppendByte writes a single byte to the Buffer.
 | ||||||
|  | func (b *Buffer) AppendByte(v byte) { | ||||||
|  | 	b.bs = append(b.bs, v) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AppendString writes a string to the Buffer.
 | ||||||
|  | func (b *Buffer) AppendString(s string) { | ||||||
|  | 	b.bs = append(b.bs, s...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AppendInt appends an integer to the underlying buffer (assuming base 10).
 | ||||||
|  | func (b *Buffer) AppendInt(i int64) { | ||||||
|  | 	b.bs = strconv.AppendInt(b.bs, i, 10) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AppendUint appends an unsigned integer to the underlying buffer (assuming
 | ||||||
|  | // base 10).
 | ||||||
|  | func (b *Buffer) AppendUint(i uint64) { | ||||||
|  | 	b.bs = strconv.AppendUint(b.bs, i, 10) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AppendBool appends a bool to the underlying buffer.
 | ||||||
|  | func (b *Buffer) AppendBool(v bool) { | ||||||
|  | 	b.bs = strconv.AppendBool(b.bs, v) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AppendFloat appends a float to the underlying buffer. It doesn't quote NaN
 | ||||||
|  | // or +/- Inf.
 | ||||||
|  | func (b *Buffer) AppendFloat(f float64, bitSize int) { | ||||||
|  | 	b.bs = strconv.AppendFloat(b.bs, f, 'f', -1, bitSize) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Len returns the length of the underlying byte slice.
 | ||||||
|  | func (b *Buffer) Len() int { | ||||||
|  | 	return len(b.bs) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Cap returns the capacity of the underlying byte slice.
 | ||||||
|  | func (b *Buffer) Cap() int { | ||||||
|  | 	return cap(b.bs) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Bytes returns a mutable reference to the underlying byte slice.
 | ||||||
|  | func (b *Buffer) Bytes() []byte { | ||||||
|  | 	return b.bs | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // String returns a string copy of the underlying byte slice.
 | ||||||
|  | func (b *Buffer) String() string { | ||||||
|  | 	return string(b.bs) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Reset resets the underlying byte slice. Subsequent writes re-use the slice's
 | ||||||
|  | // backing array.
 | ||||||
|  | func (b *Buffer) Reset() { | ||||||
|  | 	b.bs = b.bs[:0] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Write implements io.Writer.
 | ||||||
|  | func (b *Buffer) Write(bs []byte) (int, error) { | ||||||
|  | 	b.bs = append(b.bs, bs...) | ||||||
|  | 	return len(bs), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Free returns the Buffer to its Pool.
 | ||||||
|  | //
 | ||||||
|  | // Callers must not retain references to the Buffer after calling Free.
 | ||||||
|  | func (b *Buffer) Free() { | ||||||
|  | 	b.pool.put(b) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,49 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package buffer | ||||||
|  | 
 | ||||||
|  | import "sync" | ||||||
|  | 
 | ||||||
|  | // A Pool is a type-safe wrapper around a sync.Pool.
 | ||||||
|  | type Pool struct { | ||||||
|  | 	p *sync.Pool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewPool constructs a new Pool.
 | ||||||
|  | func NewPool() Pool { | ||||||
|  | 	return Pool{p: &sync.Pool{ | ||||||
|  | 		New: func() interface{} { | ||||||
|  | 			return &Buffer{bs: make([]byte, 0, _size)} | ||||||
|  | 		}, | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Get retrieves a Buffer from the pool, creating one if necessary.
 | ||||||
|  | func (p Pool) Get() *Buffer { | ||||||
|  | 	buf := p.p.Get().(*Buffer) | ||||||
|  | 	buf.Reset() | ||||||
|  | 	buf.pool = p | ||||||
|  | 	return buf | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p Pool) put(buf *Buffer) { | ||||||
|  | 	p.p.Put(buf) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | #!/bin/bash -e | ||||||
|  | 
 | ||||||
|  | ERROR_COUNT=0 | ||||||
|  | while read -r file | ||||||
|  | do | ||||||
|  | 	case "$(head -1 "${file}")" in | ||||||
|  | 		*"Copyright (c) "*" Uber Technologies, Inc.") | ||||||
|  | 			# everything's cool | ||||||
|  | 			;; | ||||||
|  | 		*) | ||||||
|  | 			echo "$file is missing license header." | ||||||
|  | 			(( ERROR_COUNT++ )) | ||||||
|  | 			;; | ||||||
|  | 	esac | ||||||
|  | done < <(git ls-files "*\.go") | ||||||
|  | 
 | ||||||
|  | exit $ERROR_COUNT | ||||||
|  | @ -0,0 +1,243 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"sort" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // SamplingConfig sets a sampling strategy for the logger. Sampling caps the
 | ||||||
|  | // global CPU and I/O load that logging puts on your process while attempting
 | ||||||
|  | // to preserve a representative subset of your logs.
 | ||||||
|  | //
 | ||||||
|  | // Values configured here are per-second. See zapcore.NewSampler for details.
 | ||||||
|  | type SamplingConfig struct { | ||||||
|  | 	Initial    int `json:"initial" yaml:"initial"` | ||||||
|  | 	Thereafter int `json:"thereafter" yaml:"thereafter"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Config offers a declarative way to construct a logger. It doesn't do
 | ||||||
|  | // anything that can't be done with New, Options, and the various
 | ||||||
|  | // zapcore.WriteSyncer and zapcore.Core wrappers, but it's a simpler way to
 | ||||||
|  | // toggle common options.
 | ||||||
|  | //
 | ||||||
|  | // Note that Config intentionally supports only the most common options. More
 | ||||||
|  | // unusual logging setups (logging to network connections or message queues,
 | ||||||
|  | // splitting output between multiple files, etc.) are possible, but require
 | ||||||
|  | // direct use of the zapcore package. For sample code, see the package-level
 | ||||||
|  | // BasicConfiguration and AdvancedConfiguration examples.
 | ||||||
|  | //
 | ||||||
|  | // For an example showing runtime log level changes, see the documentation for
 | ||||||
|  | // AtomicLevel.
 | ||||||
|  | type Config struct { | ||||||
|  | 	// Level is the minimum enabled logging level. Note that this is a dynamic
 | ||||||
|  | 	// level, so calling Config.Level.SetLevel will atomically change the log
 | ||||||
|  | 	// level of all loggers descended from this config.
 | ||||||
|  | 	Level AtomicLevel `json:"level" yaml:"level"` | ||||||
|  | 	// Development puts the logger in development mode, which changes the
 | ||||||
|  | 	// behavior of DPanicLevel and takes stacktraces more liberally.
 | ||||||
|  | 	Development bool `json:"development" yaml:"development"` | ||||||
|  | 	// DisableCaller stops annotating logs with the calling function's file
 | ||||||
|  | 	// name and line number. By default, all logs are annotated.
 | ||||||
|  | 	DisableCaller bool `json:"disableCaller" yaml:"disableCaller"` | ||||||
|  | 	// DisableStacktrace completely disables automatic stacktrace capturing. By
 | ||||||
|  | 	// default, stacktraces are captured for WarnLevel and above logs in
 | ||||||
|  | 	// development and ErrorLevel and above in production.
 | ||||||
|  | 	DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"` | ||||||
|  | 	// Sampling sets a sampling policy. A nil SamplingConfig disables sampling.
 | ||||||
|  | 	Sampling *SamplingConfig `json:"sampling" yaml:"sampling"` | ||||||
|  | 	// Encoding sets the logger's encoding. Valid values are "json" and
 | ||||||
|  | 	// "console", as well as any third-party encodings registered via
 | ||||||
|  | 	// RegisterEncoder.
 | ||||||
|  | 	Encoding string `json:"encoding" yaml:"encoding"` | ||||||
|  | 	// EncoderConfig sets options for the chosen encoder. See
 | ||||||
|  | 	// zapcore.EncoderConfig for details.
 | ||||||
|  | 	EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"` | ||||||
|  | 	// OutputPaths is a list of paths to write logging output to. See Open for
 | ||||||
|  | 	// details.
 | ||||||
|  | 	OutputPaths []string `json:"outputPaths" yaml:"outputPaths"` | ||||||
|  | 	// ErrorOutputPaths is a list of paths to write internal logger errors to.
 | ||||||
|  | 	// The default is standard error.
 | ||||||
|  | 	//
 | ||||||
|  | 	// Note that this setting only affects internal errors; for sample code that
 | ||||||
|  | 	// sends error-level logs to a different location from info- and debug-level
 | ||||||
|  | 	// logs, see the package-level AdvancedConfiguration example.
 | ||||||
|  | 	ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"` | ||||||
|  | 	// InitialFields is a collection of fields to add to the root logger.
 | ||||||
|  | 	InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewProductionEncoderConfig returns an opinionated EncoderConfig for
 | ||||||
|  | // production environments.
 | ||||||
|  | func NewProductionEncoderConfig() zapcore.EncoderConfig { | ||||||
|  | 	return zapcore.EncoderConfig{ | ||||||
|  | 		TimeKey:        "ts", | ||||||
|  | 		LevelKey:       "level", | ||||||
|  | 		NameKey:        "logger", | ||||||
|  | 		CallerKey:      "caller", | ||||||
|  | 		MessageKey:     "msg", | ||||||
|  | 		StacktraceKey:  "stacktrace", | ||||||
|  | 		LineEnding:     zapcore.DefaultLineEnding, | ||||||
|  | 		EncodeLevel:    zapcore.LowercaseLevelEncoder, | ||||||
|  | 		EncodeTime:     zapcore.EpochTimeEncoder, | ||||||
|  | 		EncodeDuration: zapcore.SecondsDurationEncoder, | ||||||
|  | 		EncodeCaller:   zapcore.ShortCallerEncoder, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewProductionConfig is a reasonable production logging configuration.
 | ||||||
|  | // Logging is enabled at InfoLevel and above.
 | ||||||
|  | //
 | ||||||
|  | // It uses a JSON encoder, writes to standard error, and enables sampling.
 | ||||||
|  | // Stacktraces are automatically included on logs of ErrorLevel and above.
 | ||||||
|  | func NewProductionConfig() Config { | ||||||
|  | 	return Config{ | ||||||
|  | 		Level:       NewAtomicLevelAt(InfoLevel), | ||||||
|  | 		Development: false, | ||||||
|  | 		Sampling: &SamplingConfig{ | ||||||
|  | 			Initial:    100, | ||||||
|  | 			Thereafter: 100, | ||||||
|  | 		}, | ||||||
|  | 		Encoding:         "json", | ||||||
|  | 		EncoderConfig:    NewProductionEncoderConfig(), | ||||||
|  | 		OutputPaths:      []string{"stderr"}, | ||||||
|  | 		ErrorOutputPaths: []string{"stderr"}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for
 | ||||||
|  | // development environments.
 | ||||||
|  | func NewDevelopmentEncoderConfig() zapcore.EncoderConfig { | ||||||
|  | 	return zapcore.EncoderConfig{ | ||||||
|  | 		// Keys can be anything except the empty string.
 | ||||||
|  | 		TimeKey:        "T", | ||||||
|  | 		LevelKey:       "L", | ||||||
|  | 		NameKey:        "N", | ||||||
|  | 		CallerKey:      "C", | ||||||
|  | 		MessageKey:     "M", | ||||||
|  | 		StacktraceKey:  "S", | ||||||
|  | 		LineEnding:     zapcore.DefaultLineEnding, | ||||||
|  | 		EncodeLevel:    zapcore.CapitalLevelEncoder, | ||||||
|  | 		EncodeTime:     zapcore.ISO8601TimeEncoder, | ||||||
|  | 		EncodeDuration: zapcore.StringDurationEncoder, | ||||||
|  | 		EncodeCaller:   zapcore.ShortCallerEncoder, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewDevelopmentConfig is a reasonable development logging configuration.
 | ||||||
|  | // Logging is enabled at DebugLevel and above.
 | ||||||
|  | //
 | ||||||
|  | // It enables development mode (which makes DPanicLevel logs panic), uses a
 | ||||||
|  | // console encoder, writes to standard error, and disables sampling.
 | ||||||
|  | // Stacktraces are automatically included on logs of WarnLevel and above.
 | ||||||
|  | func NewDevelopmentConfig() Config { | ||||||
|  | 	return Config{ | ||||||
|  | 		Level:            NewAtomicLevelAt(DebugLevel), | ||||||
|  | 		Development:      true, | ||||||
|  | 		Encoding:         "console", | ||||||
|  | 		EncoderConfig:    NewDevelopmentEncoderConfig(), | ||||||
|  | 		OutputPaths:      []string{"stderr"}, | ||||||
|  | 		ErrorOutputPaths: []string{"stderr"}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Build constructs a logger from the Config and Options.
 | ||||||
|  | func (cfg Config) Build(opts ...Option) (*Logger, error) { | ||||||
|  | 	enc, err := cfg.buildEncoder() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sink, errSink, err := cfg.openSinks() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	log := New( | ||||||
|  | 		zapcore.NewCore(enc, sink, cfg.Level), | ||||||
|  | 		cfg.buildOptions(errSink)..., | ||||||
|  | 	) | ||||||
|  | 	if len(opts) > 0 { | ||||||
|  | 		log = log.WithOptions(opts...) | ||||||
|  | 	} | ||||||
|  | 	return log, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option { | ||||||
|  | 	opts := []Option{ErrorOutput(errSink)} | ||||||
|  | 
 | ||||||
|  | 	if cfg.Development { | ||||||
|  | 		opts = append(opts, Development()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !cfg.DisableCaller { | ||||||
|  | 		opts = append(opts, AddCaller()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	stackLevel := ErrorLevel | ||||||
|  | 	if cfg.Development { | ||||||
|  | 		stackLevel = WarnLevel | ||||||
|  | 	} | ||||||
|  | 	if !cfg.DisableStacktrace { | ||||||
|  | 		opts = append(opts, AddStacktrace(stackLevel)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if cfg.Sampling != nil { | ||||||
|  | 		opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core { | ||||||
|  | 			return zapcore.NewSampler(core, time.Second, int(cfg.Sampling.Initial), int(cfg.Sampling.Thereafter)) | ||||||
|  | 		})) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(cfg.InitialFields) > 0 { | ||||||
|  | 		fs := make([]Field, 0, len(cfg.InitialFields)) | ||||||
|  | 		keys := make([]string, 0, len(cfg.InitialFields)) | ||||||
|  | 		for k := range cfg.InitialFields { | ||||||
|  | 			keys = append(keys, k) | ||||||
|  | 		} | ||||||
|  | 		sort.Strings(keys) | ||||||
|  | 		for _, k := range keys { | ||||||
|  | 			fs = append(fs, Any(k, cfg.InitialFields[k])) | ||||||
|  | 		} | ||||||
|  | 		opts = append(opts, Fields(fs...)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return opts | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) { | ||||||
|  | 	sink, closeOut, err := Open(cfg.OutputPaths...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 	errSink, _, err := Open(cfg.ErrorOutputPaths...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		closeOut() | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 	return sink, errSink, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (cfg Config) buildEncoder() (zapcore.Encoder, error) { | ||||||
|  | 	return newEncoder(cfg.Encoding, cfg.EncoderConfig) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,113 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | // Package zap provides fast, structured, leveled logging.
 | ||||||
|  | //
 | ||||||
|  | // For applications that log in the hot path, reflection-based serialization
 | ||||||
|  | // and string formatting are prohibitively expensive - they're CPU-intensive
 | ||||||
|  | // and make many small allocations. Put differently, using json.Marshal and
 | ||||||
|  | // fmt.Fprintf to log tons of interface{} makes your application slow.
 | ||||||
|  | //
 | ||||||
|  | // Zap takes a different approach. It includes a reflection-free,
 | ||||||
|  | // zero-allocation JSON encoder, and the base Logger strives to avoid
 | ||||||
|  | // serialization overhead and allocations wherever possible. By building the
 | ||||||
|  | // high-level SugaredLogger on that foundation, zap lets users choose when
 | ||||||
|  | // they need to count every allocation and when they'd prefer a more familiar,
 | ||||||
|  | // loosely typed API.
 | ||||||
|  | //
 | ||||||
|  | // Choosing a Logger
 | ||||||
|  | //
 | ||||||
|  | // In contexts where performance is nice, but not critical, use the
 | ||||||
|  | // SugaredLogger. It's 4-10x faster than other structured logging packages and
 | ||||||
|  | // supports both structured and printf-style logging. Like log15 and go-kit,
 | ||||||
|  | // the SugaredLogger's structured logging APIs are loosely typed and accept a
 | ||||||
|  | // variadic number of key-value pairs. (For more advanced use cases, they also
 | ||||||
|  | // accept strongly typed fields - see the SugaredLogger.With documentation for
 | ||||||
|  | // details.)
 | ||||||
|  | //  sugar := zap.NewExample().Sugar()
 | ||||||
|  | //  defer sugar.Sync()
 | ||||||
|  | //  sugar.Infow("failed to fetch URL",
 | ||||||
|  | //    "url", "http://example.com",
 | ||||||
|  | //    "attempt", 3,
 | ||||||
|  | //    "backoff", time.Second,
 | ||||||
|  | //  )
 | ||||||
|  | //  sugar.Printf("failed to fetch URL: %s", "http://example.com")
 | ||||||
|  | //
 | ||||||
|  | // By default, loggers are unbuffered. However, since zap's low-level APIs
 | ||||||
|  | // allow buffering, calling Sync before letting your process exit is a good
 | ||||||
|  | // habit.
 | ||||||
|  | //
 | ||||||
|  | // In the rare contexts where every microsecond and every allocation matter,
 | ||||||
|  | // use the Logger. It's even faster than the SugaredLogger and allocates far
 | ||||||
|  | // less, but it only supports strongly-typed, structured logging.
 | ||||||
|  | //  logger := zap.NewExample()
 | ||||||
|  | //  defer logger.Sync()
 | ||||||
|  | //  logger.Info("failed to fetch URL",
 | ||||||
|  | //    zap.String("url", "http://example.com"),
 | ||||||
|  | //    zap.Int("attempt", 3),
 | ||||||
|  | //    zap.Duration("backoff", time.Second),
 | ||||||
|  | //  )
 | ||||||
|  | //
 | ||||||
|  | // Choosing between the Logger and SugaredLogger doesn't need to be an
 | ||||||
|  | // application-wide decision: converting between the two is simple and
 | ||||||
|  | // inexpensive.
 | ||||||
|  | //   logger := zap.NewExample()
 | ||||||
|  | //   defer logger.Sync()
 | ||||||
|  | //   sugar := logger.Sugar()
 | ||||||
|  | //   plain := sugar.Desugar()
 | ||||||
|  | //
 | ||||||
|  | // Configuring Zap
 | ||||||
|  | //
 | ||||||
|  | // The simplest way to build a Logger is to use zap's opinionated presets:
 | ||||||
|  | // NewExample, NewProduction, and NewDevelopment. These presets build a logger
 | ||||||
|  | // with a single function call:
 | ||||||
|  | //  logger, err := zap.NewProduction()
 | ||||||
|  | //  if err != nil {
 | ||||||
|  | //    log.Fatalf("can't initialize zap logger: %v", err)
 | ||||||
|  | //  }
 | ||||||
|  | //  defer logger.Sync()
 | ||||||
|  | //
 | ||||||
|  | // Presets are fine for small projects, but larger projects and organizations
 | ||||||
|  | // naturally require a bit more customization. For most users, zap's Config
 | ||||||
|  | // struct strikes the right balance between flexibility and convenience. See
 | ||||||
|  | // the package-level BasicConfiguration example for sample code.
 | ||||||
|  | //
 | ||||||
|  | // More unusual configurations (splitting output between files, sending logs
 | ||||||
|  | // to a message queue, etc.) are possible, but require direct use of
 | ||||||
|  | // go.uber.org/zap/zapcore. See the package-level AdvancedConfiguration
 | ||||||
|  | // example for sample code.
 | ||||||
|  | //
 | ||||||
|  | // Extending Zap
 | ||||||
|  | //
 | ||||||
|  | // The zap package itself is a relatively thin wrapper around the interfaces
 | ||||||
|  | // in go.uber.org/zap/zapcore. Extending zap to support a new encoding (e.g.,
 | ||||||
|  | // BSON), a new log sink (e.g., Kafka), or something more exotic (perhaps an
 | ||||||
|  | // exception aggregation service, like Sentry or Rollbar) typically requires
 | ||||||
|  | // implementing the zapcore.Encoder, zapcore.WriteSyncer, or zapcore.Core
 | ||||||
|  | // interfaces. See the zapcore documentation for details.
 | ||||||
|  | //
 | ||||||
|  | // Similarly, package authors can use the high-performance Encoder and Core
 | ||||||
|  | // implementations in the zapcore package to build their own loggers.
 | ||||||
|  | //
 | ||||||
|  | // Frequently Asked Questions
 | ||||||
|  | //
 | ||||||
|  | // An FAQ covering everything from installation errors to design decisions is
 | ||||||
|  | // available at https://github.com/uber-go/zap/blob/master/FAQ.md.
 | ||||||
|  | package zap // import "go.uber.org/zap"
 | ||||||
|  | @ -0,0 +1,75 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	errNoEncoderNameSpecified = errors.New("no encoder name specified") | ||||||
|  | 
 | ||||||
|  | 	_encoderNameToConstructor = map[string]func(zapcore.EncoderConfig) (zapcore.Encoder, error){ | ||||||
|  | 		"console": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { | ||||||
|  | 			return zapcore.NewConsoleEncoder(encoderConfig), nil | ||||||
|  | 		}, | ||||||
|  | 		"json": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { | ||||||
|  | 			return zapcore.NewJSONEncoder(encoderConfig), nil | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	_encoderMutex sync.RWMutex | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // RegisterEncoder registers an encoder constructor, which the Config struct
 | ||||||
|  | // can then reference. By default, the "json" and "console" encoders are
 | ||||||
|  | // registered.
 | ||||||
|  | //
 | ||||||
|  | // Attempting to register an encoder whose name is already taken returns an
 | ||||||
|  | // error.
 | ||||||
|  | func RegisterEncoder(name string, constructor func(zapcore.EncoderConfig) (zapcore.Encoder, error)) error { | ||||||
|  | 	_encoderMutex.Lock() | ||||||
|  | 	defer _encoderMutex.Unlock() | ||||||
|  | 	if name == "" { | ||||||
|  | 		return errNoEncoderNameSpecified | ||||||
|  | 	} | ||||||
|  | 	if _, ok := _encoderNameToConstructor[name]; ok { | ||||||
|  | 		return fmt.Errorf("encoder already registered for name %q", name) | ||||||
|  | 	} | ||||||
|  | 	_encoderNameToConstructor[name] = constructor | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newEncoder(name string, encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { | ||||||
|  | 	_encoderMutex.RLock() | ||||||
|  | 	defer _encoderMutex.RUnlock() | ||||||
|  | 	if name == "" { | ||||||
|  | 		return nil, errNoEncoderNameSpecified | ||||||
|  | 	} | ||||||
|  | 	constructor, ok := _encoderNameToConstructor[name] | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, fmt.Errorf("no encoder registered for name %q", name) | ||||||
|  | 	} | ||||||
|  | 	return constructor(encoderConfig) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,80 @@ | ||||||
|  | // Copyright (c) 2017 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var _errArrayElemPool = sync.Pool{New: func() interface{} { | ||||||
|  | 	return &errArrayElem{} | ||||||
|  | }} | ||||||
|  | 
 | ||||||
|  | // Error is shorthand for the common idiom NamedError("error", err).
 | ||||||
|  | func Error(err error) Field { | ||||||
|  | 	return NamedError("error", err) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NamedError constructs a field that lazily stores err.Error() under the
 | ||||||
|  | // provided key. Errors which also implement fmt.Formatter (like those produced
 | ||||||
|  | // by github.com/pkg/errors) will also have their verbose representation stored
 | ||||||
|  | // under key+"Verbose". If passed a nil error, the field is a no-op.
 | ||||||
|  | //
 | ||||||
|  | // For the common case in which the key is simply "error", the Error function
 | ||||||
|  | // is shorter and less repetitive.
 | ||||||
|  | func NamedError(key string, err error) Field { | ||||||
|  | 	if err == nil { | ||||||
|  | 		return Skip() | ||||||
|  | 	} | ||||||
|  | 	return Field{Key: key, Type: zapcore.ErrorType, Interface: err} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type errArray []error | ||||||
|  | 
 | ||||||
|  | func (errs errArray) MarshalLogArray(arr zapcore.ArrayEncoder) error { | ||||||
|  | 	for i := range errs { | ||||||
|  | 		if errs[i] == nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		// To represent each error as an object with an "error" attribute and
 | ||||||
|  | 		// potentially an "errorVerbose" attribute, we need to wrap it in a
 | ||||||
|  | 		// type that implements LogObjectMarshaler. To prevent this from
 | ||||||
|  | 		// allocating, pool the wrapper type.
 | ||||||
|  | 		elem := _errArrayElemPool.Get().(*errArrayElem) | ||||||
|  | 		elem.error = errs[i] | ||||||
|  | 		arr.AppendObject(elem) | ||||||
|  | 		elem.error = nil | ||||||
|  | 		_errArrayElemPool.Put(elem) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type errArrayElem struct { | ||||||
|  | 	error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *errArrayElem) MarshalLogObject(enc zapcore.ObjectEncoder) error { | ||||||
|  | 	// Re-use the error field's logic, which supports non-standard error types.
 | ||||||
|  | 	Error(e.error).AddTo(enc) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,310 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"math" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Field is an alias for Field. Aliasing this type dramatically
 | ||||||
|  | // improves the navigability of this package's API documentation.
 | ||||||
|  | type Field = zapcore.Field | ||||||
|  | 
 | ||||||
|  | // Skip constructs a no-op field, which is often useful when handling invalid
 | ||||||
|  | // inputs in other Field constructors.
 | ||||||
|  | func Skip() Field { | ||||||
|  | 	return Field{Type: zapcore.SkipType} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Binary constructs a field that carries an opaque binary blob.
 | ||||||
|  | //
 | ||||||
|  | // Binary data is serialized in an encoding-appropriate format. For example,
 | ||||||
|  | // zap's JSON encoder base64-encodes binary blobs. To log UTF-8 encoded text,
 | ||||||
|  | // use ByteString.
 | ||||||
|  | func Binary(key string, val []byte) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.BinaryType, Interface: val} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Bool constructs a field that carries a bool.
 | ||||||
|  | func Bool(key string, val bool) Field { | ||||||
|  | 	var ival int64 | ||||||
|  | 	if val { | ||||||
|  | 		ival = 1 | ||||||
|  | 	} | ||||||
|  | 	return Field{Key: key, Type: zapcore.BoolType, Integer: ival} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ByteString constructs a field that carries UTF-8 encoded text as a []byte.
 | ||||||
|  | // To log opaque binary blobs (which aren't necessarily valid UTF-8), use
 | ||||||
|  | // Binary.
 | ||||||
|  | func ByteString(key string, val []byte) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.ByteStringType, Interface: val} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Complex128 constructs a field that carries a complex number. Unlike most
 | ||||||
|  | // numeric fields, this costs an allocation (to convert the complex128 to
 | ||||||
|  | // interface{}).
 | ||||||
|  | func Complex128(key string, val complex128) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.Complex128Type, Interface: val} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Complex64 constructs a field that carries a complex number. Unlike most
 | ||||||
|  | // numeric fields, this costs an allocation (to convert the complex64 to
 | ||||||
|  | // interface{}).
 | ||||||
|  | func Complex64(key string, val complex64) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.Complex64Type, Interface: val} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Float64 constructs a field that carries a float64. The way the
 | ||||||
|  | // floating-point value is represented is encoder-dependent, so marshaling is
 | ||||||
|  | // necessarily lazy.
 | ||||||
|  | func Float64(key string, val float64) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.Float64Type, Integer: int64(math.Float64bits(val))} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Float32 constructs a field that carries a float32. The way the
 | ||||||
|  | // floating-point value is represented is encoder-dependent, so marshaling is
 | ||||||
|  | // necessarily lazy.
 | ||||||
|  | func Float32(key string, val float32) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.Float32Type, Integer: int64(math.Float32bits(val))} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Int constructs a field with the given key and value.
 | ||||||
|  | func Int(key string, val int) Field { | ||||||
|  | 	return Int64(key, int64(val)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Int64 constructs a field with the given key and value.
 | ||||||
|  | func Int64(key string, val int64) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.Int64Type, Integer: val} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Int32 constructs a field with the given key and value.
 | ||||||
|  | func Int32(key string, val int32) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.Int32Type, Integer: int64(val)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Int16 constructs a field with the given key and value.
 | ||||||
|  | func Int16(key string, val int16) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.Int16Type, Integer: int64(val)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Int8 constructs a field with the given key and value.
 | ||||||
|  | func Int8(key string, val int8) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.Int8Type, Integer: int64(val)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // String constructs a field with the given key and value.
 | ||||||
|  | func String(key string, val string) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.StringType, String: val} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Uint constructs a field with the given key and value.
 | ||||||
|  | func Uint(key string, val uint) Field { | ||||||
|  | 	return Uint64(key, uint64(val)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Uint64 constructs a field with the given key and value.
 | ||||||
|  | func Uint64(key string, val uint64) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.Uint64Type, Integer: int64(val)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Uint32 constructs a field with the given key and value.
 | ||||||
|  | func Uint32(key string, val uint32) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.Uint32Type, Integer: int64(val)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Uint16 constructs a field with the given key and value.
 | ||||||
|  | func Uint16(key string, val uint16) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.Uint16Type, Integer: int64(val)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Uint8 constructs a field with the given key and value.
 | ||||||
|  | func Uint8(key string, val uint8) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.Uint8Type, Integer: int64(val)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Uintptr constructs a field with the given key and value.
 | ||||||
|  | func Uintptr(key string, val uintptr) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.UintptrType, Integer: int64(val)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Reflect constructs a field with the given key and an arbitrary object. It uses
 | ||||||
|  | // an encoding-appropriate, reflection-based function to lazily serialize nearly
 | ||||||
|  | // any object into the logging context, but it's relatively slow and
 | ||||||
|  | // allocation-heavy. Outside tests, Any is always a better choice.
 | ||||||
|  | //
 | ||||||
|  | // If encoding fails (e.g., trying to serialize a map[int]string to JSON), Reflect
 | ||||||
|  | // includes the error message in the final log output.
 | ||||||
|  | func Reflect(key string, val interface{}) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.ReflectType, Interface: val} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Namespace creates a named, isolated scope within the logger's context. All
 | ||||||
|  | // subsequent fields will be added to the new namespace.
 | ||||||
|  | //
 | ||||||
|  | // This helps prevent key collisions when injecting loggers into sub-components
 | ||||||
|  | // or third-party libraries.
 | ||||||
|  | func Namespace(key string) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.NamespaceType} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Stringer constructs a field with the given key and the output of the value's
 | ||||||
|  | // String method. The Stringer's String method is called lazily.
 | ||||||
|  | func Stringer(key string, val fmt.Stringer) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.StringerType, Interface: val} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Time constructs a Field with the given key and value. The encoder
 | ||||||
|  | // controls how the time is serialized.
 | ||||||
|  | func Time(key string, val time.Time) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.TimeType, Integer: val.UnixNano(), Interface: val.Location()} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Stack constructs a field that stores a stacktrace of the current goroutine
 | ||||||
|  | // under provided key. Keep in mind that taking a stacktrace is eager and
 | ||||||
|  | // expensive (relatively speaking); this function both makes an allocation and
 | ||||||
|  | // takes about two microseconds.
 | ||||||
|  | func Stack(key string) Field { | ||||||
|  | 	// Returning the stacktrace as a string costs an allocation, but saves us
 | ||||||
|  | 	// from expanding the zapcore.Field union struct to include a byte slice. Since
 | ||||||
|  | 	// taking a stacktrace is already so expensive (~10us), the extra allocation
 | ||||||
|  | 	// is okay.
 | ||||||
|  | 	return String(key, takeStacktrace()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Duration constructs a field with the given key and value. The encoder
 | ||||||
|  | // controls how the duration is serialized.
 | ||||||
|  | func Duration(key string, val time.Duration) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.DurationType, Integer: int64(val)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Object constructs a field with the given key and ObjectMarshaler. It
 | ||||||
|  | // provides a flexible, but still type-safe and efficient, way to add map- or
 | ||||||
|  | // struct-like user-defined types to the logging context. The struct's
 | ||||||
|  | // MarshalLogObject method is called lazily.
 | ||||||
|  | func Object(key string, val zapcore.ObjectMarshaler) Field { | ||||||
|  | 	return Field{Key: key, Type: zapcore.ObjectMarshalerType, Interface: val} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Any takes a key and an arbitrary value and chooses the best way to represent
 | ||||||
|  | // them as a field, falling back to a reflection-based approach only if
 | ||||||
|  | // necessary.
 | ||||||
|  | //
 | ||||||
|  | // Since byte/uint8 and rune/int32 are aliases, Any can't differentiate between
 | ||||||
|  | // them. To minimize surprises, []byte values are treated as binary blobs, byte
 | ||||||
|  | // values are treated as uint8, and runes are always treated as integers.
 | ||||||
|  | func Any(key string, value interface{}) Field { | ||||||
|  | 	switch val := value.(type) { | ||||||
|  | 	case zapcore.ObjectMarshaler: | ||||||
|  | 		return Object(key, val) | ||||||
|  | 	case zapcore.ArrayMarshaler: | ||||||
|  | 		return Array(key, val) | ||||||
|  | 	case bool: | ||||||
|  | 		return Bool(key, val) | ||||||
|  | 	case []bool: | ||||||
|  | 		return Bools(key, val) | ||||||
|  | 	case complex128: | ||||||
|  | 		return Complex128(key, val) | ||||||
|  | 	case []complex128: | ||||||
|  | 		return Complex128s(key, val) | ||||||
|  | 	case complex64: | ||||||
|  | 		return Complex64(key, val) | ||||||
|  | 	case []complex64: | ||||||
|  | 		return Complex64s(key, val) | ||||||
|  | 	case float64: | ||||||
|  | 		return Float64(key, val) | ||||||
|  | 	case []float64: | ||||||
|  | 		return Float64s(key, val) | ||||||
|  | 	case float32: | ||||||
|  | 		return Float32(key, val) | ||||||
|  | 	case []float32: | ||||||
|  | 		return Float32s(key, val) | ||||||
|  | 	case int: | ||||||
|  | 		return Int(key, val) | ||||||
|  | 	case []int: | ||||||
|  | 		return Ints(key, val) | ||||||
|  | 	case int64: | ||||||
|  | 		return Int64(key, val) | ||||||
|  | 	case []int64: | ||||||
|  | 		return Int64s(key, val) | ||||||
|  | 	case int32: | ||||||
|  | 		return Int32(key, val) | ||||||
|  | 	case []int32: | ||||||
|  | 		return Int32s(key, val) | ||||||
|  | 	case int16: | ||||||
|  | 		return Int16(key, val) | ||||||
|  | 	case []int16: | ||||||
|  | 		return Int16s(key, val) | ||||||
|  | 	case int8: | ||||||
|  | 		return Int8(key, val) | ||||||
|  | 	case []int8: | ||||||
|  | 		return Int8s(key, val) | ||||||
|  | 	case string: | ||||||
|  | 		return String(key, val) | ||||||
|  | 	case []string: | ||||||
|  | 		return Strings(key, val) | ||||||
|  | 	case uint: | ||||||
|  | 		return Uint(key, val) | ||||||
|  | 	case []uint: | ||||||
|  | 		return Uints(key, val) | ||||||
|  | 	case uint64: | ||||||
|  | 		return Uint64(key, val) | ||||||
|  | 	case []uint64: | ||||||
|  | 		return Uint64s(key, val) | ||||||
|  | 	case uint32: | ||||||
|  | 		return Uint32(key, val) | ||||||
|  | 	case []uint32: | ||||||
|  | 		return Uint32s(key, val) | ||||||
|  | 	case uint16: | ||||||
|  | 		return Uint16(key, val) | ||||||
|  | 	case []uint16: | ||||||
|  | 		return Uint16s(key, val) | ||||||
|  | 	case uint8: | ||||||
|  | 		return Uint8(key, val) | ||||||
|  | 	case []byte: | ||||||
|  | 		return Binary(key, val) | ||||||
|  | 	case uintptr: | ||||||
|  | 		return Uintptr(key, val) | ||||||
|  | 	case []uintptr: | ||||||
|  | 		return Uintptrs(key, val) | ||||||
|  | 	case time.Time: | ||||||
|  | 		return Time(key, val) | ||||||
|  | 	case []time.Time: | ||||||
|  | 		return Times(key, val) | ||||||
|  | 	case time.Duration: | ||||||
|  | 		return Duration(key, val) | ||||||
|  | 	case []time.Duration: | ||||||
|  | 		return Durations(key, val) | ||||||
|  | 	case error: | ||||||
|  | 		return NamedError(key, val) | ||||||
|  | 	case []error: | ||||||
|  | 		return Errors(key, val) | ||||||
|  | 	case fmt.Stringer: | ||||||
|  | 		return Stringer(key, val) | ||||||
|  | 	default: | ||||||
|  | 		return Reflect(key, val) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,39 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"flag" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // LevelFlag uses the standard library's flag.Var to declare a global flag
 | ||||||
|  | // with the specified name, default, and usage guidance. The returned value is
 | ||||||
|  | // a pointer to the value of the flag.
 | ||||||
|  | //
 | ||||||
|  | // If you don't want to use the flag package's global state, you can use any
 | ||||||
|  | // non-nil *Level as a flag.Value with your own *flag.FlagSet.
 | ||||||
|  | func LevelFlag(name string, defaultLevel zapcore.Level, usage string) *zapcore.Level { | ||||||
|  | 	lvl := defaultLevel | ||||||
|  | 	flag.Var(&lvl, name, usage) | ||||||
|  | 	return &lvl | ||||||
|  | } | ||||||
|  | @ -0,0 +1,76 @@ | ||||||
|  | hash: f073ba522c06c88ea3075bde32a8aaf0969a840a66cab6318a0897d141ffee92 | ||||||
|  | updated: 2017-07-22T18:06:49.598185334-07:00 | ||||||
|  | imports: | ||||||
|  | - name: go.uber.org/atomic | ||||||
|  |   version: 4e336646b2ef9fc6e47be8e21594178f98e5ebcf | ||||||
|  | - name: go.uber.org/multierr | ||||||
|  |   version: 3c4937480c32f4c13a875a1829af76c98ca3d40a | ||||||
|  | testImports: | ||||||
|  | - name: github.com/apex/log | ||||||
|  |   version: d9b960447bfa720077b2da653cc79e533455b499 | ||||||
|  |   subpackages: | ||||||
|  |   - handlers/json | ||||||
|  | - name: github.com/axw/gocov | ||||||
|  |   version: 3a69a0d2a4ef1f263e2d92b041a69593d6964fe8 | ||||||
|  |   subpackages: | ||||||
|  |   - gocov | ||||||
|  | - name: github.com/davecgh/go-spew | ||||||
|  |   version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9 | ||||||
|  |   subpackages: | ||||||
|  |   - spew | ||||||
|  | - name: github.com/fatih/color | ||||||
|  |   version: 62e9147c64a1ed519147b62a56a14e83e2be02c1 | ||||||
|  | - name: github.com/go-kit/kit | ||||||
|  |   version: e10f5bf035be9af21fd5b2fb4469d5716c6ab07d | ||||||
|  |   subpackages: | ||||||
|  |   - log | ||||||
|  | - name: github.com/go-logfmt/logfmt | ||||||
|  |   version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5 | ||||||
|  | - name: github.com/go-stack/stack | ||||||
|  |   version: 54be5f394ed2c3e19dac9134a40a95ba5a017f7b | ||||||
|  | - name: github.com/golang/lint | ||||||
|  |   version: c5fb716d6688a859aae56d26d3e6070808df29f7 | ||||||
|  |   subpackages: | ||||||
|  |   - golint | ||||||
|  | - name: github.com/kr/logfmt | ||||||
|  |   version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 | ||||||
|  | - name: github.com/mattn/go-colorable | ||||||
|  |   version: 3fa8c76f9daed4067e4a806fb7e4dc86455c6d6a | ||||||
|  | - name: github.com/mattn/go-isatty | ||||||
|  |   version: fc9e8d8ef48496124e79ae0df75490096eccf6fe | ||||||
|  | - name: github.com/mattn/goveralls | ||||||
|  |   version: 6efce81852ad1b7567c17ad71b03aeccc9dd9ae0 | ||||||
|  | - name: github.com/pborman/uuid | ||||||
|  |   version: e790cca94e6cc75c7064b1332e63811d4aae1a53 | ||||||
|  | - name: github.com/pkg/errors | ||||||
|  |   version: 645ef00459ed84a119197bfb8d8205042c6df63d | ||||||
|  | - name: github.com/pmezard/go-difflib | ||||||
|  |   version: d8ed2627bdf02c080bf22230dbb337003b7aba2d | ||||||
|  |   subpackages: | ||||||
|  |   - difflib | ||||||
|  | - name: github.com/rs/zerolog | ||||||
|  |   version: eed4c2b94d945e0b2456ad6aa518a443986b5f22 | ||||||
|  | - name: github.com/satori/go.uuid | ||||||
|  |   version: 5bf94b69c6b68ee1b541973bb8e1144db23a194b | ||||||
|  | - name: github.com/sirupsen/logrus | ||||||
|  |   version: 7dd06bf38e1e13df288d471a57d5adbac106be9e | ||||||
|  | - name: github.com/stretchr/testify | ||||||
|  |   version: f6abca593680b2315d2075e0f5e2a9751e3f431a | ||||||
|  |   subpackages: | ||||||
|  |   - assert | ||||||
|  |   - require | ||||||
|  | - name: go.pedge.io/lion | ||||||
|  |   version: 87958e8713f1fa138d993087133b97e976642159 | ||||||
|  | - name: golang.org/x/sys | ||||||
|  |   version: c4489faa6e5ab84c0ef40d6ee878f7a030281f0f | ||||||
|  |   subpackages: | ||||||
|  |   - unix | ||||||
|  | - name: golang.org/x/tools | ||||||
|  |   version: 496819729719f9d07692195e0a94d6edd2251389 | ||||||
|  |   subpackages: | ||||||
|  |   - cover | ||||||
|  | - name: gopkg.in/inconshreveable/log15.v2 | ||||||
|  |   version: b105bd37f74e5d9dc7b6ad7806715c7a2b83fd3f | ||||||
|  |   subpackages: | ||||||
|  |   - stack | ||||||
|  |   - term | ||||||
|  | @ -0,0 +1,35 @@ | ||||||
|  | package: go.uber.org/zap | ||||||
|  | license: MIT | ||||||
|  | import: | ||||||
|  | - package: go.uber.org/atomic | ||||||
|  |   version: ^1 | ||||||
|  | - package: go.uber.org/multierr | ||||||
|  |   version: ^1 | ||||||
|  | testImport: | ||||||
|  | - package: github.com/satori/go.uuid | ||||||
|  | - package: github.com/sirupsen/logrus | ||||||
|  | - package: github.com/apex/log | ||||||
|  |   subpackages: | ||||||
|  |   - handlers/json | ||||||
|  | - package: github.com/go-kit/kit | ||||||
|  |   subpackages: | ||||||
|  |   - log | ||||||
|  | - package: github.com/stretchr/testify | ||||||
|  |   subpackages: | ||||||
|  |   - assert | ||||||
|  |   - require | ||||||
|  | - package: gopkg.in/inconshreveable/log15.v2 | ||||||
|  | - package: github.com/mattn/goveralls | ||||||
|  | - package: github.com/pborman/uuid | ||||||
|  | - package: github.com/pkg/errors | ||||||
|  | - package: go.pedge.io/lion | ||||||
|  | - package: github.com/rs/zerolog | ||||||
|  | - package: golang.org/x/tools | ||||||
|  |   subpackages: | ||||||
|  |   - cover | ||||||
|  | - package: github.com/golang/lint | ||||||
|  |   subpackages: | ||||||
|  |   - golint | ||||||
|  | - package: github.com/axw/gocov | ||||||
|  |   subpackages: | ||||||
|  |   - gocov | ||||||
|  | @ -0,0 +1,169 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	_stdLogDefaultDepth      = 2 | ||||||
|  | 	_loggerWriterDepth       = 2 | ||||||
|  | 	_programmerErrorTemplate = "You've found a bug in zap! Please file a bug at " + | ||||||
|  | 		"https://github.com/uber-go/zap/issues/new and reference this error: %v" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	_globalMu sync.RWMutex | ||||||
|  | 	_globalL  = NewNop() | ||||||
|  | 	_globalS  = _globalL.Sugar() | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // L returns the global Logger, which can be reconfigured with ReplaceGlobals.
 | ||||||
|  | // It's safe for concurrent use.
 | ||||||
|  | func L() *Logger { | ||||||
|  | 	_globalMu.RLock() | ||||||
|  | 	l := _globalL | ||||||
|  | 	_globalMu.RUnlock() | ||||||
|  | 	return l | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // S returns the global SugaredLogger, which can be reconfigured with
 | ||||||
|  | // ReplaceGlobals. It's safe for concurrent use.
 | ||||||
|  | func S() *SugaredLogger { | ||||||
|  | 	_globalMu.RLock() | ||||||
|  | 	s := _globalS | ||||||
|  | 	_globalMu.RUnlock() | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ReplaceGlobals replaces the global Logger and SugaredLogger, and returns a
 | ||||||
|  | // function to restore the original values. It's safe for concurrent use.
 | ||||||
|  | func ReplaceGlobals(logger *Logger) func() { | ||||||
|  | 	_globalMu.Lock() | ||||||
|  | 	prev := _globalL | ||||||
|  | 	_globalL = logger | ||||||
|  | 	_globalS = logger.Sugar() | ||||||
|  | 	_globalMu.Unlock() | ||||||
|  | 	return func() { ReplaceGlobals(prev) } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewStdLog returns a *log.Logger which writes to the supplied zap Logger at
 | ||||||
|  | // InfoLevel. To redirect the standard library's package-global logging
 | ||||||
|  | // functions, use RedirectStdLog instead.
 | ||||||
|  | func NewStdLog(l *Logger) *log.Logger { | ||||||
|  | 	logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth)) | ||||||
|  | 	f := logger.Info | ||||||
|  | 	return log.New(&loggerWriter{f}, "" /* prefix */, 0 /* flags */) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewStdLogAt returns *log.Logger which writes to supplied zap logger at
 | ||||||
|  | // required level.
 | ||||||
|  | func NewStdLogAt(l *Logger, level zapcore.Level) (*log.Logger, error) { | ||||||
|  | 	logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth)) | ||||||
|  | 	logFunc, err := levelToFunc(logger, level) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return log.New(&loggerWriter{logFunc}, "" /* prefix */, 0 /* flags */), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RedirectStdLog redirects output from the standard library's package-global
 | ||||||
|  | // logger to the supplied logger at InfoLevel. Since zap already handles caller
 | ||||||
|  | // annotations, timestamps, etc., it automatically disables the standard
 | ||||||
|  | // library's annotations and prefixing.
 | ||||||
|  | //
 | ||||||
|  | // It returns a function to restore the original prefix and flags and reset the
 | ||||||
|  | // standard library's output to os.Stderr.
 | ||||||
|  | func RedirectStdLog(l *Logger) func() { | ||||||
|  | 	f, err := redirectStdLogAt(l, InfoLevel) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// Can't get here, since passing InfoLevel to redirectStdLogAt always
 | ||||||
|  | 		// works.
 | ||||||
|  | 		panic(fmt.Sprintf(_programmerErrorTemplate, err)) | ||||||
|  | 	} | ||||||
|  | 	return f | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RedirectStdLogAt redirects output from the standard library's package-global
 | ||||||
|  | // logger to the supplied logger at the specified level. Since zap already
 | ||||||
|  | // handles caller annotations, timestamps, etc., it automatically disables the
 | ||||||
|  | // standard library's annotations and prefixing.
 | ||||||
|  | //
 | ||||||
|  | // It returns a function to restore the original prefix and flags and reset the
 | ||||||
|  | // standard library's output to os.Stderr.
 | ||||||
|  | func RedirectStdLogAt(l *Logger, level zapcore.Level) (func(), error) { | ||||||
|  | 	return redirectStdLogAt(l, level) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func redirectStdLogAt(l *Logger, level zapcore.Level) (func(), error) { | ||||||
|  | 	flags := log.Flags() | ||||||
|  | 	prefix := log.Prefix() | ||||||
|  | 	log.SetFlags(0) | ||||||
|  | 	log.SetPrefix("") | ||||||
|  | 	logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth)) | ||||||
|  | 	logFunc, err := levelToFunc(logger, level) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	log.SetOutput(&loggerWriter{logFunc}) | ||||||
|  | 	return func() { | ||||||
|  | 		log.SetFlags(flags) | ||||||
|  | 		log.SetPrefix(prefix) | ||||||
|  | 		log.SetOutput(os.Stderr) | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func levelToFunc(logger *Logger, lvl zapcore.Level) (func(string, ...Field), error) { | ||||||
|  | 	switch lvl { | ||||||
|  | 	case DebugLevel: | ||||||
|  | 		return logger.Debug, nil | ||||||
|  | 	case InfoLevel: | ||||||
|  | 		return logger.Info, nil | ||||||
|  | 	case WarnLevel: | ||||||
|  | 		return logger.Warn, nil | ||||||
|  | 	case ErrorLevel: | ||||||
|  | 		return logger.Error, nil | ||||||
|  | 	case DPanicLevel: | ||||||
|  | 		return logger.DPanic, nil | ||||||
|  | 	case PanicLevel: | ||||||
|  | 		return logger.Panic, nil | ||||||
|  | 	case FatalLevel: | ||||||
|  | 		return logger.Fatal, nil | ||||||
|  | 	} | ||||||
|  | 	return nil, fmt.Errorf("unrecognized level: %q", lvl) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type loggerWriter struct { | ||||||
|  | 	logFunc func(msg string, fields ...Field) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *loggerWriter) Write(p []byte) (int, error) { | ||||||
|  | 	p = bytes.TrimSpace(p) | ||||||
|  | 	l.logFunc(string(p)) | ||||||
|  | 	return len(p), nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,81 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ServeHTTP is a simple JSON endpoint that can report on or change the current
 | ||||||
|  | // logging level.
 | ||||||
|  | //
 | ||||||
|  | // GET requests return a JSON description of the current logging level. PUT
 | ||||||
|  | // requests change the logging level and expect a payload like:
 | ||||||
|  | //   {"level":"info"}
 | ||||||
|  | //
 | ||||||
|  | // It's perfectly safe to change the logging level while a program is running.
 | ||||||
|  | func (lvl AtomicLevel) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	type errorResponse struct { | ||||||
|  | 		Error string `json:"error"` | ||||||
|  | 	} | ||||||
|  | 	type payload struct { | ||||||
|  | 		Level *zapcore.Level `json:"level"` | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	enc := json.NewEncoder(w) | ||||||
|  | 
 | ||||||
|  | 	switch r.Method { | ||||||
|  | 
 | ||||||
|  | 	case "GET": | ||||||
|  | 		current := lvl.Level() | ||||||
|  | 		enc.Encode(payload{Level: ¤t}) | ||||||
|  | 
 | ||||||
|  | 	case "PUT": | ||||||
|  | 		var req payload | ||||||
|  | 
 | ||||||
|  | 		if errmess := func() string { | ||||||
|  | 			if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | ||||||
|  | 				return fmt.Sprintf("Request body must be well-formed JSON: %v", err) | ||||||
|  | 			} | ||||||
|  | 			if req.Level == nil { | ||||||
|  | 				return "Must specify a logging level." | ||||||
|  | 			} | ||||||
|  | 			return "" | ||||||
|  | 		}(); errmess != "" { | ||||||
|  | 			w.WriteHeader(http.StatusBadRequest) | ||||||
|  | 			enc.Encode(errorResponse{Error: errmess}) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		lvl.SetLevel(*req.Level) | ||||||
|  | 		enc.Encode(req) | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		w.WriteHeader(http.StatusMethodNotAllowed) | ||||||
|  | 		enc.Encode(errorResponse{ | ||||||
|  | 			Error: "Only GET and PUT are supported.", | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,31 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | // Package bufferpool houses zap's shared internal buffer pool. Third-party
 | ||||||
|  | // packages can recreate the same functionality with buffers.NewPool.
 | ||||||
|  | package bufferpool | ||||||
|  | 
 | ||||||
|  | import "go.uber.org/zap/buffer" | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	_pool = buffer.NewPool() | ||||||
|  | 	// Get retrieves a buffer from the pool, creating one if necessary.
 | ||||||
|  | 	Get = _pool.Get | ||||||
|  | ) | ||||||
|  | @ -0,0 +1,44 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | // Package color adds coloring functionality for TTY output.
 | ||||||
|  | package color | ||||||
|  | 
 | ||||||
|  | import "fmt" | ||||||
|  | 
 | ||||||
|  | // Foreground colors.
 | ||||||
|  | const ( | ||||||
|  | 	Black Color = iota + 30 | ||||||
|  | 	Red | ||||||
|  | 	Green | ||||||
|  | 	Yellow | ||||||
|  | 	Blue | ||||||
|  | 	Magenta | ||||||
|  | 	Cyan | ||||||
|  | 	White | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Color represents a text color.
 | ||||||
|  | type Color uint8 | ||||||
|  | 
 | ||||||
|  | // Add adds the coloring to the given string.
 | ||||||
|  | func (c Color) Add(s string) string { | ||||||
|  | 	return fmt.Sprintf("\x1b[%dm%s\x1b[0m", uint8(c), s) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,64 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | // Package exit provides stubs so that unit tests can exercise code that calls
 | ||||||
|  | // os.Exit(1).
 | ||||||
|  | package exit | ||||||
|  | 
 | ||||||
|  | import "os" | ||||||
|  | 
 | ||||||
|  | var real = func() { os.Exit(1) } | ||||||
|  | 
 | ||||||
|  | // Exit normally terminates the process by calling os.Exit(1). If the package
 | ||||||
|  | // is stubbed, it instead records a call in the testing spy.
 | ||||||
|  | func Exit() { | ||||||
|  | 	real() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A StubbedExit is a testing fake for os.Exit.
 | ||||||
|  | type StubbedExit struct { | ||||||
|  | 	Exited bool | ||||||
|  | 	prev   func() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Stub substitutes a fake for the call to os.Exit(1).
 | ||||||
|  | func Stub() *StubbedExit { | ||||||
|  | 	s := &StubbedExit{prev: real} | ||||||
|  | 	real = s.exit | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithStub runs the supplied function with Exit stubbed. It returns the stub
 | ||||||
|  | // used, so that users can test whether the process would have crashed.
 | ||||||
|  | func WithStub(f func()) *StubbedExit { | ||||||
|  | 	s := Stub() | ||||||
|  | 	defer s.Unstub() | ||||||
|  | 	f() | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Unstub restores the previous exit function.
 | ||||||
|  | func (se *StubbedExit) Unstub() { | ||||||
|  | 	real = se.prev | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (se *StubbedExit) exit() { | ||||||
|  | 	se.Exited = true | ||||||
|  | } | ||||||
|  | @ -0,0 +1,132 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"go.uber.org/atomic" | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// DebugLevel logs are typically voluminous, and are usually disabled in
 | ||||||
|  | 	// production.
 | ||||||
|  | 	DebugLevel = zapcore.DebugLevel | ||||||
|  | 	// InfoLevel is the default logging priority.
 | ||||||
|  | 	InfoLevel = zapcore.InfoLevel | ||||||
|  | 	// WarnLevel logs are more important than Info, but don't need individual
 | ||||||
|  | 	// human review.
 | ||||||
|  | 	WarnLevel = zapcore.WarnLevel | ||||||
|  | 	// ErrorLevel logs are high-priority. If an application is running smoothly,
 | ||||||
|  | 	// it shouldn't generate any error-level logs.
 | ||||||
|  | 	ErrorLevel = zapcore.ErrorLevel | ||||||
|  | 	// DPanicLevel logs are particularly important errors. In development the
 | ||||||
|  | 	// logger panics after writing the message.
 | ||||||
|  | 	DPanicLevel = zapcore.DPanicLevel | ||||||
|  | 	// PanicLevel logs a message, then panics.
 | ||||||
|  | 	PanicLevel = zapcore.PanicLevel | ||||||
|  | 	// FatalLevel logs a message, then calls os.Exit(1).
 | ||||||
|  | 	FatalLevel = zapcore.FatalLevel | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // LevelEnablerFunc is a convenient way to implement zapcore.LevelEnabler with
 | ||||||
|  | // an anonymous function.
 | ||||||
|  | //
 | ||||||
|  | // It's particularly useful when splitting log output between different
 | ||||||
|  | // outputs (e.g., standard error and standard out). For sample code, see the
 | ||||||
|  | // package-level AdvancedConfiguration example.
 | ||||||
|  | type LevelEnablerFunc func(zapcore.Level) bool | ||||||
|  | 
 | ||||||
|  | // Enabled calls the wrapped function.
 | ||||||
|  | func (f LevelEnablerFunc) Enabled(lvl zapcore.Level) bool { return f(lvl) } | ||||||
|  | 
 | ||||||
|  | // An AtomicLevel is an atomically changeable, dynamic logging level. It lets
 | ||||||
|  | // you safely change the log level of a tree of loggers (the root logger and
 | ||||||
|  | // any children created by adding context) at runtime.
 | ||||||
|  | //
 | ||||||
|  | // The AtomicLevel itself is an http.Handler that serves a JSON endpoint to
 | ||||||
|  | // alter its level.
 | ||||||
|  | //
 | ||||||
|  | // AtomicLevels must be created with the NewAtomicLevel constructor to allocate
 | ||||||
|  | // their internal atomic pointer.
 | ||||||
|  | type AtomicLevel struct { | ||||||
|  | 	l *atomic.Int32 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewAtomicLevel creates an AtomicLevel with InfoLevel and above logging
 | ||||||
|  | // enabled.
 | ||||||
|  | func NewAtomicLevel() AtomicLevel { | ||||||
|  | 	return AtomicLevel{ | ||||||
|  | 		l: atomic.NewInt32(int32(InfoLevel)), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewAtomicLevelAt is a convenience function that creates an AtomicLevel
 | ||||||
|  | // and then calls SetLevel with the given level.
 | ||||||
|  | func NewAtomicLevelAt(l zapcore.Level) AtomicLevel { | ||||||
|  | 	a := NewAtomicLevel() | ||||||
|  | 	a.SetLevel(l) | ||||||
|  | 	return a | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Enabled implements the zapcore.LevelEnabler interface, which allows the
 | ||||||
|  | // AtomicLevel to be used in place of traditional static levels.
 | ||||||
|  | func (lvl AtomicLevel) Enabled(l zapcore.Level) bool { | ||||||
|  | 	return lvl.Level().Enabled(l) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Level returns the minimum enabled log level.
 | ||||||
|  | func (lvl AtomicLevel) Level() zapcore.Level { | ||||||
|  | 	return zapcore.Level(int8(lvl.l.Load())) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetLevel alters the logging level.
 | ||||||
|  | func (lvl AtomicLevel) SetLevel(l zapcore.Level) { | ||||||
|  | 	lvl.l.Store(int32(l)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // String returns the string representation of the underlying Level.
 | ||||||
|  | func (lvl AtomicLevel) String() string { | ||||||
|  | 	return lvl.Level().String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UnmarshalText unmarshals the text to an AtomicLevel. It uses the same text
 | ||||||
|  | // representations as the static zapcore.Levels ("debug", "info", "warn",
 | ||||||
|  | // "error", "dpanic", "panic", and "fatal").
 | ||||||
|  | func (lvl *AtomicLevel) UnmarshalText(text []byte) error { | ||||||
|  | 	if lvl.l == nil { | ||||||
|  | 		lvl.l = &atomic.Int32{} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var l zapcore.Level | ||||||
|  | 	if err := l.UnmarshalText(text); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	lvl.SetLevel(l) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalText marshals the AtomicLevel to a byte slice. It uses the same
 | ||||||
|  | // text representation as the static zapcore.Levels ("debug", "info", "warn",
 | ||||||
|  | // "error", "dpanic", "panic", and "fatal").
 | ||||||
|  | func (lvl AtomicLevel) MarshalText() (text []byte, err error) { | ||||||
|  | 	return lvl.Level().MarshalText() | ||||||
|  | } | ||||||
|  | @ -0,0 +1,305 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"os" | ||||||
|  | 	"runtime" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // A Logger provides fast, leveled, structured logging. All methods are safe
 | ||||||
|  | // for concurrent use.
 | ||||||
|  | //
 | ||||||
|  | // The Logger is designed for contexts in which every microsecond and every
 | ||||||
|  | // allocation matters, so its API intentionally favors performance and type
 | ||||||
|  | // safety over brevity. For most applications, the SugaredLogger strikes a
 | ||||||
|  | // better balance between performance and ergonomics.
 | ||||||
|  | type Logger struct { | ||||||
|  | 	core zapcore.Core | ||||||
|  | 
 | ||||||
|  | 	development bool | ||||||
|  | 	name        string | ||||||
|  | 	errorOutput zapcore.WriteSyncer | ||||||
|  | 
 | ||||||
|  | 	addCaller bool | ||||||
|  | 	addStack  zapcore.LevelEnabler | ||||||
|  | 
 | ||||||
|  | 	callerSkip int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // New constructs a new Logger from the provided zapcore.Core and Options. If
 | ||||||
|  | // the passed zapcore.Core is nil, it falls back to using a no-op
 | ||||||
|  | // implementation.
 | ||||||
|  | //
 | ||||||
|  | // This is the most flexible way to construct a Logger, but also the most
 | ||||||
|  | // verbose. For typical use cases, the highly-opinionated presets
 | ||||||
|  | // (NewProduction, NewDevelopment, and NewExample) or the Config struct are
 | ||||||
|  | // more convenient.
 | ||||||
|  | //
 | ||||||
|  | // For sample code, see the package-level AdvancedConfiguration example.
 | ||||||
|  | func New(core zapcore.Core, options ...Option) *Logger { | ||||||
|  | 	if core == nil { | ||||||
|  | 		return NewNop() | ||||||
|  | 	} | ||||||
|  | 	log := &Logger{ | ||||||
|  | 		core:        core, | ||||||
|  | 		errorOutput: zapcore.Lock(os.Stderr), | ||||||
|  | 		addStack:    zapcore.FatalLevel + 1, | ||||||
|  | 	} | ||||||
|  | 	return log.WithOptions(options...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewNop returns a no-op Logger. It never writes out logs or internal errors,
 | ||||||
|  | // and it never runs user-defined hooks.
 | ||||||
|  | //
 | ||||||
|  | // Using WithOptions to replace the Core or error output of a no-op Logger can
 | ||||||
|  | // re-enable logging.
 | ||||||
|  | func NewNop() *Logger { | ||||||
|  | 	return &Logger{ | ||||||
|  | 		core:        zapcore.NewNopCore(), | ||||||
|  | 		errorOutput: zapcore.AddSync(ioutil.Discard), | ||||||
|  | 		addStack:    zapcore.FatalLevel + 1, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewProduction builds a sensible production Logger that writes InfoLevel and
 | ||||||
|  | // above logs to standard error as JSON.
 | ||||||
|  | //
 | ||||||
|  | // It's a shortcut for NewProductionConfig().Build(...Option).
 | ||||||
|  | func NewProduction(options ...Option) (*Logger, error) { | ||||||
|  | 	return NewProductionConfig().Build(options...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewDevelopment builds a development Logger that writes DebugLevel and above
 | ||||||
|  | // logs to standard error in a human-friendly format.
 | ||||||
|  | //
 | ||||||
|  | // It's a shortcut for NewDevelopmentConfig().Build(...Option).
 | ||||||
|  | func NewDevelopment(options ...Option) (*Logger, error) { | ||||||
|  | 	return NewDevelopmentConfig().Build(options...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewExample builds a Logger that's designed for use in zap's testable
 | ||||||
|  | // examples. It writes DebugLevel and above logs to standard out as JSON, but
 | ||||||
|  | // omits the timestamp and calling function to keep example output
 | ||||||
|  | // short and deterministic.
 | ||||||
|  | func NewExample(options ...Option) *Logger { | ||||||
|  | 	encoderCfg := zapcore.EncoderConfig{ | ||||||
|  | 		MessageKey:     "msg", | ||||||
|  | 		LevelKey:       "level", | ||||||
|  | 		NameKey:        "logger", | ||||||
|  | 		EncodeLevel:    zapcore.LowercaseLevelEncoder, | ||||||
|  | 		EncodeTime:     zapcore.ISO8601TimeEncoder, | ||||||
|  | 		EncodeDuration: zapcore.StringDurationEncoder, | ||||||
|  | 	} | ||||||
|  | 	core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), os.Stdout, DebugLevel) | ||||||
|  | 	return New(core).WithOptions(options...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Sugar wraps the Logger to provide a more ergonomic, but slightly slower,
 | ||||||
|  | // API. Sugaring a Logger is quite inexpensive, so it's reasonable for a
 | ||||||
|  | // single application to use both Loggers and SugaredLoggers, converting
 | ||||||
|  | // between them on the boundaries of performance-sensitive code.
 | ||||||
|  | func (log *Logger) Sugar() *SugaredLogger { | ||||||
|  | 	core := log.clone() | ||||||
|  | 	core.callerSkip += 2 | ||||||
|  | 	return &SugaredLogger{core} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Named adds a new path segment to the logger's name. Segments are joined by
 | ||||||
|  | // periods. By default, Loggers are unnamed.
 | ||||||
|  | func (log *Logger) Named(s string) *Logger { | ||||||
|  | 	if s == "" { | ||||||
|  | 		return log | ||||||
|  | 	} | ||||||
|  | 	l := log.clone() | ||||||
|  | 	if log.name == "" { | ||||||
|  | 		l.name = s | ||||||
|  | 	} else { | ||||||
|  | 		l.name = strings.Join([]string{l.name, s}, ".") | ||||||
|  | 	} | ||||||
|  | 	return l | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WithOptions clones the current Logger, applies the supplied Options, and
 | ||||||
|  | // returns the resulting Logger. It's safe to use concurrently.
 | ||||||
|  | func (log *Logger) WithOptions(opts ...Option) *Logger { | ||||||
|  | 	c := log.clone() | ||||||
|  | 	for _, opt := range opts { | ||||||
|  | 		opt.apply(c) | ||||||
|  | 	} | ||||||
|  | 	return c | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // With creates a child logger and adds structured context to it. Fields added
 | ||||||
|  | // to the child don't affect the parent, and vice versa.
 | ||||||
|  | func (log *Logger) With(fields ...Field) *Logger { | ||||||
|  | 	if len(fields) == 0 { | ||||||
|  | 		return log | ||||||
|  | 	} | ||||||
|  | 	l := log.clone() | ||||||
|  | 	l.core = l.core.With(fields) | ||||||
|  | 	return l | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Check returns a CheckedEntry if logging a message at the specified level
 | ||||||
|  | // is enabled. It's a completely optional optimization; in high-performance
 | ||||||
|  | // applications, Check can help avoid allocating a slice to hold fields.
 | ||||||
|  | func (log *Logger) Check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { | ||||||
|  | 	return log.check(lvl, msg) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Debug logs a message at DebugLevel. The message includes any fields passed
 | ||||||
|  | // at the log site, as well as any fields accumulated on the logger.
 | ||||||
|  | func (log *Logger) Debug(msg string, fields ...Field) { | ||||||
|  | 	if ce := log.check(DebugLevel, msg); ce != nil { | ||||||
|  | 		ce.Write(fields...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Info logs a message at InfoLevel. The message includes any fields passed
 | ||||||
|  | // at the log site, as well as any fields accumulated on the logger.
 | ||||||
|  | func (log *Logger) Info(msg string, fields ...Field) { | ||||||
|  | 	if ce := log.check(InfoLevel, msg); ce != nil { | ||||||
|  | 		ce.Write(fields...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Warn logs a message at WarnLevel. The message includes any fields passed
 | ||||||
|  | // at the log site, as well as any fields accumulated on the logger.
 | ||||||
|  | func (log *Logger) Warn(msg string, fields ...Field) { | ||||||
|  | 	if ce := log.check(WarnLevel, msg); ce != nil { | ||||||
|  | 		ce.Write(fields...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Error logs a message at ErrorLevel. The message includes any fields passed
 | ||||||
|  | // at the log site, as well as any fields accumulated on the logger.
 | ||||||
|  | func (log *Logger) Error(msg string, fields ...Field) { | ||||||
|  | 	if ce := log.check(ErrorLevel, msg); ce != nil { | ||||||
|  | 		ce.Write(fields...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DPanic logs a message at DPanicLevel. The message includes any fields
 | ||||||
|  | // passed at the log site, as well as any fields accumulated on the logger.
 | ||||||
|  | //
 | ||||||
|  | // If the logger is in development mode, it then panics (DPanic means
 | ||||||
|  | // "development panic"). This is useful for catching errors that are
 | ||||||
|  | // recoverable, but shouldn't ever happen.
 | ||||||
|  | func (log *Logger) DPanic(msg string, fields ...Field) { | ||||||
|  | 	if ce := log.check(DPanicLevel, msg); ce != nil { | ||||||
|  | 		ce.Write(fields...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Panic logs a message at PanicLevel. The message includes any fields passed
 | ||||||
|  | // at the log site, as well as any fields accumulated on the logger.
 | ||||||
|  | //
 | ||||||
|  | // The logger then panics, even if logging at PanicLevel is disabled.
 | ||||||
|  | func (log *Logger) Panic(msg string, fields ...Field) { | ||||||
|  | 	if ce := log.check(PanicLevel, msg); ce != nil { | ||||||
|  | 		ce.Write(fields...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Fatal logs a message at FatalLevel. The message includes any fields passed
 | ||||||
|  | // at the log site, as well as any fields accumulated on the logger.
 | ||||||
|  | //
 | ||||||
|  | // The logger then calls os.Exit(1), even if logging at FatalLevel is
 | ||||||
|  | // disabled.
 | ||||||
|  | func (log *Logger) Fatal(msg string, fields ...Field) { | ||||||
|  | 	if ce := log.check(FatalLevel, msg); ce != nil { | ||||||
|  | 		ce.Write(fields...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Sync calls the underlying Core's Sync method, flushing any buffered log
 | ||||||
|  | // entries. Applications should take care to call Sync before exiting.
 | ||||||
|  | func (log *Logger) Sync() error { | ||||||
|  | 	return log.core.Sync() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Core returns the Logger's underlying zapcore.Core.
 | ||||||
|  | func (log *Logger) Core() zapcore.Core { | ||||||
|  | 	return log.core | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (log *Logger) clone() *Logger { | ||||||
|  | 	copy := *log | ||||||
|  | 	return © | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { | ||||||
|  | 	// check must always be called directly by a method in the Logger interface
 | ||||||
|  | 	// (e.g., Check, Info, Fatal).
 | ||||||
|  | 	const callerSkipOffset = 2 | ||||||
|  | 
 | ||||||
|  | 	// Create basic checked entry thru the core; this will be non-nil if the
 | ||||||
|  | 	// log message will actually be written somewhere.
 | ||||||
|  | 	ent := zapcore.Entry{ | ||||||
|  | 		LoggerName: log.name, | ||||||
|  | 		Time:       time.Now(), | ||||||
|  | 		Level:      lvl, | ||||||
|  | 		Message:    msg, | ||||||
|  | 	} | ||||||
|  | 	ce := log.core.Check(ent, nil) | ||||||
|  | 	willWrite := ce != nil | ||||||
|  | 
 | ||||||
|  | 	// Set up any required terminal behavior.
 | ||||||
|  | 	switch ent.Level { | ||||||
|  | 	case zapcore.PanicLevel: | ||||||
|  | 		ce = ce.Should(ent, zapcore.WriteThenPanic) | ||||||
|  | 	case zapcore.FatalLevel: | ||||||
|  | 		ce = ce.Should(ent, zapcore.WriteThenFatal) | ||||||
|  | 	case zapcore.DPanicLevel: | ||||||
|  | 		if log.development { | ||||||
|  | 			ce = ce.Should(ent, zapcore.WriteThenPanic) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Only do further annotation if we're going to write this message; checked
 | ||||||
|  | 	// entries that exist only for terminal behavior don't benefit from
 | ||||||
|  | 	// annotation.
 | ||||||
|  | 	if !willWrite { | ||||||
|  | 		return ce | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Thread the error output through to the CheckedEntry.
 | ||||||
|  | 	ce.ErrorOutput = log.errorOutput | ||||||
|  | 	if log.addCaller { | ||||||
|  | 		ce.Entry.Caller = zapcore.NewEntryCaller(runtime.Caller(log.callerSkip + callerSkipOffset)) | ||||||
|  | 		if !ce.Entry.Caller.Defined { | ||||||
|  | 			fmt.Fprintf(log.errorOutput, "%v Logger.check error: failed to get caller\n", time.Now().UTC()) | ||||||
|  | 			log.errorOutput.Sync() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if log.addStack.Enabled(ce.Entry.Level) { | ||||||
|  | 		ce.Entry.Stack = Stack("").String | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ce | ||||||
|  | } | ||||||
|  | @ -0,0 +1,109 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import "go.uber.org/zap/zapcore" | ||||||
|  | 
 | ||||||
|  | // An Option configures a Logger.
 | ||||||
|  | type Option interface { | ||||||
|  | 	apply(*Logger) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // optionFunc wraps a func so it satisfies the Option interface.
 | ||||||
|  | type optionFunc func(*Logger) | ||||||
|  | 
 | ||||||
|  | func (f optionFunc) apply(log *Logger) { | ||||||
|  | 	f(log) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WrapCore wraps or replaces the Logger's underlying zapcore.Core.
 | ||||||
|  | func WrapCore(f func(zapcore.Core) zapcore.Core) Option { | ||||||
|  | 	return optionFunc(func(log *Logger) { | ||||||
|  | 		log.core = f(log.core) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Hooks registers functions which will be called each time the Logger writes
 | ||||||
|  | // out an Entry. Repeated use of Hooks is additive.
 | ||||||
|  | //
 | ||||||
|  | // Hooks are useful for simple side effects, like capturing metrics for the
 | ||||||
|  | // number of emitted logs. More complex side effects, including anything that
 | ||||||
|  | // requires access to the Entry's structured fields, should be implemented as
 | ||||||
|  | // a zapcore.Core instead. See zapcore.RegisterHooks for details.
 | ||||||
|  | func Hooks(hooks ...func(zapcore.Entry) error) Option { | ||||||
|  | 	return optionFunc(func(log *Logger) { | ||||||
|  | 		log.core = zapcore.RegisterHooks(log.core, hooks...) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Fields adds fields to the Logger.
 | ||||||
|  | func Fields(fs ...Field) Option { | ||||||
|  | 	return optionFunc(func(log *Logger) { | ||||||
|  | 		log.core = log.core.With(fs) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ErrorOutput sets the destination for errors generated by the Logger. Note
 | ||||||
|  | // that this option only affects internal errors; for sample code that sends
 | ||||||
|  | // error-level logs to a different location from info- and debug-level logs,
 | ||||||
|  | // see the package-level AdvancedConfiguration example.
 | ||||||
|  | //
 | ||||||
|  | // The supplied WriteSyncer must be safe for concurrent use. The Open and
 | ||||||
|  | // zapcore.Lock functions are the simplest ways to protect files with a mutex.
 | ||||||
|  | func ErrorOutput(w zapcore.WriteSyncer) Option { | ||||||
|  | 	return optionFunc(func(log *Logger) { | ||||||
|  | 		log.errorOutput = w | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Development puts the logger in development mode, which makes DPanic-level
 | ||||||
|  | // logs panic instead of simply logging an error.
 | ||||||
|  | func Development() Option { | ||||||
|  | 	return optionFunc(func(log *Logger) { | ||||||
|  | 		log.development = true | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddCaller configures the Logger to annotate each message with the filename
 | ||||||
|  | // and line number of zap's caller.
 | ||||||
|  | func AddCaller() Option { | ||||||
|  | 	return optionFunc(func(log *Logger) { | ||||||
|  | 		log.addCaller = true | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddCallerSkip increases the number of callers skipped by caller annotation
 | ||||||
|  | // (as enabled by the AddCaller option). When building wrappers around the
 | ||||||
|  | // Logger and SugaredLogger, supplying this Option prevents zap from always
 | ||||||
|  | // reporting the wrapper code as the caller.
 | ||||||
|  | func AddCallerSkip(skip int) Option { | ||||||
|  | 	return optionFunc(func(log *Logger) { | ||||||
|  | 		log.callerSkip += skip | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddStacktrace configures the Logger to record a stack trace for all messages at
 | ||||||
|  | // or above a given level.
 | ||||||
|  | func AddStacktrace(lvl zapcore.LevelEnabler) Option { | ||||||
|  | 	return optionFunc(func(log *Logger) { | ||||||
|  | 		log.addStack = lvl | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,126 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"runtime" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/internal/bufferpool" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const _zapPackage = "go.uber.org/zap" | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	_stacktracePool = sync.Pool{ | ||||||
|  | 		New: func() interface{} { | ||||||
|  | 			return newProgramCounters(64) | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// We add "." and "/" suffixes to the package name to ensure we only match
 | ||||||
|  | 	// the exact package and not any package with the same prefix.
 | ||||||
|  | 	_zapStacktracePrefixes       = addPrefix(_zapPackage, ".", "/") | ||||||
|  | 	_zapStacktraceVendorContains = addPrefix("/vendor/", _zapStacktracePrefixes...) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func takeStacktrace() string { | ||||||
|  | 	buffer := bufferpool.Get() | ||||||
|  | 	defer buffer.Free() | ||||||
|  | 	programCounters := _stacktracePool.Get().(*programCounters) | ||||||
|  | 	defer _stacktracePool.Put(programCounters) | ||||||
|  | 
 | ||||||
|  | 	var numFrames int | ||||||
|  | 	for { | ||||||
|  | 		// Skip the call to runtime.Counters and takeStacktrace so that the
 | ||||||
|  | 		// program counters start at the caller of takeStacktrace.
 | ||||||
|  | 		numFrames = runtime.Callers(2, programCounters.pcs) | ||||||
|  | 		if numFrames < len(programCounters.pcs) { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		// Don't put the too-short counter slice back into the pool; this lets
 | ||||||
|  | 		// the pool adjust if we consistently take deep stacktraces.
 | ||||||
|  | 		programCounters = newProgramCounters(len(programCounters.pcs) * 2) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	i := 0 | ||||||
|  | 	skipZapFrames := true // skip all consecutive zap frames at the beginning.
 | ||||||
|  | 	frames := runtime.CallersFrames(programCounters.pcs[:numFrames]) | ||||||
|  | 
 | ||||||
|  | 	// Note: On the last iteration, frames.Next() returns false, with a valid
 | ||||||
|  | 	// frame, but we ignore this frame. The last frame is a a runtime frame which
 | ||||||
|  | 	// adds noise, since it's only either runtime.main or runtime.goexit.
 | ||||||
|  | 	for frame, more := frames.Next(); more; frame, more = frames.Next() { | ||||||
|  | 		if skipZapFrames && isZapFrame(frame.Function) { | ||||||
|  | 			continue | ||||||
|  | 		} else { | ||||||
|  | 			skipZapFrames = false | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if i != 0 { | ||||||
|  | 			buffer.AppendByte('\n') | ||||||
|  | 		} | ||||||
|  | 		i++ | ||||||
|  | 		buffer.AppendString(frame.Function) | ||||||
|  | 		buffer.AppendByte('\n') | ||||||
|  | 		buffer.AppendByte('\t') | ||||||
|  | 		buffer.AppendString(frame.File) | ||||||
|  | 		buffer.AppendByte(':') | ||||||
|  | 		buffer.AppendInt(int64(frame.Line)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return buffer.String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isZapFrame(function string) bool { | ||||||
|  | 	for _, prefix := range _zapStacktracePrefixes { | ||||||
|  | 		if strings.HasPrefix(function, prefix) { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// We can't use a prefix match here since the location of the vendor
 | ||||||
|  | 	// directory affects the prefix. Instead we do a contains match.
 | ||||||
|  | 	for _, contains := range _zapStacktraceVendorContains { | ||||||
|  | 		if strings.Contains(function, contains) { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type programCounters struct { | ||||||
|  | 	pcs []uintptr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newProgramCounters(size int) *programCounters { | ||||||
|  | 	return &programCounters{make([]uintptr, size)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func addPrefix(prefix string, ss ...string) []string { | ||||||
|  | 	withPrefix := make([]string, len(ss)) | ||||||
|  | 	for i, s := range ss { | ||||||
|  | 		withPrefix[i] = prefix + s | ||||||
|  | 	} | ||||||
|  | 	return withPrefix | ||||||
|  | } | ||||||
|  | @ -0,0 +1,304 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/multierr" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	_oddNumberErrMsg    = "Ignored key without a value." | ||||||
|  | 	_nonStringKeyErrMsg = "Ignored key-value pairs with non-string keys." | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // A SugaredLogger wraps the base Logger functionality in a slower, but less
 | ||||||
|  | // verbose, API. Any Logger can be converted to a SugaredLogger with its Sugar
 | ||||||
|  | // method.
 | ||||||
|  | //
 | ||||||
|  | // Unlike the Logger, the SugaredLogger doesn't insist on structured logging.
 | ||||||
|  | // For each log level, it exposes three methods: one for loosely-typed
 | ||||||
|  | // structured logging, one for println-style formatting, and one for
 | ||||||
|  | // printf-style formatting. For example, SugaredLoggers can produce InfoLevel
 | ||||||
|  | // output with Infow ("info with" structured context), Info, or Infof.
 | ||||||
|  | type SugaredLogger struct { | ||||||
|  | 	base *Logger | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Desugar unwraps a SugaredLogger, exposing the original Logger. Desugaring
 | ||||||
|  | // is quite inexpensive, so it's reasonable for a single application to use
 | ||||||
|  | // both Loggers and SugaredLoggers, converting between them on the boundaries
 | ||||||
|  | // of performance-sensitive code.
 | ||||||
|  | func (s *SugaredLogger) Desugar() *Logger { | ||||||
|  | 	base := s.base.clone() | ||||||
|  | 	base.callerSkip -= 2 | ||||||
|  | 	return base | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Named adds a sub-scope to the logger's name. See Logger.Named for details.
 | ||||||
|  | func (s *SugaredLogger) Named(name string) *SugaredLogger { | ||||||
|  | 	return &SugaredLogger{base: s.base.Named(name)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // With adds a variadic number of fields to the logging context. It accepts a
 | ||||||
|  | // mix of strongly-typed Field objects and loosely-typed key-value pairs. When
 | ||||||
|  | // processing pairs, the first element of the pair is used as the field key
 | ||||||
|  | // and the second as the field value.
 | ||||||
|  | //
 | ||||||
|  | // For example,
 | ||||||
|  | //   sugaredLogger.With(
 | ||||||
|  | //     "hello", "world",
 | ||||||
|  | //     "failure", errors.New("oh no"),
 | ||||||
|  | //     Stack(),
 | ||||||
|  | //     "count", 42,
 | ||||||
|  | //     "user", User{Name: "alice"},
 | ||||||
|  | //  )
 | ||||||
|  | // is the equivalent of
 | ||||||
|  | //   unsugared.With(
 | ||||||
|  | //     String("hello", "world"),
 | ||||||
|  | //     String("failure", "oh no"),
 | ||||||
|  | //     Stack(),
 | ||||||
|  | //     Int("count", 42),
 | ||||||
|  | //     Object("user", User{Name: "alice"}),
 | ||||||
|  | //   )
 | ||||||
|  | //
 | ||||||
|  | // Note that the keys in key-value pairs should be strings. In development,
 | ||||||
|  | // passing a non-string key panics. In production, the logger is more
 | ||||||
|  | // forgiving: a separate error is logged, but the key-value pair is skipped
 | ||||||
|  | // and execution continues. Passing an orphaned key triggers similar behavior:
 | ||||||
|  | // panics in development and errors in production.
 | ||||||
|  | func (s *SugaredLogger) With(args ...interface{}) *SugaredLogger { | ||||||
|  | 	return &SugaredLogger{base: s.base.With(s.sweetenFields(args)...)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Debug uses fmt.Sprint to construct and log a message.
 | ||||||
|  | func (s *SugaredLogger) Debug(args ...interface{}) { | ||||||
|  | 	s.log(DebugLevel, "", args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Info uses fmt.Sprint to construct and log a message.
 | ||||||
|  | func (s *SugaredLogger) Info(args ...interface{}) { | ||||||
|  | 	s.log(InfoLevel, "", args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Warn uses fmt.Sprint to construct and log a message.
 | ||||||
|  | func (s *SugaredLogger) Warn(args ...interface{}) { | ||||||
|  | 	s.log(WarnLevel, "", args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Error uses fmt.Sprint to construct and log a message.
 | ||||||
|  | func (s *SugaredLogger) Error(args ...interface{}) { | ||||||
|  | 	s.log(ErrorLevel, "", args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DPanic uses fmt.Sprint to construct and log a message. In development, the
 | ||||||
|  | // logger then panics. (See DPanicLevel for details.)
 | ||||||
|  | func (s *SugaredLogger) DPanic(args ...interface{}) { | ||||||
|  | 	s.log(DPanicLevel, "", args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Panic uses fmt.Sprint to construct and log a message, then panics.
 | ||||||
|  | func (s *SugaredLogger) Panic(args ...interface{}) { | ||||||
|  | 	s.log(PanicLevel, "", args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit.
 | ||||||
|  | func (s *SugaredLogger) Fatal(args ...interface{}) { | ||||||
|  | 	s.log(FatalLevel, "", args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Debugf uses fmt.Sprintf to log a templated message.
 | ||||||
|  | func (s *SugaredLogger) Debugf(template string, args ...interface{}) { | ||||||
|  | 	s.log(DebugLevel, template, args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Infof uses fmt.Sprintf to log a templated message.
 | ||||||
|  | func (s *SugaredLogger) Infof(template string, args ...interface{}) { | ||||||
|  | 	s.log(InfoLevel, template, args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Warnf uses fmt.Sprintf to log a templated message.
 | ||||||
|  | func (s *SugaredLogger) Warnf(template string, args ...interface{}) { | ||||||
|  | 	s.log(WarnLevel, template, args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Errorf uses fmt.Sprintf to log a templated message.
 | ||||||
|  | func (s *SugaredLogger) Errorf(template string, args ...interface{}) { | ||||||
|  | 	s.log(ErrorLevel, template, args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DPanicf uses fmt.Sprintf to log a templated message. In development, the
 | ||||||
|  | // logger then panics. (See DPanicLevel for details.)
 | ||||||
|  | func (s *SugaredLogger) DPanicf(template string, args ...interface{}) { | ||||||
|  | 	s.log(DPanicLevel, template, args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Panicf uses fmt.Sprintf to log a templated message, then panics.
 | ||||||
|  | func (s *SugaredLogger) Panicf(template string, args ...interface{}) { | ||||||
|  | 	s.log(PanicLevel, template, args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit.
 | ||||||
|  | func (s *SugaredLogger) Fatalf(template string, args ...interface{}) { | ||||||
|  | 	s.log(FatalLevel, template, args, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Debugw logs a message with some additional context. The variadic key-value
 | ||||||
|  | // pairs are treated as they are in With.
 | ||||||
|  | //
 | ||||||
|  | // When debug-level logging is disabled, this is much faster than
 | ||||||
|  | //  s.With(keysAndValues).Debug(msg)
 | ||||||
|  | func (s *SugaredLogger) Debugw(msg string, keysAndValues ...interface{}) { | ||||||
|  | 	s.log(DebugLevel, msg, nil, keysAndValues) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Infow logs a message with some additional context. The variadic key-value
 | ||||||
|  | // pairs are treated as they are in With.
 | ||||||
|  | func (s *SugaredLogger) Infow(msg string, keysAndValues ...interface{}) { | ||||||
|  | 	s.log(InfoLevel, msg, nil, keysAndValues) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Warnw logs a message with some additional context. The variadic key-value
 | ||||||
|  | // pairs are treated as they are in With.
 | ||||||
|  | func (s *SugaredLogger) Warnw(msg string, keysAndValues ...interface{}) { | ||||||
|  | 	s.log(WarnLevel, msg, nil, keysAndValues) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Errorw logs a message with some additional context. The variadic key-value
 | ||||||
|  | // pairs are treated as they are in With.
 | ||||||
|  | func (s *SugaredLogger) Errorw(msg string, keysAndValues ...interface{}) { | ||||||
|  | 	s.log(ErrorLevel, msg, nil, keysAndValues) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DPanicw logs a message with some additional context. In development, the
 | ||||||
|  | // logger then panics. (See DPanicLevel for details.) The variadic key-value
 | ||||||
|  | // pairs are treated as they are in With.
 | ||||||
|  | func (s *SugaredLogger) DPanicw(msg string, keysAndValues ...interface{}) { | ||||||
|  | 	s.log(DPanicLevel, msg, nil, keysAndValues) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Panicw logs a message with some additional context, then panics. The
 | ||||||
|  | // variadic key-value pairs are treated as they are in With.
 | ||||||
|  | func (s *SugaredLogger) Panicw(msg string, keysAndValues ...interface{}) { | ||||||
|  | 	s.log(PanicLevel, msg, nil, keysAndValues) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Fatalw logs a message with some additional context, then calls os.Exit. The
 | ||||||
|  | // variadic key-value pairs are treated as they are in With.
 | ||||||
|  | func (s *SugaredLogger) Fatalw(msg string, keysAndValues ...interface{}) { | ||||||
|  | 	s.log(FatalLevel, msg, nil, keysAndValues) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Sync flushes any buffered log entries.
 | ||||||
|  | func (s *SugaredLogger) Sync() error { | ||||||
|  | 	return s.base.Sync() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *SugaredLogger) log(lvl zapcore.Level, template string, fmtArgs []interface{}, context []interface{}) { | ||||||
|  | 	// If logging at this level is completely disabled, skip the overhead of
 | ||||||
|  | 	// string formatting.
 | ||||||
|  | 	if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Format with Sprint, Sprintf, or neither.
 | ||||||
|  | 	msg := template | ||||||
|  | 	if msg == "" && len(fmtArgs) > 0 { | ||||||
|  | 		msg = fmt.Sprint(fmtArgs...) | ||||||
|  | 	} else if msg != "" && len(fmtArgs) > 0 { | ||||||
|  | 		msg = fmt.Sprintf(template, fmtArgs...) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if ce := s.base.Check(lvl, msg); ce != nil { | ||||||
|  | 		ce.Write(s.sweetenFields(context)...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *SugaredLogger) sweetenFields(args []interface{}) []Field { | ||||||
|  | 	if len(args) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Allocate enough space for the worst case; if users pass only structured
 | ||||||
|  | 	// fields, we shouldn't penalize them with extra allocations.
 | ||||||
|  | 	fields := make([]Field, 0, len(args)) | ||||||
|  | 	var invalid invalidPairs | ||||||
|  | 
 | ||||||
|  | 	for i := 0; i < len(args); { | ||||||
|  | 		// This is a strongly-typed field. Consume it and move on.
 | ||||||
|  | 		if f, ok := args[i].(Field); ok { | ||||||
|  | 			fields = append(fields, f) | ||||||
|  | 			i++ | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Make sure this element isn't a dangling key.
 | ||||||
|  | 		if i == len(args)-1 { | ||||||
|  | 			s.base.DPanic(_oddNumberErrMsg, Any("ignored", args[i])) | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Consume this value and the next, treating them as a key-value pair. If the
 | ||||||
|  | 		// key isn't a string, add this pair to the slice of invalid pairs.
 | ||||||
|  | 		key, val := args[i], args[i+1] | ||||||
|  | 		if keyStr, ok := key.(string); !ok { | ||||||
|  | 			// Subsequent errors are likely, so allocate once up front.
 | ||||||
|  | 			if cap(invalid) == 0 { | ||||||
|  | 				invalid = make(invalidPairs, 0, len(args)/2) | ||||||
|  | 			} | ||||||
|  | 			invalid = append(invalid, invalidPair{i, key, val}) | ||||||
|  | 		} else { | ||||||
|  | 			fields = append(fields, Any(keyStr, val)) | ||||||
|  | 		} | ||||||
|  | 		i += 2 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// If we encountered any invalid key-value pairs, log an error.
 | ||||||
|  | 	if len(invalid) > 0 { | ||||||
|  | 		s.base.DPanic(_nonStringKeyErrMsg, Array("invalid", invalid)) | ||||||
|  | 	} | ||||||
|  | 	return fields | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type invalidPair struct { | ||||||
|  | 	position   int | ||||||
|  | 	key, value interface{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p invalidPair) MarshalLogObject(enc zapcore.ObjectEncoder) error { | ||||||
|  | 	enc.AddInt64("position", int64(p.position)) | ||||||
|  | 	Any("key", p.key).AddTo(enc) | ||||||
|  | 	Any("value", p.value).AddTo(enc) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type invalidPairs []invalidPair | ||||||
|  | 
 | ||||||
|  | func (ps invalidPairs) MarshalLogArray(enc zapcore.ArrayEncoder) error { | ||||||
|  | 	var err error | ||||||
|  | 	for i := range ps { | ||||||
|  | 		err = multierr.Append(err, enc.AppendObject(ps[i])) | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | @ -0,0 +1,27 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
|  | func timeToMillis(t time.Time) int64 { | ||||||
|  | 	return t.UnixNano() / int64(time.Millisecond) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,96 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zap | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"os" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/zapcore" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/multierr" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Open is a high-level wrapper that takes a variadic number of paths, opens or
 | ||||||
|  | // creates each of the specified files, and combines them into a locked
 | ||||||
|  | // WriteSyncer. It also returns any error encountered and a function to close
 | ||||||
|  | // any opened files.
 | ||||||
|  | //
 | ||||||
|  | // Passing no paths returns a no-op WriteSyncer. The special paths "stdout" and
 | ||||||
|  | // "stderr" are interpreted as os.Stdout and os.Stderr, respectively.
 | ||||||
|  | func Open(paths ...string) (zapcore.WriteSyncer, func(), error) { | ||||||
|  | 	writers, close, err := open(paths) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	writer := CombineWriteSyncers(writers...) | ||||||
|  | 	return writer, close, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func open(paths []string) ([]zapcore.WriteSyncer, func(), error) { | ||||||
|  | 	var openErr error | ||||||
|  | 	writers := make([]zapcore.WriteSyncer, 0, len(paths)) | ||||||
|  | 	files := make([]*os.File, 0, len(paths)) | ||||||
|  | 	close := func() { | ||||||
|  | 		for _, f := range files { | ||||||
|  | 			f.Close() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	for _, path := range paths { | ||||||
|  | 		switch path { | ||||||
|  | 		case "stdout": | ||||||
|  | 			writers = append(writers, os.Stdout) | ||||||
|  | 			// Don't close standard out.
 | ||||||
|  | 			continue | ||||||
|  | 		case "stderr": | ||||||
|  | 			writers = append(writers, os.Stderr) | ||||||
|  | 			// Don't close standard error.
 | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) | ||||||
|  | 		openErr = multierr.Append(openErr, err) | ||||||
|  | 		if err == nil { | ||||||
|  | 			writers = append(writers, f) | ||||||
|  | 			files = append(files, f) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if openErr != nil { | ||||||
|  | 		close() | ||||||
|  | 		return writers, nil, openErr | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return writers, close, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CombineWriteSyncers is a utility that combines multiple WriteSyncers into a
 | ||||||
|  | // single, locked WriteSyncer. If no inputs are supplied, it returns a no-op
 | ||||||
|  | // WriteSyncer.
 | ||||||
|  | //
 | ||||||
|  | // It's provided purely as a convenience; the result is no different from
 | ||||||
|  | // using zapcore.NewMultiWriteSyncer and zapcore.Lock individually.
 | ||||||
|  | func CombineWriteSyncers(writers ...zapcore.WriteSyncer) zapcore.WriteSyncer { | ||||||
|  | 	if len(writers) == 0 { | ||||||
|  | 		return zapcore.AddSync(ioutil.Discard) | ||||||
|  | 	} | ||||||
|  | 	return zapcore.Lock(zapcore.NewMultiWriteSyncer(writers...)) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,147 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/buffer" | ||||||
|  | 	"go.uber.org/zap/internal/bufferpool" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var _sliceEncoderPool = sync.Pool{ | ||||||
|  | 	New: func() interface{} { | ||||||
|  | 		return &sliceArrayEncoder{elems: make([]interface{}, 0, 2)} | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getSliceEncoder() *sliceArrayEncoder { | ||||||
|  | 	return _sliceEncoderPool.Get().(*sliceArrayEncoder) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func putSliceEncoder(e *sliceArrayEncoder) { | ||||||
|  | 	e.elems = e.elems[:0] | ||||||
|  | 	_sliceEncoderPool.Put(e) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type consoleEncoder struct { | ||||||
|  | 	*jsonEncoder | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewConsoleEncoder creates an encoder whose output is designed for human -
 | ||||||
|  | // rather than machine - consumption. It serializes the core log entry data
 | ||||||
|  | // (message, level, timestamp, etc.) in a plain-text format and leaves the
 | ||||||
|  | // structured context as JSON.
 | ||||||
|  | //
 | ||||||
|  | // Note that although the console encoder doesn't use the keys specified in the
 | ||||||
|  | // encoder configuration, it will omit any element whose key is set to the empty
 | ||||||
|  | // string.
 | ||||||
|  | func NewConsoleEncoder(cfg EncoderConfig) Encoder { | ||||||
|  | 	return consoleEncoder{newJSONEncoder(cfg, true)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c consoleEncoder) Clone() Encoder { | ||||||
|  | 	return consoleEncoder{c.jsonEncoder.Clone().(*jsonEncoder)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) { | ||||||
|  | 	line := bufferpool.Get() | ||||||
|  | 
 | ||||||
|  | 	// We don't want the entry's metadata to be quoted and escaped (if it's
 | ||||||
|  | 	// encoded as strings), which means that we can't use the JSON encoder. The
 | ||||||
|  | 	// simplest option is to use the memory encoder and fmt.Fprint.
 | ||||||
|  | 	//
 | ||||||
|  | 	// If this ever becomes a performance bottleneck, we can implement
 | ||||||
|  | 	// ArrayEncoder for our plain-text format.
 | ||||||
|  | 	arr := getSliceEncoder() | ||||||
|  | 	if c.TimeKey != "" && c.EncodeTime != nil { | ||||||
|  | 		c.EncodeTime(ent.Time, arr) | ||||||
|  | 	} | ||||||
|  | 	if c.LevelKey != "" && c.EncodeLevel != nil { | ||||||
|  | 		c.EncodeLevel(ent.Level, arr) | ||||||
|  | 	} | ||||||
|  | 	if ent.LoggerName != "" && c.NameKey != "" { | ||||||
|  | 		nameEncoder := c.EncodeName | ||||||
|  | 
 | ||||||
|  | 		if nameEncoder == nil { | ||||||
|  | 			// Fall back to FullNameEncoder for backward compatibility.
 | ||||||
|  | 			nameEncoder = FullNameEncoder | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		nameEncoder(ent.LoggerName, arr) | ||||||
|  | 	} | ||||||
|  | 	if ent.Caller.Defined && c.CallerKey != "" && c.EncodeCaller != nil { | ||||||
|  | 		c.EncodeCaller(ent.Caller, arr) | ||||||
|  | 	} | ||||||
|  | 	for i := range arr.elems { | ||||||
|  | 		if i > 0 { | ||||||
|  | 			line.AppendByte('\t') | ||||||
|  | 		} | ||||||
|  | 		fmt.Fprint(line, arr.elems[i]) | ||||||
|  | 	} | ||||||
|  | 	putSliceEncoder(arr) | ||||||
|  | 
 | ||||||
|  | 	// Add the message itself.
 | ||||||
|  | 	if c.MessageKey != "" { | ||||||
|  | 		c.addTabIfNecessary(line) | ||||||
|  | 		line.AppendString(ent.Message) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Add any structured context.
 | ||||||
|  | 	c.writeContext(line, fields) | ||||||
|  | 
 | ||||||
|  | 	// If there's no stacktrace key, honor that; this allows users to force
 | ||||||
|  | 	// single-line output.
 | ||||||
|  | 	if ent.Stack != "" && c.StacktraceKey != "" { | ||||||
|  | 		line.AppendByte('\n') | ||||||
|  | 		line.AppendString(ent.Stack) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if c.LineEnding != "" { | ||||||
|  | 		line.AppendString(c.LineEnding) | ||||||
|  | 	} else { | ||||||
|  | 		line.AppendString(DefaultLineEnding) | ||||||
|  | 	} | ||||||
|  | 	return line, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c consoleEncoder) writeContext(line *buffer.Buffer, extra []Field) { | ||||||
|  | 	context := c.jsonEncoder.Clone().(*jsonEncoder) | ||||||
|  | 	defer context.buf.Free() | ||||||
|  | 
 | ||||||
|  | 	addFields(context, extra) | ||||||
|  | 	context.closeOpenNamespaces() | ||||||
|  | 	if context.buf.Len() == 0 { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	c.addTabIfNecessary(line) | ||||||
|  | 	line.AppendByte('{') | ||||||
|  | 	line.Write(context.buf.Bytes()) | ||||||
|  | 	line.AppendByte('}') | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c consoleEncoder) addTabIfNecessary(line *buffer.Buffer) { | ||||||
|  | 	if line.Len() > 0 { | ||||||
|  | 		line.AppendByte('\t') | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,113 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | // Core is a minimal, fast logger interface. It's designed for library authors
 | ||||||
|  | // to wrap in a more user-friendly API.
 | ||||||
|  | type Core interface { | ||||||
|  | 	LevelEnabler | ||||||
|  | 
 | ||||||
|  | 	// With adds structured context to the Core.
 | ||||||
|  | 	With([]Field) Core | ||||||
|  | 	// Check determines whether the supplied Entry should be logged (using the
 | ||||||
|  | 	// embedded LevelEnabler and possibly some extra logic). If the entry
 | ||||||
|  | 	// should be logged, the Core adds itself to the CheckedEntry and returns
 | ||||||
|  | 	// the result.
 | ||||||
|  | 	//
 | ||||||
|  | 	// Callers must use Check before calling Write.
 | ||||||
|  | 	Check(Entry, *CheckedEntry) *CheckedEntry | ||||||
|  | 	// Write serializes the Entry and any Fields supplied at the log site and
 | ||||||
|  | 	// writes them to their destination.
 | ||||||
|  | 	//
 | ||||||
|  | 	// If called, Write should always log the Entry and Fields; it should not
 | ||||||
|  | 	// replicate the logic of Check.
 | ||||||
|  | 	Write(Entry, []Field) error | ||||||
|  | 	// Sync flushes buffered logs (if any).
 | ||||||
|  | 	Sync() error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type nopCore struct{} | ||||||
|  | 
 | ||||||
|  | // NewNopCore returns a no-op Core.
 | ||||||
|  | func NewNopCore() Core                                        { return nopCore{} } | ||||||
|  | func (nopCore) Enabled(Level) bool                            { return false } | ||||||
|  | func (n nopCore) With([]Field) Core                           { return n } | ||||||
|  | func (nopCore) Check(_ Entry, ce *CheckedEntry) *CheckedEntry { return ce } | ||||||
|  | func (nopCore) Write(Entry, []Field) error                    { return nil } | ||||||
|  | func (nopCore) Sync() error                                   { return nil } | ||||||
|  | 
 | ||||||
|  | // NewCore creates a Core that writes logs to a WriteSyncer.
 | ||||||
|  | func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core { | ||||||
|  | 	return &ioCore{ | ||||||
|  | 		LevelEnabler: enab, | ||||||
|  | 		enc:          enc, | ||||||
|  | 		out:          ws, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ioCore struct { | ||||||
|  | 	LevelEnabler | ||||||
|  | 	enc Encoder | ||||||
|  | 	out WriteSyncer | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *ioCore) With(fields []Field) Core { | ||||||
|  | 	clone := c.clone() | ||||||
|  | 	addFields(clone.enc, fields) | ||||||
|  | 	return clone | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *ioCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { | ||||||
|  | 	if c.Enabled(ent.Level) { | ||||||
|  | 		return ce.AddCore(ent, c) | ||||||
|  | 	} | ||||||
|  | 	return ce | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *ioCore) Write(ent Entry, fields []Field) error { | ||||||
|  | 	buf, err := c.enc.EncodeEntry(ent, fields) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	_, err = c.out.Write(buf.Bytes()) | ||||||
|  | 	buf.Free() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if ent.Level > ErrorLevel { | ||||||
|  | 		// Since we may be crashing the program, sync the output. Ignore Sync
 | ||||||
|  | 		// errors, pending a clean solution to issue #370.
 | ||||||
|  | 		c.Sync() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *ioCore) Sync() error { | ||||||
|  | 	return c.out.Sync() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *ioCore) clone() *ioCore { | ||||||
|  | 	return &ioCore{ | ||||||
|  | 		LevelEnabler: c.LevelEnabler, | ||||||
|  | 		enc:          c.enc.Clone(), | ||||||
|  | 		out:          c.out, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | // Package zapcore defines and implements the low-level interfaces upon which
 | ||||||
|  | // zap is built. By providing alternate implementations of these interfaces,
 | ||||||
|  | // external packages can extend zap's capabilities.
 | ||||||
|  | package zapcore // import "go.uber.org/zap/zapcore"
 | ||||||
|  | @ -0,0 +1,348 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/buffer" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // DefaultLineEnding defines the default line ending when writing logs.
 | ||||||
|  | // Alternate line endings specified in EncoderConfig can override this
 | ||||||
|  | // behavior.
 | ||||||
|  | const DefaultLineEnding = "\n" | ||||||
|  | 
 | ||||||
|  | // A LevelEncoder serializes a Level to a primitive type.
 | ||||||
|  | type LevelEncoder func(Level, PrimitiveArrayEncoder) | ||||||
|  | 
 | ||||||
|  | // LowercaseLevelEncoder serializes a Level to a lowercase string. For example,
 | ||||||
|  | // InfoLevel is serialized to "info".
 | ||||||
|  | func LowercaseLevelEncoder(l Level, enc PrimitiveArrayEncoder) { | ||||||
|  | 	enc.AppendString(l.String()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // LowercaseColorLevelEncoder serializes a Level to a lowercase string and adds coloring.
 | ||||||
|  | // For example, InfoLevel is serialized to "info" and colored blue.
 | ||||||
|  | func LowercaseColorLevelEncoder(l Level, enc PrimitiveArrayEncoder) { | ||||||
|  | 	s, ok := _levelToLowercaseColorString[l] | ||||||
|  | 	if !ok { | ||||||
|  | 		s = _unknownLevelColor.Add(l.String()) | ||||||
|  | 	} | ||||||
|  | 	enc.AppendString(s) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CapitalLevelEncoder serializes a Level to an all-caps string. For example,
 | ||||||
|  | // InfoLevel is serialized to "INFO".
 | ||||||
|  | func CapitalLevelEncoder(l Level, enc PrimitiveArrayEncoder) { | ||||||
|  | 	enc.AppendString(l.CapitalString()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CapitalColorLevelEncoder serializes a Level to an all-caps string and adds color.
 | ||||||
|  | // For example, InfoLevel is serialized to "INFO" and colored blue.
 | ||||||
|  | func CapitalColorLevelEncoder(l Level, enc PrimitiveArrayEncoder) { | ||||||
|  | 	s, ok := _levelToCapitalColorString[l] | ||||||
|  | 	if !ok { | ||||||
|  | 		s = _unknownLevelColor.Add(l.CapitalString()) | ||||||
|  | 	} | ||||||
|  | 	enc.AppendString(s) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UnmarshalText unmarshals text to a LevelEncoder. "capital" is unmarshaled to
 | ||||||
|  | // CapitalLevelEncoder, "coloredCapital" is unmarshaled to CapitalColorLevelEncoder,
 | ||||||
|  | // "colored" is unmarshaled to LowercaseColorLevelEncoder, and anything else
 | ||||||
|  | // is unmarshaled to LowercaseLevelEncoder.
 | ||||||
|  | func (e *LevelEncoder) UnmarshalText(text []byte) error { | ||||||
|  | 	switch string(text) { | ||||||
|  | 	case "capital": | ||||||
|  | 		*e = CapitalLevelEncoder | ||||||
|  | 	case "capitalColor": | ||||||
|  | 		*e = CapitalColorLevelEncoder | ||||||
|  | 	case "color": | ||||||
|  | 		*e = LowercaseColorLevelEncoder | ||||||
|  | 	default: | ||||||
|  | 		*e = LowercaseLevelEncoder | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A TimeEncoder serializes a time.Time to a primitive type.
 | ||||||
|  | type TimeEncoder func(time.Time, PrimitiveArrayEncoder) | ||||||
|  | 
 | ||||||
|  | // EpochTimeEncoder serializes a time.Time to a floating-point number of seconds
 | ||||||
|  | // since the Unix epoch.
 | ||||||
|  | func EpochTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { | ||||||
|  | 	nanos := t.UnixNano() | ||||||
|  | 	sec := float64(nanos) / float64(time.Second) | ||||||
|  | 	enc.AppendFloat64(sec) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // EpochMillisTimeEncoder serializes a time.Time to a floating-point number of
 | ||||||
|  | // milliseconds since the Unix epoch.
 | ||||||
|  | func EpochMillisTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { | ||||||
|  | 	nanos := t.UnixNano() | ||||||
|  | 	millis := float64(nanos) / float64(time.Millisecond) | ||||||
|  | 	enc.AppendFloat64(millis) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // EpochNanosTimeEncoder serializes a time.Time to an integer number of
 | ||||||
|  | // nanoseconds since the Unix epoch.
 | ||||||
|  | func EpochNanosTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { | ||||||
|  | 	enc.AppendInt64(t.UnixNano()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ISO8601TimeEncoder serializes a time.Time to an ISO8601-formatted string
 | ||||||
|  | // with millisecond precision.
 | ||||||
|  | func ISO8601TimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { | ||||||
|  | 	enc.AppendString(t.Format("2006-01-02T15:04:05.000Z0700")) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UnmarshalText unmarshals text to a TimeEncoder. "iso8601" and "ISO8601" are
 | ||||||
|  | // unmarshaled to ISO8601TimeEncoder, "millis" is unmarshaled to
 | ||||||
|  | // EpochMillisTimeEncoder, and anything else is unmarshaled to EpochTimeEncoder.
 | ||||||
|  | func (e *TimeEncoder) UnmarshalText(text []byte) error { | ||||||
|  | 	switch string(text) { | ||||||
|  | 	case "iso8601", "ISO8601": | ||||||
|  | 		*e = ISO8601TimeEncoder | ||||||
|  | 	case "millis": | ||||||
|  | 		*e = EpochMillisTimeEncoder | ||||||
|  | 	case "nanos": | ||||||
|  | 		*e = EpochNanosTimeEncoder | ||||||
|  | 	default: | ||||||
|  | 		*e = EpochTimeEncoder | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A DurationEncoder serializes a time.Duration to a primitive type.
 | ||||||
|  | type DurationEncoder func(time.Duration, PrimitiveArrayEncoder) | ||||||
|  | 
 | ||||||
|  | // SecondsDurationEncoder serializes a time.Duration to a floating-point number of seconds elapsed.
 | ||||||
|  | func SecondsDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { | ||||||
|  | 	enc.AppendFloat64(float64(d) / float64(time.Second)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NanosDurationEncoder serializes a time.Duration to an integer number of
 | ||||||
|  | // nanoseconds elapsed.
 | ||||||
|  | func NanosDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { | ||||||
|  | 	enc.AppendInt64(int64(d)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // StringDurationEncoder serializes a time.Duration using its built-in String
 | ||||||
|  | // method.
 | ||||||
|  | func StringDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { | ||||||
|  | 	enc.AppendString(d.String()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UnmarshalText unmarshals text to a DurationEncoder. "string" is unmarshaled
 | ||||||
|  | // to StringDurationEncoder, and anything else is unmarshaled to
 | ||||||
|  | // NanosDurationEncoder.
 | ||||||
|  | func (e *DurationEncoder) UnmarshalText(text []byte) error { | ||||||
|  | 	switch string(text) { | ||||||
|  | 	case "string": | ||||||
|  | 		*e = StringDurationEncoder | ||||||
|  | 	case "nanos": | ||||||
|  | 		*e = NanosDurationEncoder | ||||||
|  | 	default: | ||||||
|  | 		*e = SecondsDurationEncoder | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A CallerEncoder serializes an EntryCaller to a primitive type.
 | ||||||
|  | type CallerEncoder func(EntryCaller, PrimitiveArrayEncoder) | ||||||
|  | 
 | ||||||
|  | // FullCallerEncoder serializes a caller in /full/path/to/package/file:line
 | ||||||
|  | // format.
 | ||||||
|  | func FullCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder) { | ||||||
|  | 	// TODO: consider using a byte-oriented API to save an allocation.
 | ||||||
|  | 	enc.AppendString(caller.String()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ShortCallerEncoder serializes a caller in package/file:line format, trimming
 | ||||||
|  | // all but the final directory from the full path.
 | ||||||
|  | func ShortCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder) { | ||||||
|  | 	// TODO: consider using a byte-oriented API to save an allocation.
 | ||||||
|  | 	enc.AppendString(caller.TrimmedPath()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UnmarshalText unmarshals text to a CallerEncoder. "full" is unmarshaled to
 | ||||||
|  | // FullCallerEncoder and anything else is unmarshaled to ShortCallerEncoder.
 | ||||||
|  | func (e *CallerEncoder) UnmarshalText(text []byte) error { | ||||||
|  | 	switch string(text) { | ||||||
|  | 	case "full": | ||||||
|  | 		*e = FullCallerEncoder | ||||||
|  | 	default: | ||||||
|  | 		*e = ShortCallerEncoder | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A NameEncoder serializes a period-separated logger name to a primitive
 | ||||||
|  | // type.
 | ||||||
|  | type NameEncoder func(string, PrimitiveArrayEncoder) | ||||||
|  | 
 | ||||||
|  | // FullNameEncoder serializes the logger name as-is.
 | ||||||
|  | func FullNameEncoder(loggerName string, enc PrimitiveArrayEncoder) { | ||||||
|  | 	enc.AppendString(loggerName) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UnmarshalText unmarshals text to a NameEncoder. Currently, everything is
 | ||||||
|  | // unmarshaled to FullNameEncoder.
 | ||||||
|  | func (e *NameEncoder) UnmarshalText(text []byte) error { | ||||||
|  | 	switch string(text) { | ||||||
|  | 	case "full": | ||||||
|  | 		*e = FullNameEncoder | ||||||
|  | 	default: | ||||||
|  | 		*e = FullNameEncoder | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // An EncoderConfig allows users to configure the concrete encoders supplied by
 | ||||||
|  | // zapcore.
 | ||||||
|  | type EncoderConfig struct { | ||||||
|  | 	// Set the keys used for each log entry. If any key is empty, that portion
 | ||||||
|  | 	// of the entry is omitted.
 | ||||||
|  | 	MessageKey    string `json:"messageKey" yaml:"messageKey"` | ||||||
|  | 	LevelKey      string `json:"levelKey" yaml:"levelKey"` | ||||||
|  | 	TimeKey       string `json:"timeKey" yaml:"timeKey"` | ||||||
|  | 	NameKey       string `json:"nameKey" yaml:"nameKey"` | ||||||
|  | 	CallerKey     string `json:"callerKey" yaml:"callerKey"` | ||||||
|  | 	StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"` | ||||||
|  | 	LineEnding    string `json:"lineEnding" yaml:"lineEnding"` | ||||||
|  | 	// Configure the primitive representations of common complex types. For
 | ||||||
|  | 	// example, some users may want all time.Times serialized as floating-point
 | ||||||
|  | 	// seconds since epoch, while others may prefer ISO8601 strings.
 | ||||||
|  | 	EncodeLevel    LevelEncoder    `json:"levelEncoder" yaml:"levelEncoder"` | ||||||
|  | 	EncodeTime     TimeEncoder     `json:"timeEncoder" yaml:"timeEncoder"` | ||||||
|  | 	EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"` | ||||||
|  | 	EncodeCaller   CallerEncoder   `json:"callerEncoder" yaml:"callerEncoder"` | ||||||
|  | 	// Unlike the other primitive type encoders, EncodeName is optional. The
 | ||||||
|  | 	// zero value falls back to FullNameEncoder.
 | ||||||
|  | 	EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ObjectEncoder is a strongly-typed, encoding-agnostic interface for adding a
 | ||||||
|  | // map- or struct-like object to the logging context. Like maps, ObjectEncoders
 | ||||||
|  | // aren't safe for concurrent use (though typical use shouldn't require locks).
 | ||||||
|  | type ObjectEncoder interface { | ||||||
|  | 	// Logging-specific marshalers.
 | ||||||
|  | 	AddArray(key string, marshaler ArrayMarshaler) error | ||||||
|  | 	AddObject(key string, marshaler ObjectMarshaler) error | ||||||
|  | 
 | ||||||
|  | 	// Built-in types.
 | ||||||
|  | 	AddBinary(key string, value []byte)     // for arbitrary bytes
 | ||||||
|  | 	AddByteString(key string, value []byte) // for UTF-8 encoded bytes
 | ||||||
|  | 	AddBool(key string, value bool) | ||||||
|  | 	AddComplex128(key string, value complex128) | ||||||
|  | 	AddComplex64(key string, value complex64) | ||||||
|  | 	AddDuration(key string, value time.Duration) | ||||||
|  | 	AddFloat64(key string, value float64) | ||||||
|  | 	AddFloat32(key string, value float32) | ||||||
|  | 	AddInt(key string, value int) | ||||||
|  | 	AddInt64(key string, value int64) | ||||||
|  | 	AddInt32(key string, value int32) | ||||||
|  | 	AddInt16(key string, value int16) | ||||||
|  | 	AddInt8(key string, value int8) | ||||||
|  | 	AddString(key, value string) | ||||||
|  | 	AddTime(key string, value time.Time) | ||||||
|  | 	AddUint(key string, value uint) | ||||||
|  | 	AddUint64(key string, value uint64) | ||||||
|  | 	AddUint32(key string, value uint32) | ||||||
|  | 	AddUint16(key string, value uint16) | ||||||
|  | 	AddUint8(key string, value uint8) | ||||||
|  | 	AddUintptr(key string, value uintptr) | ||||||
|  | 
 | ||||||
|  | 	// AddReflected uses reflection to serialize arbitrary objects, so it's slow
 | ||||||
|  | 	// and allocation-heavy.
 | ||||||
|  | 	AddReflected(key string, value interface{}) error | ||||||
|  | 	// OpenNamespace opens an isolated namespace where all subsequent fields will
 | ||||||
|  | 	// be added. Applications can use namespaces to prevent key collisions when
 | ||||||
|  | 	// injecting loggers into sub-components or third-party libraries.
 | ||||||
|  | 	OpenNamespace(key string) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ArrayEncoder is a strongly-typed, encoding-agnostic interface for adding
 | ||||||
|  | // array-like objects to the logging context. Of note, it supports mixed-type
 | ||||||
|  | // arrays even though they aren't typical in Go. Like slices, ArrayEncoders
 | ||||||
|  | // aren't safe for concurrent use (though typical use shouldn't require locks).
 | ||||||
|  | type ArrayEncoder interface { | ||||||
|  | 	// Built-in types.
 | ||||||
|  | 	PrimitiveArrayEncoder | ||||||
|  | 
 | ||||||
|  | 	// Time-related types.
 | ||||||
|  | 	AppendDuration(time.Duration) | ||||||
|  | 	AppendTime(time.Time) | ||||||
|  | 
 | ||||||
|  | 	// Logging-specific marshalers.
 | ||||||
|  | 	AppendArray(ArrayMarshaler) error | ||||||
|  | 	AppendObject(ObjectMarshaler) error | ||||||
|  | 
 | ||||||
|  | 	// AppendReflected uses reflection to serialize arbitrary objects, so it's
 | ||||||
|  | 	// slow and allocation-heavy.
 | ||||||
|  | 	AppendReflected(value interface{}) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // PrimitiveArrayEncoder is the subset of the ArrayEncoder interface that deals
 | ||||||
|  | // only in Go's built-in types. It's included only so that Duration- and
 | ||||||
|  | // TimeEncoders cannot trigger infinite recursion.
 | ||||||
|  | type PrimitiveArrayEncoder interface { | ||||||
|  | 	// Built-in types.
 | ||||||
|  | 	AppendBool(bool) | ||||||
|  | 	AppendByteString([]byte) // for UTF-8 encoded bytes
 | ||||||
|  | 	AppendComplex128(complex128) | ||||||
|  | 	AppendComplex64(complex64) | ||||||
|  | 	AppendFloat64(float64) | ||||||
|  | 	AppendFloat32(float32) | ||||||
|  | 	AppendInt(int) | ||||||
|  | 	AppendInt64(int64) | ||||||
|  | 	AppendInt32(int32) | ||||||
|  | 	AppendInt16(int16) | ||||||
|  | 	AppendInt8(int8) | ||||||
|  | 	AppendString(string) | ||||||
|  | 	AppendUint(uint) | ||||||
|  | 	AppendUint64(uint64) | ||||||
|  | 	AppendUint32(uint32) | ||||||
|  | 	AppendUint16(uint16) | ||||||
|  | 	AppendUint8(uint8) | ||||||
|  | 	AppendUintptr(uintptr) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Encoder is a format-agnostic interface for all log entry marshalers. Since
 | ||||||
|  | // log encoders don't need to support the same wide range of use cases as
 | ||||||
|  | // general-purpose marshalers, it's possible to make them faster and
 | ||||||
|  | // lower-allocation.
 | ||||||
|  | //
 | ||||||
|  | // Implementations of the ObjectEncoder interface's methods can, of course,
 | ||||||
|  | // freely modify the receiver. However, the Clone and EncodeEntry methods will
 | ||||||
|  | // be called concurrently and shouldn't modify the receiver.
 | ||||||
|  | type Encoder interface { | ||||||
|  | 	ObjectEncoder | ||||||
|  | 
 | ||||||
|  | 	// Clone copies the encoder, ensuring that adding fields to the copy doesn't
 | ||||||
|  | 	// affect the original.
 | ||||||
|  | 	Clone() Encoder | ||||||
|  | 
 | ||||||
|  | 	// EncodeEntry encodes an entry and fields, along with any accumulated
 | ||||||
|  | 	// context, into a byte buffer and returns it.
 | ||||||
|  | 	EncodeEntry(Entry, []Field) (*buffer.Buffer, error) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,257 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/internal/bufferpool" | ||||||
|  | 	"go.uber.org/zap/internal/exit" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/multierr" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	_cePool = sync.Pool{New: func() interface{} { | ||||||
|  | 		// Pre-allocate some space for cores.
 | ||||||
|  | 		return &CheckedEntry{ | ||||||
|  | 			cores: make([]Core, 4), | ||||||
|  | 		} | ||||||
|  | 	}} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func getCheckedEntry() *CheckedEntry { | ||||||
|  | 	ce := _cePool.Get().(*CheckedEntry) | ||||||
|  | 	ce.reset() | ||||||
|  | 	return ce | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func putCheckedEntry(ce *CheckedEntry) { | ||||||
|  | 	if ce == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	_cePool.Put(ce) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewEntryCaller makes an EntryCaller from the return signature of
 | ||||||
|  | // runtime.Caller.
 | ||||||
|  | func NewEntryCaller(pc uintptr, file string, line int, ok bool) EntryCaller { | ||||||
|  | 	if !ok { | ||||||
|  | 		return EntryCaller{} | ||||||
|  | 	} | ||||||
|  | 	return EntryCaller{ | ||||||
|  | 		PC:      pc, | ||||||
|  | 		File:    file, | ||||||
|  | 		Line:    line, | ||||||
|  | 		Defined: true, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // EntryCaller represents the caller of a logging function.
 | ||||||
|  | type EntryCaller struct { | ||||||
|  | 	Defined bool | ||||||
|  | 	PC      uintptr | ||||||
|  | 	File    string | ||||||
|  | 	Line    int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // String returns the full path and line number of the caller.
 | ||||||
|  | func (ec EntryCaller) String() string { | ||||||
|  | 	return ec.FullPath() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FullPath returns a /full/path/to/package/file:line description of the
 | ||||||
|  | // caller.
 | ||||||
|  | func (ec EntryCaller) FullPath() string { | ||||||
|  | 	if !ec.Defined { | ||||||
|  | 		return "undefined" | ||||||
|  | 	} | ||||||
|  | 	buf := bufferpool.Get() | ||||||
|  | 	buf.AppendString(ec.File) | ||||||
|  | 	buf.AppendByte(':') | ||||||
|  | 	buf.AppendInt(int64(ec.Line)) | ||||||
|  | 	caller := buf.String() | ||||||
|  | 	buf.Free() | ||||||
|  | 	return caller | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TrimmedPath returns a package/file:line description of the caller,
 | ||||||
|  | // preserving only the leaf directory name and file name.
 | ||||||
|  | func (ec EntryCaller) TrimmedPath() string { | ||||||
|  | 	if !ec.Defined { | ||||||
|  | 		return "undefined" | ||||||
|  | 	} | ||||||
|  | 	// nb. To make sure we trim the path correctly on Windows too, we
 | ||||||
|  | 	// counter-intuitively need to use '/' and *not* os.PathSeparator here,
 | ||||||
|  | 	// because the path given originates from Go stdlib, specifically
 | ||||||
|  | 	// runtime.Caller() which (as of Mar/17) returns forward slashes even on
 | ||||||
|  | 	// Windows.
 | ||||||
|  | 	//
 | ||||||
|  | 	// See https://github.com/golang/go/issues/3335
 | ||||||
|  | 	// and https://github.com/golang/go/issues/18151
 | ||||||
|  | 	//
 | ||||||
|  | 	// for discussion on the issue on Go side.
 | ||||||
|  | 	//
 | ||||||
|  | 	// Find the last separator.
 | ||||||
|  | 	//
 | ||||||
|  | 	idx := strings.LastIndexByte(ec.File, '/') | ||||||
|  | 	if idx == -1 { | ||||||
|  | 		return ec.FullPath() | ||||||
|  | 	} | ||||||
|  | 	// Find the penultimate separator.
 | ||||||
|  | 	idx = strings.LastIndexByte(ec.File[:idx], '/') | ||||||
|  | 	if idx == -1 { | ||||||
|  | 		return ec.FullPath() | ||||||
|  | 	} | ||||||
|  | 	buf := bufferpool.Get() | ||||||
|  | 	// Keep everything after the penultimate separator.
 | ||||||
|  | 	buf.AppendString(ec.File[idx+1:]) | ||||||
|  | 	buf.AppendByte(':') | ||||||
|  | 	buf.AppendInt(int64(ec.Line)) | ||||||
|  | 	caller := buf.String() | ||||||
|  | 	buf.Free() | ||||||
|  | 	return caller | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // An Entry represents a complete log message. The entry's structured context
 | ||||||
|  | // is already serialized, but the log level, time, message, and call site
 | ||||||
|  | // information are available for inspection and modification.
 | ||||||
|  | //
 | ||||||
|  | // Entries are pooled, so any functions that accept them MUST be careful not to
 | ||||||
|  | // retain references to them.
 | ||||||
|  | type Entry struct { | ||||||
|  | 	Level      Level | ||||||
|  | 	Time       time.Time | ||||||
|  | 	LoggerName string | ||||||
|  | 	Message    string | ||||||
|  | 	Caller     EntryCaller | ||||||
|  | 	Stack      string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CheckWriteAction indicates what action to take after a log entry is
 | ||||||
|  | // processed. Actions are ordered in increasing severity.
 | ||||||
|  | type CheckWriteAction uint8 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// WriteThenNoop indicates that nothing special needs to be done. It's the
 | ||||||
|  | 	// default behavior.
 | ||||||
|  | 	WriteThenNoop CheckWriteAction = iota | ||||||
|  | 	// WriteThenPanic causes a panic after Write.
 | ||||||
|  | 	WriteThenPanic | ||||||
|  | 	// WriteThenFatal causes a fatal os.Exit after Write.
 | ||||||
|  | 	WriteThenFatal | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // CheckedEntry is an Entry together with a collection of Cores that have
 | ||||||
|  | // already agreed to log it.
 | ||||||
|  | //
 | ||||||
|  | // CheckedEntry references should be created by calling AddCore or Should on a
 | ||||||
|  | // nil *CheckedEntry. References are returned to a pool after Write, and MUST
 | ||||||
|  | // NOT be retained after calling their Write method.
 | ||||||
|  | type CheckedEntry struct { | ||||||
|  | 	Entry | ||||||
|  | 	ErrorOutput WriteSyncer | ||||||
|  | 	dirty       bool // best-effort detection of pool misuse
 | ||||||
|  | 	should      CheckWriteAction | ||||||
|  | 	cores       []Core | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ce *CheckedEntry) reset() { | ||||||
|  | 	ce.Entry = Entry{} | ||||||
|  | 	ce.ErrorOutput = nil | ||||||
|  | 	ce.dirty = false | ||||||
|  | 	ce.should = WriteThenNoop | ||||||
|  | 	for i := range ce.cores { | ||||||
|  | 		// don't keep references to cores
 | ||||||
|  | 		ce.cores[i] = nil | ||||||
|  | 	} | ||||||
|  | 	ce.cores = ce.cores[:0] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Write writes the entry to the stored Cores, returns any errors, and returns
 | ||||||
|  | // the CheckedEntry reference to a pool for immediate re-use. Finally, it
 | ||||||
|  | // executes any required CheckWriteAction.
 | ||||||
|  | func (ce *CheckedEntry) Write(fields ...Field) { | ||||||
|  | 	if ce == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if ce.dirty { | ||||||
|  | 		if ce.ErrorOutput != nil { | ||||||
|  | 			// Make a best effort to detect unsafe re-use of this CheckedEntry.
 | ||||||
|  | 			// If the entry is dirty, log an internal error; because the
 | ||||||
|  | 			// CheckedEntry is being used after it was returned to the pool,
 | ||||||
|  | 			// the message may be an amalgamation from multiple call sites.
 | ||||||
|  | 			fmt.Fprintf(ce.ErrorOutput, "%v Unsafe CheckedEntry re-use near Entry %+v.\n", time.Now(), ce.Entry) | ||||||
|  | 			ce.ErrorOutput.Sync() | ||||||
|  | 		} | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	ce.dirty = true | ||||||
|  | 
 | ||||||
|  | 	var err error | ||||||
|  | 	for i := range ce.cores { | ||||||
|  | 		err = multierr.Append(err, ce.cores[i].Write(ce.Entry, fields)) | ||||||
|  | 	} | ||||||
|  | 	if ce.ErrorOutput != nil { | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Fprintf(ce.ErrorOutput, "%v write error: %v\n", time.Now(), err) | ||||||
|  | 			ce.ErrorOutput.Sync() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	should, msg := ce.should, ce.Message | ||||||
|  | 	putCheckedEntry(ce) | ||||||
|  | 
 | ||||||
|  | 	switch should { | ||||||
|  | 	case WriteThenPanic: | ||||||
|  | 		panic(msg) | ||||||
|  | 	case WriteThenFatal: | ||||||
|  | 		exit.Exit() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddCore adds a Core that has agreed to log this CheckedEntry. It's intended to be
 | ||||||
|  | // used by Core.Check implementations, and is safe to call on nil CheckedEntry
 | ||||||
|  | // references.
 | ||||||
|  | func (ce *CheckedEntry) AddCore(ent Entry, core Core) *CheckedEntry { | ||||||
|  | 	if ce == nil { | ||||||
|  | 		ce = getCheckedEntry() | ||||||
|  | 		ce.Entry = ent | ||||||
|  | 	} | ||||||
|  | 	ce.cores = append(ce.cores, core) | ||||||
|  | 	return ce | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Should sets this CheckedEntry's CheckWriteAction, which controls whether a
 | ||||||
|  | // Core will panic or fatal after writing this log entry. Like AddCore, it's
 | ||||||
|  | // safe to call on nil CheckedEntry references.
 | ||||||
|  | func (ce *CheckedEntry) Should(ent Entry, should CheckWriteAction) *CheckedEntry { | ||||||
|  | 	if ce == nil { | ||||||
|  | 		ce = getCheckedEntry() | ||||||
|  | 		ce.Entry = ent | ||||||
|  | 	} | ||||||
|  | 	ce.should = should | ||||||
|  | 	return ce | ||||||
|  | } | ||||||
|  | @ -0,0 +1,120 @@ | ||||||
|  | // Copyright (c) 2017 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"sync" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Encodes the given error into fields of an object. A field with the given
 | ||||||
|  | // name is added for the error message.
 | ||||||
|  | //
 | ||||||
|  | // If the error implements fmt.Formatter, a field with the name ${key}Verbose
 | ||||||
|  | // is also added with the full verbose error message.
 | ||||||
|  | //
 | ||||||
|  | // Finally, if the error implements errorGroup (from go.uber.org/multierr) or
 | ||||||
|  | // causer (from github.com/pkg/errors), a ${key}Causes field is added with an
 | ||||||
|  | // array of objects containing the errors this error was comprised of.
 | ||||||
|  | //
 | ||||||
|  | //  {
 | ||||||
|  | //    "error": err.Error(),
 | ||||||
|  | //    "errorVerbose": fmt.Sprintf("%+v", err),
 | ||||||
|  | //    "errorCauses": [
 | ||||||
|  | //      ...
 | ||||||
|  | //    ],
 | ||||||
|  | //  }
 | ||||||
|  | func encodeError(key string, err error, enc ObjectEncoder) error { | ||||||
|  | 	basic := err.Error() | ||||||
|  | 	enc.AddString(key, basic) | ||||||
|  | 
 | ||||||
|  | 	switch e := err.(type) { | ||||||
|  | 	case errorGroup: | ||||||
|  | 		return enc.AddArray(key+"Causes", errArray(e.Errors())) | ||||||
|  | 	case fmt.Formatter: | ||||||
|  | 		verbose := fmt.Sprintf("%+v", e) | ||||||
|  | 		if verbose != basic { | ||||||
|  | 			// This is a rich error type, like those produced by
 | ||||||
|  | 			// github.com/pkg/errors.
 | ||||||
|  | 			enc.AddString(key+"Verbose", verbose) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type errorGroup interface { | ||||||
|  | 	// Provides read-only access to the underlying list of errors, preferably
 | ||||||
|  | 	// without causing any allocs.
 | ||||||
|  | 	Errors() []error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type causer interface { | ||||||
|  | 	// Provides access to the error that caused this error.
 | ||||||
|  | 	Cause() error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Note that errArry and errArrayElem are very similar to the version
 | ||||||
|  | // implemented in the top-level error.go file. We can't re-use this because
 | ||||||
|  | // that would require exporting errArray as part of the zapcore API.
 | ||||||
|  | 
 | ||||||
|  | // Encodes a list of errors using the standard error encoding logic.
 | ||||||
|  | type errArray []error | ||||||
|  | 
 | ||||||
|  | func (errs errArray) MarshalLogArray(arr ArrayEncoder) error { | ||||||
|  | 	for i := range errs { | ||||||
|  | 		if errs[i] == nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		el := newErrArrayElem(errs[i]) | ||||||
|  | 		arr.AppendObject(el) | ||||||
|  | 		el.Free() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _errArrayElemPool = sync.Pool{New: func() interface{} { | ||||||
|  | 	return &errArrayElem{} | ||||||
|  | }} | ||||||
|  | 
 | ||||||
|  | // Encodes any error into a {"error": ...} re-using the same errors logic.
 | ||||||
|  | //
 | ||||||
|  | // May be passed in place of an array to build a single-element array.
 | ||||||
|  | type errArrayElem struct{ err error } | ||||||
|  | 
 | ||||||
|  | func newErrArrayElem(err error) *errArrayElem { | ||||||
|  | 	e := _errArrayElemPool.Get().(*errArrayElem) | ||||||
|  | 	e.err = err | ||||||
|  | 	return e | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *errArrayElem) MarshalLogArray(arr ArrayEncoder) error { | ||||||
|  | 	return arr.AppendObject(e) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *errArrayElem) MarshalLogObject(enc ObjectEncoder) error { | ||||||
|  | 	return encodeError("error", e.err, enc) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *errArrayElem) Free() { | ||||||
|  | 	e.err = nil | ||||||
|  | 	_errArrayElemPool.Put(e) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,201 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"math" | ||||||
|  | 	"reflect" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // A FieldType indicates which member of the Field union struct should be used
 | ||||||
|  | // and how it should be serialized.
 | ||||||
|  | type FieldType uint8 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// UnknownType is the default field type. Attempting to add it to an encoder will panic.
 | ||||||
|  | 	UnknownType FieldType = iota | ||||||
|  | 	// ArrayMarshalerType indicates that the field carries an ArrayMarshaler.
 | ||||||
|  | 	ArrayMarshalerType | ||||||
|  | 	// ObjectMarshalerType indicates that the field carries an ObjectMarshaler.
 | ||||||
|  | 	ObjectMarshalerType | ||||||
|  | 	// BinaryType indicates that the field carries an opaque binary blob.
 | ||||||
|  | 	BinaryType | ||||||
|  | 	// BoolType indicates that the field carries a bool.
 | ||||||
|  | 	BoolType | ||||||
|  | 	// ByteStringType indicates that the field carries UTF-8 encoded bytes.
 | ||||||
|  | 	ByteStringType | ||||||
|  | 	// Complex128Type indicates that the field carries a complex128.
 | ||||||
|  | 	Complex128Type | ||||||
|  | 	// Complex64Type indicates that the field carries a complex128.
 | ||||||
|  | 	Complex64Type | ||||||
|  | 	// DurationType indicates that the field carries a time.Duration.
 | ||||||
|  | 	DurationType | ||||||
|  | 	// Float64Type indicates that the field carries a float64.
 | ||||||
|  | 	Float64Type | ||||||
|  | 	// Float32Type indicates that the field carries a float32.
 | ||||||
|  | 	Float32Type | ||||||
|  | 	// Int64Type indicates that the field carries an int64.
 | ||||||
|  | 	Int64Type | ||||||
|  | 	// Int32Type indicates that the field carries an int32.
 | ||||||
|  | 	Int32Type | ||||||
|  | 	// Int16Type indicates that the field carries an int16.
 | ||||||
|  | 	Int16Type | ||||||
|  | 	// Int8Type indicates that the field carries an int8.
 | ||||||
|  | 	Int8Type | ||||||
|  | 	// StringType indicates that the field carries a string.
 | ||||||
|  | 	StringType | ||||||
|  | 	// TimeType indicates that the field carries a time.Time.
 | ||||||
|  | 	TimeType | ||||||
|  | 	// Uint64Type indicates that the field carries a uint64.
 | ||||||
|  | 	Uint64Type | ||||||
|  | 	// Uint32Type indicates that the field carries a uint32.
 | ||||||
|  | 	Uint32Type | ||||||
|  | 	// Uint16Type indicates that the field carries a uint16.
 | ||||||
|  | 	Uint16Type | ||||||
|  | 	// Uint8Type indicates that the field carries a uint8.
 | ||||||
|  | 	Uint8Type | ||||||
|  | 	// UintptrType indicates that the field carries a uintptr.
 | ||||||
|  | 	UintptrType | ||||||
|  | 	// ReflectType indicates that the field carries an interface{}, which should
 | ||||||
|  | 	// be serialized using reflection.
 | ||||||
|  | 	ReflectType | ||||||
|  | 	// NamespaceType signals the beginning of an isolated namespace. All
 | ||||||
|  | 	// subsequent fields should be added to the new namespace.
 | ||||||
|  | 	NamespaceType | ||||||
|  | 	// StringerType indicates that the field carries a fmt.Stringer.
 | ||||||
|  | 	StringerType | ||||||
|  | 	// ErrorType indicates that the field carries an error.
 | ||||||
|  | 	ErrorType | ||||||
|  | 	// SkipType indicates that the field is a no-op.
 | ||||||
|  | 	SkipType | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // A Field is a marshaling operation used to add a key-value pair to a logger's
 | ||||||
|  | // context. Most fields are lazily marshaled, so it's inexpensive to add fields
 | ||||||
|  | // to disabled debug-level log statements.
 | ||||||
|  | type Field struct { | ||||||
|  | 	Key       string | ||||||
|  | 	Type      FieldType | ||||||
|  | 	Integer   int64 | ||||||
|  | 	String    string | ||||||
|  | 	Interface interface{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddTo exports a field through the ObjectEncoder interface. It's primarily
 | ||||||
|  | // useful to library authors, and shouldn't be necessary in most applications.
 | ||||||
|  | func (f Field) AddTo(enc ObjectEncoder) { | ||||||
|  | 	var err error | ||||||
|  | 
 | ||||||
|  | 	switch f.Type { | ||||||
|  | 	case ArrayMarshalerType: | ||||||
|  | 		err = enc.AddArray(f.Key, f.Interface.(ArrayMarshaler)) | ||||||
|  | 	case ObjectMarshalerType: | ||||||
|  | 		err = enc.AddObject(f.Key, f.Interface.(ObjectMarshaler)) | ||||||
|  | 	case BinaryType: | ||||||
|  | 		enc.AddBinary(f.Key, f.Interface.([]byte)) | ||||||
|  | 	case BoolType: | ||||||
|  | 		enc.AddBool(f.Key, f.Integer == 1) | ||||||
|  | 	case ByteStringType: | ||||||
|  | 		enc.AddByteString(f.Key, f.Interface.([]byte)) | ||||||
|  | 	case Complex128Type: | ||||||
|  | 		enc.AddComplex128(f.Key, f.Interface.(complex128)) | ||||||
|  | 	case Complex64Type: | ||||||
|  | 		enc.AddComplex64(f.Key, f.Interface.(complex64)) | ||||||
|  | 	case DurationType: | ||||||
|  | 		enc.AddDuration(f.Key, time.Duration(f.Integer)) | ||||||
|  | 	case Float64Type: | ||||||
|  | 		enc.AddFloat64(f.Key, math.Float64frombits(uint64(f.Integer))) | ||||||
|  | 	case Float32Type: | ||||||
|  | 		enc.AddFloat32(f.Key, math.Float32frombits(uint32(f.Integer))) | ||||||
|  | 	case Int64Type: | ||||||
|  | 		enc.AddInt64(f.Key, f.Integer) | ||||||
|  | 	case Int32Type: | ||||||
|  | 		enc.AddInt32(f.Key, int32(f.Integer)) | ||||||
|  | 	case Int16Type: | ||||||
|  | 		enc.AddInt16(f.Key, int16(f.Integer)) | ||||||
|  | 	case Int8Type: | ||||||
|  | 		enc.AddInt8(f.Key, int8(f.Integer)) | ||||||
|  | 	case StringType: | ||||||
|  | 		enc.AddString(f.Key, f.String) | ||||||
|  | 	case TimeType: | ||||||
|  | 		if f.Interface != nil { | ||||||
|  | 			enc.AddTime(f.Key, time.Unix(0, f.Integer).In(f.Interface.(*time.Location))) | ||||||
|  | 		} else { | ||||||
|  | 			// Fall back to UTC if location is nil.
 | ||||||
|  | 			enc.AddTime(f.Key, time.Unix(0, f.Integer)) | ||||||
|  | 		} | ||||||
|  | 	case Uint64Type: | ||||||
|  | 		enc.AddUint64(f.Key, uint64(f.Integer)) | ||||||
|  | 	case Uint32Type: | ||||||
|  | 		enc.AddUint32(f.Key, uint32(f.Integer)) | ||||||
|  | 	case Uint16Type: | ||||||
|  | 		enc.AddUint16(f.Key, uint16(f.Integer)) | ||||||
|  | 	case Uint8Type: | ||||||
|  | 		enc.AddUint8(f.Key, uint8(f.Integer)) | ||||||
|  | 	case UintptrType: | ||||||
|  | 		enc.AddUintptr(f.Key, uintptr(f.Integer)) | ||||||
|  | 	case ReflectType: | ||||||
|  | 		err = enc.AddReflected(f.Key, f.Interface) | ||||||
|  | 	case NamespaceType: | ||||||
|  | 		enc.OpenNamespace(f.Key) | ||||||
|  | 	case StringerType: | ||||||
|  | 		enc.AddString(f.Key, f.Interface.(fmt.Stringer).String()) | ||||||
|  | 	case ErrorType: | ||||||
|  | 		encodeError(f.Key, f.Interface.(error), enc) | ||||||
|  | 	case SkipType: | ||||||
|  | 		break | ||||||
|  | 	default: | ||||||
|  | 		panic(fmt.Sprintf("unknown field type: %v", f)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		enc.AddString(fmt.Sprintf("%sError", f.Key), err.Error()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Equals returns whether two fields are equal. For non-primitive types such as
 | ||||||
|  | // errors, marshalers, or reflect types, it uses reflect.DeepEqual.
 | ||||||
|  | func (f Field) Equals(other Field) bool { | ||||||
|  | 	if f.Type != other.Type { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	if f.Key != other.Key { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch f.Type { | ||||||
|  | 	case BinaryType, ByteStringType: | ||||||
|  | 		return bytes.Equal(f.Interface.([]byte), other.Interface.([]byte)) | ||||||
|  | 	case ArrayMarshalerType, ObjectMarshalerType, ErrorType, ReflectType: | ||||||
|  | 		return reflect.DeepEqual(f.Interface, other.Interface) | ||||||
|  | 	default: | ||||||
|  | 		return f == other | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func addFields(enc ObjectEncoder, fields []Field) { | ||||||
|  | 	for i := range fields { | ||||||
|  | 		fields[i].AddTo(enc) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,68 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | import "go.uber.org/multierr" | ||||||
|  | 
 | ||||||
|  | type hooked struct { | ||||||
|  | 	Core | ||||||
|  | 	funcs []func(Entry) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RegisterHooks wraps a Core and runs a collection of user-defined callback
 | ||||||
|  | // hooks each time a message is logged. Execution of the callbacks is blocking.
 | ||||||
|  | //
 | ||||||
|  | // This offers users an easy way to register simple callbacks (e.g., metrics
 | ||||||
|  | // collection) without implementing the full Core interface.
 | ||||||
|  | func RegisterHooks(core Core, hooks ...func(Entry) error) Core { | ||||||
|  | 	funcs := append([]func(Entry) error{}, hooks...) | ||||||
|  | 	return &hooked{ | ||||||
|  | 		Core:  core, | ||||||
|  | 		funcs: funcs, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (h *hooked) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { | ||||||
|  | 	// Let the wrapped Core decide whether to log this message or not. This
 | ||||||
|  | 	// also gives the downstream a chance to register itself directly with the
 | ||||||
|  | 	// CheckedEntry.
 | ||||||
|  | 	if downstream := h.Core.Check(ent, ce); downstream != nil { | ||||||
|  | 		return downstream.AddCore(ent, h) | ||||||
|  | 	} | ||||||
|  | 	return ce | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (h *hooked) With(fields []Field) Core { | ||||||
|  | 	return &hooked{ | ||||||
|  | 		Core:  h.Core.With(fields), | ||||||
|  | 		funcs: h.funcs, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (h *hooked) Write(ent Entry, _ []Field) error { | ||||||
|  | 	// Since our downstream had a chance to register itself directly with the
 | ||||||
|  | 	// CheckedMessage, we don't need to call it here.
 | ||||||
|  | 	var err error | ||||||
|  | 	for i := range h.funcs { | ||||||
|  | 		err = multierr.Append(err, h.funcs[i](ent)) | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | @ -0,0 +1,480 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"math" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | 	"unicode/utf8" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap/buffer" | ||||||
|  | 	"go.uber.org/zap/internal/bufferpool" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // For JSON-escaping; see jsonEncoder.safeAddString below.
 | ||||||
|  | const _hex = "0123456789abcdef" | ||||||
|  | 
 | ||||||
|  | var _jsonPool = sync.Pool{New: func() interface{} { | ||||||
|  | 	return &jsonEncoder{} | ||||||
|  | }} | ||||||
|  | 
 | ||||||
|  | func getJSONEncoder() *jsonEncoder { | ||||||
|  | 	return _jsonPool.Get().(*jsonEncoder) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func putJSONEncoder(enc *jsonEncoder) { | ||||||
|  | 	enc.EncoderConfig = nil | ||||||
|  | 	enc.buf = nil | ||||||
|  | 	enc.spaced = false | ||||||
|  | 	enc.openNamespaces = 0 | ||||||
|  | 	_jsonPool.Put(enc) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type jsonEncoder struct { | ||||||
|  | 	*EncoderConfig | ||||||
|  | 	buf            *buffer.Buffer | ||||||
|  | 	spaced         bool // include spaces after colons and commas
 | ||||||
|  | 	openNamespaces int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewJSONEncoder creates a fast, low-allocation JSON encoder. The encoder
 | ||||||
|  | // appropriately escapes all field keys and values.
 | ||||||
|  | //
 | ||||||
|  | // Note that the encoder doesn't deduplicate keys, so it's possible to produce
 | ||||||
|  | // a message like
 | ||||||
|  | //   {"foo":"bar","foo":"baz"}
 | ||||||
|  | // This is permitted by the JSON specification, but not encouraged. Many
 | ||||||
|  | // libraries will ignore duplicate key-value pairs (typically keeping the last
 | ||||||
|  | // pair) when unmarshaling, but users should attempt to avoid adding duplicate
 | ||||||
|  | // keys.
 | ||||||
|  | func NewJSONEncoder(cfg EncoderConfig) Encoder { | ||||||
|  | 	return newJSONEncoder(cfg, false) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newJSONEncoder(cfg EncoderConfig, spaced bool) *jsonEncoder { | ||||||
|  | 	return &jsonEncoder{ | ||||||
|  | 		EncoderConfig: &cfg, | ||||||
|  | 		buf:           bufferpool.Get(), | ||||||
|  | 		spaced:        spaced, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AddArray(key string, arr ArrayMarshaler) error { | ||||||
|  | 	enc.addKey(key) | ||||||
|  | 	return enc.AppendArray(arr) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AddObject(key string, obj ObjectMarshaler) error { | ||||||
|  | 	enc.addKey(key) | ||||||
|  | 	return enc.AppendObject(obj) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AddBinary(key string, val []byte) { | ||||||
|  | 	enc.AddString(key, base64.StdEncoding.EncodeToString(val)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AddByteString(key string, val []byte) { | ||||||
|  | 	enc.addKey(key) | ||||||
|  | 	enc.AppendByteString(val) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AddBool(key string, val bool) { | ||||||
|  | 	enc.addKey(key) | ||||||
|  | 	enc.AppendBool(val) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AddComplex128(key string, val complex128) { | ||||||
|  | 	enc.addKey(key) | ||||||
|  | 	enc.AppendComplex128(val) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AddDuration(key string, val time.Duration) { | ||||||
|  | 	enc.addKey(key) | ||||||
|  | 	enc.AppendDuration(val) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AddFloat64(key string, val float64) { | ||||||
|  | 	enc.addKey(key) | ||||||
|  | 	enc.AppendFloat64(val) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AddInt64(key string, val int64) { | ||||||
|  | 	enc.addKey(key) | ||||||
|  | 	enc.AppendInt64(val) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AddReflected(key string, obj interface{}) error { | ||||||
|  | 	marshaled, err := json.Marshal(obj) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	enc.addKey(key) | ||||||
|  | 	_, err = enc.buf.Write(marshaled) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) OpenNamespace(key string) { | ||||||
|  | 	enc.addKey(key) | ||||||
|  | 	enc.buf.AppendByte('{') | ||||||
|  | 	enc.openNamespaces++ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AddString(key, val string) { | ||||||
|  | 	enc.addKey(key) | ||||||
|  | 	enc.AppendString(val) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AddTime(key string, val time.Time) { | ||||||
|  | 	enc.addKey(key) | ||||||
|  | 	enc.AppendTime(val) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AddUint64(key string, val uint64) { | ||||||
|  | 	enc.addKey(key) | ||||||
|  | 	enc.AppendUint64(val) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AppendArray(arr ArrayMarshaler) error { | ||||||
|  | 	enc.addElementSeparator() | ||||||
|  | 	enc.buf.AppendByte('[') | ||||||
|  | 	err := arr.MarshalLogArray(enc) | ||||||
|  | 	enc.buf.AppendByte(']') | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AppendObject(obj ObjectMarshaler) error { | ||||||
|  | 	enc.addElementSeparator() | ||||||
|  | 	enc.buf.AppendByte('{') | ||||||
|  | 	err := obj.MarshalLogObject(enc) | ||||||
|  | 	enc.buf.AppendByte('}') | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AppendBool(val bool) { | ||||||
|  | 	enc.addElementSeparator() | ||||||
|  | 	enc.buf.AppendBool(val) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AppendByteString(val []byte) { | ||||||
|  | 	enc.addElementSeparator() | ||||||
|  | 	enc.buf.AppendByte('"') | ||||||
|  | 	enc.safeAddByteString(val) | ||||||
|  | 	enc.buf.AppendByte('"') | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AppendComplex128(val complex128) { | ||||||
|  | 	enc.addElementSeparator() | ||||||
|  | 	// Cast to a platform-independent, fixed-size type.
 | ||||||
|  | 	r, i := float64(real(val)), float64(imag(val)) | ||||||
|  | 	enc.buf.AppendByte('"') | ||||||
|  | 	// Because we're always in a quoted string, we can use strconv without
 | ||||||
|  | 	// special-casing NaN and +/-Inf.
 | ||||||
|  | 	enc.buf.AppendFloat(r, 64) | ||||||
|  | 	enc.buf.AppendByte('+') | ||||||
|  | 	enc.buf.AppendFloat(i, 64) | ||||||
|  | 	enc.buf.AppendByte('i') | ||||||
|  | 	enc.buf.AppendByte('"') | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AppendDuration(val time.Duration) { | ||||||
|  | 	cur := enc.buf.Len() | ||||||
|  | 	enc.EncodeDuration(val, enc) | ||||||
|  | 	if cur == enc.buf.Len() { | ||||||
|  | 		// User-supplied EncodeDuration is a no-op. Fall back to nanoseconds to keep
 | ||||||
|  | 		// JSON valid.
 | ||||||
|  | 		enc.AppendInt64(int64(val)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AppendInt64(val int64) { | ||||||
|  | 	enc.addElementSeparator() | ||||||
|  | 	enc.buf.AppendInt(val) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AppendReflected(val interface{}) error { | ||||||
|  | 	marshaled, err := json.Marshal(val) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	enc.addElementSeparator() | ||||||
|  | 	_, err = enc.buf.Write(marshaled) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AppendString(val string) { | ||||||
|  | 	enc.addElementSeparator() | ||||||
|  | 	enc.buf.AppendByte('"') | ||||||
|  | 	enc.safeAddString(val) | ||||||
|  | 	enc.buf.AppendByte('"') | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AppendTime(val time.Time) { | ||||||
|  | 	cur := enc.buf.Len() | ||||||
|  | 	enc.EncodeTime(val, enc) | ||||||
|  | 	if cur == enc.buf.Len() { | ||||||
|  | 		// User-supplied EncodeTime is a no-op. Fall back to nanos since epoch to keep
 | ||||||
|  | 		// output JSON valid.
 | ||||||
|  | 		enc.AppendInt64(val.UnixNano()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AppendUint64(val uint64) { | ||||||
|  | 	enc.addElementSeparator() | ||||||
|  | 	enc.buf.AppendUint(val) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) AddComplex64(k string, v complex64) { enc.AddComplex128(k, complex128(v)) } | ||||||
|  | func (enc *jsonEncoder) AddFloat32(k string, v float32)     { enc.AddFloat64(k, float64(v)) } | ||||||
|  | func (enc *jsonEncoder) AddInt(k string, v int)             { enc.AddInt64(k, int64(v)) } | ||||||
|  | func (enc *jsonEncoder) AddInt32(k string, v int32)         { enc.AddInt64(k, int64(v)) } | ||||||
|  | func (enc *jsonEncoder) AddInt16(k string, v int16)         { enc.AddInt64(k, int64(v)) } | ||||||
|  | func (enc *jsonEncoder) AddInt8(k string, v int8)           { enc.AddInt64(k, int64(v)) } | ||||||
|  | func (enc *jsonEncoder) AddUint(k string, v uint)           { enc.AddUint64(k, uint64(v)) } | ||||||
|  | func (enc *jsonEncoder) AddUint32(k string, v uint32)       { enc.AddUint64(k, uint64(v)) } | ||||||
|  | func (enc *jsonEncoder) AddUint16(k string, v uint16)       { enc.AddUint64(k, uint64(v)) } | ||||||
|  | func (enc *jsonEncoder) AddUint8(k string, v uint8)         { enc.AddUint64(k, uint64(v)) } | ||||||
|  | func (enc *jsonEncoder) AddUintptr(k string, v uintptr)     { enc.AddUint64(k, uint64(v)) } | ||||||
|  | func (enc *jsonEncoder) AppendComplex64(v complex64)        { enc.AppendComplex128(complex128(v)) } | ||||||
|  | func (enc *jsonEncoder) AppendFloat64(v float64)            { enc.appendFloat(v, 64) } | ||||||
|  | func (enc *jsonEncoder) AppendFloat32(v float32)            { enc.appendFloat(float64(v), 32) } | ||||||
|  | func (enc *jsonEncoder) AppendInt(v int)                    { enc.AppendInt64(int64(v)) } | ||||||
|  | func (enc *jsonEncoder) AppendInt32(v int32)                { enc.AppendInt64(int64(v)) } | ||||||
|  | func (enc *jsonEncoder) AppendInt16(v int16)                { enc.AppendInt64(int64(v)) } | ||||||
|  | func (enc *jsonEncoder) AppendInt8(v int8)                  { enc.AppendInt64(int64(v)) } | ||||||
|  | func (enc *jsonEncoder) AppendUint(v uint)                  { enc.AppendUint64(uint64(v)) } | ||||||
|  | func (enc *jsonEncoder) AppendUint32(v uint32)              { enc.AppendUint64(uint64(v)) } | ||||||
|  | func (enc *jsonEncoder) AppendUint16(v uint16)              { enc.AppendUint64(uint64(v)) } | ||||||
|  | func (enc *jsonEncoder) AppendUint8(v uint8)                { enc.AppendUint64(uint64(v)) } | ||||||
|  | func (enc *jsonEncoder) AppendUintptr(v uintptr)            { enc.AppendUint64(uint64(v)) } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) Clone() Encoder { | ||||||
|  | 	clone := enc.clone() | ||||||
|  | 	clone.buf.Write(enc.buf.Bytes()) | ||||||
|  | 	return clone | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) clone() *jsonEncoder { | ||||||
|  | 	clone := getJSONEncoder() | ||||||
|  | 	clone.EncoderConfig = enc.EncoderConfig | ||||||
|  | 	clone.spaced = enc.spaced | ||||||
|  | 	clone.openNamespaces = enc.openNamespaces | ||||||
|  | 	clone.buf = bufferpool.Get() | ||||||
|  | 	return clone | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) { | ||||||
|  | 	final := enc.clone() | ||||||
|  | 	final.buf.AppendByte('{') | ||||||
|  | 
 | ||||||
|  | 	if final.LevelKey != "" { | ||||||
|  | 		final.addKey(final.LevelKey) | ||||||
|  | 		cur := final.buf.Len() | ||||||
|  | 		final.EncodeLevel(ent.Level, final) | ||||||
|  | 		if cur == final.buf.Len() { | ||||||
|  | 			// User-supplied EncodeLevel was a no-op. Fall back to strings to keep
 | ||||||
|  | 			// output JSON valid.
 | ||||||
|  | 			final.AppendString(ent.Level.String()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if final.TimeKey != "" { | ||||||
|  | 		final.AddTime(final.TimeKey, ent.Time) | ||||||
|  | 	} | ||||||
|  | 	if ent.LoggerName != "" && final.NameKey != "" { | ||||||
|  | 		final.addKey(final.NameKey) | ||||||
|  | 		cur := final.buf.Len() | ||||||
|  | 		nameEncoder := final.EncodeName | ||||||
|  | 
 | ||||||
|  | 		// if no name encoder provided, fall back to FullNameEncoder for backwards
 | ||||||
|  | 		// compatibility
 | ||||||
|  | 		if nameEncoder == nil { | ||||||
|  | 			nameEncoder = FullNameEncoder | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		nameEncoder(ent.LoggerName, final) | ||||||
|  | 		if cur == final.buf.Len() { | ||||||
|  | 			// User-supplied EncodeName was a no-op. Fall back to strings to
 | ||||||
|  | 			// keep output JSON valid.
 | ||||||
|  | 			final.AppendString(ent.LoggerName) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if ent.Caller.Defined && final.CallerKey != "" { | ||||||
|  | 		final.addKey(final.CallerKey) | ||||||
|  | 		cur := final.buf.Len() | ||||||
|  | 		final.EncodeCaller(ent.Caller, final) | ||||||
|  | 		if cur == final.buf.Len() { | ||||||
|  | 			// User-supplied EncodeCaller was a no-op. Fall back to strings to
 | ||||||
|  | 			// keep output JSON valid.
 | ||||||
|  | 			final.AppendString(ent.Caller.String()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if final.MessageKey != "" { | ||||||
|  | 		final.addKey(enc.MessageKey) | ||||||
|  | 		final.AppendString(ent.Message) | ||||||
|  | 	} | ||||||
|  | 	if enc.buf.Len() > 0 { | ||||||
|  | 		final.addElementSeparator() | ||||||
|  | 		final.buf.Write(enc.buf.Bytes()) | ||||||
|  | 	} | ||||||
|  | 	addFields(final, fields) | ||||||
|  | 	final.closeOpenNamespaces() | ||||||
|  | 	if ent.Stack != "" && final.StacktraceKey != "" { | ||||||
|  | 		final.AddString(final.StacktraceKey, ent.Stack) | ||||||
|  | 	} | ||||||
|  | 	final.buf.AppendByte('}') | ||||||
|  | 	if final.LineEnding != "" { | ||||||
|  | 		final.buf.AppendString(final.LineEnding) | ||||||
|  | 	} else { | ||||||
|  | 		final.buf.AppendString(DefaultLineEnding) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ret := final.buf | ||||||
|  | 	putJSONEncoder(final) | ||||||
|  | 	return ret, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) truncate() { | ||||||
|  | 	enc.buf.Reset() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) closeOpenNamespaces() { | ||||||
|  | 	for i := 0; i < enc.openNamespaces; i++ { | ||||||
|  | 		enc.buf.AppendByte('}') | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) addKey(key string) { | ||||||
|  | 	enc.addElementSeparator() | ||||||
|  | 	enc.buf.AppendByte('"') | ||||||
|  | 	enc.safeAddString(key) | ||||||
|  | 	enc.buf.AppendByte('"') | ||||||
|  | 	enc.buf.AppendByte(':') | ||||||
|  | 	if enc.spaced { | ||||||
|  | 		enc.buf.AppendByte(' ') | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) addElementSeparator() { | ||||||
|  | 	last := enc.buf.Len() - 1 | ||||||
|  | 	if last < 0 { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	switch enc.buf.Bytes()[last] { | ||||||
|  | 	case '{', '[', ':', ',', ' ': | ||||||
|  | 		return | ||||||
|  | 	default: | ||||||
|  | 		enc.buf.AppendByte(',') | ||||||
|  | 		if enc.spaced { | ||||||
|  | 			enc.buf.AppendByte(' ') | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) appendFloat(val float64, bitSize int) { | ||||||
|  | 	enc.addElementSeparator() | ||||||
|  | 	switch { | ||||||
|  | 	case math.IsNaN(val): | ||||||
|  | 		enc.buf.AppendString(`"NaN"`) | ||||||
|  | 	case math.IsInf(val, 1): | ||||||
|  | 		enc.buf.AppendString(`"+Inf"`) | ||||||
|  | 	case math.IsInf(val, -1): | ||||||
|  | 		enc.buf.AppendString(`"-Inf"`) | ||||||
|  | 	default: | ||||||
|  | 		enc.buf.AppendFloat(val, bitSize) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // safeAddString JSON-escapes a string and appends it to the internal buffer.
 | ||||||
|  | // Unlike the standard library's encoder, it doesn't attempt to protect the
 | ||||||
|  | // user from browser vulnerabilities or JSONP-related problems.
 | ||||||
|  | func (enc *jsonEncoder) safeAddString(s string) { | ||||||
|  | 	for i := 0; i < len(s); { | ||||||
|  | 		if enc.tryAddRuneSelf(s[i]) { | ||||||
|  | 			i++ | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		r, size := utf8.DecodeRuneInString(s[i:]) | ||||||
|  | 		if enc.tryAddRuneError(r, size) { | ||||||
|  | 			i++ | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		enc.buf.AppendString(s[i : i+size]) | ||||||
|  | 		i += size | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // safeAddByteString is no-alloc equivalent of safeAddString(string(s)) for s []byte.
 | ||||||
|  | func (enc *jsonEncoder) safeAddByteString(s []byte) { | ||||||
|  | 	for i := 0; i < len(s); { | ||||||
|  | 		if enc.tryAddRuneSelf(s[i]) { | ||||||
|  | 			i++ | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		r, size := utf8.DecodeRune(s[i:]) | ||||||
|  | 		if enc.tryAddRuneError(r, size) { | ||||||
|  | 			i++ | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		enc.buf.Write(s[i : i+size]) | ||||||
|  | 		i += size | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // tryAddRuneSelf appends b if it is valid UTF-8 character represented in a single byte.
 | ||||||
|  | func (enc *jsonEncoder) tryAddRuneSelf(b byte) bool { | ||||||
|  | 	if b >= utf8.RuneSelf { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	if 0x20 <= b && b != '\\' && b != '"' { | ||||||
|  | 		enc.buf.AppendByte(b) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	switch b { | ||||||
|  | 	case '\\', '"': | ||||||
|  | 		enc.buf.AppendByte('\\') | ||||||
|  | 		enc.buf.AppendByte(b) | ||||||
|  | 	case '\n': | ||||||
|  | 		enc.buf.AppendByte('\\') | ||||||
|  | 		enc.buf.AppendByte('n') | ||||||
|  | 	case '\r': | ||||||
|  | 		enc.buf.AppendByte('\\') | ||||||
|  | 		enc.buf.AppendByte('r') | ||||||
|  | 	case '\t': | ||||||
|  | 		enc.buf.AppendByte('\\') | ||||||
|  | 		enc.buf.AppendByte('t') | ||||||
|  | 	default: | ||||||
|  | 		// Encode bytes < 0x20, except for the escape sequences above.
 | ||||||
|  | 		enc.buf.AppendString(`\u00`) | ||||||
|  | 		enc.buf.AppendByte(_hex[b>>4]) | ||||||
|  | 		enc.buf.AppendByte(_hex[b&0xF]) | ||||||
|  | 	} | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (enc *jsonEncoder) tryAddRuneError(r rune, size int) bool { | ||||||
|  | 	if r == utf8.RuneError && size == 1 { | ||||||
|  | 		enc.buf.AppendString(`\ufffd`) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | @ -0,0 +1,175 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var errUnmarshalNilLevel = errors.New("can't unmarshal a nil *Level") | ||||||
|  | 
 | ||||||
|  | // A Level is a logging priority. Higher levels are more important.
 | ||||||
|  | type Level int8 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// DebugLevel logs are typically voluminous, and are usually disabled in
 | ||||||
|  | 	// production.
 | ||||||
|  | 	DebugLevel Level = iota - 1 | ||||||
|  | 	// InfoLevel is the default logging priority.
 | ||||||
|  | 	InfoLevel | ||||||
|  | 	// WarnLevel logs are more important than Info, but don't need individual
 | ||||||
|  | 	// human review.
 | ||||||
|  | 	WarnLevel | ||||||
|  | 	// ErrorLevel logs are high-priority. If an application is running smoothly,
 | ||||||
|  | 	// it shouldn't generate any error-level logs.
 | ||||||
|  | 	ErrorLevel | ||||||
|  | 	// DPanicLevel logs are particularly important errors. In development the
 | ||||||
|  | 	// logger panics after writing the message.
 | ||||||
|  | 	DPanicLevel | ||||||
|  | 	// PanicLevel logs a message, then panics.
 | ||||||
|  | 	PanicLevel | ||||||
|  | 	// FatalLevel logs a message, then calls os.Exit(1).
 | ||||||
|  | 	FatalLevel | ||||||
|  | 
 | ||||||
|  | 	_minLevel = DebugLevel | ||||||
|  | 	_maxLevel = FatalLevel | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // String returns a lower-case ASCII representation of the log level.
 | ||||||
|  | func (l Level) String() string { | ||||||
|  | 	switch l { | ||||||
|  | 	case DebugLevel: | ||||||
|  | 		return "debug" | ||||||
|  | 	case InfoLevel: | ||||||
|  | 		return "info" | ||||||
|  | 	case WarnLevel: | ||||||
|  | 		return "warn" | ||||||
|  | 	case ErrorLevel: | ||||||
|  | 		return "error" | ||||||
|  | 	case DPanicLevel: | ||||||
|  | 		return "dpanic" | ||||||
|  | 	case PanicLevel: | ||||||
|  | 		return "panic" | ||||||
|  | 	case FatalLevel: | ||||||
|  | 		return "fatal" | ||||||
|  | 	default: | ||||||
|  | 		return fmt.Sprintf("Level(%d)", l) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CapitalString returns an all-caps ASCII representation of the log level.
 | ||||||
|  | func (l Level) CapitalString() string { | ||||||
|  | 	// Printing levels in all-caps is common enough that we should export this
 | ||||||
|  | 	// functionality.
 | ||||||
|  | 	switch l { | ||||||
|  | 	case DebugLevel: | ||||||
|  | 		return "DEBUG" | ||||||
|  | 	case InfoLevel: | ||||||
|  | 		return "INFO" | ||||||
|  | 	case WarnLevel: | ||||||
|  | 		return "WARN" | ||||||
|  | 	case ErrorLevel: | ||||||
|  | 		return "ERROR" | ||||||
|  | 	case DPanicLevel: | ||||||
|  | 		return "DPANIC" | ||||||
|  | 	case PanicLevel: | ||||||
|  | 		return "PANIC" | ||||||
|  | 	case FatalLevel: | ||||||
|  | 		return "FATAL" | ||||||
|  | 	default: | ||||||
|  | 		return fmt.Sprintf("LEVEL(%d)", l) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalText marshals the Level to text. Note that the text representation
 | ||||||
|  | // drops the -Level suffix (see example).
 | ||||||
|  | func (l Level) MarshalText() ([]byte, error) { | ||||||
|  | 	return []byte(l.String()), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UnmarshalText unmarshals text to a level. Like MarshalText, UnmarshalText
 | ||||||
|  | // expects the text representation of a Level to drop the -Level suffix (see
 | ||||||
|  | // example).
 | ||||||
|  | //
 | ||||||
|  | // In particular, this makes it easy to configure logging levels using YAML,
 | ||||||
|  | // TOML, or JSON files.
 | ||||||
|  | func (l *Level) UnmarshalText(text []byte) error { | ||||||
|  | 	if l == nil { | ||||||
|  | 		return errUnmarshalNilLevel | ||||||
|  | 	} | ||||||
|  | 	if !l.unmarshalText(text) && !l.unmarshalText(bytes.ToLower(text)) { | ||||||
|  | 		return fmt.Errorf("unrecognized level: %q", text) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *Level) unmarshalText(text []byte) bool { | ||||||
|  | 	switch string(text) { | ||||||
|  | 	case "debug", "DEBUG": | ||||||
|  | 		*l = DebugLevel | ||||||
|  | 	case "info", "INFO", "": // make the zero value useful
 | ||||||
|  | 		*l = InfoLevel | ||||||
|  | 	case "warn", "WARN": | ||||||
|  | 		*l = WarnLevel | ||||||
|  | 	case "error", "ERROR": | ||||||
|  | 		*l = ErrorLevel | ||||||
|  | 	case "dpanic", "DPANIC": | ||||||
|  | 		*l = DPanicLevel | ||||||
|  | 	case "panic", "PANIC": | ||||||
|  | 		*l = PanicLevel | ||||||
|  | 	case "fatal", "FATAL": | ||||||
|  | 		*l = FatalLevel | ||||||
|  | 	default: | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Set sets the level for the flag.Value interface.
 | ||||||
|  | func (l *Level) Set(s string) error { | ||||||
|  | 	return l.UnmarshalText([]byte(s)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Get gets the level for the flag.Getter interface.
 | ||||||
|  | func (l *Level) Get() interface{} { | ||||||
|  | 	return *l | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Enabled returns true if the given level is at or above this level.
 | ||||||
|  | func (l Level) Enabled(lvl Level) bool { | ||||||
|  | 	return lvl >= l | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // LevelEnabler decides whether a given logging level is enabled when logging a
 | ||||||
|  | // message.
 | ||||||
|  | //
 | ||||||
|  | // Enablers are intended to be used to implement deterministic filters;
 | ||||||
|  | // concerns like sampling are better implemented as a Core.
 | ||||||
|  | //
 | ||||||
|  | // Each concrete Level value implements a static LevelEnabler which returns
 | ||||||
|  | // true for itself and all higher logging levels. For example WarnLevel.Enabled()
 | ||||||
|  | // will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and
 | ||||||
|  | // FatalLevel, but return false for InfoLevel and DebugLevel.
 | ||||||
|  | type LevelEnabler interface { | ||||||
|  | 	Enabled(Level) bool | ||||||
|  | } | ||||||
|  | @ -0,0 +1,46 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | import "go.uber.org/zap/internal/color" | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	_levelToColor = map[Level]color.Color{ | ||||||
|  | 		DebugLevel:  color.Magenta, | ||||||
|  | 		InfoLevel:   color.Blue, | ||||||
|  | 		WarnLevel:   color.Yellow, | ||||||
|  | 		ErrorLevel:  color.Red, | ||||||
|  | 		DPanicLevel: color.Red, | ||||||
|  | 		PanicLevel:  color.Red, | ||||||
|  | 		FatalLevel:  color.Red, | ||||||
|  | 	} | ||||||
|  | 	_unknownLevelColor = color.Red | ||||||
|  | 
 | ||||||
|  | 	_levelToLowercaseColorString = make(map[Level]string, len(_levelToColor)) | ||||||
|  | 	_levelToCapitalColorString   = make(map[Level]string, len(_levelToColor)) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	for level, color := range _levelToColor { | ||||||
|  | 		_levelToLowercaseColorString[level] = color.Add(level.String()) | ||||||
|  | 		_levelToCapitalColorString[level] = color.Add(level.CapitalString()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,53 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | // ObjectMarshaler allows user-defined types to efficiently add themselves to the
 | ||||||
|  | // logging context, and to selectively omit information which shouldn't be
 | ||||||
|  | // included in logs (e.g., passwords).
 | ||||||
|  | type ObjectMarshaler interface { | ||||||
|  | 	MarshalLogObject(ObjectEncoder) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ObjectMarshalerFunc is a type adapter that turns a function into an
 | ||||||
|  | // ObjectMarshaler.
 | ||||||
|  | type ObjectMarshalerFunc func(ObjectEncoder) error | ||||||
|  | 
 | ||||||
|  | // MarshalLogObject calls the underlying function.
 | ||||||
|  | func (f ObjectMarshalerFunc) MarshalLogObject(enc ObjectEncoder) error { | ||||||
|  | 	return f(enc) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ArrayMarshaler allows user-defined types to efficiently add themselves to the
 | ||||||
|  | // logging context, and to selectively omit information which shouldn't be
 | ||||||
|  | // included in logs (e.g., passwords).
 | ||||||
|  | type ArrayMarshaler interface { | ||||||
|  | 	MarshalLogArray(ArrayEncoder) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ArrayMarshalerFunc is a type adapter that turns a function into an
 | ||||||
|  | // ArrayMarshaler.
 | ||||||
|  | type ArrayMarshalerFunc func(ArrayEncoder) error | ||||||
|  | 
 | ||||||
|  | // MarshalLogArray calls the underlying function.
 | ||||||
|  | func (f ArrayMarshalerFunc) MarshalLogArray(enc ArrayEncoder) error { | ||||||
|  | 	return f(enc) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,179 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
|  | // MapObjectEncoder is an ObjectEncoder backed by a simple
 | ||||||
|  | // map[string]interface{}. It's not fast enough for production use, but it's
 | ||||||
|  | // helpful in tests.
 | ||||||
|  | type MapObjectEncoder struct { | ||||||
|  | 	// Fields contains the entire encoded log context.
 | ||||||
|  | 	Fields map[string]interface{} | ||||||
|  | 	// cur is a pointer to the namespace we're currently writing to.
 | ||||||
|  | 	cur map[string]interface{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewMapObjectEncoder creates a new map-backed ObjectEncoder.
 | ||||||
|  | func NewMapObjectEncoder() *MapObjectEncoder { | ||||||
|  | 	m := make(map[string]interface{}) | ||||||
|  | 	return &MapObjectEncoder{ | ||||||
|  | 		Fields: m, | ||||||
|  | 		cur:    m, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddArray implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddArray(key string, v ArrayMarshaler) error { | ||||||
|  | 	arr := &sliceArrayEncoder{} | ||||||
|  | 	err := v.MarshalLogArray(arr) | ||||||
|  | 	m.cur[key] = arr.elems | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddObject implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddObject(k string, v ObjectMarshaler) error { | ||||||
|  | 	newMap := NewMapObjectEncoder() | ||||||
|  | 	m.cur[k] = newMap.Fields | ||||||
|  | 	return v.MarshalLogObject(newMap) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddBinary implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddBinary(k string, v []byte) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddByteString implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddByteString(k string, v []byte) { m.cur[k] = string(v) } | ||||||
|  | 
 | ||||||
|  | // AddBool implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddBool(k string, v bool) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddDuration implements ObjectEncoder.
 | ||||||
|  | func (m MapObjectEncoder) AddDuration(k string, v time.Duration) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddComplex128 implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddComplex128(k string, v complex128) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddComplex64 implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddComplex64(k string, v complex64) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddFloat64 implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddFloat64(k string, v float64) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddFloat32 implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddFloat32(k string, v float32) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddInt implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddInt(k string, v int) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddInt64 implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddInt64(k string, v int64) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddInt32 implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddInt32(k string, v int32) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddInt16 implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddInt16(k string, v int16) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddInt8 implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddInt8(k string, v int8) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddString implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddString(k string, v string) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddTime implements ObjectEncoder.
 | ||||||
|  | func (m MapObjectEncoder) AddTime(k string, v time.Time) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddUint implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddUint(k string, v uint) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddUint64 implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddUint64(k string, v uint64) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddUint32 implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddUint32(k string, v uint32) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddUint16 implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddUint16(k string, v uint16) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddUint8 implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddUint8(k string, v uint8) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddUintptr implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddUintptr(k string, v uintptr) { m.cur[k] = v } | ||||||
|  | 
 | ||||||
|  | // AddReflected implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) AddReflected(k string, v interface{}) error { | ||||||
|  | 	m.cur[k] = v | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // OpenNamespace implements ObjectEncoder.
 | ||||||
|  | func (m *MapObjectEncoder) OpenNamespace(k string) { | ||||||
|  | 	ns := make(map[string]interface{}) | ||||||
|  | 	m.cur[k] = ns | ||||||
|  | 	m.cur = ns | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // sliceArrayEncoder is an ArrayEncoder backed by a simple []interface{}. Like
 | ||||||
|  | // the MapObjectEncoder, it's not designed for production use.
 | ||||||
|  | type sliceArrayEncoder struct { | ||||||
|  | 	elems []interface{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *sliceArrayEncoder) AppendArray(v ArrayMarshaler) error { | ||||||
|  | 	enc := &sliceArrayEncoder{} | ||||||
|  | 	err := v.MarshalLogArray(enc) | ||||||
|  | 	s.elems = append(s.elems, enc.elems) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *sliceArrayEncoder) AppendObject(v ObjectMarshaler) error { | ||||||
|  | 	m := NewMapObjectEncoder() | ||||||
|  | 	err := v.MarshalLogObject(m) | ||||||
|  | 	s.elems = append(s.elems, m.Fields) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *sliceArrayEncoder) AppendReflected(v interface{}) error { | ||||||
|  | 	s.elems = append(s.elems, v) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *sliceArrayEncoder) AppendBool(v bool)              { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendByteString(v []byte)      { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendComplex128(v complex128)  { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendComplex64(v complex64)    { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendDuration(v time.Duration) { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendFloat64(v float64)        { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendFloat32(v float32)        { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendInt(v int)                { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendInt64(v int64)            { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendInt32(v int32)            { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendInt16(v int16)            { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendInt8(v int8)              { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendString(v string)          { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendTime(v time.Time)         { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendUint(v uint)              { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendUint64(v uint64)          { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendUint32(v uint32)          { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendUint16(v uint16)          { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendUint8(v uint8)            { s.elems = append(s.elems, v) } | ||||||
|  | func (s *sliceArrayEncoder) AppendUintptr(v uintptr)        { s.elems = append(s.elems, v) } | ||||||
|  | @ -0,0 +1,134 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/atomic" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	_numLevels        = _maxLevel - _minLevel + 1 | ||||||
|  | 	_countersPerLevel = 4096 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type counter struct { | ||||||
|  | 	resetAt atomic.Int64 | ||||||
|  | 	counter atomic.Uint64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type counters [_numLevels][_countersPerLevel]counter | ||||||
|  | 
 | ||||||
|  | func newCounters() *counters { | ||||||
|  | 	return &counters{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (cs *counters) get(lvl Level, key string) *counter { | ||||||
|  | 	i := lvl - _minLevel | ||||||
|  | 	j := fnv32a(key) % _countersPerLevel | ||||||
|  | 	return &cs[i][j] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // fnv32a, adapted from "hash/fnv", but without a []byte(string) alloc
 | ||||||
|  | func fnv32a(s string) uint32 { | ||||||
|  | 	const ( | ||||||
|  | 		offset32 = 2166136261 | ||||||
|  | 		prime32  = 16777619 | ||||||
|  | 	) | ||||||
|  | 	hash := uint32(offset32) | ||||||
|  | 	for i := 0; i < len(s); i++ { | ||||||
|  | 		hash ^= uint32(s[i]) | ||||||
|  | 		hash *= prime32 | ||||||
|  | 	} | ||||||
|  | 	return hash | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *counter) IncCheckReset(t time.Time, tick time.Duration) uint64 { | ||||||
|  | 	tn := t.UnixNano() | ||||||
|  | 	resetAfter := c.resetAt.Load() | ||||||
|  | 	if resetAfter > tn { | ||||||
|  | 		return c.counter.Inc() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	c.counter.Store(1) | ||||||
|  | 
 | ||||||
|  | 	newResetAfter := tn + tick.Nanoseconds() | ||||||
|  | 	if !c.resetAt.CAS(resetAfter, newResetAfter) { | ||||||
|  | 		// We raced with another goroutine trying to reset, and it also reset
 | ||||||
|  | 		// the counter to 1, so we need to reincrement the counter.
 | ||||||
|  | 		return c.counter.Inc() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sampler struct { | ||||||
|  | 	Core | ||||||
|  | 
 | ||||||
|  | 	counts            *counters | ||||||
|  | 	tick              time.Duration | ||||||
|  | 	first, thereafter uint64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewSampler creates a Core that samples incoming entries, which caps the CPU
 | ||||||
|  | // and I/O load of logging while attempting to preserve a representative subset
 | ||||||
|  | // of your logs.
 | ||||||
|  | //
 | ||||||
|  | // Zap samples by logging the first N entries with a given level and message
 | ||||||
|  | // each tick. If more Entries with the same level and message are seen during
 | ||||||
|  | // the same interval, every Mth message is logged and the rest are dropped.
 | ||||||
|  | //
 | ||||||
|  | // Keep in mind that zap's sampling implementation is optimized for speed over
 | ||||||
|  | // absolute precision; under load, each tick may be slightly over- or
 | ||||||
|  | // under-sampled.
 | ||||||
|  | func NewSampler(core Core, tick time.Duration, first, thereafter int) Core { | ||||||
|  | 	return &sampler{ | ||||||
|  | 		Core:       core, | ||||||
|  | 		tick:       tick, | ||||||
|  | 		counts:     newCounters(), | ||||||
|  | 		first:      uint64(first), | ||||||
|  | 		thereafter: uint64(thereafter), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *sampler) With(fields []Field) Core { | ||||||
|  | 	return &sampler{ | ||||||
|  | 		Core:       s.Core.With(fields), | ||||||
|  | 		tick:       s.tick, | ||||||
|  | 		counts:     s.counts, | ||||||
|  | 		first:      s.first, | ||||||
|  | 		thereafter: s.thereafter, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *sampler) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { | ||||||
|  | 	if !s.Enabled(ent.Level) { | ||||||
|  | 		return ce | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	counter := s.counts.get(ent.Level, ent.Message) | ||||||
|  | 	n := counter.IncCheckReset(ent.Time, s.tick) | ||||||
|  | 	if n > s.first && (n-s.first)%s.thereafter != 0 { | ||||||
|  | 		return ce | ||||||
|  | 	} | ||||||
|  | 	return s.Core.Check(ent, ce) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,81 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | import "go.uber.org/multierr" | ||||||
|  | 
 | ||||||
|  | type multiCore []Core | ||||||
|  | 
 | ||||||
|  | // NewTee creates a Core that duplicates log entries into two or more
 | ||||||
|  | // underlying Cores.
 | ||||||
|  | //
 | ||||||
|  | // Calling it with a single Core returns the input unchanged, and calling
 | ||||||
|  | // it with no input returns a no-op Core.
 | ||||||
|  | func NewTee(cores ...Core) Core { | ||||||
|  | 	switch len(cores) { | ||||||
|  | 	case 0: | ||||||
|  | 		return NewNopCore() | ||||||
|  | 	case 1: | ||||||
|  | 		return cores[0] | ||||||
|  | 	default: | ||||||
|  | 		return multiCore(cores) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (mc multiCore) With(fields []Field) Core { | ||||||
|  | 	clone := make(multiCore, len(mc)) | ||||||
|  | 	for i := range mc { | ||||||
|  | 		clone[i] = mc[i].With(fields) | ||||||
|  | 	} | ||||||
|  | 	return clone | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (mc multiCore) Enabled(lvl Level) bool { | ||||||
|  | 	for i := range mc { | ||||||
|  | 		if mc[i].Enabled(lvl) { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (mc multiCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { | ||||||
|  | 	for i := range mc { | ||||||
|  | 		ce = mc[i].Check(ent, ce) | ||||||
|  | 	} | ||||||
|  | 	return ce | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (mc multiCore) Write(ent Entry, fields []Field) error { | ||||||
|  | 	var err error | ||||||
|  | 	for i := range mc { | ||||||
|  | 		err = multierr.Append(err, mc[i].Write(ent, fields)) | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (mc multiCore) Sync() error { | ||||||
|  | 	var err error | ||||||
|  | 	for i := range mc { | ||||||
|  | 		err = multierr.Append(err, mc[i].Sync()) | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | @ -0,0 +1,123 @@ | ||||||
|  | // Copyright (c) 2016 Uber Technologies, Inc.
 | ||||||
|  | //
 | ||||||
|  | // Permission is hereby granted, free of charge, to any person obtaining a copy
 | ||||||
|  | // of this software and associated documentation files (the "Software"), to deal
 | ||||||
|  | // in the Software without restriction, including without limitation the rights
 | ||||||
|  | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | ||||||
|  | // copies of the Software, and to permit persons to whom the Software is
 | ||||||
|  | // furnished to do so, subject to the following conditions:
 | ||||||
|  | //
 | ||||||
|  | // The above copyright notice and this permission notice shall be included in
 | ||||||
|  | // all copies or substantial portions of the Software.
 | ||||||
|  | //
 | ||||||
|  | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||||
|  | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||||
|  | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||||
|  | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||||
|  | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||||
|  | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | ||||||
|  | // THE SOFTWARE.
 | ||||||
|  | 
 | ||||||
|  | package zapcore | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"io" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/multierr" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // A WriteSyncer is an io.Writer that can also flush any buffered data. Note
 | ||||||
|  | // that *os.File (and thus, os.Stderr and os.Stdout) implement WriteSyncer.
 | ||||||
|  | type WriteSyncer interface { | ||||||
|  | 	io.Writer | ||||||
|  | 	Sync() error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AddSync converts an io.Writer to a WriteSyncer. It attempts to be
 | ||||||
|  | // intelligent: if the concrete type of the io.Writer implements WriteSyncer,
 | ||||||
|  | // we'll use the existing Sync method. If it doesn't, we'll add a no-op Sync.
 | ||||||
|  | func AddSync(w io.Writer) WriteSyncer { | ||||||
|  | 	switch w := w.(type) { | ||||||
|  | 	case WriteSyncer: | ||||||
|  | 		return w | ||||||
|  | 	default: | ||||||
|  | 		return writerWrapper{w} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type lockedWriteSyncer struct { | ||||||
|  | 	sync.Mutex | ||||||
|  | 	ws WriteSyncer | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Lock wraps a WriteSyncer in a mutex to make it safe for concurrent use. In
 | ||||||
|  | // particular, *os.Files must be locked before use.
 | ||||||
|  | func Lock(ws WriteSyncer) WriteSyncer { | ||||||
|  | 	if _, ok := ws.(*lockedWriteSyncer); ok { | ||||||
|  | 		// no need to layer on another lock
 | ||||||
|  | 		return ws | ||||||
|  | 	} | ||||||
|  | 	return &lockedWriteSyncer{ws: ws} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *lockedWriteSyncer) Write(bs []byte) (int, error) { | ||||||
|  | 	s.Lock() | ||||||
|  | 	n, err := s.ws.Write(bs) | ||||||
|  | 	s.Unlock() | ||||||
|  | 	return n, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *lockedWriteSyncer) Sync() error { | ||||||
|  | 	s.Lock() | ||||||
|  | 	err := s.ws.Sync() | ||||||
|  | 	s.Unlock() | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type writerWrapper struct { | ||||||
|  | 	io.Writer | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w writerWrapper) Sync() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type multiWriteSyncer []WriteSyncer | ||||||
|  | 
 | ||||||
|  | // NewMultiWriteSyncer creates a WriteSyncer that duplicates its writes
 | ||||||
|  | // and sync calls, much like io.MultiWriter.
 | ||||||
|  | func NewMultiWriteSyncer(ws ...WriteSyncer) WriteSyncer { | ||||||
|  | 	if len(ws) == 1 { | ||||||
|  | 		return ws[0] | ||||||
|  | 	} | ||||||
|  | 	// Copy to protect against https://github.com/golang/go/issues/7809
 | ||||||
|  | 	return multiWriteSyncer(append([]WriteSyncer(nil), ws...)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // See https://golang.org/src/io/multi.go
 | ||||||
|  | // When not all underlying syncers write the same number of bytes,
 | ||||||
|  | // the smallest number is returned even though Write() is called on
 | ||||||
|  | // all of them.
 | ||||||
|  | func (ws multiWriteSyncer) Write(p []byte) (int, error) { | ||||||
|  | 	var writeErr error | ||||||
|  | 	nWritten := 0 | ||||||
|  | 	for _, w := range ws { | ||||||
|  | 		n, err := w.Write(p) | ||||||
|  | 		writeErr = multierr.Append(writeErr, err) | ||||||
|  | 		if nWritten == 0 && n != 0 { | ||||||
|  | 			nWritten = n | ||||||
|  | 		} else if n < nWritten { | ||||||
|  | 			nWritten = n | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nWritten, writeErr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ws multiWriteSyncer) Sync() error { | ||||||
|  | 	var err error | ||||||
|  | 	for _, w := range ws { | ||||||
|  | 		err = multierr.Append(err, w.Sync()) | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue