Documentation
¶
Overview ¶
Package logging handles your application logs. Logging is built upon the very popular 'zerolog' package and extends it by making it easier to manage logging context.
This module provides a thin wrapper on top of zerolog that allows you to set up contextual logging, and code links We expose some of the zerolog API for adding fields to logs, so it can help to understand zerolog.
Logging API ¶
Logging is built around the zerolog.Event type. To log something, invoke a log event through one of the base log functions or their equivalents on a logger object.
logging.Info logging.Debug logging.Error
Any fields defined in context will already be added to this event, but you can add more. See the zerolog.Event docs or examples. To close off the event and log a message, use the Msg method. A complete example might look like.
logging.Error(ctx, err). // Invoke a log event
Str("failure_type", "validation"). // Add a field
Msg("Request failed") // execute log with msg
JSON logs ¶
By default the logging package produces logs in JSON format. Every piece of log information has an associated key, and you can define nested objects and arrays
{
"time": "timestamp",
"level": "info",
"message": "Hello World",
"service": {
"name": "service",
"version": "v1.0.0"
},
"tags": ["t1", "t2"]
}
Contextual logging ¶
Contextual logging the the key feature this library provides over zerolog. It refers to the ability to create loggers with values takes dynamically from context. The key to doing this is using this libraries Context object.
type Context struct {
logger *Logger
funcs []ContextFunc
}
This object acts as a go-between for loggers and context. Create a logging context by adding ContextFuncs to a logger. You can then either add it to a real context or create a new logger with values loaded from context.
logCtx := logger.With(ContextFunc...) ctx = logCtx.ToContext(ctx) // adds logging context to context logger = logCtx.FromContext(ctx) // loads values from context to create a logger with context values set
ToContext allows you to use the package log functions later without worrying about passing values in or calling the correct function to retrieve values before your log.
ctx = logCtx.ToContext(ctx)
logging.Info(ctx).Msg("Hello World")
Duplicate fields ¶
Zerolog provides zero protection from duplicate fields. If you are not careful, you may accidentally run into this problem. This package provides one level of protection from field duplication, identifiers for context funcs. Each context func has an identifier that the logger uses to discover duplicate.
logger := logger.With(Str("foo", "bar"), Str("foo", "baz"))
// {"foo","bar"} no duplicate
This does NOT completely protect from duplication. Fields added after context information has been collected CAN still result in duplications
logger := logger.With(Str("foo", "bar"))
logger.Info(ctx).Str("foo", "baz").Msg("Hello World")
// {"foo":"bar","foo":"baz","msg":"Hello World"}
Custom context funcs CAN also still accidentally duplicate fields if they set the same fields but have different function identifiers. There are two rules to help avoid this
1. All common packages that define context funcs MUST document the fields they set.
// MyContextFunc sets fields to log xyz // // Field abc - short description // // Field xyz - short description // ...
2. Make sure you context funcs declare the same keys it intends to set, its easy for typos to creep in.
Index ¶
- Variables
- func Array() *zerolog.Array
- func Debug(ctx context.Context, args ...interface{}) *zerolog.Event
- func Dict() *zerolog.Event
- func Error(ctx context.Context, err error) *zerolog.Event
- func Info(ctx context.Context) *zerolog.Event
- func SetGlobalLogContext(logCtx *Context)
- type Context
- func (c Context) FromContext(ctx context.Context) *Logger
- func (c Context) ToContext(ctx context.Context) context.Context
- func (c Context) With(funcs ...ContextFunc) *Context
- func (c Context) WithArray(key string, val zerolog.LogArrayMarshaler) *Context
- func (c Context) WithBool(key string, val bool) *Context
- func (c Context) WithDict(key string, val *zerolog.Event) *Context
- func (c Context) WithDur(key string, val time.Duration) *Context
- func (c Context) WithInt(key string, val int) *Context
- func (c Context) WithStr(key string, val string) *Context
- func (c Context) WithTime(key string, val time.Time) *Context
- type ContextFunc
- type Level
- type Logger
- func (l *Logger) Debug() *zerolog.Event
- func (l Logger) Discard() *Logger
- func (l *Logger) Error(err error) *zerolog.Event
- func (l *Logger) Info() *zerolog.Event
- func (l Logger) ToContext(ctx context.Context) context.Context
- func (l Logger) With(funcs ...ContextFunc) *Context
- func (l Logger) WithArray(key string, val zerolog.LogArrayMarshaler) *Logger
- func (l Logger) WithBool(key string, val bool) *Logger
- func (l Logger) WithCodeLinks(links bool, linker codelinks.CodeLinker) *Logger
- func (l Logger) WithDict(key string, val *zerolog.Event) *Logger
- func (l Logger) WithDur(key string, val time.Duration) *Logger
- func (l Logger) WithInt(key string, val int) *Logger
- func (l Logger) WithLevel(level Level) *Logger
- func (l Logger) WithOutput(out io.Writer) *Logger
- func (l Logger) WithStr(key string, val string) *Logger
- func (l Logger) WithTime(key string, val time.Time) *Logger
- func (l Logger) WithTimeDiff(key string, start time.Time) *Logger
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNoLoggerInContext is used when a log call occurs on a context with no logger ErrNoLoggerInContext = errors.New("log: context has no logger") )
Functions ¶
func Array ¶
Array defines the value for an array field
returns a LogArrayMarshaller. Use methods on this to add array elements to an array entry
func Dict ¶
Dict defines the value for a dict field
returns a zerolog.logCtx. Use the logCtx methods to add sub fields to a dict entry
func Info ¶
Info extracts the logger from context and logs a message at the Info level
Example ¶
package main
import (
"context"
"io"
"os"
"github.com/anz-bank/pkg/logging"
)
func testContext(out io.Writer) context.Context {
logger := logging.New(out).WithLevel(logging.DebugLevel)
return logger.With().ToContext(context.Background())
}
func main() {
// Standard log calls will look like this.
// MAKE SURE your context has a logger in it, otherwise it will panic
logging.Info(testContext(os.Stdout)).Msg("Hello World")
}
Output: {"level":"info","message":"Hello World"}
Example (EmbeddedObject) ¶
package main
import (
"context"
"io"
"os"
"github.com/anz-bank/pkg/logging"
)
func testContext(out io.Writer) context.Context {
logger := logging.New(out).WithLevel(logging.DebugLevel)
return logger.With().ToContext(context.Background())
}
func main() {
// You can also embed an object
logging.Info(testContext(os.Stdout)).
Dict("subfields", logging.Dict().
Str("field1", "foo").
Int("field2", 123),
).
Msg("Hello World")
}
Output: {"level":"info","subfields":{"field1":"foo","field2":123},"message":"Hello World"}
Example (ExtraFields) ¶
package main
import (
"context"
"io"
"os"
"github.com/anz-bank/pkg/logging"
)
func testContext(out io.Writer) context.Context {
logger := logging.New(out).WithLevel(logging.DebugLevel)
return logger.With().ToContext(context.Background())
}
func main() {
// You can add log specific fields
logging.Info(testContext(os.Stdout)).Int("count", 1).Msg("Hello World")
}
Output: {"level":"info","count":1,"message":"Hello World"}
func SetGlobalLogContext ¶
func SetGlobalLogContext(logCtx *Context)
SetGlobalLogContext sets the global logger to the given logger
Allows adding static application values to a logger
Types ¶
type Context ¶
type Context struct {
// contains filtered or unexported fields
}
Context creates loggers from context
This type acts as a go-between between loggers and context. Context is able to create a logger with values extracted from context according to the stored ContextFuncs.
Context can also be added to context itself to support the main log functions (eg: logging.Info(ctx))
func ContextFromContext ¶
ContextFromContext returns a Context from context
If no log context is defined, this will return the global log context
func (Context) FromContext ¶
FromContext creates a logger from the given context and Context
func (Context) With ¶
func (c Context) With(funcs ...ContextFunc) *Context
With adds context funcs to the log context
func (Context) WithArray ¶
func (c Context) WithArray(key string, val zerolog.LogArrayMarshaler) *Context
WithArray creates a static array field on the logger
func (Context) WithDict ¶
WithDict creates a static dictionary field (json object) on the log context
type ContextFunc ¶
type ContextFunc struct {
Keys []string
Function func(ctx context.Context, event zerolog.Context) zerolog.Context
}
ContextFunc defines the function signature used by the logger to extract fields from context
Includes an ID that loggers use to prevent duplication
type Level ¶
type Level uint32
Level types the level construct
Mirrors zerolog.Level
const ( DebugLevel Level = Level(zerolog.DebugLevel) InfoLevel Level = Level(zerolog.InfoLevel) ErrorLevel Level = Level(zerolog.ErrorLevel) )
zerolog level constants included here. Use these to avoid importing logrus directly Not all levels are included, only those used by this library
func MustParseLevel ¶
MustParseLevel panics if input is not a valid level string
func ParseLevel ¶
ParseLevel parses a level string and returns the equivalent level value
Valid levels are info, debug, error. NOT CASE SENSITIVE
type Logger ¶
type Logger struct {
// contains filtered or unexported fields
}
Logger is responsible for executing log calls
This logger is a wrapper on zerolog.Logger that adds contextual logging and code linking. Invoke a log with one of Info, Debug or Error, and use the zerolog api to add fields and execute the log.
eg: logger.Info(ctx).Str("key", "value").Msg("Hello World")
Add contextual logging with the With method. This takes an array of ContextFunc function instances to call on every log call. This should be done to set up the logger before it is used, usually in application init.
Loggers are immutable. Mutations return a new logger with the mutation applied
Example (DuplicateKeys) ¶
package main
import (
"os"
"github.com/anz-bank/pkg/logging"
)
func main() {
// logger fields are protected from duplication
logger := logging.New(os.Stdout).
WithStr("foo", "bar").
WithStr("foo", "baz")
// log contains a single 'foo' field
logger.Info().Msg("Hello World")
// Unfortunately there is no duplication protection after context has been applied
logger.Info().Str("foo", "baz").Msg("Hello World")
}
Output: {"level":"info","foo":"bar","message":"Hello World"} {"level":"info","foo":"bar","foo":"baz","message":"Hello World"}
Example (StaticFields) ¶
package main
import (
"os"
"github.com/anz-bank/pkg/logging"
)
func main() {
// logging provides a few convenience functions for static fields
logger := logging.New(os.Stdout).
WithStr("string", "foo").
WithInt("some_int", 1).
WithDict("metadata", logging.Dict().
Str("service", "service_name").
Str("version", "v1.0.0"),
)
logger.Info().Msg("Hello World")
}
Output: {"level":"info","string":"foo","some_int":1,"metadata":{"service":"service_name","version":"v1.0.0"},"message":"Hello World"}
func FromContext ¶
FromContext returns a logger with values taken from context
If no log context is defined, this will return a logger created from the global log context
func (Logger) Discard ¶
Discard causes any log call on the logger to be ignored
This is useful for creating a logger that shhould not log depending on some criteria
if cond { logger = logger.Discard() }
func (Logger) With ¶
func (l Logger) With(funcs ...ContextFunc) *Context
With adds ContextFuncs to the logger
Use context funcs to add any field to the logger, static or contextual.
Example ¶
package main
import (
"context"
"os"
"github.com/anz-bank/pkg/logging"
"github.com/rs/zerolog"
)
type keyStruct struct{}
var key = &keyStruct{}
func main() {
// With adds a ContextFunc to the logger. This is invoked on every log call
// The ID is a unique identifier for the function.
// This function will log a 'context_value' if it exists in context
ctx := logging.New(os.Stdout).With(
logging.ContextFunc{
Keys: []string{"context_value"},
Function: func(ctx context.Context, logCtx zerolog.Context) zerolog.Context {
ctxValue, ok := ctx.Value(key).(string)
if ok {
logCtx = logCtx.Str("context_value", ctxValue)
}
return logCtx
},
},
).ToContext(context.Background())
// this context does not have a value set yet so it won't be logged
logging.Info(ctx).Msg("Hello World")
// This context does have it. We don't need to manually extract the value
ctx = context.WithValue(ctx, key, "I am contextual")
logging.Info(ctx).Msg("Hello World")
}
Output: {"level":"info","message":"Hello World"} {"level":"info","context_value":"I am contextual","message":"Hello World"}
func (Logger) WithArray ¶
func (l Logger) WithArray(key string, val zerolog.LogArrayMarshaler) *Logger
WithArray creates a static array field on the logger
func (Logger) WithCodeLinks ¶
func (l Logger) WithCodeLinks(links bool, linker codelinks.CodeLinker) *Logger
WithCodeLinks sets the code linker
set 'links' to false to turn links off
func (Logger) WithOutput ¶
WithOutput creates a copy of the logger with a new writer to write logs to
func (Logger) WithTimeDiff ¶
WithTimeDiff adds a time diff field to the logger
Example ¶
package main
import (
"os"
"time"
"github.com/anz-bank/pkg/logging"
)
func main() {
logger := logging.New(os.Stdout).WithTimeDiff("time_since_request_start", time.Now())
// This log time_since_request_start field will be fairly small
logger.Info().Msg("Hello World")
// This log time_since_request_start field will have progressed by approx 10ms
time.Sleep(10 * time.Millisecond)
logger.Info().Msg("Hello World")
}
Output: