slogutil

package module
v1.2.2 Latest Latest
Warning

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

Go to latest
Published: Jan 13, 2025 License: MIT Imports: 6 Imported by: 0

README

slogutil

Package slogutil provides enhanced logging capabilities for the standard library log/slog package, focusing on context integration and testability.

Test Coverage Go Report Card License

Features

  • Context-Aware Logging: Enriches log records with contextual information from context.Context:

    • The slogctx sub-package provides a handler (slogctx.Handler) and an Extractor API to extract values from the context.
    • Supports adding attributes to the root of the log context or appending them within the current log group.
  • Testability: Enables easy testing of log output:

    • Provides an in-memory handler (slogmem) to capture log records during tests, allowing for assertions and verification.
  • Attribute Consistency: Provides consistent handling of log attributes:

    • Deduplicates attributes with the same respecting groups. For example: duplicate, duplicate#01, duplcate#02.

Quick Start

package main

import (
	"context"
	"log/slog"

	"github.com/nickbryan/slogutil"
	"github.com/nickbryan/slogutil/slogctx"
)

func main() {
	ctx := slogctx.WithAttrs(context.Background(), slog.String("my_appended_attribute", "my_appended_value"))
	ctx = slogctx.WithRootAttrs(ctx, slog.String("my_root_attribute", "my_root_value"))

	logger := slogutil.NewJSONLogger().WithGroup("my_group")
	logger.InfoContext(ctx, "Info log message", slog.String("my_attribute", "my_value"))

	// Output:
	// {"time":"2024-11-29T10:27:41.460372Z","level":"INFO","source":{"function":"main.main","file":main.go","line":16},"msg":"Info log message","my_root_attribute":"my_root_value","my_group":{"my_attribute":"my_value","my_appended_attribute":"my_appended_value"}}
}

Testing

slogutil provides an in-memory handler (slogmem) to capture log records, enabling you to test log output effectively:

func TestThing(t *testing.T) {
	ctx := slogctx.WithPrependAttrs(context.Background(), slog.String("prepend_attribute", "prepend_value"))
	ctx = slogctx.WithAppendAttrs(ctx, slog.String("append_attribute", "append_value"))

	logger, logs := slogutil.NewInMemoryLogger(slog.LevelDebug)
	logger = logger.With(slog.Int("my_root_attribute", 123))
	logger = logger.WithGroup("my_group")

	logger.InfoContext(ctx, "Info log message", slog.String("my_grouped_attribute", "my_value"))

	if ok, diff := logs.Contains(slogmem.RecordQuery{
		Level:   slog.LevelInfo,
		Message: "Info log message",
		Attrs: map[string]slog.Value{
			"prepend_attribute":             slog.StringValue("prepend_value"),
			"my_root_attribute":             slog.IntValue(123),
			"my_group.my_grouped_attribute": slog.StringValue("my_value"),
			"my_group.append_attribute":     slog.StringValue("append_value"),
		},
	}); !ok {
		t.Errorf("expected log not written, got: %s", diff)
	}
}

Documentation

Overview

Package slogutil provides constructors for the slogctx and slogmem packages.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewInMemoryLogger

func NewInMemoryLogger(level slog.Leveler) (*slog.Logger, *slogmem.LoggedRecords)

NewInMemoryLogger creates a new slog.Logger configured with a slogmem.Handler to capture logged records in-memory for testing.

A slogmem.LoggedRecords will also be returned containing the records created by the returned slog.Logger.

Example
package main

import (
	"context"
	"fmt"
	"log/slog"

	"github.com/nickbryan/slogutil"
	"github.com/nickbryan/slogutil/slogctx"
	"github.com/nickbryan/slogutil/slogmem"
)

func main() {
	ctx := context.Background()

	ctx = slogctx.WithRootAttrs(ctx, slog.String("prepend_attribute", "prepend_value"))
	ctx = slogctx.WithAttrs(ctx, slog.String("append_attribute", "append_value"))

	logger, logs := slogutil.NewInMemoryLogger(slog.LevelInfo)
	logger = logger.With(slog.Int("my_root_attribute", 123))
	logger = logger.WithGroup("my_group")

	logger.DebugContext(ctx, "Debug log message") // Not logged due to the level set on the logger.
	logger.InfoContext(ctx, "Info log message", slog.String("my_grouped_attribute", "my_value"))

	if ok, diff := logs.Contains(slogmem.RecordQuery{
		Level:   slog.LevelInfo,
		Message: "Info log message",
		Attrs: map[string]slog.Value{
			"prepend_attribute":             slog.StringValue("prepend_value"),
			"my_root_attribute":             slog.IntValue(123),
			"my_group.my_grouped_attribute": slog.StringValue("my_value"),
			"my_group.append_attribute":     slog.StringValue("append_value"),
		},
	}); !ok {
		fmt.Print(diff)
	} else {
		fmt.Print("Record contains query")
	}

}
Output:

