Skip to content

Commit

Permalink
Add colored LevelEncoders (#307)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bufdev authored and akshayjshah committed Mar 6, 2017
1 parent 1b7cf6e commit 3e4a6c3
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 5 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
44 changes: 44 additions & 0 deletions internal/color/color.go
Original file line number Diff line number Diff line change
@@ -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)
}
36 changes: 36 additions & 0 deletions internal/color/color_test.go
Original file line number Diff line number Diff line change
@@ -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.",
)
}
31 changes: 28 additions & 3 deletions zapcore/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
package zapcore

import (
"strings"
"time"

"go.uber.org/zap/buffer"
Expand All @@ -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
}
Expand Down
24 changes: 24 additions & 0 deletions zapcore/level.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
46 changes: 46 additions & 0 deletions zapcore/level_strings.go
Original file line number Diff line number Diff line change
@@ -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())
}
}
38 changes: 38 additions & 0 deletions zapcore/level_strings_test.go
Original file line number Diff line number Diff line change
@@ -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.")
}
3 changes: 2 additions & 1 deletion zapcore/level_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
}
}

Expand Down

0 comments on commit 3e4a6c3

Please sign in to comment.