Skip to content

Commit e15c490

Browse files
author
Lars Ekman
committed
Add structured logging
1 parent ca262b1 commit e15c490

File tree

5 files changed

+537
-3
lines changed

5 files changed

+537
-3
lines changed

docs/logging.md

+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# Meridio - logging
2+
3+
Meridio uses structured logging implemented by
4+
[go-logr/logr](https://github.com/go-logr/logr). Structured logging
5+
means that printf-like formatted messages are not used, instead
6+
key/object pairs are passed to the log functions.
7+
8+
```go
9+
import "github.com/nordix/meridio/pkg/log"
10+
11+
var config Config
12+
err := envconfig.Process("ipam", &config)
13+
if err != nil {
14+
panic(err) // We can't log since we have no logger yet
15+
}
16+
logger := log.New("Meridio-ipam", config.LogLevel)
17+
logger.Info("Configuration read", "config", config)
18+
```
19+
20+
When executed this will produce (formatted with
21+
[jq](https://stedolan.github.io/jq/));
22+
23+
```json
24+
{
25+
"severity": "info",
26+
"timestamp": "2022-08-31T09:04:03.482+00:00",
27+
"service_id": "Meridio-ipam",
28+
"message": "Configuration read",
29+
"version": "1.0.0",
30+
"extra_data": {
31+
"config": {
32+
"Port": 7777,
33+
"Datasource": "/run/ipam/data/registry.db",
34+
"TrenchName": "red",
35+
"NSPService": "meridio-nsp-red:7778",
36+
"PrefixIPv4": "172.16.0.0/16",
37+
"ConduitPrefixLengthIPv4": 20,
38+
"NodePrefixLengthIPv4": 24,
39+
"PrefixIPv6": "fd00::172.16.0.0/112",
40+
"ConduitPrefixLengthIPv6": 116,
41+
"NodePrefixLengthIPv6": 120,
42+
"IPFamily": "dualstack",
43+
"LogLevel": "DEBUG"
44+
}
45+
}
46+
}
47+
```
48+
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+
59+
## Logger from context
60+
61+
A logger should be created in `main()` and be used for logging
62+
everywhere. The logger is not passed in every call but a
63+
[go context](https://pkg.go.dev/context) should. Functions should
64+
use the logger from the context;
65+
66+
```go
67+
// In main();
68+
ctx = logr.NewContext(ctx, logger)
69+
// In a function;
70+
logger = log.FromContextOrGlobal(ctx)
71+
```
72+
73+
Functions really should always have a context as first parameter but
74+
they might not. A global logger is provided;
75+
76+
```
77+
log.Logger.Info("Configuration read", "config", config)
78+
```
79+
80+
The global logger is set by the *first* call to `log.New`. A global logger
81+
named "Meridio" on INFO level is pre-installed before `log.New` is called.
82+
83+
84+
85+
## Log levels
86+
87+
Severity `debug`, `info`, `error` and `critical` are used (not
88+
`warning`). The `Info()` call can have different "verbosity", set with the
89+
`V(n)` method;
90+
91+
```go
92+
logger.Info("This is a normal info message")
93+
logger.V(1).Info("This is a debug message")
94+
logger.V(2).Info("This is a trace message")
95+
```
96+
97+
There is no defined "trace" level in output so both trace and debug
98+
messages will have severity "debug". The level filtering is still valid
99+
though, trace messages are suppressed unless TRACE level is set.
100+
101+
The `Fatal()` function logs on `critical` level.
102+
103+
### Costly parameter computations
104+
105+
**This is important!**
106+
107+
Consider;
108+
109+
```go
110+
logger.V(2).Info("Gathered data", "collected", verySlowFunction())
111+
```
112+
113+
The `verySlowFunction()` will *always* be executed, even if not on
114+
`trace` level. A few of these may have a severe impact on
115+
performance but you may *really* want them for trace. Luckily there is
116+
a [trick](https://github.com/go-logr/logr/issues/149);
117+
118+
```
119+
if loggerV := logger.V(2); loggerV.Enabled() {
120+
loggerV.Info("Gathered data", "collected", verySlowFunction())
121+
}
122+
```
123+
124+
Now the `verySlowFunction()` is *only* executed when trace level is set.
125+
126+
127+
## Fatal
128+
129+
```go
130+
import "github.com/nordix/meridio/pkg/log"
131+
logger := log.New("Meridio-ipam", config.LogLevel)
132+
log.Fatal(logger, "Can't read crucial data", "error", err)
133+
```
134+
135+
The logger is a pure `logr.Logger` logger so there is no `Fatal()`
136+
method. However we want to print a termination message using the same
137+
formatting as other log items so the `logger` is passed as a parameter.
138+
139+
Example output;
140+
```json
141+
{
142+
"severity": "critical",
143+
"timestamp": "2022-08-31T13:42:29.345+02:00",
144+
"service_id": "Meridio-test",
145+
"message": "Can't read crucial data",
146+
"version": "1.1.0",
147+
"extra_data": {
148+
"error": "Not found"
149+
}
150+
}
151+
```
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
158+
over-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 // (optional; 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", "object", h)
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. The instance field must be
197+
capitalized if you want it visible.
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+

go.mod

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ go 1.19
55
require (
66
github.com/antonfisher/nested-logrus-formatter v1.3.1
77
github.com/edwarnicke/grpcfd v1.1.2
8+
github.com/go-logr/logr v1.2.3
9+
github.com/go-logr/zapr v1.2.3
810
github.com/golang/mock v1.6.0
911
github.com/golang/protobuf v1.5.2
1012
github.com/google/nftables v0.0.0-20220808154552-2eca00135732
@@ -21,6 +23,7 @@ require (
2123
github.com/stretchr/testify v1.8.0
2224
github.com/vishvananda/netlink v1.2.1-beta.2.0.20220630165224-c591ada0fb2b
2325
go.uber.org/goleak v1.1.12
26+
go.uber.org/zap v1.23.0
2427
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261
2528
google.golang.org/grpc v1.49.0
2629
google.golang.org/protobuf v1.28.1
@@ -45,7 +48,6 @@ require (
4548
github.com/edwarnicke/serialize v1.0.7 // indirect
4649
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
4750
github.com/ghodss/yaml v1.0.0 // indirect
48-
github.com/go-logr/logr v1.2.3 // indirect
4951
github.com/go-logr/stdr v1.2.0 // indirect
5052
github.com/go-openapi/jsonpointer v0.19.5 // indirect
5153
github.com/go-openapi/jsonreference v0.19.5 // indirect
@@ -93,6 +95,8 @@ require (
9395
go.opentelemetry.io/otel/sdk/metric v0.26.0 // indirect
9496
go.opentelemetry.io/otel/trace v1.3.0 // indirect
9597
go.opentelemetry.io/proto/otlp v0.11.0 // indirect
98+
go.uber.org/atomic v1.7.0 // indirect
99+
go.uber.org/multierr v1.6.0 // indirect
96100
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
97101
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
98102
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect

go.sum

+11-2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd
4949
github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
5050
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
5151
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
52+
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
5253
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
5354
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
5455
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -107,11 +108,13 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
107108
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
108109
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
109110
github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
111+
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
110112
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
111113
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
112114
github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE=
113115
github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI=
114-
github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk=
116+
github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=
117+
github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4=
115118
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
116119
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
117120
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@@ -302,6 +305,7 @@ github.com/open-policy-agent/opa v0.16.1 h1:BDADmi1Xl08aPcubaYgSEU0lJ/zrWDwmFMRX
302305
github.com/open-policy-agent/opa v0.16.1/go.mod h1:P0xUE/GQAAgnvV537GzA0Ikw4+icPELRT327QJPkaKY=
303306
github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
304307
github.com/pkg/errors v0.0.0-20181023235946-059132a15dd0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
308+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
305309
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
306310
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
307311
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -393,11 +397,15 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
393397
go.opentelemetry.io/proto/otlp v0.11.0 h1:cLDgIBTf4lLOlztkhzAEdQsJ4Lj+i5Wc9k6Nn0K1VyU=
394398
go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ=
395399
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
400+
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
396401
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
397402
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
398403
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
399404
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
400-
go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
405+
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
406+
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
407+
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
408+
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
401409
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
402410
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
403411
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -748,6 +756,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
748756
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
749757
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
750758
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
759+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
751760
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
752761
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
753762
gorm.io/driver/sqlite v1.3.6 h1:Fi8xNYCUplOqWiPa3/GuCeowRNBRGTf62DEmhMDHeQQ=

0 commit comments

Comments
 (0)