logg

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2025 License: MIT Imports: 3 Imported by: 0

README

 __        ______     _______   _______
|  |      /  __  \   /  _____| /  _____|
|  |     |  |  |  | |  |  __  |  |  __
|  |     |  |  |  | |  | |_ | |  | |_ |
|  `----.|  `--'  | |  |__| | |  |__| |
|_______| \______/   \______|  \______|

codecov

Package logg is a thin wrapper around log/slog. The primary goal is to leverage all the things offered by that package, but also make it easier to separate application metadata from event-specific data. It's opinionated and offers a limited feature set.

The feature set is:

  • It's log/slog with light attribute management. After package setup, attributes and groups added to a logger via slog.Logger.With or slog.Logger.WithGroup are placed under a pre-built group. Attributes passed to a log output method are also placed under this group.
  • Pass in a slog.Handler for further customization.

Usage

Call the SetDefaults function as early as possible in your application. This initializes a root logger, which functions like a prototype for subsequent events. Things initialized are the output sinks and an optional "application_metadata" field. The data appears in its own group for each log event.

To add more event-specific fields to a logging entry, call New. This function creates a slog.Logger with an optional trace ID and optional data attributes. Attributes, including group attributes, added through the logger methods, would be applied to one pre-built group attribute.

See more in the godoc examples.

Event shape

When using the slog.JSONHandler with default settings, these top-level fields are present:

  • time: string, rfc3339 timestamp.
  • level: string, sometimes this is called "severity". One of: "DEBUG", "INFO","WARN", "ERROR".
  • msg: string, what happened.

These top-level fields may or may not be present, depending on configuration and how the event is emitted:

  • application_metadata: map[string]any, optional versioning metadata from your application. Will only be present when this data is passed in to the SetDefaults function.
  • trace_id: string, a tracing ID. Present when a non-empty value is passed into the New function.
  • data: map[string]any, other event-specific fields.

Example events

These examples use the slog.Handler implementation slog.JSONHandler.

Info level

{
  "time":"2025-09-22T08:59:52.657053724Z",
  "level":"INFO",
  "msg":"TestLogger",
  "data":{
    "alfa": "anything",
    "bravo": {
      "bool": true,
      "duration_ns": 1234,
      "float": 1.23,
      "int": 10,
      "string": "nevada"
    }
  }
}

Versioning metadata can be added with the SetDefaults function.

{
  "time":"2025-09-22T08:59:52.347111271Z",
  "level":"INFO",
  "msg":"TestLogger",
  "application_metadata":{
    "branch_name":"main",
    "go_version":"v1.25",
    "commit_hash":"deadbeef"
  },
  "data":{
    "alfa": "anything",
    "bravo": {
      "bool": true,
      "duration_ns": 1234,
      "float": 1.23,
      "int": 10,
      "string": "nevada"
    }
  }
}

Documentation

Overview

Package logg is a thin wrapper around log/slog to decorate a slog.Logger with commonly-needed metadata for applications and events. Events will have the same keys described by the slog package for built-in attributes; ie: "time", "level", "msg", "source".

Additionally, these default top-level keys may be present:

  • "application_metadata": []slog.Attr. ie: version control data, build times, etc.
  • "trace_id": string. A tracing ID, may be present for output from loggers created via New
  • "data": []slog.Attr. Event-specific attributes, may be present for output from loggers created with New.

These key names may be configured with the SetDefaults function.

Example

Setup the application logger with some preset fields and a logging destination. Then log.

package main

import (
	"errors"
	"fmt"
	"log/slog"
	"os"

	"github.com/rafaelespinoza/logg"
)

func main() {
	logg.SetDefaults(
		slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}),
		&logg.Settings{
			ApplicationMetadata: []slog.Attr{
				slog.String("branch_name", "main"),
				slog.String("build_time", "20060102T150415"),
				slog.String("commit_hash", "deadbeef"),
			},
		},
	)

	//
	// Log at the INFO level.
	//
	slog.Info("hello world")

	// Want message string interpolation? Do this beforehand.
	variable := "there"
	slog.Info(fmt.Sprintf("hello %s", variable))

	// Log message with attributes
	slog.Info("hello with attributes", slog.Bool("simple", true), slog.Int("rating", 42))

	//
	// Log at the ERROR level.
	//
	err := errors.New("example")
	slog.Error("uh-oh", slog.Any("error", err))
}
Example (Context)

A demo of context-based loggers. For brevity's sake, this example lacks some things you may want in a real application, such as using the context to transmit an existing tracing ID or generating a tracing ID in case the context doesn't have one. That exercise is up to you.

package main

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

	"github.com/rafaelespinoza/logg"
)

// Use values of these types as keys for accessing values on a context. It's
// best practice to use unexported types to reduce the likelihood of collisions.
type (
	traceContextKey  struct{}
	loggerContextKey struct{}
)

// SetContextLoggerWithID creates a new logger with a tracing ID and dataAttrs,
// and returns a new context with the logger and tracing ID. Retrieve the logger
// with GetContextLogger.
func SetContextLoggerWithID(ctx context.Context, traceID string, dataAttrs ...slog.Attr) context.Context {
	lgr := logg.New(traceID, dataAttrs...)
	outCtx := context.WithValue(ctx, loggerContextKey{}, lgr)
	outCtx = context.WithValue(outCtx, traceContextKey{}, traceID)
	return outCtx
}

// GetContextLogger fetches the logger on the context, which would have been set
// by SetContextLoggerWithID. If no logger is found, then it returns the default
// logger from the slog package.
func GetContextLogger(ctx context.Context) *slog.Logger {
	lgr, found := ctx.Value(loggerContextKey{}).(*slog.Logger)
	if found {
		return lgr
	}
	return slog.Default()
}

// A demo of context-based loggers. For brevity's sake, this example lacks some
// things you may want in a real application, such as using the context to
// transmit an existing tracing ID or generating a tracing ID in case the
// context doesn't have one. That exercise is up to you.
func main() {
	defaultHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
		// Remove the time key so that output is consistent.
		ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
			if len(groups) < 1 && a.Key == slog.TimeKey {
				a = slog.Attr{}
			}
			return a
		},
	})
	logg.SetDefaults(defaultHandler, &logg.Settings{
		ApplicationMetadata:    []slog.Attr{slog.String("branch", "main")},
		ApplicationMetadataKey: "version",
	})

	aCtx := SetContextLoggerWithID(context.Background(), "unique_id")
	aLogger := GetContextLogger(aCtx)
	aLogger.Info("Avalanches in Atlanta")

	bCtx := SetContextLoggerWithID(context.Background(), "example_with_data_attrs", slog.String("foo", "bar"))
	bLogger := GetContextLogger(bCtx)
	bLogger.Info("Blizzards in Bakersfield")
}
Output:
level=INFO msg="Avalanches in Atlanta" version.branch=main trace_id=unique_id
level=INFO msg="Blizzards in Bakersfield" version.branch=main trace_id=example_with_data_attrs data.foo=bar

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(traceID string, dataAttrs ...slog.Attr) *slog.Logger

New creates a slog.Logger, with the same configuration and handler set via SetDefaults. The traceID, if non-empty, will be at a top-level key for subsequent logging output with the created Logger. Similarly, dataAttrs will be grouped at a top-level key.

Example

Initialize the logger with or without data fields.

package main

import (
	"log/slog"

	"github.com/rafaelespinoza/logg"
)

func main() {
	// Writes with the same handler provisioned in SetupDefaults.
	// This logger has its own data fields.
	logger := logg.New("", slog.Bool("whiskey", true), slog.Float64("tango", 1.23), slog.Int("foxtrot", 10))
	logger.Info("hello, world")

	// This logger also writes to the package configured handler, but doesn't
	// have any data fields of its own.
	loggerNoFields := logg.New("")
	loggerNoFields.Info("no data fields here")

	// This logger has its own tracing ID.
	loggerWithTraceID := logg.New("unique_trace_id")
	loggerWithTraceID.Info("that happened")
}

func SetDefaults added in v0.1.0

func SetDefaults(h slog.Handler, settings *Settings)

SetDefaults initializes a package-level prototype logger from which subsequent logs are based upon. This function is intended to be called only once, shortly after application startup. It will also call the slog.SetDefault function, thereby affecting all output functionality in that package. Loggers created with New consider values set in this function as defaults. When the handler input h, is empty, then the current Handler value of slog.Default is used. When the input settings is empty, then defaults are used. Default settings are mentioned in the package documentation.

The side effects of this function may put this data at top-level keys for every log entry:

  • application metadata.
  • a trace ID value, only present when a logger is created via New.
  • event-specific data attributes.
Example

In a real application, SetDefaults probably only needs to be called once.

package main

import (
	"io"
	"log/slog"
	"os"

	"github.com/rafaelespinoza/logg"
)

func main() {
	//
	// Customize keys of metadata attributes.
	//
	logg.SetDefaults(
		slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}),
		&logg.Settings{
			ApplicationMetadata: []slog.Attr{
				slog.String("branch_name", "main"),
				slog.String("build_time", "now"),
				slog.Any("foo", "bar"),
			},
			ApplicationMetadataKey: "application_metadata",
			TraceIDKey:             "request_uuid",
			DataKey:                "event_data",
		},
	)

	//
	// Output text format.
	//
	logg.SetDefaults(
		slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}),
		&logg.Settings{ApplicationMetadata: []slog.Attr{slog.String("branch_name", "main")}},
	)

	//
	// Write to standard error as well as some file.
	//
	var file io.Writer
	sink := io.MultiWriter(os.Stderr, file)
	logg.SetDefaults(
		slog.NewJSONHandler(sink, &slog.HandlerOptions{Level: slog.LevelInfo}),
		&logg.Settings{ApplicationMetadata: []slog.Attr{slog.String("branch_name", "main")}},
	)
}

Types

type Settings added in v0.1.0

type Settings struct {
	ApplicationMetadata    []slog.Attr
	ApplicationMetadataKey string
	TraceIDKey             string
	DataKey                string
}

Settings is package configuration data for overriding package defaults via SetDefaults. ApplicationMetadata could be versioning metadata, ie: commit hash, build time, etc. The placement of this data within a logging event can be set with ApplicationMetadataKey. The location of event tracing IDs can be set with TraceIDKey. Other event-specific attributes would be placed at DataKey.

Jump to

Keyboard shortcuts

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