Skip to content

Commit a829993

Browse files
author
Lars Ekman
committed
Simplify and correct the Logger
And some design pattern proposals added
1 parent b8053b7 commit a829993

File tree

3 files changed

+116
-128
lines changed

3 files changed

+116
-128
lines changed

docs/logging.md

+60
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ When executed this will produce (formatted with
4646
}
4747
```
4848

49+
Structured logs can be scanned with [jq](https://stedolan.github.io/jq/).
50+
51+
```
52+
kubectl logs -n red meridio-load-balancer-6dbbb9556f-f5cc4 -c load-balancer \
53+
| grep '^{' | jq 'select(.extra_data.class == "SimpleNetworkService")'
54+
kubectl logs -n red meridio-load-balancer-6dbbb9556f-f5cc4 -c load-balancer \
55+
| grep '^{' | jq 'select(.extra_data.class == "SimpleNetworkService")|select(.message == "updateVips")'
56+
57+
```
58+
4959
## Logger from context
5060

5161
A logger should be created in `main()` and be used for logging
@@ -139,3 +149,53 @@ Example output;
139149
}
140150
}
141151
```
152+
153+
154+
## Design patterns
155+
156+
Patterns must evolve slowly to get really good so these are mere
157+
ideas. It is very easy to get carried away and impose some over
158+
structured logging that floods the logs with useless data.
159+
160+
161+
### Class logger
162+
163+
A logger used in a type (Class) can be decorated with `class` and
164+
`instance` records;
165+
166+
```go
167+
type someHandler struct {
168+
ctx context.Context
169+
logger logr.Logger
170+
Adr *net.TCPAddr // Capitalized to make it visible!
171+
}
172+
173+
func newHandler(ctx context.Context, addr string) *someHandler {
174+
logger := log.FromContextOrGlobal(ctx).WithValues("class", "someHandler")
175+
adr, err := net.ResolveTCPAddr("tcp", addr)
176+
if err != nil {
177+
log.Fatal(logger, "ResolveTCPAddr", "error", err)
178+
}
179+
h := &someHandler{
180+
ctx: ctx,
181+
logger: logger.WithValues("instance", adr),
182+
Adr: adr,
183+
}
184+
h.logger.Info("Created")
185+
return h
186+
}
187+
188+
func (h *someHandler) connect() error {
189+
logger := h.logger.WithValues("func", "connect")
190+
logger.Info("Called")
191+
return nil
192+
}
193+
```
194+
195+
The `class` is the name of the type and `instance` can be anything
196+
that identifies an instance. Remember that the instance field must be
197+
visible (Capitalized).
198+
199+
The example shows a `func` entry to identify a function. This should
200+
*not* be used as a common pattern but may be handy in some cases.
201+

pkg/log/logger.go

+11-124
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
nsmlog "github.com/networkservicemesh/sdk/pkg/tools/log"
2525
)
2626

27-
2827
// Several different logr backends can be tested by remove/add comment
2928
// below.
3029

@@ -142,7 +141,6 @@ import (
142141

143142
"github.com/go-logr/zapr"
144143
"go.uber.org/zap"
145-
"go.uber.org/zap/buffer"
146144
"go.uber.org/zap/zapcore"
147145
)
148146