Record contains query

func NewJSONLogger

func NewJSONLogger(options ...Option) *slog.Logger

NewJSONLogger creates a new slog.Logger configured with a slogctx.Handler which wraps a slog.JSONHandler.

Example
package main

import (
	"context"
	"log/slog"
	"os"
	"time"

	"github.com/nickbryan/slogutil"
)

func constantTimeFactory() time.Time {
	t, err := time.Parse(time.RFC3339, "2024-03-05T12:00:00Z")
	if err != nil {
		panic("unable to parse time value: " + err.Error())
	}

	return t
}

func main() {
	ctx := context.Background()

	logger := slogutil.NewJSONLogger(
		slogutil.WithLevel(slog.LevelInfo),
		slogutil.WithWriter(os.Stdout),
		slogutil.WithSourceAdded(false),
		slogutil.WithTimeFactory(constantTimeFactory),
	)
	logger = logger.With(slog.Int("my_root_attribute", 123))
	logger = logger.WithGroup("my_group")

	logger.DebugContext(ctx, "Debug log message") // Not logged due to the level set on the logger.
	logger.InfoContext(ctx, "Info log message", slog.String("my_grouped_attribute", "my_value"))

}
Output:

{"time":"2024-03-05T12:00:00Z","level":"INFO","msg":"Info log message","my_root_attribute":123,"my_group":{"my_grouped_attribute":"my_value"}}
Example (Context)
package main

import (
	"context"
	"log/slog"
	"os"
	"time"

	"github.com/nickbryan/slogutil"
	"github.com/nickbryan/slogutil/slogctx"
)

func constantTimeFactory() time.Time {
	t, err := time.Parse(time.RFC3339, "2024-03-05T12:00:00Z")
	if err != nil {
		panic("unable to parse time value: " + err.Error())
	}

	return t
}

func main() {
	ctx := slogctx.WithRootAttrs(context.Background(), slog.String("prepend_attribute", "prepend_value"))
	ctx = slogctx.WithAttrs(ctx, slog.String("append_attribute", "append_value"))

	logger := slogutil.NewJSONLogger(
		slogutil.WithLevel(slog.LevelInfo),
		slogutil.WithWriter(os.Stdout),
		slogutil.WithSourceAdded(false),
		slogutil.WithTimeFactory(constantTimeFactory),
	)
	logger = logger.With(slog.Int("my_root_attribute", 123))
	logger = logger.WithGroup("my_group")

	logger.DebugContext(ctx, "Debug log message") // Not logged due to the level set on the logger.
	logger.InfoContext(ctx, "Info log message", slog.String("my_grouped_attribute", "my_value"))

}
Output:

{"time":"2024-03-05T12:00:00Z","level":"INFO","msg":"Info log message","prepend_attribute":"prepend_value","my_root_attribute":123,"my_group":{"my_grouped_attribute":"my_value","append_attribute":"append_value"}}

Types

type Option

type Option func(*options)

Option is an optional configuration value used to configure a logger.

func WithLevel

func WithLevel(level slog.Leveler) Option

WithLevel will set the log level. The default is slog.LevelInfo.

func WithSourceAdded

func WithSourceAdded(addSource bool) Option

WithSourceAdded sets slog.HandlerOptions.AddSource. The default is true.

func WithTimeFactory

func WithTimeFactory(factory TimeFactoryFunc) Option

WithTimeFactory sets the TimeFactoryFunc on the Logger that will be used to determine the time values of the logs. This is useful in rare situations when simulation of time is required, for example, in example test functions. Prefer using the in-memory handler where possible. The default is [time.Now()].

func WithWriter

func WithWriter(writer io.Writer) Option

WithWriter sets the io.Writer that the logs are written to. The default is io.Stderr.

type TimeFactoryFunc

type TimeFactoryFunc func() time.Time

TimeFactoryFunc represents a function that knows how to create time.Time values to be used by the logger when setting the time value of the log.

Directories

Path Synopsis
Package internal encapsulates attribute handling code shared by the exported packages.
Package internal encapsulates attribute handling code shared by the exported packages.
Package slogctx provides a context aware slog.Handler.
Package slogctx provides a context aware slog.Handler.
Package slogmem provides a slog.Handler that captures log records in memory.
Package slogmem provides a slog.Handler that captures log records in memory.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
JackTT - Gopher 🇻🇳