Skip to content

Commit 45c3453

Browse files
committed
Add open telemetry logging hook for logrus
This adds valuable logging data to the open telemetry traces. When the trace is not recording we don't bother doing anything as it is relatively expensive to convert logrus data to otel just due to the nature of how logrus works. The way this works is that we now set a context on the logrus.Entry that gets passed around which the hook then uses to determine if there is an active span to forward the logs to. Signed-off-by: Brian Goff <[email protected]>
1 parent 6fd80de commit 45c3453

4 files changed

Lines changed: 75 additions & 4 deletions

File tree

cmd/containerd/command/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,8 @@ func applyFlags(context *cli.Context, config *srvconfig.Config) error {
320320
if err := setLogFormat(config); err != nil {
321321
return err
322322
}
323+
setLogHooks()
324+
323325
for _, v := range []struct {
324326
name string
325327
d *string
@@ -385,6 +387,10 @@ func setLogFormat(config *srvconfig.Config) error {
385387
return nil
386388
}
387389

390+
func setLogHooks() {
391+
logrus.StandardLogger().AddHook(tracing.NewLogrusHook())
392+
}
393+
388394
func dumpStacks(writeToFile bool) {
389395
var (
390396
buf []byte

log/context.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ const (
5252
// WithLogger returns a new context with the provided logger. Use in
5353
// combination with logger.WithField(s) for great effect.
5454
func WithLogger(ctx context.Context, logger *logrus.Entry) context.Context {
55-
return context.WithValue(ctx, loggerKey{}, logger)
55+
e := logger.WithContext(ctx)
56+
return context.WithValue(ctx, loggerKey{}, e)
5657
}
5758

5859
// GetLogger retrieves the current logger from the context. If no logger is
@@ -61,7 +62,7 @@ func GetLogger(ctx context.Context) *logrus.Entry {
6162
logger := ctx.Value(loggerKey{})
6263

6364
if logger == nil {
64-
return L
65+
return L.WithContext(ctx)
6566
}
6667

6768
return logger.(*logrus.Entry)

log/context_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ import (
2525

2626
func TestLoggerContext(t *testing.T) {
2727
ctx := context.Background()
28-
assert.Equal(t, GetLogger(ctx), L) // should be same as L variable
29-
assert.Equal(t, G(ctx), GetLogger(ctx)) // these should be the same.
3028

3129
ctx = WithLogger(ctx, G(ctx).WithField("test", "one"))
3230
assert.Equal(t, GetLogger(ctx).Data["test"], "one")

tracing/log.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package tracing
18+
19+
import (
20+
"github.com/sirupsen/logrus"
21+
"go.opentelemetry.io/otel/attribute"
22+
"go.opentelemetry.io/otel/trace"
23+
)
24+
25+
// NewLogrusHook creates a new logrus hook
26+
func NewLogrusHook() *LogrusHook {
27+
return &LogrusHook{}
28+
}
29+
30+
// LogrusHook is a logrus hook which adds logrus events to active spans.
31+
// If the span is not recording or the span context is invalid, the hook is a no-op.
32+
type LogrusHook struct{}
33+
34+
// Levels returns the logrus levels that this hook is interested in.
35+
func (h *LogrusHook) Levels() []logrus.Level {
36+
return logrus.AllLevels
37+
}
38+
39+
// Fire is called when a log event occurs.
40+
func (h *LogrusHook) Fire(entry *logrus.Entry) error {
41+
span := trace.SpanFromContext(entry.Context)
42+
if span == nil {
43+
return nil
44+
}
45+
46+
if !span.SpanContext().IsValid() || !span.IsRecording() {
47+
return nil
48+
}
49+
50+
span.AddEvent(
51+
entry.Message,
52+
trace.WithAttributes(logrusDataToAttrs(entry.Data)...),
53+
trace.WithAttributes(attribute.String("level", entry.Level.String())),
54+
trace.WithTimestamp(entry.Time),
55+
)
56+
57+
return nil
58+
}
59+
60+
func logrusDataToAttrs(data logrus.Fields) []attribute.KeyValue {
61+
attrs := make([]attribute.KeyValue, 0, len(data))
62+
for k, v := range data {
63+
attrs = append(attrs, attribute.Any(k, v))
64+
}
65+
return attrs
66+
}

0 commit comments

Comments
 (0)