go-log4g

September 1, 2026 · View on GitHub

Log4g provides Log4j-style configuration and pattern layouts for Go's standard log/slog logging facade. Applications can use standard slog directly or the optional log4g facade for {} parameterized messages.

Initialization

go-log4g configures the standard log/slog logger automatically when the core package is initialized.

Import core as a blank import in the application bootstrap:

import _ "github.com/go-log4g/core"

During initialization, Log4g searches for a configuration file in this order:

  • The file specified by the --log4g.configurationFile command-line property
  • The file specified by the LOG4G_CONFIGURATION_FILE environment variable
  • config/log4g.yaml
  • log4g.yaml

The first existing configuration file is loaded.
If no configuration file is found, Log4g installs its default configuration and logs ERROR and higher events to the console.
If a configuration file is found but cannot be read, parsed, or processed, Log4g reports the error to stderr and falls back to the default configuration.

Once initialized, use standard slog normally:

slog.Info("Application started")
slog.Debug("Loading user", "userId", 123)

Or use the optional log4g facade for {} parameterized messages:

import "github.com/go-log4g/core/log4g"

log4g.Info("User {} authenticated", 123)
log4g.Debug("Loaded {} records in {}", count, elapsed)

Testing

Go runs tests with the package directory as the working directory rather than the module root. Log4g automatically detects the Go test runner and locates the module root by searching parent directories for go.mod.

When running tests, configuration is resolved in the following order:

  • The file specified by the --log4g.configurationFile command-line property
  • The file specified by the LOG4G_CONFIGURATION_FILE environment variable
  • module/config/log4g-test.yaml
  • module/log4g-test.yaml
  • module/config/log4g.yaml
  • module/log4g.yaml

The first existing configuration file is loaded.

This allows a project to keep a dedicated test configuration without any additional test setup:

config/
  log4g.yaml
  log4g-test.yaml

log4g-test.yaml is used automatically when running tests, while log4g.yaml remains the fallback when no test-specific configuration is provided.

Configuration

Create config/log4g.yaml:

properties:
  pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS}{UTC} %-5p %c:%L - %m%n"

appenders:
  stdout:
    type: console
    target: stdout
    filter:
      type: levelRange
      minLevel: debug
      maxLevel: warn
    layout:
      type: pattern
      pattern: "${pattern}"

  stderr:
    type: console
    target: stderr
    filter:
      type: threshold
      level: error
    layout:
      type: pattern
      pattern: "${pattern}"

  file:
    type: file
    file: logs/file.log
    # append: true
    # immediateFlush: true
    # bufferSize: 8192
    layout:
      type: pattern
      pattern: "${pattern}"

  rollingFile:
    type: rollingFile
    file: logs/rollingFile.log
    filePattern: logs/arch/rollingFile-%d{yyyyMMdd}-%i.log.zip
    # append: true
    # immediateFlush: true
    # bufferSize: 8192
    policies:
      onStartupTriggeringPolicy:
        minSize: 1B    
      timeBasedTriggeringPolicy:
        interval: 1
        # modulate: false
      sizeBasedTriggeringPolicy:
        size: 300MB
    defaultRolloverStrategy:
      # max: 7
      delete:
        maxAge: 30d
        # maxFiles: 100
        # maxTotalSize: 5GB
    layout:
      type: pattern
      pattern: "${pattern}"

root:
  level: error
  appenderRefs:
    - ref: stdout
    - ref: stderr
    - ref: file
      level: warn
    - ref: rollingFile
      filter:
        type: threshold
        level: error

loggers:
  playground/internal:
    level: debug
  playground/internal/network:
    level: warn
  github.com/go-beans:
    level: info
  github.com/go-external-config:
    level: info

This configuration writes DEBUG through WARN events to stdout and ERROR events to stderr. The root level is ERROR, while configured logger hierarchies can override or inherit their effective level.

Logger levels

Logger levels are inherited hierarchically. A logger without an explicit level inherits the level of its nearest configured parent, falling back to the root logger.

If the root logger has no explicit level, --log4g.level is used when provided, followed by the LOG4G_LEVEL environment variable. If none is configured, the root level defaults to ERROR.

Properties

Configuration properties can be defined once and reused:

properties:
  pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS}{UTC} %-5p %c:%L - %m%n"

Properties, environment variables, and application parameters use separate namespaces:

${pattern}          Configuration property
${env:LOG_PATTERN}  Environment variable
${arg:log.pattern}  Application parameter

