Skip to content

Commit bd236e7

Browse files
authored
implement ObjectEncoder (#1528)
* feat: support zap.Object in zapai set caller in traceTelemetry if caller is defined
1 parent e11b452 commit bd236e7

File tree

3 files changed

+193
-15
lines changed

3 files changed

+193
-15
lines changed

zapai/core.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/microsoft/ApplicationInsights-Go/appinsights/contracts"
66
"github.com/pkg/errors"
77
"go.uber.org/zap/zapcore"
8+
"sync"
89
)
910

1011
var levelToSev = map[zapcore.Level]contracts.SeverityLevel{
@@ -31,6 +32,7 @@ type Core struct {
3132
fieldMappers map[string]fieldTagMapper
3233
fields []zapcore.Field
3334
out zapcore.WriteSyncer
35+
lock sync.Mutex
3436
}
3537

3638
// NewCore creates a new appinsights zap core. Should only be initialized using an appinsights Sink as the
@@ -76,21 +78,33 @@ func (c *Core) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcor
7678
// Write implements zapcore.Core
7779
//nolint:gocritic // ignore hugeparam in interface impl
7880
func (c *Core) Write(entry zapcore.Entry, fields []zapcore.Field) error {
81+
c.lock.Lock()
82+
defer c.lock.Unlock()
7983
t := appinsights.NewTraceTelemetry(entry.Message, levelToSev[entry.Level])
8084

8185
// add fields from core
8286
fields = append(c.fields, fields...)
8387

88+
// reset the traceTelemetry in encoder
89+
c.enc.setTraceTelemetry(t)
90+
91+
// set caller
92+
if entry.Caller.Defined {
93+
t.Properties["caller"] = entry.Caller.String()
94+
}
95+
8496
// set fields
8597
for i := range fields {
86-
// check mapped fields first
87-
if mapper, ok := c.fieldMappers[fields[i].Key]; ok {
98+
// handle zap object first
99+
if fields[i].Type == zapcore.ObjectMarshalerType {
100+
fields[i].AddTo(c.enc)
101+
} else if mapper, ok := c.fieldMappers[fields[i].Key]; ok {
102+
// check mapped fields
88103
mapper(t, fieldStringer(&fields[i]))
89104
} else {
90105
t.Properties[fields[i].Key] = fieldStringer(&fields[i])
91106
}
92107
}
93-
94108
b, err := c.enc.encode(t)
95109
if err != nil {
96110
return errors.Wrap(err, "core failed to encode trace")
@@ -121,5 +135,6 @@ func (c *Core) clone() *Core {
121135
fieldMappers: fieldMappers,
122136
fields: fields,
123137
out: c.out,
138+
lock: c.lock,
124139
}
125140
}

zapai/encoder.go

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ import (
66
"fmt"
77
"strconv"
88
"sync"
9+
"time"
910

1011
"github.com/microsoft/ApplicationInsights-Go/appinsights"
1112
"github.com/pkg/errors"
1213
"go.uber.org/zap/zapcore"
1314
)
1415

1516
type traceEncoder interface {
17+
zapcore.ObjectEncoder
1618
encode(*appinsights.TraceTelemetry) ([]byte, error)
19+
setTraceTelemetry(*appinsights.TraceTelemetry)
1720
}
1821

1922
type traceDecoder interface {
@@ -31,12 +34,128 @@ type traceDecoder interface {
3134
// Encoders and Decoders also need to be matched up 1:1, as the first thing an Encoder sends (once!) is type data, and
3235
// it is an error for a Decoder to receive the same type data from its stream more than once.
3336
type gobber struct {
34-
encoder *gob.Encoder
35-
decoder *gob.Decoder
36-
buffer *bytes.Buffer
37+
encoder *gob.Encoder
38+
decoder *gob.Decoder
39+
buffer *bytes.Buffer
40+
traceTelemetry *appinsights.TraceTelemetry
41+
keyPrefix string
3742
sync.Mutex
3843
}
3944

45+
func (g *gobber) AddObject(key string, marshaler zapcore.ObjectMarshaler) error {
46+
curPrefix := g.keyPrefix
47+
if len(g.keyPrefix) == 0 {
48+
g.keyPrefix = key
49+
} else {
50+
g.keyPrefix = g.keyPrefix + "_" + key
51+
}
52+
marshaler.MarshalLogObject(g)
53+
g.keyPrefix = curPrefix
54+
return nil
55+
}
56+
57+
func (g *gobber) AddString(key, value string) {
58+
g.traceTelemetry.Properties[g.keyPrefix+"_"+key] = value
59+
}
60+
61+
func (g *gobber) AddBool(key string, value bool) {
62+
g.traceTelemetry.Properties[g.keyPrefix+"_"+key] = strconv.FormatBool(value)
63+
}
64+
65+
func (g *gobber) AddInt(key string, value int) {
66+
g.traceTelemetry.Properties[g.keyPrefix+"_"+key] = strconv.Itoa(value)
67+
}
68+
69+
func (g *gobber) AddInt64(key string, value int64) {
70+
g.traceTelemetry.Properties[g.keyPrefix+"_"+key] = strconv.FormatInt(value, 10)
71+
}
72+
73+
func (g *gobber) AddUint16(key string, value uint16) {
74+
g.traceTelemetry.Properties[g.keyPrefix+"_"+key] = strconv.FormatUint(uint64(value), 10)
75+
}
76+
77+
func (g *gobber) AddArray(_ string, _ zapcore.ArrayMarshaler) error {
78+
// TODO to be implemented
79+
return nil
80+
}
81+
82+
func (g *gobber) AddBinary(_ string, _ []byte) {
83+
// TODO to be implemented
84+
}
85+
86+
func (g *gobber) AddByteString(_ string, _ []byte) {
87+
// TODO to be implemented
88+
}
89+
90+
func (g *gobber) AddComplex128(_ string, _ complex128) {
91+
// TODO to be implemented
92+
}
93+
94+
func (g *gobber) AddComplex64(_ string, _ complex64) {
95+
// TODO to be implemented
96+
}
97+
98+
func (g *gobber) AddDuration(_ string, _ time.Duration) {
99+
// TODO to be implemented
100+
}
101+
102+
func (g *gobber) AddFloat64(_ string, _ float64) {
103+
// TODO to be implemented
104+
}
105+
106+
func (g *gobber) AddFloat32(_ string, _ float32) {
107+
// TODO to be implemented
108+
}
109+
110+
func (g *gobber) AddInt32(_ string, _ int32) {
111+
// TODO to be implemented
112+
}
113+
114+
func (g *gobber) AddInt16(_ string, _ int16) {
115+
// TODO to be implemented
116+
}
117+
118+
func (g *gobber) AddInt8(_ string, _ int8) {
119+
// TODO to be implemented
120+
}
121+
122+
func (g *gobber) AddTime(_ string, _ time.Time) {
123+
// TODO to be implemented
124+
}
125+
126+
func (g *gobber) AddUint(_ string, _ uint) {
127+
// TODO to be implemented
128+
}
129+
130+
func (g *gobber) AddUint64(_ string, _ uint64) {
131+
// TODO to be implemented
132+
}
133+
134+
func (g *gobber) AddUint32(_ string, _ uint32) {
135+
// TODO to be implemented
136+
}
137+
138+
func (g *gobber) AddUint8(_ string, _ uint8) {
139+
// TODO to be implemented
140+
}
141+
142+
func (g *gobber) AddUintptr(_ string, _ uintptr) {
143+
// TODO to be implemented
144+
}
145+
146+
func (g *gobber) AddReflected(_ string, _ interface{}) error {
147+
// TODO to be implemented
148+
return nil
149+
}
150+
151+
func (g *gobber) OpenNamespace(_ string) {
152+
// TODO to be implemented
153+
}
154+
155+
func (g *gobber) setTraceTelemetry(traceTelemetry *appinsights.TraceTelemetry) {
156+
g.traceTelemetry = traceTelemetry
157+
}
158+
40159
// newTraceEncoder creates a gobber that can only encode.
41160
func newTraceEncoder() traceEncoder {
42161
buf := &bytes.Buffer{}

zapai/example/main.go

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,44 @@ package main
22

33
import (
44
"fmt"
5-
"net/http"
6-
"os"
7-
"runtime"
8-
"time"
9-
105
"github.com/Azure/azure-container-networking/zapai"
116
logfmt "github.com/jsternberg/zap-logfmt"
127
"github.com/microsoft/ApplicationInsights-Go/appinsights"
138
"go.uber.org/zap"
149
"go.uber.org/zap/zapcore"
10+
"net/http"
11+
"os"
12+
"runtime"
13+
"time"
1514
)
1615

1716
const version = "1.2.3"
1817

18+
type Example struct {
19+
NetworkContainerID string
20+
NetworkID string
21+
ReservationID string
22+
Sub Sub
23+
}
24+
25+
type Sub struct {
26+
subnet string
27+
num int
28+
}
29+
30+
func (s Sub) MarshalLogObject(encoder zapcore.ObjectEncoder) error {
31+
encoder.AddString("subnet", s.subnet)
32+
encoder.AddInt("num", s.num)
33+
return nil
34+
}
35+
36+
func (e Example) MarshalLogObject(encoder zapcore.ObjectEncoder) error {
37+
encoder.AddString("ncId", e.NetworkContainerID)
38+
encoder.AddString("vnetId", e.NetworkID)
39+
encoder.AddObject("sub", e.Sub)
40+
return nil
41+
}
42+
1943
func main() {
2044
// stdoutcore logs to stdout with a default JSON encoding
2145
stdoutcore := zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), os.Stdout, zapcore.DebugLevel)
@@ -26,7 +50,7 @@ func main() {
2650
logfmtcore := zapcore.NewCore(logfmt.NewEncoder(zap.NewProductionEncoderConfig()), os.Stdout, zapcore.DebugLevel)
2751
log = zap.New(logfmtcore) // reassign log
2852
log.Error("subnet failed to join", zap.String("subnet", "podnet"), zap.String("prefix", "10.0.0.0/8"))
29-
53+
jsoncore := zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), os.Stdout, zapcore.DebugLevel)
3054
// build the AI config
3155
sinkcfg := zapai.SinkConfig{
3256
GracePeriod: 30 * time.Second, //nolint:gomnd // ignore
@@ -49,12 +73,12 @@ func main() {
4973
aicore = aicore.WithFieldMappers(zapai.DefaultMappers)
5074

5175
// compose the logfmt and aicore in to a virtual tee core so they both receive all log events
52-
teecore := zapcore.NewTee(logfmtcore, aicore)
76+
teecore := zapcore.NewTee(logfmtcore, jsoncore, aicore)
5377

5478
// reassign log using the teecore
55-
log = zap.New(teecore)
79+
log = zap.New(teecore, zap.AddCaller())
5680

57-
// (optional): add normalized fields for the built-in AI Tags
81+
//(optional): add normalized fields for the built-in AI Tags
5882
log = log.With(
5983
zap.String("user_id", runtime.GOOS),
6084
zap.String("operation_id", ""),
@@ -71,6 +95,26 @@ func main() {
7195
zap.String("VMID", "VMID"),
7296
)
7397

98+
subn := Sub{
99+
subnet: "123.222.222",
100+
num: 123,
101+
}
102+
ex1 := Example{
103+
NetworkID: "vetId-1",
104+
NetworkContainerID: "nc-1",
105+
Sub: subn,
106+
}
107+
108+
ex2 := Example{
109+
NetworkID: "vetId-2",
110+
NetworkContainerID: "nc-2",
111+
Sub: subn,
112+
}
113+
114+
// log with zap.Object
115+
log.Debug("testing message-1", zap.Object("ex1", &ex1))
116+
log.Debug("testing message-2", zap.Object("ex2", &ex2))
117+
74118
// muxlog adds a component=mux field to every log that it writes
75119
muxlog := log.With(zap.String("component", "mux"))
76120
m := &mux{log: muxlog}

0 commit comments

Comments
 (0)