Skip to content

Commit ba244b3

Browse files
committed
Logging bridge for Zap
1 parent f3ba8c2 commit ba244b3

File tree

4 files changed

+303
-0
lines changed

4 files changed

+303
-0
lines changed

bridges/zap/go.mod

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module zap
2+
3+
go 1.21.3
4+
5+
require (
6+
github.com/stretchr/testify v1.9.0
7+
go.opentelemetry.io/otel/log v0.0.1-alpha
8+
go.uber.org/zap v1.27.0
9+
)
10+
11+
require (
12+
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/go-logr/logr v1.4.1 // indirect
14+
github.com/go-logr/stdr v1.2.2 // indirect
15+
github.com/kr/pretty v0.3.1 // indirect
16+
github.com/pmezard/go-difflib v1.0.0 // indirect
17+
github.com/rogpeppe/go-internal v1.11.0 // indirect
18+
go.opentelemetry.io/otel v1.24.0 // indirect
19+
go.opentelemetry.io/otel/metric v1.24.0 // indirect
20+
go.opentelemetry.io/otel/trace v1.24.0 // indirect
21+
go.uber.org/multierr v1.10.0 // indirect
22+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
23+
gopkg.in/yaml.v3 v3.0.1 // indirect
24+
)

bridges/zap/go.sum

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
5+
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
6+
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
7+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
8+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
9+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
10+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
11+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
12+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
13+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
14+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
15+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
16+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
17+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
18+
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
19+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
20+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
21+
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
22+
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
23+
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
24+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
25+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
26+
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
27+
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
28+
go.opentelemetry.io/otel/log v0.0.1-alpha h1:Gy4SxFnkHv2wmmzv//sblb4/PoCYVtuZbdFY/XamvHM=
29+
go.opentelemetry.io/otel/log v0.0.1-alpha/go.mod h1:fg1zxLfxAyzlCLyULJTWXUbFVYyOwQZD/DgtGm7VvgA=
30+
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
31+
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
32+
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
33+
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
34+
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
35+
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
36+
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
37+
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
38+
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
39+
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
40+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
41+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
42+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
43+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
44+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