Application parameters use the following form:

--log.pattern=value

Substitution is recursive, so a configuration property may itself reference another property, environment variable, or application parameter.

File appenders

file writes log events to a file:

file:
  type: file
  file: logs/application.log
  # append: true
  # immediateFlush: true
  # bufferSize: 8192
  layout:
    type: pattern
    pattern: "${pattern}"

append controls whether an existing file is appended to or truncated when the appender is created. The default is true.

immediateFlush controls whether each log event is written immediately. The default is true. When disabled, Log4g uses buffered asynchronous file writes.

bufferSize specifies the size of each write buffer in bytes. The default is 8192. Buffered file appenders use two fixed-size buffers so logging remains memory-bounded. If file output cannot keep up with log production, writers are blocked rather than allocating an unbounded queue or dropping log events.

File writes are synchronized fairly, preserving the order in which concurrent log operations acquire the appender for writing.

Rolling file appenders

rollingFile extends file output with size- and time-based rollover:

rollingFile:
  type: rollingFile
  file: logs/application.log
  filePattern: logs/arch/application-%d{yyyyMMdd}-%i.log.zip
  # append: true
  # immediateFlush: true
  # bufferSize: 8192
  policies:
    onStartupTriggeringPolicy:
      minSize: 1B    
    timeBasedTriggeringPolicy:
      interval: 1
      # modulate: false
    sizeBasedTriggeringPolicy:
      size: 300MB
  defaultRolloverStrategy:
    # max: 7
    delete:
      maxAge: 30d
      # maxFiles: 100
      # maxTotalSize: 5GB
  layout:
    type: pattern
    pattern: "${pattern}"

file is the active log file. filePattern specifies the names of rolled files. %d{...} inserts the rollover period and %i inserts the rollover index.

Rolled files can optionally be compressed by adding a supported compression extension to filePattern:

filePattern: logs/arch/application-%d{yyyyMMdd}-%i.log.zip

Supported rollover formats are:

.log       Uncompressed
.log.gz    GZIP compressed
.log.zip   ZIP compressed

onStartupTriggeringPolicy rolls an existing active log file when the appender is initialized and the file size is at least minSize. The default minSize is 1B.
The startup rollover happens immediately during appender initialization, before the active log file is opened. It does not wait for the first log event.

File sizes can be specified in bytes or using B, K, KB, M, MB, G, GB, T, and TB suffixes.

timeBasedTriggeringPolicy rolls the active file according to the smallest time unit present in filePattern.

interval defaults to 1. An interval of 5 with a minute-based file pattern rolls every five minutes.

modulate defaults to false. When enabled, rollover intervals are aligned to natural time boundaries. For example, a five-minute policy started at 10:07 rolls at 10:10, 10:15, 10:20, and so on.

sizeBasedTriggeringPolicy rolls the active file when it reaches the configured size. The default size is 10MB.

When multiple triggering policies are configured, rollover occurs when any policy triggers.

defaultRolloverStrategy.max defaults to 7. Index 1 is the oldest retained rolled file and the maximum index is the newest. Once the maximum number of files for a rollover period is reached, the oldest file is removed and the remaining indexes are shifted down.

For example:

application.log               active
application-20260817-1.log    oldest
application-20260817-2.log
...
application-20260817-7.log    newest

defaultRolloverStrategy.delete can remove expired rolled files after a successful rollover. Only files matching this appender's filePattern are considered for deletion. Other files in the same directory are not affected.

All retention limits are optional:

maxAge removes files older than the configured duration. Go duration syntax such as 12h and 24h is supported, with d also supported for days, for example 7d or 30d.
maxFiles limits the total number of retained rolled files.
maxTotalSize limits their combined size and accepts the same file size syntax as triggering policies, for example 500MB or 5GB.

When multiple limits are configured, age retention is applied first, followed by file count and total size. When a count or size limit is exceeded, the oldest files are removed first.
Retention is evaluated after each successful rollover; it does not run as a background cleanup task.

Loggers

Loggers configuration is hierarchical. For example:

playground/internal/app

also matches:

playground/internal/app/Service1
playground/internal/app/service/UserService

Pattern layout

The configured pattern:

%d{yyyy-MM-dd HH:mm:ss.SSS}{UTC} %-5p %c:%L - %m%n

produces output such as:

2026-08-13 12:34:56.789 INFO  playground/internal/app/Service1:23 - Service initialized

