ctxlog

package module
v2.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 22, 2021 License: MIT Imports: 2 Imported by: 1

README

ctxlog

Ctxlog is a wrapper around https://logur.dev/ that uses values stored in context to enrich log messages.

Imagine you have a function that needs to log something:

func MyFunction() {
    log.Debug("I'm doing stuff")
}

func handler() {
    MyFunction()
}
DEBU[0001] I'm doing stuff 

When you are in production, how do you know which lines of log are related to a certain request?

One way would be to send those info through the chain of functions calls, but it's tedious and prone to errors

Imagine if you could just do this

func MyFunction(ctx context.Context) {
    log.Debug(ctx, "I'm doing stuff")
}

func handler() {
    ctx := context.WithValue(context.Background(), "reqID", "uniqueReqID")
    MyFunction(ctx)
}
DEBU[0001] I'm doing stuff      reqID=uniqueReqID

Well, with this package you can, although the actual function calls differ a bit:

func MyFunction(ctx context.Context) {
    log.Debug(ctx, "I'm doing stuff")
}

func handler() {
    ctx := ctxlog.WithFields(context.Background(), map[string]interface{}{"reqID": "uniqueReqID"})
    MyFunction(ctx)
}
DEBU[0001] I'm doing stuff      reqID=uniqueReqID

How does it work

ctxlog.WithFields() populates a map[string]interface{} in the given context with the key ctxlog.LogKey Debug|Info|Warn|Error() retrieve the map[string]interface{} from the given context and use it to call the Debug|Info|Warn|Error function of the underlying logger

How to use it

If you have a logur logger, it's simple, you just wrap it. In this example we are using a logrus logur logger (try saying that quickly):

import (
    logrusadapter "logur.dev/adapter/logrus"
    github.com/codeclysm/ctxlog/v2
)

func main() {
    // Create logrus logger
    logrusLog := logrus.New()

    logrusLog.SetOutput(os.Stdout)
    logrusLog.SetFormatter(&logrus.TextFormatter{
        EnvironmentOverrideColors: true,
    })

    // Create logur logger from logrus logger
    logurLog := logrusadapter.New(logrusLog)

    // Create ctxlog logger from logur logger
    ctxLog := ctxlog.New(logurLog)

    // You can use it with the context
    log.Debug(context.Background(), "I'm doing stuff") // Will print "I'm doing stuff"

    // Put fields into the context and log again
    ctx := ctxlog.WithFields(context.Background(), map[string]interface{}{
        "reqID": "uniqueReqID",
        "method": "GET"
    })

    log.Debug(ctx, "I'm doing stuff") // Will print "I'm doing stuff reqID=uniqueRedID method-GET"
}


FAQ

Why should I save a map[string]interface{} in the context instead of just logging all the context values?

Because you probably don't want to log everything in the context, there could be structs and pointer in there. Also it's not trivial to walk through context values

Why not use directly logrus?

Because logur has a cleaner interface with less methods to override

What happens if there are no values set?

No additional fields are added, no panics

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var LogKey = logKey("ctxlog")

LogKey is the key used to store and retrieve the entry log You'll probably want to store the initial entry at the very beginning of the request/trace/whatever

Functions

func WithFields

func WithFields(ctx context.Context, newFieldsList ...map[string]interface{}) context.Context

WithFields adds a map of fields to the context. If a map already exists, entries will be merged. Keys with the same name will be overwritten

Example
ctx := ctxlog.WithFields(context.Background(), map[string]interface{}{"banana": true})
fields := ctx.Value(ctxlog.LogKey).(map[string]interface{})

fmt.Println(fields)
Output:

map[banana:true]
Example (Merge)
ctx := context.Background()
fields, _ := ctx.Value(ctxlog.LogKey).(map[string]interface{})
fmt.Println("background", fields)

ctx = ctxlog.WithFields(ctx, map[string]interface{}{"banana": true})
fields = ctx.Value(ctxlog.LogKey).(map[string]interface{})
fmt.Println("banana", fields)

ctx = ctxlog.WithFields(ctx, map[string]interface{}{"apple": true})
fields = ctx.Value(ctxlog.LogKey).(map[string]interface{})
fmt.Println("apple", fields)
Output:

background map[]
banana map[banana:true]
apple map[apple:true banana:true]

Types

type CtxLogger

type CtxLogger struct {
	Log logur.Logger
}

CtxLogger is a wrapper around a regular logur Logger. When one of its method is called. it forwards to the logger not only the fields specified in the method call, but also fields that were saved into the context with the function WithFields

func New

func New(log logur.Logger) CtxLogger

New returns a new CtxLogger from a regular logur Logger

func (CtxLogger) Debug

func (c CtxLogger) Debug(ctx context.Context, msg string, fields ...map[string]interface{})

func (CtxLogger) Error

func (c CtxLogger) Error(ctx context.Context, msg string, fields ...map[string]interface{})

func (CtxLogger) Info

func (c CtxLogger) Info(ctx context.Context, msg string, fields ...map[string]interface{})

func (CtxLogger) Trace

func (c CtxLogger) Trace(ctx context.Context, msg string, fields ...map[string]interface{})

func (CtxLogger) Warn

func (c CtxLogger) Warn(ctx context.Context, msg string, fields ...map[string]interface{})

type Logger

type Logger interface {
	Trace(ctx context.Context, msg string, fields ...map[string]interface{})
	Debug(ctx context.Context, msg string, fields ...map[string]interface{})
	Info(ctx context.Context, msg string, fields ...map[string]interface{})
	Warn(ctx context.Context, msg string, fields ...map[string]interface{})
	Error(ctx context.Context, msg string, fields ...map[string]interface{})
}

Logger is an interface very similar to the one from logur.dev, except for the first parameter which is context

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL