Skip to content

Commit

Permalink
otelslog: Add WithSource option (#6253)
Browse files Browse the repository at this point in the history
Add an otelslog option to include the source file location in the log
attributes.

Resolves
#6244
  • Loading branch information
Jesse0Michael authored Oct 18, 2024
1 parent 13536dd commit 1c56a7c
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

- Transform raw (`slog.KindAny`) attribute values to matching `log.Value` types.
For example, `[]string{"foo", "bar"}` attribute value is now transformed to `log.SliceValue(log.StringValue("foo"), log.StringValue("bar"))` instead of `log.String("[foo bar"])`. (#6254)
- Add the `WithSource` option to the `go.opentelemetry.io/contrib/bridges/otelslog` log bridge to set the `code.*` attributes in the log record that includes the source location where the record was emitted. (#6253)

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion bridges/otelslog/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.22

require (
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/otel v1.31.0
go.opentelemetry.io/otel/log v0.7.0
)

Expand All @@ -12,7 +13,6 @@ require (
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/otel v1.31.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect
go.opentelemetry.io/otel/trace v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
29 changes: 28 additions & 1 deletion bridges/otelslog/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ import (
"context"
"fmt"
"log/slog"
"runtime"
"slices"

"go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/log/global"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)

// NewLogger returns a new [slog.Logger] backed by a new [Handler]. See
Expand All @@ -64,6 +66,7 @@ type config struct {
provider log.LoggerProvider
version string
schemaURL string
source bool
}

func newConfig(options []Option) config {
Expand Down Expand Up @@ -131,6 +134,15 @@ func WithLoggerProvider(provider log.LoggerProvider) Option {
})
}

// WithSource returns an [Option] that configures the [Handler] to include
// the source location of the log record in log attributes.
func WithSource(source bool) Option {
return optFunc(func(c config) config {
c.source = source
return c
})
}

// Handler is an [slog.Handler] that sends all logging records it receives to
// OpenTelemetry. See package documentation for how conversions are made.
type Handler struct {
Expand All @@ -140,6 +152,8 @@ type Handler struct {
attrs *kvBuffer
group *group
logger log.Logger

source bool
}

// Compile-time check *Handler implements slog.Handler.
Expand All @@ -155,7 +169,10 @@ var _ slog.Handler = (*Handler)(nil)
// [log.Logger] implementation may override this value with a default.
func NewHandler(name string, options ...Option) *Handler {
cfg := newConfig(options)
return &Handler{logger: cfg.logger(name)}
return &Handler{
logger: cfg.logger(name),
source: cfg.source,
}
}

// Handle handles the passed record.
Expand All @@ -172,6 +189,16 @@ func (h *Handler) convertRecord(r slog.Record) log.Record {
const sevOffset = slog.Level(log.SeverityDebug) - slog.LevelDebug
record.SetSeverity(log.Severity(r.Level + sevOffset))

if h.source {
fs := runtime.CallersFrames([]uintptr{r.PC})
f, _ := fs.Next()
record.AddAttributes(
log.String(string(semconv.CodeFilepathKey), f.File),
log.String(string(semconv.CodeFunctionKey), f.Function),
log.Int(string(semconv.CodeLineNumberKey), f.Line),
)
}

if h.attrs.Len() > 0 {
record.AddAttributes(h.attrs.KeyValues()...)
}
Expand Down
28 changes: 27 additions & 1 deletion bridges/otelslog/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/log/embedded"
"go.opentelemetry.io/otel/log/global"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)

var now = time.Now()
Expand Down Expand Up @@ -149,6 +150,8 @@ type testCase struct {
// checks is a list of checks to run on the result. Each item is a slice of
// checks that will be evaluated for the corresponding record emitted.
checks [][]check
// options are passed to the Handler constructed for this test case.
options []Option
}

// copied from slogtest (1.22.1).
Expand Down Expand Up @@ -225,6 +228,10 @@ func (h *wrapper) Handle(ctx context.Context, r slog.Record) error {
}

func TestSLogHandler(t *testing.T) {
// Capture the PC of this line
pc, file, line, _ := runtime.Caller(0)
funcName := runtime.FuncForPC(pc).Name()

cases := []testCase{
{
name: "Values",
Expand Down Expand Up @@ -394,13 +401,31 @@ func TestSLogHandler(t *testing.T) {
inGroup("G", missingKey("a")),
}},
},
{
name: "WithSource",
explanation: withSource("a Handler using the WithSource Option should include file attributes from where the log was emitted"),
f: func(l *slog.Logger) {
l.Info("msg")
},
mod: func(r *slog.Record) {
// Assign the PC of record to the one captured above.
r.PC = pc
},
checks: [][]check{{
hasAttr(string(semconv.CodeFilepathKey), file),
hasAttr(string(semconv.CodeFunctionKey), funcName),
hasAttr(string(semconv.CodeLineNumberKey), int64(line)),
}},
options: []Option{WithSource(true)},
},
}

// Based on slogtest.Run.
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
r := new(recorder)
var h slog.Handler = NewHandler("", WithLoggerProvider(r))
opts := append([]Option{WithLoggerProvider(r)}, c.options...)
var h slog.Handler = NewHandler("", opts...)
if c.mod != nil {
h = &wrapper{h, c.mod}
}
Expand Down Expand Up @@ -459,6 +484,7 @@ func TestNewHandlerConfiguration(t *testing.T) {
WithLoggerProvider(r),
WithVersion("ver"),
WithSchemaURL("url"),
WithSource(true),
)
})
require.NotNil(t, h.logger)
Expand Down

0 comments on commit 1c56a7c

Please sign in to comment.