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 <cpuguy83@gmail.com>
This commit is contained in:
Brian Goff 2021-09-15 01:26:56 +00:00
parent 6fd80dea34
commit 45c3453a7c
4 changed files with 75 additions and 4 deletions

View File

@ -320,6 +320,8 @@ func applyFlags(context *cli.Context, config *srvconfig.Config) error {
if err := setLogFormat(config); err != nil { if err := setLogFormat(config); err != nil {
return err return err
} }
setLogHooks()
for _, v := range []struct { for _, v := range []struct {
name string name string
d *string d *string
@ -385,6 +387,10 @@ func setLogFormat(config *srvconfig.Config) error {
return nil return nil
} }
func setLogHooks() {
logrus.StandardLogger().AddHook(tracing.NewLogrusHook())
}
func dumpStacks(writeToFile bool) { func dumpStacks(writeToFile bool) {
var ( var (
buf []byte buf []byte

View File

@ -52,7 +52,8 @@ const (
// WithLogger returns a new context with the provided logger. Use in // WithLogger returns a new context with the provided logger. Use in
// combination with logger.WithField(s) for great effect. // combination with logger.WithField(s) for great effect.
func WithLogger(ctx context.Context, logger *logrus.Entry) context.Context { func WithLogger(ctx context.Context, logger *logrus.Entry) context.Context {
return context.WithValue(ctx, loggerKey{}, logger) e := logger.WithContext(ctx)
return context.WithValue(ctx, loggerKey{}, e)
} }
// GetLogger retrieves the current logger from the context. If no logger is // GetLogger retrieves the current logger from the context. If no logger is
@ -61,7 +62,7 @@ func GetLogger(ctx context.Context) *logrus.Entry {
logger := ctx.Value(loggerKey{}) logger := ctx.Value(loggerKey{})
if logger == nil { if logger == nil {
return L return L.WithContext(ctx)
} }
return logger.(*logrus.Entry) return logger.(*logrus.Entry)

View File

@ -25,8 +25,6 @@ import (
func TestLoggerContext(t *testing.T) { func TestLoggerContext(t *testing.T) {
ctx := context.Background() ctx := context.Background()
assert.Equal(t, GetLogger(ctx), L) // should be same as L variable
assert.Equal(t, G(ctx), GetLogger(ctx)) // these should be the same.
ctx = WithLogger(ctx, G(ctx).WithField("test", "one")) ctx = WithLogger(ctx, G(ctx).WithField("test", "one"))
assert.Equal(t, GetLogger(ctx).Data["test"], "one") assert.Equal(t, GetLogger(ctx).Data["test"], "one")

66
tracing/log.go Normal file
View File

@ -0,0 +1,66 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tracing
import (
"github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// NewLogrusHook creates a new logrus hook
func NewLogrusHook() *LogrusHook {
return &LogrusHook{}
}
// LogrusHook is a logrus hook which adds logrus events to active spans.
// If the span is not recording or the span context is invalid, the hook is a no-op.
type LogrusHook struct{}
// Levels returns the logrus levels that this hook is interested in.
func (h *LogrusHook) Levels() []logrus.Level {
return logrus.AllLevels
}
// Fire is called when a log event occurs.
func (h *LogrusHook) Fire(entry *logrus.Entry) error {
span := trace.SpanFromContext(entry.Context)
if span == nil {
return nil
}
if !span.SpanContext().IsValid() || !span.IsRecording() {
return nil
}
span.AddEvent(
entry.Message,
trace.WithAttributes(logrusDataToAttrs(entry.Data)...),
trace.WithAttributes(attribute.String("level", entry.Level.String())),
trace.WithTimestamp(entry.Time),
)
return nil
}
func logrusDataToAttrs(data logrus.Fields) []attribute.KeyValue {
attrs := make([]attribute.KeyValue, 0, len(data))
for k, v := range data {
attrs = append(attrs, attribute.Any(k, v))
}
return attrs
}