@@ -190,7 +188,6 @@ func newLogger(level string) logr.Logger {
190188
//zc.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder (almost works)
191189
zc.EncoderConfig.EncodeTime = timeEncoder
192190
zc.EncoderConfig.EncodeLevel = adpLevelEncoder
193-
zc.Encoding = "ADP"
194191
z, err := zc.Build()
195192
if err != nil {
196193
zc.Encoding = "json"
@@ -199,122 +196,11 @@ func newLogger(level string) logr.Logger {
199196
panic(fmt.Sprintf("Can't create a zap logger (%v)?", err))
200197
}
201198
}
202-
return zapr.NewLogger(z.With(zap.String("version", "1.1.0")))
203-
}
204-
205-
// We just want to add "extra_data: { ... }" for additional fields
206-
// but I found no other way than to define an own zapcore.Encoder.
207-
// Everything except the EncodeEntry() is passed to the JSONEncoder.
208-
type adpEncoder struct {
209-
jsonEncoder zapcore.Encoder
210-
}
211-
212-
func (e *adpEncoder) Clone() zapcore.Encoder {
213-
return &adpEncoder{jsonEncoder: e.jsonEncoder.Clone()}
214-
}
215-
216-
// https://stackoverflow.com/questions/53737435/how-to-prepend-int-to-slice
217-
func prependField(x []zapcore.Field, y zapcore.Field) []zapcore.Field {
218-
x = append(x, zapcore.Field{})
219-
copy(x[1:], x)
220-
x[0] = y
221-
return x
222-
}
223-
func (e *adpEncoder) EncodeEntry(
224-
entry zapcore.Entry, f []zapcore.Field) (*buffer.Buffer, error) {
225-
if len(f) == 0 {
226-
return e.jsonEncoder.EncodeEntry(entry, f)
227-
}
228-
ns := zapcore.Field{
229-
Key: "extra_data",
230-
Type: zapcore.NamespaceType,
231-
}
232-
return e.jsonEncoder.EncodeEntry(entry, prependField(f, ns))
233-
}
234-
func (e *adpEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error {
235-
return e.jsonEncoder.AddArray(key, marshaler)
236-
}
237-
func (e *adpEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error {
238-
return e.jsonEncoder.AddObject(key, marshaler)
239-
}
240-
func (e *adpEncoder) AddBinary(key string, value []byte) {
241-
e.jsonEncoder.AddBinary(key, value)
242-
}
243-
func (e *adpEncoder) AddByteString(key string, value []byte) {
244-
e.jsonEncoder.AddByteString(key, value)
245-
}
246-
func (e *adpEncoder) AddBool(key string, value bool) {
247-
e.jsonEncoder.AddBool(key, value)
248-
}
249-
func (e *adpEncoder) AddComplex128(key string, value complex128) {
250-
e.jsonEncoder.AddComplex128(key, value)
251-
}
252-
func (e *adpEncoder) AddComplex64(key string, value complex64) {
253-
e.jsonEncoder.AddComplex64(key, value)
254-
}
255-
func (e *adpEncoder) AddDuration(key string, value time.Duration) {
256-
e.jsonEncoder.AddDuration(key, value)
257-
}
258-
func (e *adpEncoder) AddFloat64(key string, value float64) {
259-
e.jsonEncoder.AddFloat64(key, value)
260-
}
261-
func (e *adpEncoder) AddFloat32(key string, value float32) {
262-
e.jsonEncoder.AddFloat32(key, value)
263-
}
264-
func (e *adpEncoder) AddInt(key string, value int) {
265-
e.jsonEncoder.AddInt(key, value)
266-
}
267-
func (e *adpEncoder) AddInt64(key string, value int64) {
268-
e.jsonEncoder.AddInt64(key, value)
269-
}
270-
func (e *adpEncoder) AddInt32(key string, value int32) {
271-
e.jsonEncoder.AddInt32(key, value)
272-
}
273-
func (e *adpEncoder) AddInt16(key string, value int16) {
274-
e.jsonEncoder.AddInt16(key, value)
275-
}
276-
func (e *adpEncoder) AddInt8(key string, value int8) {
277-
e.jsonEncoder.AddInt8(key, value)
278-
}
279-
func (e *adpEncoder) AddString(key, value string) {
280-
e.jsonEncoder.AddString(key, value)
281-
}
282-
func (e *adpEncoder) AddTime(key string, value time.Time) {
283-
e.jsonEncoder.AddTime(key, value)
284-
}
285-
func (e *adpEncoder) AddUint(key string, value uint) {
286-
e.jsonEncoder.AddUint(key, value)
287-
}
288-
func (e *adpEncoder) AddUint64(key string, value uint64) {
289-
e.jsonEncoder.AddUint64(key, value)
290-
}
291-
func (e *adpEncoder) AddUint32(key string, value uint32) {
292-
e.jsonEncoder.AddUint32(key, value)
293-
}
294-
func (e *adpEncoder) AddUint16(key string, value uint16) {
295-
e.jsonEncoder.AddUint16(key, value)
296-
}
297-
func (e *adpEncoder) AddUint8(key string, value uint8) {
298-
e.jsonEncoder.AddUint8(key, value)
299-
}
300-
func (e *adpEncoder) AddUintptr(key string, value uintptr) {
301-
e.jsonEncoder.AddUintptr(key, value)
302-
}
303-
func (e *adpEncoder) AddReflected(key string, value interface{}) error {
304-
return e.jsonEncoder.AddReflected(key, value)
305-
}
306-
func (e *adpEncoder) OpenNamespace(key string) {
307-
e.jsonEncoder.OpenNamespace(key)
199+
return zapr.NewLogger(z.With(
200+
zap.String("version", "1.0.0"), zap.Namespace("extra_data")))
308201
}
309202

310203
func init() {
311-
_ = zap.RegisterEncoder(
312-
"ADP", func(cfg zapcore.EncoderConfig) (zapcore.Encoder, error) {
313-
//return zapcore.NewJSONEncoder(cfg), nil
314-
return &adpEncoder{
315-
jsonEncoder: zapcore.NewJSONEncoder(cfg),
316-
}, nil
317-
})
318204
Logger = newLogger("").WithName("Meridio")
319205
}
320206

@@ -341,17 +227,19 @@ func Fatal(logger logr.Logger, msg string, keysAndValues ...interface{}) {
341227
// NSMLogger return a logger to use for NSM logging.
342228
func NSMLogger(logger logr.Logger) nsmlog.Logger {
343229
// Get the zap logger
344-
if underlier, ok := logger.GetSink().(zapr.Underlier); ok {
345-
z := underlier.GetUnderlying()
346-
return &nsmLogger{ z: z, s: z.Sugar() }
347-
} else {
230+
z := ZapLogger(logger)
231+
if z == nil {
348232
panic("NSMLogger: Can't get the Zap logger")
349233
}
234+
nsmz := z.With(zap.String("subsystem", "NSM"))
235+
return &nsmLogger{z: nsmz, s: nsmz.Sugar()}
350236
}
237+
351238
type nsmLogger struct {
352239
z *zap.Logger
353240
s *zap.SugaredLogger
354241
}
242+
355243
func (l *nsmLogger) Info(v ...interface{}) {
356244
l.s.Info(v...)
357245
}
@@ -393,15 +281,14 @@ func (l *nsmLogger) Tracef(format string, v ...interface{}) {
393281
}
394282
}
395283
func (l *nsmLogger) Object(k, v interface{}) {
396-
l.s.Info(k, v)
284+
l.z.Info("Object", zap.Any(fmt.Sprintf("%v", k), v))
397285
}
398286
func (l *nsmLogger) WithField(key, value interface{}) nsmlog.Logger {
399287
return &nsmLogger{
400288
z: l.z.With(zap.Any(fmt.Sprintf("%v", key), value)),
401289
}
402290
}
403291

404-
405292
// Logger The global logger
406293
var Logger logr.Logger
407294

@@ -419,8 +306,8 @@ func FromContextOrGlobal(ctx context.Context) logr.Logger {
419306
// the global Logger is set.
420307
func New(name, level string) logr.Logger {
421308
logger := newLogger(level).WithName(name)
422-
once.Do(func () {Logger = logger})
309+
once.Do(func() { Logger = logger })
423310
return logger
424311
}
425-
var once sync.Once
426312

313+
var once sync.Once

pkg/log/logger_test.go

+45-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package log_test
22

33
import (
4+
"context"
45
"fmt"
6+
"net"
57
"os"
68
"testing"
79
"time"
810

11+
"github.com/go-logr/logr"
912
"github.com/nordix/meridio/pkg/log"
1013
)
1114

@@ -18,7 +21,7 @@ func TestLogger(t *testing.T) {
1821
log.Logger.Info("From the default logger before New")
1922
logger := log.New("Meridio-test-global", os.Getenv("FOO"))
2023
log.Logger.Info("From the default logger AFTER New")
21-
24+
2225
logger.Info("Started", "at", time.Now())
2326
log.Logger.Info("Printed by the global logger")
2427
logger.Error(fmt.Errorf("THIS IS THE ERROR OBJ"), "An error!", "a number", 44)
@@ -46,10 +49,48 @@ func TestLogger(t *testing.T) {
4649
log.Logger.V(2).Info("INVISIBLE TRACE message")
4750

4851
//log.Fatal(logger, "Can't read crucial data", "error", fmt.Errorf("Not found"))
52+
}
4953

54+
func TestNSMLogger(t *testing.T) {
5055
nsmlogger := log.NSMLogger(log.New("NSMLogger-info", ""))
51-
if nsmlogger != nil {
52-
nsmlogger.Info("one", "two", "three")
53-
nsmlogger.Infof("%v, %v, %v", "one", "two", "three")
56+
if nsmlogger == nil {
57+
return
58+
}
59+
nsmlogger.Info("one", "two", "three")
60+
nsmlogger.Infof("%v, %v, %v", "one", "two", "three")
61+
nsmlogger.Object(44, "Key is an int")
62+
}
63+
64+
type someHandler struct {
65+
ctx context.Context
66+
logger logr.Logger
67+
Adr *net.TCPAddr // Capitalized to make it visible!
68+
}
69+
70+
func newHandler(ctx context.Context, addr string) *someHandler {
71+
logger := log.FromContextOrGlobal(ctx).WithValues("class", "someHandler")
72+
adr, err := net.ResolveTCPAddr("tcp", addr)
73+
if err != nil {
74+
log.Fatal(logger, "ResolveTCPAddr", "error", err)
75+
}
76+
h := &someHandler{
77+
ctx: ctx,
78+
logger: logger.WithValues("instance", adr),
79+
Adr: adr,
5480
}
81+
h.logger.Info("Created")
82+
return h
83+
}
84+
85+
func (h *someHandler) connect() error {
86+
logger := h.logger.WithValues("func", "connect")
87+
logger.Info("Called")
88+
return nil
89+
}
90+
91+
func TestPatterns(t *testing.T) {
92+
logger := log.New("HandlerApp", "")
93+
ctx := logr.NewContext(context.TODO(), logger)
94+
h := newHandler(ctx, "[1000::]:80")
95+
_ = h.connect()
5596
}

0 commit comments

Comments
 (0)