Date/time formats

%d accepts either a Java-style date/time pattern or one of the predefined formats.

Without an explicit format, DEFAULT is used:

%d
%d{DEFAULT}
→ 2026-08-17 12:34:56,789

Supported predefined formats:

DEFAULT
→ 2026-08-17 12:34:56,789

ISO8601
→ 2026-08-17T12:34:56,789

ISO8601_BASIC
→ 20260817T123456,789

ABSOLUTE
→ 12:34:56,789

DATE
→ 17 Aug 2026 12:34:56,789

COMPACT
→ 20260817123456789

Custom Java-style patterns are also supported:

%d{yyyy-MM-dd HH:mm:ss.SSS}
→ 2026-08-17 12:34:56.789

%d{yyyyMMdd-HHmmss}
→ 20260817-123456

A timezone can be supplied as the second date option:

%d{yyyy-MM-dd HH:mm:ss.SSS}{UTC}
→ 2026-08-17 12:34:56.789

Supported patterns

%d{pattern}          Date/time
%d{pattern}{UTC}     Date/time in UTC

%p                   Log level
%level               Same as %p

%c                   Logger name
%logger              Same as %c

%M                   Method/function name
%method              Same as %M

%F                   Source file
%file                Same as %F

%L                   Source line
%line                Same as %L

%m                   Message
%msg                 Same as %m
%message             Same as %m

%X{key}              MDC value
%X                   All MDC values

%n                   Platform newline
%%                   Literal %

Examples:

%d{yyyy-MM-dd HH:mm:ss.SSS}{UTC}
→ 2026-08-13 12:34:56.789

%p
→ INFO

%c
→ playground/internal/app/Service1

%M
→ AfterPropertiesSet

%F:%L
→ Service1.go:23

%m
→ Service initialized

%X{requestId}
→ 8f24...

%X
→ {requestId=8f24..., userId=123}

Width and alignment

A minimum width can be specified before a converter:

%5p       minimum width 5, right aligned
%-5p      minimum width 5, left aligned

Example:

%-5p

produces aligned levels:

DEBUG
INFO 
WARN 
ERROR

Width is also useful for optional MDC values. For example, a compact request ID can use the last UUID section:

[%12X{requestId}]

With a request ID:

[446655440000]

Without a request ID:

[            ]

Logger name precision

Given:

playground/internal/app/Service1

the following patterns produce:

%c        → playground/internal/app/Service1
%c{1}     → Service1
%c{2}     → app/Service1
%c{3}     → internal/app/Service1

%c{-1}    → internal/app/Service1
%c{-2}    → app/Service1

Positive precision keeps rightmost components. Negative precision removes components from the left.

Logger name abbreviation

Given:

org/apache/commons/test/Foo

simple abbreviation:

%c{1.}    → o/a/c/t/Foo
%c{2.}    → or/ap/co/te/Foo

Explicit component rules:

%c{1.1.1.*}
→ o/a/c/test/Foo

Here the first three components are shortened to one character and * leaves the remaining components unchanged.

Dynamic abbreviation:

%c{1.2.*}
→ o/a/c/test/Foo

Here leading components are shortened to one character while the last two components are preserved.

Another example:

%c{1.3.*}
→ o/a/commons/test/Foo

This style is useful for Go logger names because a long module prefix can be abbreviated while the local package and type remain readable.

MDC

MDC associates logging values with a context.Context.

ctx = mdc.Put(ctx, "requestId", requestId)
ctx = mdc.Put(ctx, "userId", userId)

log4g.InfoContext(ctx, "Processing request")

Use one MDC value in a pattern:

%X{requestId}

or all values:

%X

mdc.Put follows Go context semantics and returns a derived context:

ctx = mdc.Put(ctx, "requestId", requestId)

Each derived MDC context contains the complete MDC snapshot, so subsequent Put operations retain previously added values.

Extension appenders

Additional appender types can be provided by external go-log4g modules.

For example, go-log4g-shared provides the sharedRollingFile appender for multiple processes writing to the same rolling log file.

Import the extension package to register its appender:

import _ "github.com/go-log4g/shared"

It can then be used in the standard Log4g configuration:

appenders:
  shared:
    type: sharedRollingFile
    file: logs/shared.log
    filePattern: logs/arch/shared-%d{yyyyMMddHHmm}-%i.log.zip

See the go-log4g-shared documentation for shared file locking and configuration details.