Skip to content

Commit

Permalink
Logging with context values
Browse files Browse the repository at this point in the history
  • Loading branch information
ziyaozclk committed Mar 10, 2022
1 parent 6f34060 commit d66040c
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 3 deletions.
107 changes: 104 additions & 3 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@
package zap

import (
"context"
"fmt"
"go.uber.org/zap/internal/bufferpool"
"go.uber.org/zap/zapcore"
"io/ioutil"
"os"
"runtime"
"strings"

"go.uber.org/zap/internal/bufferpool"
"go.uber.org/zap/zapcore"
)

// A Logger provides fast, leveled, structured logging. All methods are safe
Expand All @@ -52,6 +53,8 @@ type Logger struct {
callerSkip int

clock zapcore.Clock

contextFunc func(ctx context.Context) []Field
}

// New constructs a new Logger from the provided zapcore.Core and Options. If
Expand Down Expand Up @@ -242,6 +245,78 @@ func (log *Logger) Fatal(msg string, fields ...Field) {
}
}

// InfoCtx logs a message at InfoLevel. The message includes any request context's fields
// and any fields passed at the log site, as well as any fields accumulated on the logger.
func (log *Logger) InfoCtx(ctx context.Context, msg string, fields ...Field) {
if ce := log.check(InfoLevel, msg); ce != nil {
generatedFields := log.generateFields(ctx, fields...)
ce.Write(generatedFields...)
}
}

// WarnCtx logs a message at WarnLevel. The message includes any request context's fields
// and any fields passed at the log site, as well as any fields accumulated on the logger.
func (log *Logger) WarnCtx(ctx context.Context, msg string, fields ...Field) {
if ce := log.check(WarnLevel, msg); ce != nil {
generatedFields := log.generateFields(ctx, fields...)
ce.Write(generatedFields...)
}
}

// ErrorCtx logs a message at ErrorLevel. The message includes any request context's fields
// and any fields passed at the log site, as well as any fields accumulated on the logger.
func (log *Logger) ErrorCtx(ctx context.Context, msg string, fields ...Field) {
if ce := log.check(ErrorLevel, msg); ce != nil {
generatedFields := log.generateFields(ctx, fields...)
ce.Write(generatedFields...)
}
}

// DPanicCtx logs a message at DPanicLevel. The message includes any request context's fields
// and any fields passed at the log site, as well as any fields accumulated on the logger.
//
// If the logger is in development mode, it then panics (DPanic means
// "development panic"). This is useful for catching errors that are
// recoverable, but shouldn't ever happen.
func (log *Logger) DPanicCtx(ctx context.Context, msg string, fields ...Field) {
if ce := log.check(DPanicLevel, msg); ce != nil {
generatedFields := log.generateFields(ctx, fields...)
ce.Write(generatedFields...)
}
}

// PanicCtx logs a message at PanicLevel. The message includes any request context's fields
// and any fields passed at the log site, as well as any fields accumulated on the logger.
//
// The logger then panics, even if logging at PanicLevel is disabled.
func (log *Logger) PanicCtx(ctx context.Context, msg string, fields ...Field) {
if ce := log.check(PanicLevel, msg); ce != nil {
generatedFields := log.generateFields(ctx, fields...)
ce.Write(generatedFields...)
}
}

// FatalCtx logs a message at FatalLevel. The message includes any request context's fields
// and fields passed at the log site, as well as any fields accumulated on the logger.
//
// The logger then calls os.Exit(1), even if logging at FatalLevel is
// disabled.
func (log *Logger) FatalCtx(ctx context.Context, msg string, fields ...Field) {
if ce := log.check(FatalLevel, msg); ce != nil {
generatedFields := log.generateFields(ctx, fields...)
ce.Write(generatedFields...)
}
}

// DebugCtx logs a message at DebugLevel. The message includes any request context's fields
// and any fields passed at the log site, as well as any fields accumulated on the logger.
func (log *Logger) DebugCtx(ctx context.Context, msg string, fields ...Field) {
if ce := log.check(DebugLevel, msg); ce != nil {
generatedFields := log.generateFields(ctx, fields...)
ce.Write(generatedFields...)
}
}

