Skip to content

Commit

Permalink
feat: slog init func
Browse files Browse the repository at this point in the history
  • Loading branch information
zstone12 committed Oct 27, 2024
1 parent f46a1e2 commit b4fe74b
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 1 deletion.
76 changes: 76 additions & 0 deletions logging/slog/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package slog

import (
"context"
"errors"
"io"
"log/slog"

Check failure on line 7 in logging/slog/handler.go

View workflow job for this annotation

GitHub Actions / lint-and-ut

package log/slog is not in GOROOT (/opt/hostedtoolcache/go/1.20.14/x64/src/log/slog)

"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)

const (
traceIDKey = "trace_id"
spanIDKey = "span_id"
traceFlagsKey = "trace_flags"
)

type traceConfig struct {
recordStackTraceInSpan bool
errorSpanLevel slog.Level
}

type traceHandler struct {
slog.Handler
tcfg *traceConfig
}

func NewTraceHandler(w io.Writer, opts *slog.HandlerOptions, traceConfig *traceConfig) *traceHandler {
if opts == nil {
opts = &slog.HandlerOptions{}
}
return &traceHandler{
slog.NewJSONHandler(w, opts),
traceConfig,
}
}

func (t *traceHandler) Enabled(ctx context.Context, level slog.Level) bool {
return t.Handler.Enabled(ctx, level)
}

func (t *traceHandler) Handle(ctx context.Context, record slog.Record) error {
// trace span add
span := trace.SpanFromContext(ctx)
if span.SpanContext().TraceID().IsValid() {
record.Add(traceIDKey, span.SpanContext().TraceID())
}
if span.SpanContext().SpanID().IsValid() {
record.Add(spanIDKey, span.SpanContext().SpanID())
}
if span.SpanContext().TraceFlags().IsSampled() {
record.Add(traceFlagsKey, span.SpanContext().TraceFlags())
}

// non recording spans do not support modifying
if !span.IsRecording() {
return t.Handler.Handle(ctx, record)
}

// set span status
if record.Level >= t.tcfg.errorSpanLevel {
span.SetStatus(codes.Error, "")
span.RecordError(errors.New(record.Message), trace.WithStackTrace(t.tcfg.recordStackTraceInSpan))
}

return t.Handler.Handle(ctx, record)
}

func (t *traceHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return t.Handler.WithAttrs(attrs)
}

func (t *traceHandler) WithGroup(name string) slog.Handler {
return t.Handler.WithGroup(name)
}
31 changes: 31 additions & 0 deletions logging/slog/logger.go
Original file line number Diff line number Diff line change
@@ -1 +1,32 @@
package slog

import (
"fmt"
"log/slog"
)

type Writer struct {
log *slog.Logger
config *config
}

func NewWriter(opts ...Option) *Writer {
cfg := defaultConfig()

// apply options
for _, opt := range opts {
opt.apply(cfg)
}

logger := slog.New(NewTraceHandler(cfg.coreConfig.writer, cfg.coreConfig.opt, cfg.traceConfig))

return &Writer{log: logger, config: cfg}
}

func (w *Writer) Logger() *slog.Logger {
return w.log
}

func (w *Writer) Printf(format string, v ...interface{}) {
w.log.Info(fmt.Sprintf(format, v...))
}
74 changes: 74 additions & 0 deletions logging/slog/logger_test.go
Original file line number Diff line number Diff line change
@@ -1 +1,75 @@
package slog

import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"testing"
"time"
)

func stdoutProvider(ctx context.Context) func() {
provider := sdktrace.NewTracerProvider()
otel.SetTracerProvider(provider)

exp, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
if err != nil {
panic(err)
}

bsp := sdktrace.NewBatchSpanProcessor(exp)
provider.RegisterSpanProcessor(bsp)

return func() {
if err := provider.Shutdown(ctx); err != nil {
panic(err)
}
}
}

func TestLogger(t *testing.T) {
ctx := context.Background()
shutdown := stdoutProvider(ctx)
defer shutdown()

logger := logger.New(
NewWriter(),
logger.Config{
SlowThreshold: time.Millisecond,
LogLevel: logger.Warn,
Colorful: false,
},
)
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{Logger: logger})
if err != nil {
panic(err)
}

db.Logger.Info(ctx, "log from origin logrus")

tracer := otel.Tracer("test otel std logger")

ctx, span := tracer.Start(ctx, "root")

db.Logger.Info(ctx, "hello %s", "world")

span.End()

ctx, child := tracer.Start(ctx, "child")

db.Logger.Warn(ctx, "foo %s", "bar")

child.End()

ctx, errSpan := tracer.Start(ctx, "error")

db.Logger.Error(ctx, "error %s", "this is a error")

db.Logger.Info(ctx, "no trace context")

errSpan.End()
}
91 changes: 91 additions & 0 deletions logging/slog/option.go
Original file line number Diff line number Diff line change
@@ -1 +1,92 @@
package slog

import (
"io"
"log/slog"
"os"
)

type Option interface {
apply(cfg *config)
}

type option func(cfg *config)

func (fn option) apply(cfg *config) {
fn(cfg)
}

type coreConfig struct {
opt *slog.HandlerOptions
writer io.Writer
level *slog.LevelVar
withLevel bool
withHandlerOptions bool
}

type config struct {
coreConfig coreConfig
traceConfig *traceConfig
}

func defaultConfig() *config {
coreConfig := defaultCoreConfig()
return &config{
coreConfig: *coreConfig,
traceConfig: &traceConfig{
recordStackTraceInSpan: true,
errorSpanLevel: slog.LevelError,
},
}
}

func defaultCoreConfig() *coreConfig {
level := new(slog.LevelVar)
level.Set(slog.LevelInfo)
return &coreConfig{
opt: &slog.HandlerOptions{
Level: level,
},
writer: os.Stdout,
level: level,
withLevel: false,
withHandlerOptions: false,
}
}

// WithHandlerOptions slog handler-options
func WithHandlerOptions(opt *slog.HandlerOptions) Option {
return option(func(cfg *config) {
cfg.coreConfig.opt = opt
cfg.coreConfig.withHandlerOptions = true
})
}

// WithOutput slog writer
func WithOutput(iow io.Writer) Option {
return option(func(cfg *config) {
cfg.coreConfig.writer = iow
})
}

// WithLevel slog level
func WithLevel(lvl *slog.LevelVar) Option {
return option(func(cfg *config) {
cfg.coreConfig.level = lvl
cfg.coreConfig.withLevel = true
})
}

// WithTraceErrorSpanLevel trace error span level option
func WithTraceErrorSpanLevel(level slog.Level) Option {
return option(func(cfg *config) {
cfg.traceConfig.errorSpanLevel = level
})
}

// WithRecordStackTraceInSpan record stack track option
func WithRecordStackTraceInSpan(recordStackTraceInSpan bool) Option {
return option(func(cfg *config) {
cfg.traceConfig.recordStackTraceInSpan = recordStackTraceInSpan
})
}
1 change: 0 additions & 1 deletion logging/slog/utils.go

This file was deleted.

0 comments on commit b4fe74b

Please sign in to comment.