bridges/zap/zap_logger.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package zap
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"math"
7+
8+
"go.opentelemetry.io/otel/log"
9+
"go.opentelemetry.io/otel/log/noop"
10+
"go.uber.org/zap/zapcore"
11+
)
12+
13+
const (
14+
bridgeName = "go.opentelemetry.io/contrib/bridge/zapcore"
15+
bridgeVersion = "0.0.1-alpha"
16+
)
17+
18+
type OtelZapCore struct {
19+
zapcore.Core
20+
logger log.Logger
21+
attr []log.KeyValue
22+
}
23+
24+
var (
25+
_ zapcore.Core = (*OtelZapCore)(nil)
26+
)
27+
28+
// this function creates a new zapcore.Core that can be used with zap.New()
29+
// this instance will translate zap logs to opentelemetry logs and export them
30+
31+
func NewOtelZapCore(lp log.LoggerProvider, opts ...log.LoggerOption) zapcore.Core {
32+
if lp == nil {
33+
// Do not panic.
34+
lp = noop.NewLoggerProvider()
35+
}
36+
// these options
37+
return &OtelZapCore{
38+
logger: lp.Logger(bridgeName,
39+
log.WithInstrumentationVersion(bridgeVersion),
40+
),
41+
}
42+
}
43+
44+
// TODO: Enabled method being added to Logger (WIP)
45+
func (o *OtelZapCore) Enabled(zapcore.Level) bool {
46+
return true
47+
// return o.logger.Enabled()
48+
}
49+
50+
// return new zapcore with provided attr
51+
func (o *OtelZapCore) With(fields []zapcore.Field) zapcore.Core {
52+
clone := o.clone()
53+
for _, field := range fields {
54+
clone.attr = append(clone.attr, convertAttr(field))
55+
}
56+
return clone
57+
}
58+
59+
// TODO
60+
func (o *OtelZapCore) Sync() error {
61+
return nil
62+
}
63+
func (o *OtelZapCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
64+
return ce.AddCore(ent, o)
65+
}
66+
67+
func (o *OtelZapCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {
68+
// add fields to encoder
69+
for _, field := range fields {
70+
o.attr = append(o.attr, convertAttr(field))
71+
}
72+
73+
// we create record here to avoid heap allocation
74+
r := log.Record{}
75+
r.SetTimestamp(ent.Time)
76+
r.SetBody(log.StringValue(ent.Message))
77+
78+
// should confirm this
79+
sevOffset := 4*(ent.Level+2) + 1
80+
r.SetSeverity(log.Severity(ent.Level + sevOffset))
81+
82+
// should map this as well
83+
r.AddAttributes(o.attr...)
84+
85+
// need to check how to pass context here
86+
ctx := context.Background()
87+
o.logger.Emit(ctx, r)
88+
return nil
89+
}
90+
91+
func (o *OtelZapCore) clone() *OtelZapCore {
92+
return &OtelZapCore{
93+
logger: o.logger,
94+
}
95+
}
96+
97+
func convertAttr(f zapcore.Field) log.KeyValue {
98+
val := convertValue(f)
99+
return log.KeyValue{Key: f.Key, Value: val}
100+
}
101+
102+
// this does not cover all Field types yet
103+
func convertValue(f zapcore.Field) log.Value {
104+
switch f.Type {
105+
case zapcore.ArrayMarshalerType:
106+
return log.StringValue("ArrayMarshalerType")
107+
//return log.SliceValue(f.Interface.(zapcore.ArrayMarshaler))
108+
case zapcore.ObjectMarshalerType:
109+
return log.StringValue("ObjectMarshalerType")
110+
// f.Interface.(ObjectMarshaler))
111+
case zapcore.InlineMarshalerType:
112+
return log.StringValue("InlineMarshalerType")
113+
//return log f.Interface.(ObjectMarshaler).MarshalLogObject(enc)
114+
case zapcore.BinaryType:
115+
return log.BytesValue(f.Interface.([]byte))
116+
case zapcore.BoolType:
117+
return log.BoolValue(f.Integer == 1)
118+
case zapcore.ByteStringType:
119+
return log.BytesValue(f.Interface.([]byte))
120+
case zapcore.Complex128Type:
121+
return log.StringValue("complex128type")
122+
//return log.Float64Value(f.Interface.(complex128))
123+
case zapcore.Complex64Type:
124+
return log.StringValue("Complex64Type")
125+
//return log.Float64Value(f.Interface.(complex64))
126+
case zapcore.DurationType:
127+
return log.Int64Value(f.Integer) // do we have log.Duration?
128+
case zapcore.Float64Type:
129+
return log.Float64Value(math.Float64frombits(uint64(f.Integer)))
130+
case zapcore.Float32Type:
131+
return log.Float64Value(math.Float64frombits(uint64(f.Integer)))
132+
case zapcore.Int64Type:
133+
return log.Int64Value(f.Integer)
134+
case zapcore.Int32Type:
135+
return log.Int64Value(f.Integer)
136+
case zapcore.Int16Type:
137+
return log.Int64Value(f.Integer)
138+
case zapcore.Int8Type:
139+
return log.Int64Value(f.Integer)
140+
case zapcore.StringType:
141+
return log.StringValue(f.String)
142+
case zapcore.TimeType:
143+
// if f.Interface != nil {
144+
// return time.Unix(0, f.Integer).In(f.Interface.(*time.Location))
145+
// } else {
146+
// // Fall back to UTC if location is nil.
147+
// return time.Unix(0, f.Integer)
148+
// }
149+
return log.Int64Value(f.Integer)
150+
case zapcore.TimeFullType:
151+
return log.StringValue("TimeFullType")
152+
// enc.AddTime(f.Key, f.Interface.(time.Time))
153+
case zapcore.Uint64Type:
154+
return log.Int64Value(f.Integer)
155+
case zapcore.Uint32Type:
156+
return log.Int64Value(f.Integer)
157+
case zapcore.Uint16Type:
158+
return log.Int64Value(f.Integer)
159+
case zapcore.Uint8Type:
160+
return log.Int64Value(f.Integer)
161+
162+
// how to handle these types
163+
// case zapcore.SkipType:
164+
// break
165+
// case UintptrType:
166+
// enc.AddUintptr(f.Key, uintptr(f.Integer))
167+
// case ReflectType:
168+
// err = enc.AddReflected(f.Key, f.Interface)
169+
// case NamespaceType:
170+
// enc.OpenNamespace(f.Key)
171+
// case StringerType:
172+
// err = encodeStringer(f.Key, f.Interface, enc)
173+
// case ErrorType:
174+
// err = encodeError(f.Key, f.Interface.(error), enc)
175+
default:
176+
panic(fmt.Sprintf("unhandled attribute kind: %c", f.Type))
177+
}
178+
}

bridges/zap/zap_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package zap
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"go.opentelemetry.io/otel/log"
9+
"go.opentelemetry.io/otel/log/embedded"
10+
"go.uber.org/zap"
11+
"go.uber.org/zap/zapcore"
12+
)
13+
14+
var (
15+
testBodyString = "log message"
16+
testSeverity = log.SeverityInfo
17+
)
18+
19+
type spyLogger struct {
20+
embedded.Logger
21+
Context context.Context
22+
Record log.Record
23+
}
24+
25+
func (l *spyLogger) Emit(ctx context.Context, r log.Record) {
26+
l.Context = ctx
27+
l.Record = r
28+
}
29+
30+
func NewTestOtelLogger(log log.Logger) zapcore.Core {
31+
return &OtelZapCore{
32+
logger: log,
33+
}
34+
}
35+
func TestZapCore(t *testing.T) {
36+
spy := &spyLogger{}
37+
logger := zap.New(NewTestOtelLogger(spy))
38+
logger.Info(testBodyString, zap.String("username", "johndoe"))
39+
40+
assert.Equal(t, testBodyString, spy.Record.Body().AsString())
41+
assert.Equal(t, testSeverity, spy.Record.Severity())
42+
assert.Equal(t, 1, spy.Record.AttributesLen())
43+
spy.Record.WalkAttributes(func(kv log.KeyValue) bool {
44+
assert.Equal(t, "username", string(kv.Key))
45+
assert.Equal(t, "johndoe", kv.Value.AsString())
46+
return true
47+
})
48+
49+
childlogger := logger.With(zap.String("workplace", "otel"))
50+
childlogger.Info(testBodyString)
51+
spy.Record.WalkAttributes(func(kv log.KeyValue) bool {
52+
assert.Equal(t, "workplace", string(kv.Key))
53+
assert.Equal(t, "otel", kv.Value.AsString())
54+
return true
55+
})
56+
57+
}

0 commit comments

Comments
 (0)