// Sync calls the underlying Core's Sync method, flushing any buffered log
// entries. Applications should take care to call Sync before exiting.
func (log *Logger) Sync() error {
Expand Down Expand Up @@ -361,3 +436,29 @@ func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry {

return ce
}

func (log *Logger) generateFields(ctx context.Context, fields ...Field) []Field {
if ctx != nil && log.contextFunc != nil {
contextFields := log.contextFunc(ctx)
return append(contextFields, fields...)
}
return nil
}

// getCallerFrame gets caller frame. The argument skip is the number of stack
// frames to ascend, with 0 identifying the caller of getCallerFrame. The
// boolean ok is false if it was not possible to recover the information.
//
// Note: This implementation is similar to runtime.Caller, but it returns the whole frame.
func getCallerFrame(skip int) (frame runtime.Frame, ok bool) {
const skipOffset = 2 // skip getCallerFrame and Callers

pc := make([]uintptr, 1)
numFrames := runtime.Callers(skip+skipOffset, pc)
if numFrames < 1 {
return
}

frame, _ = runtime.CallersFrames(pc).Next()
return frame, frame.PC != 0
}
42 changes: 42 additions & 0 deletions logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package zap

import (
"context"
"errors"
"sync"
"testing"
Expand Down Expand Up @@ -600,6 +601,47 @@ func TestNopLogger(t *testing.T) {
})
}

func TestRequestContext(t *testing.T) {
t.Run("Context option use while logger create", func(t *testing.T) {
t.Run("Context is not nil", func(t *testing.T) {
requestContextOption := Context(func(ctx context.Context) []Field {
var fields []Field

if ctxRequestPath, ok := ctx.Value("Correlation-ID").(string); ok {
fields = append(fields, String("Correlation-ID", ctxRequestPath))
}

return fields
})
logger, _ := NewProduction(requestContextOption)

ctx := context.TODO()
ctx = context.WithValue(ctx, "Correlation-ID", "e9718aab-aa1a-4ad8-b1e6-690f6c43bd15")

logger.InfoCtx(ctx, "Hello Zap Logger Community !!!")
logger.DebugCtx(ctx, "Hello Zap Logger Community !!!")
logger.ErrorCtx(ctx, "Hello Zap Logger Community !!!")
logger.WarnCtx(ctx, "Hello Zap Logger Community !!!")
logger.DPanicCtx(ctx, "Hello Zap Logger Community !!!")
})
})

t.Run("Context option not use while logger create", func(t *testing.T) {
t.Run("Context is not nil", func(t *testing.T) {
logger, _ := NewProduction()

ctx := context.TODO()
ctx = context.WithValue(ctx, "Correlation-ID", "e9718aab-aa1a-4ad8-b1e6-690f6c43bd15")

logger.InfoCtx(ctx, "Hello Zap Logger Community !!!")
logger.DebugCtx(ctx, "Hello Zap Logger Community !!!")
logger.ErrorCtx(ctx, "Hello Zap Logger Community !!!")
logger.WarnCtx(ctx, "Hello Zap Logger Community !!!")
logger.DPanicCtx(ctx, "Hello Zap Logger Community !!!")
})
})
}

func infoLog(logger *Logger, msg string, fields ...Field) {
logger.Info(msg, fields...)
}
Expand Down
10 changes: 10 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package zap

import (
"context"
"fmt"

"go.uber.org/zap/zapcore"
Expand Down Expand Up @@ -86,6 +87,15 @@ func Development() Option {
})
}

// Context It is used to decide which of the values in the context will
// be used as the base log field. It takes a function as a parameter and
// this function does this job.
func Context(contextFunc func(ctx context.Context) []Field) Option {
return optionFunc(func(log *Logger) {
log.contextFunc = contextFunc
})
}

// AddCaller configures the Logger to annotate each message with the filename,
// line number, and function name of zap's caller. See also WithCaller.
func AddCaller() Option {
Expand Down

0 comments on commit d66040c

Please sign in to comment.