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 = ["."] | ||||
|   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]] | ||||
|   branch = "master" | ||||
|   name = "golang.org/x/crypto" | ||||
|  | @ -60,6 +85,6 @@ | |||
| [solve-meta] | ||||
|   analyzer-name = "dep" | ||||
|   analyzer-version = 1 | ||||
|   inputs-digest = "2f414b2f5156dc8144a90cdbbb9685d5b8af323024f986bb52e66601f02f3c0f" | ||||
|   inputs-digest = "57e868f6ae57c81a07ee682742f3b71bf5c7956311a3bb8ea76459677fc104c7" | ||||
|   solver-name = "gps-cdcl" | ||||
|   solver-version = 1 | ||||
|  |  | |||
|  | @ -1,9 +1,11 @@ | |||
| package helmexec | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"go.uber.org/zap" | ||||
| 	"go.uber.org/zap/zapcore" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
|  | @ -13,16 +15,33 @@ const ( | |||
| type execer struct { | ||||
| 	helmBinary  string | ||||
| 	runner      Runner | ||||
| 	writer      io.Writer | ||||
| 	logger      *zap.SugaredLogger | ||||
| 	kubeContext 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
 | ||||
| func New(writer io.Writer, kubeContext string) *execer { | ||||
| func New(logger *zap.SugaredLogger, kubeContext string) *execer { | ||||
| 	return &execer{ | ||||
| 		helmBinary:  command, | ||||
| 		writer:      writer, | ||||
| 		logger:      logger, | ||||
| 		kubeContext: kubeContext, | ||||
| 		runner:      &ShellRunner{}, | ||||
| 	} | ||||
|  | @ -45,68 +64,77 @@ func (helm *execer) AddRepo(name, repository, certfile, keyfile, username, passw | |||
| 	if username != "" && password != "" { | ||||
| 		args = append(args, "--username", username, "--password", password) | ||||
| 	} | ||||
| 	helm.logger.Infof("Adding repo %v %v", name, repository) | ||||
| 	out, err := helm.exec(args...) | ||||
| 	helm.write(out) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) UpdateRepo() error { | ||||
| 	helm.logger.Info("Updating repo") | ||||
| 	out, err := helm.exec("repo", "update") | ||||
| 	helm.write(out) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) UpdateDeps(chart string) error { | ||||
| 	helm.logger.Infof("Updating dependency %v", chart) | ||||
| 	out, err := helm.exec("dependency", "update", chart) | ||||
| 	helm.write(out) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| 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...)...) | ||||
| 	helm.write(out) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) ReleaseStatus(name string) error { | ||||
| 	helm.logger.Infof("Getting status %v", name) | ||||
| 	out, err := helm.exec(append([]string{"status", name})...) | ||||
| 	if helm.writer != nil { | ||||
| 		helm.writer.Write(out) | ||||
| 	} | ||||
| 	helm.write(out) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) DecryptSecret(name string) (string, error) { | ||||
| 	helm.logger.Infof("Decrypting secret %v", name) | ||||
| 	out, err := helm.exec(append([]string{"secrets", "dec", name})...) | ||||
| 	helm.write(out) | ||||
| 	return name + ".dec", err | ||||
| } | ||||
| 
 | ||||
| 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...)...) | ||||
| 	helm.write(out) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) Lint(chart string, flags ...string) error { | ||||
| 	helm.logger.Infof("Linting %v", chart) | ||||
| 	out, err := helm.exec(append([]string{"lint", chart}, flags...)...) | ||||
| 	helm.write(out) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) Fetch(chart string, flags ...string) error { | ||||
| 	helm.logger.Infof("Fetching %v", chart) | ||||
| 	out, err := helm.exec(append([]string{"fetch", chart}, flags...)...) | ||||
| 	helm.write(out) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) DeleteRelease(name string, flags ...string) error { | ||||
| 	helm.logger.Infof("Deleting %v", name) | ||||
| 	out, err := helm.exec(append([]string{"delete", name}, flags...)...) | ||||
| 	helm.write(out) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) TestRelease(name string, flags ...string) error { | ||||
| 	helm.logger.Infof("Testing %v", name) | ||||
| 	out, err := helm.exec(append([]string{"test", name}, flags...)...) | ||||
| 	helm.write(out) | ||||
| 	return err | ||||
|  | @ -120,12 +148,12 @@ func (helm *execer) exec(args ...string) ([]byte, error) { | |||
| 	if 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) | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) write(out []byte) { | ||||
| 	if helm.writer != nil { | ||||
| 		helm.writer.Write(out) | ||||
| 	if len(out) > 0 { | ||||
| 		helm.logger.Infof("%s", out) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -2,9 +2,11 @@ package helmexec | |||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"go.uber.org/zap" | ||||
| ) | ||||
| 
 | ||||
| // Mocking the command-line runner
 | ||||
|  | @ -18,8 +20,8 @@ func (mock *mockRunner) Execute(cmd string, args []string) ([]byte, error) { | |||
| 	return []byte{}, nil | ||||
| } | ||||
| 
 | ||||
| func MockExecer(writer io.Writer, kubeContext string) *execer { | ||||
| 	execer := New(writer, kubeContext) | ||||
| func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer { | ||||
| 	execer := New(logger, kubeContext) | ||||
| 	execer.runner = &mockRunner{} | ||||
| 	return execer | ||||
| } | ||||
|  | @ -28,7 +30,8 @@ func MockExecer(writer io.Writer, kubeContext string) *execer { | |||
| 
 | ||||
| func TestNewHelmExec(t *testing.T) { | ||||
| 	buffer := bytes.NewBufferString("something") | ||||
| 	helm := New(buffer, "dev") | ||||
| 	logger := NewLogger(buffer, "debug") | ||||
| 	helm := New(logger, "dev") | ||||
| 	if helm.kubeContext != "dev" { | ||||
| 		t.Error("helmexec.New() - kubeContext") | ||||
| 	} | ||||
|  | @ -41,7 +44,7 @@ func TestNewHelmExec(t *testing.T) { | |||
| } | ||||
| 
 | ||||
| func Test_SetExtraArgs(t *testing.T) { | ||||
| 	helm := New(new(bytes.Buffer), "dev") | ||||
| 	helm := New(NewLogger(os.Stdout, "info"), "dev") | ||||
| 	helm.SetExtraArgs() | ||||
| 	if len(helm.extra) != 0 { | ||||
| 		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) { | ||||
| 	helm := New(new(bytes.Buffer), "dev") | ||||
| 	helm := New(NewLogger(os.Stdout, "info"), "dev") | ||||
| 	if helm.helmBinary != "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) { | ||||
| 	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", "", "") | ||||
| 	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 { | ||||
| 		t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||
| 	} | ||||
| 
 | ||||
| 	buffer.Reset() | ||||
| 	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 { | ||||
| 		t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||
| 	} | ||||
| 
 | ||||
| 	buffer.Reset() | ||||
| 	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 { | ||||
| 		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) { | ||||
| 	var buffer bytes.Buffer | ||||
| 	helm := MockExecer(&buffer, "dev") | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	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 { | ||||
| 		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) { | ||||
| 	var buffer bytes.Buffer | ||||
| 	helm := MockExecer(&buffer, "dev") | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	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 { | ||||
| 		t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||
| 	} | ||||
| 
 | ||||
| 	buffer.Reset() | ||||
| 	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 { | ||||
| 		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) { | ||||
| 	var buffer bytes.Buffer | ||||
| 	helm := MockExecer(&buffer, "dev") | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	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 { | ||||
| 		t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||
| 		t.Errorf("helmexec.UpdateDeps()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||
| 	} | ||||
| 
 | ||||
| 	buffer.Reset() | ||||
| 	helm.SetExtraArgs("--verify") | ||||
| 	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 { | ||||
| 		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) { | ||||
| 	var buffer bytes.Buffer | ||||
| 	helm := MockExecer(&buffer, "dev") | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	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 { | ||||
| 		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) { | ||||
| 	var buffer bytes.Buffer | ||||
| 	helm := MockExecer(&buffer, "dev") | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	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 { | ||||
| 		t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||
| 	} | ||||
| 
 | ||||
| 	buffer.Reset() | ||||
| 	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 { | ||||
| 		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) { | ||||
| 	var buffer bytes.Buffer | ||||
| 	helm := MockExecer(&buffer, "dev") | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	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 { | ||||
| 		t.Errorf("helmexec.DeleteRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||
| 	} | ||||
| } | ||||
| func Test_DeleteRelease_Flags(t *testing.T) { | ||||
| 	var buffer bytes.Buffer | ||||
| 	helm := MockExecer(&buffer, "dev") | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	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 { | ||||
| 		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) { | ||||
| 	var buffer bytes.Buffer | ||||
| 	helm := MockExecer(&buffer, "dev") | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	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 { | ||||
| 		t.Errorf("helmexec.TestRelease()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||
| 	} | ||||
| } | ||||
| func Test_TestRelease_Flags(t *testing.T) { | ||||
| 	var buffer bytes.Buffer | ||||
| 	helm := MockExecer(&buffer, "dev") | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	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 { | ||||
| 		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) { | ||||
| 	var buffer bytes.Buffer | ||||
| 	helm := MockExecer(&buffer, "dev") | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	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 { | ||||
| 		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) { | ||||
| 	var buffer bytes.Buffer | ||||
| 	helm := MockExecer(&buffer, "") | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "") | ||||
| 	helm.exec("version") | ||||
| 	expected := "exec: helm version\n" | ||||
| 	if 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") | ||||
| 	if len(ret) != 0 { | ||||
| 		t.Error("helmexec.exec() - expected empty return value") | ||||
| 	} | ||||
| 
 | ||||
| 	buffer.Reset() | ||||
| 	helm = MockExecer(&buffer, "dev") | ||||
| 	helm = MockExecer(logger, "dev") | ||||
| 	helm.exec("diff", "release", "chart", "--timeout 10", "--wait") | ||||
| 	expected = "exec: helm diff release chart --timeout 10 --wait --kube-context dev\n" | ||||
| 	if buffer.String() != expected { | ||||
|  | @ -250,7 +297,7 @@ func Test_exec(t *testing.T) { | |||
| 	} | ||||
| 
 | ||||
| 	buffer.Reset() | ||||
| 	helm = MockExecer(&buffer, "") | ||||
| 	helm = MockExecer(logger, "") | ||||
| 	helm.SetHelmBinary("overwritten") | ||||
| 	helm.exec("version") | ||||
| 	expected = "exec: overwritten version\n" | ||||
|  | @ -261,9 +308,12 @@ func Test_exec(t *testing.T) { | |||
| 
 | ||||
| func Test_Lint(t *testing.T) { | ||||
| 	var buffer bytes.Buffer | ||||
| 	helm := MockExecer(&buffer, "dev") | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	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 { | ||||
| 		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) { | ||||
| 	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") | ||||
| 	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 { | ||||
| 		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 ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"path/filepath" | ||||
| 	"sort" | ||||
| 
 | ||||
| 	"github.com/roboll/helmfile/helmexec" | ||||
| 	"github.com/roboll/helmfile/state" | ||||
| 	"github.com/urfave/cli" | ||||
| 	"path/filepath" | ||||
| 	"sort" | ||||
| 	"go.uber.org/zap" | ||||
| 	"go.uber.org/zap/zapcore" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
|  | @ -24,6 +26,28 @@ const ( | |||
| 
 | ||||
| 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() { | ||||
| 
 | ||||
| 	app := cli.NewApp() | ||||
|  | @ -42,12 +66,16 @@ func main() { | |||
| 		}, | ||||
| 		cli.BoolFlag{ | ||||
| 			Name:  "quiet, q", | ||||
| 			Usage: "silence output", | ||||
| 			Usage: "Silence output. Equivalent to log-level warn", | ||||
| 		}, | ||||
| 		cli.StringFlag{ | ||||
| 			Name:  "kube-context", | ||||
| 			Usage: "Set kubectl context. Uses current context by default", | ||||
| 		}, | ||||
| 		cli.StringFlag{ | ||||
| 			Name:  "log-level", | ||||
| 			Usage: "Set log level, default info", | ||||
| 		}, | ||||
| 		cli.StringFlag{ | ||||
| 			Name:  "namespace, n", | ||||
| 			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{ | ||||
| 		{ | ||||
| 			Name:  "repos", | ||||
|  | @ -422,7 +451,6 @@ func directoryExistsAt(path string) bool { | |||
| } | ||||
| 
 | ||||
| func loadDesiredStateFromFile(c *cli.Context, file string) (*state.HelmState, helmexec.Interface, error) { | ||||
| 	quiet := c.GlobalBool("quiet") | ||||
| 	kubeContext := c.GlobalString("kube-context") | ||||
| 	namespace := c.GlobalString("namespace") | ||||
| 	labels := c.GlobalStringSlice("selector") | ||||
|  | @ -455,10 +483,6 @@ func loadDesiredStateFromFile(c *cli.Context, file string) (*state.HelmState, he | |||
| 			os.Exit(1) | ||||
| 		} | ||||
| 	} | ||||
| 	var writer io.Writer | ||||
| 	if !quiet { | ||||
| 		writer = os.Stdout | ||||
| 	} | ||||
| 
 | ||||
| 	sigs := make(chan os.Signal, 1) | ||||
| 	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) | ||||
|  | @ -469,7 +493,8 @@ func loadDesiredStateFromFile(c *cli.Context, file string) (*state.HelmState, he | |||
| 		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 { | ||||
|  |  | |||
|  | @ -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