From 0696825e7b5f7a523cba1278700f4124db1716d1 Mon Sep 17 00:00:00 2001 From: Suyash Date: Mon, 6 Mar 2017 22:29:46 +0530 Subject: [PATCH 1/3] config: export NewDevelopmentEncoderConfig and NewProductionEncoderConfig (#345) This makes creating loggers with custom write paths easier. For example ``` var ws zapcore.WriteSync // probably obtained using zap.Open core := zapcore.NewCore( zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()), ws, zapcore.DebugLevel) logger := zap.New(core) ``` --- config.go | 66 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/config.go b/config.go index b117b5781..0709fc25e 100644 --- a/config.go +++ b/config.go @@ -77,6 +77,22 @@ type Config struct { 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", + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.EpochTimeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + } +} + // NewProductionConfig is the recommended production configuration. Logging is // enabled at InfoLevel and above. // @@ -90,23 +106,30 @@ func NewProductionConfig() Config { Initial: 100, Thereafter: 100, }, - Encoding: "json", - EncoderConfig: zapcore.EncoderConfig{ - TimeKey: "ts", - LevelKey: "level", - NameKey: "logger", - CallerKey: "caller", - MessageKey: "msg", - StacktraceKey: "stacktrace", - EncodeLevel: zapcore.LowercaseLevelEncoder, - EncodeTime: zapcore.EpochTimeEncoder, - EncodeDuration: zapcore.SecondsDurationEncoder, - }, + 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", + EncodeLevel: zapcore.CapitalLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + } +} + // NewDevelopmentConfig is a reasonable development configuration. Logging is // enabled at DebugLevel and above. // @@ -118,21 +141,10 @@ func NewDevelopmentConfig() Config { dyn.SetLevel(DebugLevel) return Config{ - Level: dyn, - Development: true, - Encoding: "console", - EncoderConfig: zapcore.EncoderConfig{ - // Keys can be anything except the empty string. - TimeKey: "T", - LevelKey: "L", - NameKey: "N", - CallerKey: "C", - MessageKey: "M", - StacktraceKey: "S", - EncodeLevel: zapcore.CapitalLevelEncoder, - EncodeTime: zapcore.ISO8601TimeEncoder, - EncodeDuration: zapcore.StringDurationEncoder, - }, + Level: dyn, + Development: true, + Encoding: "console", + EncoderConfig: NewDevelopmentEncoderConfig(), OutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"}, } From 1b7cf6e2a2879c52e16d16501946fb3d14d498ae Mon Sep 17 00:00:00 2001 From: Will Hughes Date: Mon, 6 Mar 2017 09:27:42 -0800 Subject: [PATCH 2/3] Make bufferpool test more complete (#340) To test the length and capacity of the pooled buffers properly, write some dummy data into them on each iteration. --- internal/bufferpool/bufferpool_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/bufferpool/bufferpool_test.go b/internal/bufferpool/bufferpool_test.go index 7c38b0883..9f847b437 100644 --- a/internal/bufferpool/bufferpool_test.go +++ b/internal/bufferpool/bufferpool_test.go @@ -28,6 +28,8 @@ import ( ) func TestBuffers(t *testing.T) { + const dummyData = "dummy data" + var wg sync.WaitGroup for g := 0; g < 10; g++ { wg.Add(1) @@ -36,6 +38,10 @@ func TestBuffers(t *testing.T) { buf := Get() assert.Zero(t, buf.Len(), "Expected truncated buffer") assert.NotZero(t, buf.Cap(), "Expected non-zero capacity") + + buf.AppendString(dummyData) + assert.Equal(t, buf.Len(), len(dummyData), "Expected buffer to contain dummy data") + Put(buf) } wg.Done() From 3e4a6c3d072da3b0fa6f84c116325b887514b344 Mon Sep 17 00:00:00 2001 From: Peter Edge Date: Mon, 6 Mar 2017 13:49:28 -0500 Subject: [PATCH 3/3] Add colored LevelEncoders (#307) First, create a small package for TTY coloring. Then, use the internal package to add small, colored LevelEncoders. For safety (since we don't check that the output is a TTY), keep the default uncolored. --- Makefile | 2 +- internal/color/color.go | 44 +++++++++++++++++++++++++++++++++ internal/color/color_test.go | 36 +++++++++++++++++++++++++++ zapcore/encoder.go | 31 ++++++++++++++++++++--- zapcore/level.go | 24 ++++++++++++++++++ zapcore/level_strings.go | 46 +++++++++++++++++++++++++++++++++++ zapcore/level_strings_test.go | 38 +++++++++++++++++++++++++++++ zapcore/level_test.go | 3 ++- 8 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 internal/color/color.go create mode 100644 internal/color/color_test.go create mode 100644 zapcore/level_strings.go create mode 100644 zapcore/level_strings_test.go diff --git a/Makefile b/Makefile index be388ba9f..fb0409698 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ 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 testutils internal/bufferpool internal/exit internal/multierror internal/observer +PKG_FILES ?= *.go zapcore benchmarks buffer testutils internal/bufferpool internal/exit internal/multierror internal/observer internal/color # The linting tools evolve with each Go version, so run them only on the latest # stable release. diff --git a/internal/color/color.go b/internal/color/color.go new file mode 100644 index 000000000..c4d5d02ab --- /dev/null +++ b/internal/color/color.go @@ -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) +} diff --git a/internal/color/color_test.go b/internal/color/color_test.go new file mode 100644 index 000000000..4982903aa --- /dev/null +++ b/internal/color/color_test.go @@ -0,0 +1,36 @@ +// 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 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestColorFormatting(t *testing.T) { + assert.Equal( + t, + "\x1b[31mfoo\x1b[0m", + Red.Add("foo"), + "Unexpected colored output.", + ) +} diff --git a/zapcore/encoder.go b/zapcore/encoder.go index 86cbd194d..1bb684ae3 100644 --- a/zapcore/encoder.go +++ b/zapcore/encoder.go @@ -21,7 +21,6 @@ package zapcore import ( - "strings" "time" "go.uber.org/zap/buffer" @@ -36,18 +35,44 @@ 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(strings.ToUpper(l.String())) + 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, and anything else is unmarshaled to LowercaseLevelEncoder. +// 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 } diff --git a/zapcore/level.go b/zapcore/level.go index 763b44b06..4997f0aca 100644 --- a/zapcore/level.go +++ b/zapcore/level.go @@ -76,6 +76,30 @@ func (l Level) String() string { } } +// 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) { diff --git a/zapcore/level_strings.go b/zapcore/level_strings.go new file mode 100644 index 000000000..7af8dadcb --- /dev/null +++ b/zapcore/level_strings.go @@ -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()) + } +} diff --git a/zapcore/level_strings_test.go b/zapcore/level_strings_test.go new file mode 100644 index 000000000..14b0bac62 --- /dev/null +++ b/zapcore/level_strings_test.go @@ -0,0 +1,38 @@ +// 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 ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAllLevelsCoveredByLevelString(t *testing.T) { + numLevels := int((_maxLevel - _minLevel) + 1) + + isComplete := func(m map[Level]string) bool { + return len(m) == numLevels + } + + assert.True(t, isComplete(_levelToLowercaseColorString), "Colored lowercase strings don't cover all levels.") + assert.True(t, isComplete(_levelToCapitalColorString), "Colored capital strings don't cover all levels.") +} diff --git a/zapcore/level_test.go b/zapcore/level_test.go index 37bbbf9c5..b711b797b 100644 --- a/zapcore/level_test.go +++ b/zapcore/level_test.go @@ -42,7 +42,8 @@ func TestLevelString(t *testing.T) { } for lvl, stringLevel := range tests { - assert.Equal(t, stringLevel, lvl.String()) + assert.Equal(t, stringLevel, lvl.String(), "Unexpected lowercase level string.") + assert.Equal(t, strings.ToUpper(stringLevel), lvl.CapitalString(), "Unexpected all-caps level string.") } }