diff --git a/internal/internal_event_handlers.go b/internal/internal_event_handlers.go index 3bb3f0589..e72286f28 100644 --- a/internal/internal_event_handlers.go +++ b/internal/internal_event_handlers.go @@ -239,7 +239,8 @@ func newWorkflowExecutionEventHandler( mutableSideEffectCallCounter: make(map[string]int), sdkFlags: newSDKFlags(capabilities), } - context.logger = ilog.NewReplayLogger( + // Attempt to skip 1 log level to remove the ReplayLogger from the stack. + context.logger = log.Skip(ilog.NewReplayLogger( log.With(logger, tagWorkflowType, workflowInfo.WorkflowType.Name, tagWorkflowID, workflowInfo.WorkflowExecution.ID, @@ -247,7 +248,7 @@ func newWorkflowExecutionEventHandler( tagAttempt, workflowInfo.Attempt, ), &context.isReplay, - &context.enableLoggingInReplay) + &context.enableLoggingInReplay), 1) if metricsHandler != nil { context.metricsHandler = metrics.NewReplayAwareHandler(&context.isReplay, metricsHandler). diff --git a/internal/log/replay_logger.go b/internal/log/replay_logger.go index 71556cd36..16b8e1b25 100644 --- a/internal/log/replay_logger.go +++ b/internal/log/replay_logger.go @@ -80,3 +80,11 @@ func (l *ReplayLogger) Error(msg string, keyvals ...interface{}) { func (l *ReplayLogger) With(keyvals ...interface{}) log.Logger { return NewReplayLogger(log.With(l.logger, keyvals...), l.isReplay, l.enableLoggingInReplay) } + +// AddCallerSkip increases the caller skip depth if the underlying logger supports it. +func (l *ReplayLogger) AddCallerSkip(depth int) log.Logger { + if sl, ok := l.logger.(log.WithSkipCallers); ok { + return NewReplayLogger(sl.AddCallerSkip(depth), l.isReplay, l.enableLoggingInReplay) + } + return l +} diff --git a/log/logger.go b/log/logger.go index dc9d400e5..77586b4df 100644 --- a/log/logger.go +++ b/log/logger.go @@ -32,4 +32,10 @@ type ( Warn(msg string, keyvals ...interface{}) Error(msg string, keyvals ...interface{}) } + + // WithSkipCallers is an optional interface that a Logger can implement that + // indicate the number of stack frames to skip. + WithSkipCallers interface { + AddCallerSkip(int) Logger + } ) diff --git a/log/slog.go b/log/slog.go new file mode 100644 index 000000000..d1791dbe2 --- /dev/null +++ b/log/slog.go @@ -0,0 +1,94 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build go1.21 + +package log + +import ( + "context" + "log/slog" + "runtime" + "time" +) + +type slogLogger struct { + logger *slog.Logger + depth int +} + +// NewStructuredLogger creates an adapter around the given logger to be passed to Temporal. +func NewStructuredLogger(logger *slog.Logger) Logger { + return &slogLogger{ + logger: logger, + depth: 3, + } +} + +func (s *slogLogger) Debug(msg string, keyvals ...interface{}) { + s.log(context.Background(), slog.LevelDebug, msg, keyvals...) +} + +func (s *slogLogger) Info(msg string, keyvals ...interface{}) { + s.log(context.Background(), slog.LevelInfo, msg, keyvals...) +} + +func (s *slogLogger) Warn(msg string, keyvals ...interface{}) { + s.log(context.Background(), slog.LevelWarn, msg, keyvals...) +} + +func (s *slogLogger) Error(msg string, keyvals ...interface{}) { + s.log(context.Background(), slog.LevelError, msg, keyvals...) +} + +func (s *slogLogger) log(ctx context.Context, level slog.Level, msg string, args ...any) { + if !s.logger.Enabled(ctx, level) { + return + } + + var pcs [1]uintptr + runtime.Callers(s.depth, pcs[:]) + + record := slog.NewRecord(time.Now(), level, msg, pcs[0]) + record.Add(args...) + + if ctx == nil { + ctx = context.Background() + } + _ = s.logger.Handler().Handle(ctx, record) +} + +func (s *slogLogger) With(keyvals ...interface{}) Logger { + return &slogLogger{ + logger: s.logger.With(keyvals...), + depth: s.depth, + } +} + +func (s *slogLogger) AddCallerSkip(depth int) Logger { + return &slogLogger{ + logger: s.logger, + depth: s.depth + depth, + } +} diff --git a/log/slog_test.go b/log/slog_test.go new file mode 100644 index 000000000..225227a2e --- /dev/null +++ b/log/slog_test.go @@ -0,0 +1,31 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build go1.21 + +package log + +import "testing" + +func TestSlogAddapter(t *testing.T) {} diff --git a/log/with_logger.go b/log/with_logger.go index 03e592953..be82bd796 100644 --- a/log/with_logger.go +++ b/log/with_logger.go @@ -39,6 +39,15 @@ func With(logger Logger, keyvals ...interface{}) Logger { return newWithLogger(logger, keyvals...) } +// Skip creates a child Logger that increase increases its' caller skip depth if it +// implements [WithSkipCallers]. Otherwise returns the original logger. +func Skip(logger Logger, depth int) Logger { + if sl, ok := logger.(WithSkipCallers); ok { + return sl.AddCallerSkip(depth) + } + return logger +} + type withLogger struct { logger Logger keyvals []interface{} @@ -71,3 +80,11 @@ func (l *withLogger) Warn(msg string, keyvals ...interface{}) { func (l *withLogger) Error(msg string, keyvals ...interface{}) { l.logger.Error(msg, l.prependKeyvals(keyvals)...) } + +// AddCallerSkip increases the caller skip depth if the underlying logger supports it. +func (l *withLogger) AddCallerSkip(depth int) Logger { + if sl, ok := l.logger.(WithSkipCallers); ok { + return newWithLogger(sl.AddCallerSkip(depth), l.keyvals) + } + return l +}