Skip to content

Commit e4cd1a0

Browse files
committed
fix: zap.With(zap.Any("ctx", ctx))
1 parent cea6e1a commit e4cd1a0

File tree

6 files changed

+253
-10
lines changed

6 files changed

+253
-10
lines changed

app/app.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,7 @@ func Run(f func(ctx context.Context, lg *zap.Logger, t *Telemetry) error, op ...
111111
}
112112
opts.zapConfig.Level.SetLevel(lvl)
113113
}
114-
lg, err := opts.zapConfig.Build(opts.zapOptions...)
115-
if err != nil {
116-
panic(err)
117-
}
114+
lg := opts.buildLogger()
118115
defer func() { _ = lg.Sync() }()
119116
// Add logger to root context.
120117
ctx = zctx.Base(ctx, lg)

app/option.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package app
33
import (
44
"context"
55

6+
"github.com/go-faster/sdk/internal/zapencoder"
67
"go.opentelemetry.io/otel/sdk/resource"
78
semconv "go.opentelemetry.io/otel/semconv/v1.27.0"
89
"go.uber.org/zap"
@@ -13,11 +14,12 @@ import (
1314
)
1415

1516
type options struct {
16-
zapConfig zap.Config
17-
zapOptions []zap.Option
18-
zapTee bool
19-
otelZap bool
20-
ctx context.Context
17+
zapConfig zap.Config
18+
zapCustomConfig bool
19+
zapOptions []zap.Option
20+
zapTee bool
21+
otelZap bool
22+
ctx context.Context
2123

2224
meterOptions []autometer.Option
2325
tracerOptions []autotracer.Option
@@ -26,6 +28,26 @@ type options struct {
2628
resourceFn func(ctx context.Context) (*resource.Resource, error)
2729
}
2830

31+
func (o *options) buildLogger() *zap.Logger {
32+
if !o.zapCustomConfig {
33+
// HACK: to use custom encoding for Any("ctx', ctx) fields
34+
// we need to use custom zap.Config and custom encoder.
35+
cfg := zapencoder.Config(defaultZapConfig())
36+
lg, err := cfg.Build(o.zapOptions...)
37+
if err != nil {
38+
panic("failed to build zap logger: " + err.Error())
39+
}
40+
return lg
41+
}
42+
43+
lg, err := o.zapConfig.Build(o.zapOptions...)
44+
if err != nil {
45+
panic("failed to build zap logger: " + err.Error())
46+
}
47+
48+
return lg
49+
}
50+
2951
type optionFunc func(*options)
3052

3153
func (f optionFunc) apply(o *options) {
@@ -48,6 +70,7 @@ func WithZapTee(teeToStderr bool) Option {
4870
func WithZapConfig(cfg zap.Config) Option {
4971
return optionFunc(func(o *options) {
5072
o.zapConfig = cfg
73+
o.zapCustomConfig = true
5174
})
5275
}
5376

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
{"level":"info","ts":"2024-01-02T15:04:05Z","msg":"With context"}
2+
{"level":"info","ts":"2024-01-02T15:04:05Z","msg":"With context"}
23
{"level":"info","ts":"2024-01-02T15:04:05Z","msg":"With span","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736","span_id":"00f067aa0ba902b7"}

internal/zapencoder/config.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright (c) 2016 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
package zapencoder
22+
23+
import (
24+
"errors"
25+
"sort"
26+
"time"
27+
28+
"go.uber.org/zap"
29+
"go.uber.org/zap/zapcore"
30+
)
31+
32+
type SamplingConfig = zap.SamplingConfig
33+
34+
type Config zap.Config
35+
36+
// Build constructs a logger from the Config and Options.
37+
func (cfg Config) Build(opts ...zap.Option) (*zap.Logger, error) {
38+
enc, err := cfg.buildEncoder()
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
sink, errSink, err := cfg.openSinks()
44+
if err != nil {
45+
return nil, err
46+
}
47+
48+
if cfg.Level == (zap.AtomicLevel{}) {
49+
return nil, errors.New("missing Level")
50+
}
51+
52+
log := zap.New(
53+
NewCustomCore(enc, sink, cfg.Level),
54+
cfg.buildOptions(errSink)...,
55+
)
56+
if len(opts) > 0 {
57+
log = log.WithOptions(opts...)
58+
}
59+
return log, nil
60+
}
61+
62+
func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []zap.Option {
63+
opts := []zap.Option{zap.ErrorOutput(errSink)}
64+
65+
if cfg.Development {
66+
opts = append(opts, zap.Development())
67+
}
68+
69+
if !cfg.DisableCaller {
70+
opts = append(opts, zap.AddCaller())
71+
}
72+
73+
stackLevel := zap.ErrorLevel
74+
if cfg.Development {
75+
stackLevel = zap.WarnLevel
76+
}
77+
if !cfg.DisableStacktrace {
78+
opts = append(opts, zap.AddStacktrace(stackLevel))
79+
}
80+
81+
if scfg := cfg.Sampling; scfg != nil {
82+
opts = append(opts, zap.WrapCore(func(core zapcore.Core) zapcore.Core {
83+
var samplerOpts []zapcore.SamplerOption
84+
if scfg.Hook != nil {
85+
samplerOpts = append(samplerOpts, zapcore.SamplerHook(scfg.Hook))
86+
}
87+
return zapcore.NewSamplerWithOptions(
88+
core,
89+
time.Second,
90+
cfg.Sampling.Initial,
91+
cfg.Sampling.Thereafter,
92+
samplerOpts...,
93+
)
94+
}))
95+
}
96+
97+
if len(cfg.InitialFields) > 0 {
98+
fs := make([]zap.Field, 0, len(cfg.InitialFields))
99+
keys := make([]string, 0, len(cfg.InitialFields))
100+
for k := range cfg.InitialFields {
101+
keys = append(keys, k)
102+
}
103+
sort.Strings(keys)
104+
for _, k := range keys {
105+
fs = append(fs, zap.Any(k, cfg.InitialFields[k]))
106+
}
107+
opts = append(opts, zap.Fields(fs...))
108+
}
109+
110+
return opts
111+
}
112+
113+
func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) {
114+
sink, closeOut, err := zap.Open(cfg.OutputPaths...)
115+
if err != nil {
116+
return nil, nil, err
117+
}
118+
errSink, _, err := zap.Open(cfg.ErrorOutputPaths...)
119+
if err != nil {
120+
closeOut()
121+
return nil, nil, err
122+
}
123+
return sink, errSink, nil
124+
}
125+
126+
func (cfg Config) buildEncoder() (zapcore.Encoder, error) {
127+
return New(cfg.EncoderConfig), nil
128+
}

internal/zapencoder/core.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright (c) 2016 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
package zapencoder
22+
23+
import (
24+
"go.uber.org/zap/zapcore"
25+
)
26+
27+
// NewCustomCore creates a [zapcore.Core] that writes logs to a WriteSyncer.
28+
func NewCustomCore(enc zapcore.Encoder, ws zapcore.WriteSyncer, enab zapcore.LevelEnabler) zapcore.Core {
29+
return &ioCore{
30+
LevelEnabler: enab,
31+
enc: enc,
32+
out: ws,
33+
}
34+
}
35+
36+
type ioCore struct {
37+
zapcore.LevelEnabler
38+
enc zapcore.Encoder
39+
out zapcore.WriteSyncer
40+
}
41+
42+
var (
43+
_ zapcore.Core = (*ioCore)(nil)
44+
)
45+
46+
func (c *ioCore) Level() zapcore.Level {
47+
return zapcore.LevelOf(c.LevelEnabler)
48+
}
49+
50+
func (c *ioCore) With(fields []zapcore.Field) zapcore.Core {
51+
clone := c.clone()
52+
addFields(clone.enc, fields)
53+
return clone
54+
}
55+
56+
func (c *ioCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
57+
if c.Enabled(ent.Level) {
58+
return ce.AddCore(ent, c)
59+
}
60+
return ce
61+
}
62+
63+
func (c *ioCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {
64+
buf, err := c.enc.EncodeEntry(ent, fields)
65+
if err != nil {
66+
return err
67+
}
68+
_, err = c.out.Write(buf.Bytes())
69+
buf.Free()
70+
if err != nil {
71+
return err
72+
}
73+
if ent.Level > zapcore.ErrorLevel {
74+
// Since we may be crashing the program, sync the output.
75+
// Ignore Sync errors, pending a clean solution to issue #370.
76+
_ = c.Sync()
77+
}
78+
return nil
79+
}
80+
81+
func (c *ioCore) Sync() error {
82+
return c.out.Sync()
83+
}
84+
85+
func (c *ioCore) clone() *ioCore {
86+
return &ioCore{
87+
LevelEnabler: c.LevelEnabler,
88+
enc: c.enc.Clone(),
89+
out: c.out,
90+
}
91+
}

internal/zapencoder/patch_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,13 @@ func TestEncoder(t *testing.T) {
7171
encoderConfig := zap.NewProductionEncoderConfig()
7272
encoderConfig.EncodeTime = constantTimeEncoder(time.Date(2024, 1, 2, 15, 4, 5, 0, time.UTC))
7373

74-
core := zapcore.NewCore(zapencoder.New(encoderConfig), writer, zap.NewAtomicLevel())
74+
core := zapencoder.NewCustomCore(zapencoder.New(encoderConfig), writer, zap.NewAtomicLevel())
7575
lg := zap.New(core)
7676

7777
ctx := t.Context()
78+
lg.With(
79+
zap.Any("ctx", ctx),
80+
).Info("With context")
7881
lg.Info("With context",
7982
zap.Any("ctx", ctx),
8083
)

0 commit comments

Comments
 (0)