Skip to content

Commit

Permalink
Add ability to print pre-formatted strings
Browse files Browse the repository at this point in the history
  • Loading branch information
thessem committed Mar 23, 2024
1 parent 19d5512 commit 0922045
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 22 deletions.
42 changes: 31 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,26 @@ I am a big fan of error wrapping and error stacktraces, I am not a fan of needin
When objects that do not satisfy `ObjectMarshaler` are logged, zap-prettyconsole will use reflection (via the delightful [dd][dd] library) to print it instead:
![reflection](https://github.com/thessem/zap-prettyconsole/blob/main/internal/readme/images/Reflection.png?raw=true)

Strings passed to the logger will have their formatting printed and colourised, but you can opt out of this and print the raw strings.

![formatting](https://github.com/thessem/zap-prettyconsole/blob/main/internal/readme/images/Formatting.png?raw=true)

Generated using:

```go
logger := prettyconsole.NewLogger(zap.DebugLevel)
// Non-sugared version
logger = logger.With(prettyconsole.FormattedString("sql", "SELECT * FROM\n\tusers\nWHERE\n\tname = 'James'"))
sugar := logger.Sugar()
mdb := "db.users.find({\n\tname: \"\x1b[31mJames\x1b[0m\"\n});"
sugar.Debugw("string formatting",
zap.Namespace("mdb"),
// Sugared version
"formatted", prettyconsole.FormattedStringValue(mdb),
"unformatted", mdb,
)
```

This encoder respects all the normal encoder configuration settings.
You can change your separator character, newline characters, add caller/function information and add stacktraces if you like.

Expand All @@ -123,28 +143,28 @@ Log a message and 10 fields:

| Package | Time | Time % to zap | Objects Allocated |
| :------ | :--: | :-----------: | :---------------: |
| :zap: zap | 526 ns/op | +0% | 5 allocs/op
| :zap: zap (sugared) | 848 ns/op | +61% | 10 allocs/op
| :zap: :nail_care: zap-prettyconsole | 1872 ns/op | +256% | 12 allocs/op
| :zap: :nail_care: zap-prettyconsole (sugared) | 2454 ns/op | +367% | 17 allocs/op
| :zap: zap | 529 ns/op | +0% | 5 allocs/op
| :zap: zap (sugared) | 858 ns/op | +62% | 10 allocs/op
| :zap: :nail_care: zap-prettyconsole | 1904 ns/op | +260% | 12 allocs/op
| :zap: :nail_care: zap-prettyconsole (sugared) | 2372 ns/op | +348% | 17 allocs/op

Log a message with a logger that already has 10 fields of context:

| Package | Time | Time % to zap | Objects Allocated |
| :------ | :--: | :-----------: | :---------------: |
| :zap: zap | 52 ns/op | +0% | 0 allocs/op
| :zap: zap (sugared) | 65 ns/op | +25% | 1 allocs/op
| :zap: :nail_care: zap-prettyconsole | 1678 ns/op | +3127% | 7 allocs/op
| :zap: :nail_care: zap-prettyconsole (sugared) | 1691 ns/op | +3152% | 8 allocs/op
| :zap: zap (sugared) | 64 ns/op | +23% | 1 allocs/op
| :zap: :nail_care: zap-prettyconsole | 1565 ns/op | +2910% | 7 allocs/op
| :zap: :nail_care: zap-prettyconsole (sugared) | 1637 ns/op | +3048% | 8 allocs/op

Log a static string, without any context or `printf`-style templating:

| Package | Time | Time % to zap | Objects Allocated |
| :------ | :--: | :-----------: | :---------------: |
| :zap: zap | 38 ns/op | +0% | 0 allocs/op
| :zap: :nail_care: zap-prettyconsole | 47 ns/op | +24% | 0 allocs/op
| :zap: zap (sugared) | 60 ns/op | +58% | 1 allocs/op
| :zap: :nail_care: zap-prettyconsole (sugared) | 60 ns/op | +58% | 1 allocs/op
| :zap: :nail_care: zap-prettyconsole | 36 ns/op | -16% | 0 allocs/op
| :zap: zap | 43 ns/op | +0% | 0 allocs/op
| :zap: zap (sugared) | 58 ns/op | +35% | 1 allocs/op
| :zap: :nail_care: zap-prettyconsole (sugared) | 62 ns/op | +44% | 1 allocs/op

Released under the [MIT License](LICENSE.txt)

Expand Down
28 changes: 26 additions & 2 deletions encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ import (
"time"

pkgerrors "github.com/pkg/errors"
"go.uber.org/zap/buffer"

"github.com/stretchr/testify/assert"
"go.uber.org/multierr"
"go.uber.org/zap"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
)

Expand Down Expand Up @@ -88,6 +87,31 @@ func TestEncodeEntry(t *testing.T) {
zap.Namespace("namespace5"),
},
},
{
desc: "Pre-formatted strings",
// 4:33PM INF > test message test_string=test_message
// ↳ colours=RED STRING!
// ↳ namespace.mdb=db.users.find({
// name: "James"
// });
// .sql=SELECT * FROM
// users
// WHERE
// name = 'James'
expected: "\x1b[90m4:33PM\x1b[0m\x1b[32m \x1b[0m\x1b[32mINF\x1b[0m\x1b[32m \x1b[0m\x1b[1m\x1b[32m>\x1b[0m\x1b[0m\x1b[32m \x1b[0mtest message\x1b[32m \x1b[0m\x1b[32mtest_string=\x1b[0mtest_message\n\x1b[32m ↳ colours\x1b[0m\x1b[32m=\x1b[0m\x1b[0m\x1b[31mRED STRING!\x1b[0m\x1b[31m\n\x1b[32m ↳ namespace\x1b[0m\x1b[32m.mdb\x1b[0m\x1b[32m=\x1b[0mdb.users.find({\n \tname: \"James\"\n });\n \x1b[32m.sql\x1b[0m\x1b[32m=\x1b[0mSELECT * FROM\n \tusers\n WHERE\n \tname = 'James'\n",
ent: zapcore.Entry{
Level: zapcore.InfoLevel,
Message: "test message",
Time: time.Date(2018, 6, 19, 16, 33, 42, 99, time.UTC),
},
fields: []zapcore.Field{
zap.String("test_string", "test_message"),
FormattedString("colours", "\x1b[0m\x1b[31mRED STRING!\x1b[0m\x1b[31m"),
zap.Namespace("namespace"),
FormattedString("sql", "SELECT * FROM\n\tusers\nWHERE\n\tname = 'James'"),
zap.Any("mdb", FormattedStringValue("db.users.find({\n\tname: \"James\"\n});")),
},
},
{
desc: "Objects",
// 4:33PM INF > test message
Expand Down
16 changes: 15 additions & 1 deletion internal/readme/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"

"github.com/thessem/zap-prettyconsole"
prettyconsole "github.com/thessem/zap-prettyconsole"
)

type User struct {
Expand Down Expand Up @@ -125,6 +125,20 @@ func TestReflection(t *testing.T) {
)
}

func TestFormatting(t *testing.T) {
logger := prettyconsole.NewLogger(zap.DebugLevel)
// Non-sugared version
logger = logger.With(prettyconsole.FormattedString("sql", "SELECT * FROM\n\tusers\nWHERE\n\tname = 'James'"))
sugar := logger.Sugar()
mdb := "db.users.find({\n\tname: \"\x1b[31mJames\x1b[0m\"\n});"
sugar.Debugw("string formatting",
zap.Namespace("mdb"),
// Sugared version
"formatted", prettyconsole.FormattedStringValue(mdb),
"unformatted", mdb,
)
}

func TestErrors(t *testing.T) {
logger := prettyconsole.NewLogger(zap.DebugLevel).WithOptions()
err := errors.Wrap(
Expand Down
Binary file added internal/readme/images/Formatting.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions internal/readme/readme.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,26 @@ I am a big fan of error wrapping and error stacktraces, I am not a fan of needin
When objects that do not satisfy `ObjectMarshaler` are logged, zap-prettyconsole will use reflection (via the delightful [dd][dd] library) to print it instead:
![reflection](https://github.com/thessem/zap-prettyconsole/blob/main/internal/readme/images/Reflection.png?raw=true)

Strings passed to the logger will have their formatting printed and colourised, but you can opt out of this and print the raw strings.

![formatting](https://github.com/thessem/zap-prettyconsole/blob/main/internal/readme/images/Formatting.png?raw=true)

Generated using:

```go
logger := prettyconsole.NewLogger(zap.DebugLevel)
// Non-sugared version
logger = logger.With(prettyconsole.FormattedString("sql", "SELECT * FROM\n\tusers\nWHERE\n\tname = 'James'"))
sugar := logger.Sugar()
mdb := "db.users.find({\n\tname: \"\x1b[31mJames\x1b[0m\"\n});"
sugar.Debugw("string formatting",
zap.Namespace("mdb"),
// Sugared version
"formatted", prettyconsole.FormattedStringValue(mdb),
"unformatted", mdb,
)
```

This encoder respects all the normal encoder configuration settings.
You can change your separator character, newline characters, add caller/function information and add stacktraces if you like.

Expand Down
46 changes: 38 additions & 8 deletions object.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"
"time"

"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

Expand All @@ -26,9 +27,11 @@ func (e *prettyConsoleEncoder) AddUintptr(k string, v uintptr) { e.AddUint64(k,
func (e *prettyConsoleEncoder) AddBinary(key string, value []byte) {
e.AddString(key, base64.StdEncoding.EncodeToString(value))
}

func (e *prettyConsoleEncoder) AddComplex64(k string, v complex64) {
e.addComplex(k, complex128(v), 32)
}

func (e *prettyConsoleEncoder) AddComplex128(k string, v complex128) {
e.addComplex(k, v, 64)
}
Expand Down Expand Up @@ -111,15 +114,20 @@ func (e *prettyConsoleEncoder) AddReflected(key string, value interface{}) error
lineEnding: []byte(e.cfg.LineEnding),
}

if reflectedEncoder := e.cfg.NewReflectedEncoder(iw); e != nil {
if err := reflectedEncoder.Encode(value); err != nil {
return err
switch v := value.(type) {
case formattedString:
iw.Write([]byte(v))
default:
if reflectedEncoder := e.cfg.NewReflectedEncoder(iw); e != nil {
if err := reflectedEncoder.Encode(value); err != nil {
return err
}
}
}
if l-enc.buf.Len() == 0 {
// User-supplied reflectedEncoder is a no-op. Fall back to dd
if err := defaultReflectedEncoder(iw).Encode(value); err != nil {
return err
if l-enc.buf.Len() == 0 {
// User-supplied reflectedEncoder is a no-op. Fall back to dd
if err := defaultReflectedEncoder(iw).Encode(value); err != nil {
return err
}
}
}

Expand Down Expand Up @@ -214,6 +222,28 @@ func (e *prettyConsoleEncoder) AddString(key, value string) {
e.listSep = e._listSepSpace
}

// FormattedString is similar to zap.String() but it does not escape the
// printed value. This is useful for users who have formatted strings they want
// to preserve when they are logged.
//
// This is for use with a non-sugared logger. For a wrapper designed for use
// with a sugar logger, see FormattedStringValue().
func FormattedString(key string, value string) zap.Field {
return zap.Any(key, formattedString(value))
}

// FormattedStringValue is similar to zap.String() but it does not escape the
// printed value. This is useful for users who have formatted strings they want
// to preserve when they are logged.
//
// This is for use with a sugared logger. For a wrapper designed for use with a
// non-sugar logger, see FormattedStringValue().
func FormattedStringValue(value string) formattedString {
return formattedString(value)
}

type formattedString string

// addIndentedString appends a string, replacing any newlines with the
// current indent.
func (e *prettyConsoleEncoder) addIndentedString(key string, s string) {
Expand Down

0 comments on commit 0922045

Please sign in to comment.