log

package
v1.38.5 Latest Latest
Warning

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

Go to latest
Published: Feb 23, 2026 License: MIT Imports: 6 Imported by: 0

README

Log Package

The log package acts as a helpful abstraction around the slog Logger that is built into the standard library in Go.

Engineers in GitLab must use this package in order to instantiate loggers for their application to ensure that there is consistency across all of our services in how we emit logs from our systems.

Usage

// returns a new JSON logger that outputs logs to the stdout
logger := log.New()

// A standard Info line
// Note: We strongly encourage that you use the *Context methods
// for observability purposes to ensure you'll be benefiting from
// field enrichment.
logger.InfoContext(ctx, "some info")
Logger Configuration
logger := log.NewWithConfig(&log.Config{
    // log.WithWriter - allows you to pass in a custom io.Writer
    // should you wish.
    Writer: os.Stderr,
    // allows you to define the minimum logging level for said logger
    LogLevel: slog.LevelError,
    // allows you to set the output to text format
    UseTextFormat: true,
})
Writing to Files

If you need to write to specific files, you can achieve this like so:

logger := log.NewWithFile(filePath, &log.Config{...})
logger.Info("hello!")

This would create or append logs to a file at filePath file.

Testing Your Observability

The logs that our systems output represent an often-neglected part of our API. Additional reporting systems and alerts are typically built on top of log lines and a lack of testing makes these setups rather fragile in nature.

It's strongly encouraged that all engineers bake in some form of assertions on the logs that they rely on for additional observability configuration within their tests.

// NewWithRecorder returns a logRecorder struct that
// captures all log lines emitted by the `logger`
logger, recorder := logtest.NewWithRecorder(nil)

// We can then perform assertions on things like how many log lines
// have been emitted
assert.Len(t, recorder.Records, 1)

// As well as the shape of individual log lines
assert.Equal(t, "test message", recorder.Records[0].Message)
assert.Equal(t, tt.expectedLevel, recorder.Records[0].Level.String())
assert.Contains(t, recorder.Records[0].Attrs, slog.Attr{Key: "key", Value: slog.AnyValue("value")})

These log lines are captured in a Records field which is a slice of type testRecord:

// testRecord - a representation of a single log message
type testRecord struct {
    Level   slog.Level
    Message string
    Attrs   []slog.Attr
}

Context Logger

There is some important information we need to emit for every log message whenever a request is processed. This could be things such as the pipeline_id or the GitLab project ID.

You can store this information within the context and it will automatically enrich subsequent log emissions with these fields. This is super handy if you need to correlate log lines together by various fields for investigatory purposes.

ctx := log.WithFields(
    slog.String(fields.GitLabUserName, "1234abc")
)

logger.InfoContext(ctx, "hello world")
// emitted log line will contain the `gl_user_name` field alongside
// "hello world" - this is imported from the `fields` package.
Canonical Logger

Canonical logging is a technique that can be used to help reduce the volume of logs being emitted from your systems. You are effectively aggregating all of the fields that you care about through the lifecycle of a request and then logging a single line at the end of the lifecycle.

// pass this logger along the request lifecycle
ctx := context.Background()
ctx = log.WithFields(ctx, slog.String("step", "1"))

// at the point at which you need to emit the
// final log line which will then include all of the
// fields that have been collected within the
// context up until this point.
logger.InfoContext(ctx, "canonical_log")

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EnrichRecordWithMetaData

func EnrichRecordWithMetaData(ctx context.Context, r slog.Record) slog.Record

func Fields

func Fields(ctx context.Context) []slog.Attr

func New

func New() *slog.Logger

New - a handy wrapper that configures the slog.Logger in a consistent fashion. Engineers should always default to using this constructor to ensure that they can take advantage of future global enhancements to our logging setup.

func NewContextHandler

func NewContextHandler(baseHandler slog.Handler) slog.Handler

func NewWithConfig added in v1.35.0

func NewWithConfig(cfg *Config) *slog.Logger

NewWithConfig - a constructor that allows you to overwrite some of the core constructs within the Logger for your own nefarious purposes.

func NewWithFile added in v1.37.0

func NewWithFile(filePath string, cfg *Config) (*slog.Logger, io.Closer, error)

NewWithFile creates a logger writing to a file. Caller is responsible for closing the returned file. Should there be an error with the opening of the filePath this will gracefully degrade and provide a Stderr logger alongside an error. This allows the consumer to decide how they wish to proceed rather than outright blocking application startup.

func WithFields

func WithFields(ctx context.Context, attrs ...slog.Attr) context.Context

WithField - provides a way to inject more fields into a logger that is then persisted in the context. This is useful in situations where they need to employ canonical logging in order to limit the number of log lines that they emit.

Types

type Config

type Config struct {
	// Writer - a lower level construct that allows
	// engineers to have finer-grained control over
	// how and where logs are written to file.
	Writer io.Writer

	// UseTextFormat - set this to true if you require
	// text formatted logs.
	UseTextFormat bool

	// Clock - allows the consumer to provide their own
	// TimeFunc that is used to provide the timestamp when
	// emitting logs.
	// The logger defaults to UTC to try and ensure
	// consistency across our services.
	Clock TimeFunc

	// LogLevel - represents the minimum log level that
	// should be output by the logger.
	LogLevel slog.Level
}

Config holds the configuration for creating a new logger.

type ContextHandler

type ContextHandler struct {
	BaseHandler slog.Handler
}

func (*ContextHandler) Enabled

func (c *ContextHandler) Enabled(ctx context.Context, level slog.Level) bool

func (*ContextHandler) Handle

func (c *ContextHandler) Handle(ctx context.Context, r slog.Record) error

func (*ContextHandler) WithAttrs

func (c *ContextHandler) WithAttrs(attrs []slog.Attr) slog.Handler

func (*ContextHandler) WithGroup

func (c *ContextHandler) WithGroup(name string) slog.Handler

type TimeFunc added in v1.35.0

type TimeFunc func() time.Time

TimeFunc - exclusively used for testing purposes please do not use in production.

Jump to

Keyboard shortcuts

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