Asteria

February 13, 2026 · View on GitHub

Build Status Coverage Status Go Report Card codecov GoDoc GitHub

Asteria is a structured logging library for Go — simple to start, powerful to scale.

Features

  • Zero-config quick start — one import, immediately usable with sensible defaults
  • Module-based logging — isolate logs by module with independent config per module
  • Structured context — attach key-value fields to any log entry via WithFields, F, KV, or With
  • 8 log levelsDebug, Info, Notice, Warning, Error, Critical, Alert, Emergency
  • Pluggable formatters — Text (with color), JSON, JSON with timestamp, or implement your own
  • Pluggable writers — Stdout, File, RotatingFile, Syslog, Stream, or implement your own
  • StackWriter — fan-out logs to multiple destinations with per-level routing
  • Filter chain — intercept, modify, or suppress log entries before output (global or per-module)
  • Built-in filtersWithStacktrace (auto-attach stack traces), EmergencyExit (exit on fatal)
  • Colorful terminal output — level-colored output with module name abbreviation
  • Rotating log files — automatic file rotation with configurable naming and GC for stale handles
  • Thread-safe — all operations are safe for concurrent use
  • Caller info — optionally include file name, line number, and package in log context

Table of Contents

Installation

go get -u github.com/mylxsw/asteria/log

Quick Start

package main

import "github.com/mylxsw/asteria/log"

func main() {
    // Simple logging — works out of the box
    log.Debug("application started")
    log.Info("processing request")
    log.Error("something went wrong")

    // Logging with structured context
    log.WithFields(log.Fields{
        "user_id":  123,
        "username": "Tom",
    }).Warningf("login attempt failed for user %s", "Tom")

    // Shorthand syntax — F() is an alias for WithFields()
    log.F(log.M{
        "order_id": 456,
        "amount":   99.9,
    }).Info("order created")

    // Key-value pairs — even more concise
    log.KV("user_id", 123, "action", "login").Info("user action")
}

Module-based Logging

Organize your logs by module — each module can have its own formatter, writer, and log level:

package main

import "github.com/mylxsw/asteria/log"

// Create a module logger — typically as a package-level variable
var logger = log.Module("app.user.service")

func main() {
    logger.Debug("initializing user service")
    logger.Error("failed to connect to database")

    logger.WithFields(log.Fields{
        "user_id":  123,
        "username": "Tom",
    }).Warningf("user %s has too many failed login attempts", "Tom")
}

Configuration

Log Level

Control the minimum level of log output:

import "github.com/mylxsw/asteria/level"

// Set globally (affects all existing and new modules)
log.All().LogLevel(level.Warning)

// Set for default module only
log.SetLevel(level.Error)

// Set for a specific module
log.Module("app.db").LogLevel(level.Error)

Log levels from highest to lowest severity: Emergency > Alert > Critical > Error > Warning > Notice > Info > Debug

You can also check whether a level is enabled before constructing expensive log messages:

if log.DebugEnabled() {
    log.Debugf("detailed state: %s", expensiveSerialize())
}

Caller Info (File & Line Number)

// Enable globally for all modules
log.DefaultWithFileLine(true)

// Enable for a specific module
log.Module("app.db").WithFileLine(true)

When enabled, each log entry automatically includes #file, #line, and #package in the context.

Time Location

import "time"

// Set globally
log.DefaultLocation(time.UTC)

// Set for a specific module
log.Module("app").Location(time.UTC)

Formatters

Asteria ships with 3 built-in formatters. You can also create your own by implementing the formatter.Formatter interface:

type Formatter interface {
    Format(f event.Event) string
}

Text (Default)

Colorful, human-readable output — perfect for development:

import "github.com/mylxsw/asteria/formatter"

// With color (default)
log.SetFormatter(formatter.NewDefaultFormatter(true))

// Without color (for log files or CI)
log.SetFormatter(formatter.NewDefaultFormatter(false))

// Per-module
log.Module("app").Formatter(formatter.NewDefaultFormatter(true))

Output format:

[RFC3339 time] [LEVEL] module.name log message {context}

colorful output

Context fields prefixed with # are system-generated (file, line, package). Other fields are user-set via WithFields.

Disable color globally: log.DefaultLogFormatter(formatter.NewDefaultFormatter(false))

JSON

Machine-readable output — ideal for log aggregation systems (ELK, Loki, etc.):

log.SetFormatter(formatter.NewJSONFormatter())
// Or per-module
log.Module("app").Formatter(formatter.NewJSONFormatter())

Sample output:

{"module":"app.user","level_name":"ERROR","level":4,"context":{"#file":"main.go","#line":42,"user_id":123},"message":"request failed","datetime":"2024-01-15T10:30:00+08:00"}

JSON with Timestamp Prefix

Same as JSON, but each line is prefixed with a timestamp for easy visual scanning:

log.SetFormatter(formatter.NewJSONWithTimeFormatter())
// Or per-module
log.Module("app").Formatter(formatter.NewJSONWithTimeFormatter())

Sample output:

[2024-01-15T10:30:00+08:00] {"module":"app.user","level_name":"ERROR","level":4,...}

Writers

Asteria supports multiple log output destinations. Implement the writer.Writer interface for custom writers:

type Writer interface {
    Write(le level.Level, module string, message string) error
    ReOpen() error
    Close() error
}

Stdout (Default)

import "github.com/mylxsw/asteria/writer"

log.SetWriter(writer.NewStdoutWriter())
// Or per-module
log.Module("app").Writer(writer.NewStdoutWriter())

File

Write logs to a single file:

// Simple — with sensible defaults
log.SetWriter(writer.NewSingleFileWriter("/var/log/app.log"))

// Custom flags and permissions
log.SetWriter(writer.NewFileWriter("/var/log/app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644))

// Per-module
log.Module("app").Writer(writer.NewSingleFileWriter("/var/log/app.log"))

Rotating File

Automatically rotate log files based on your own rules (e.g., one file per day):

import (
    "context"
    "fmt"
    "time"
)

fw := writer.NewDefaultRotatingFileWriter(context.TODO(), func(le level.Level, module string) string {
    return fmt.Sprintf("/var/log/app.%s.log", time.Now().Format("20060102"))
})
log.SetWriter(fw)

Inactive file handles are automatically garbage-collected (default interval: 10 minutes).

Syslog

import "log/syslog"

log.SetWriter(writer.NewSyslogWriter("", "", syslog.LOG_DEBUG|syslog.LOG_SYSLOG, "app"))
// Or per-module
log.Module("app").Writer(writer.NewSyslogWriter("", "", syslog.LOG_DEBUG|syslog.LOG_SYSLOG, "app"))

Stream

Write to any io.Writer:

log.SetWriter(writer.NewStreamWriter(os.Stderr))
// Or per-module
log.Module("app").Writer(writer.NewStreamWriter(os.Stderr))

StackWriter (Multi-destination)

Fan out logs to multiple destinations with per-level routing:

stack := writer.NewStackWriter()

// Debug logs → stdout only
stack.PushWithLevels(writer.NewStdoutWriter(), level.Debug)

// Error + Emergency → syslog
stack.PushWithLevels(
    writer.NewSyslogWriter("", "", syslog.LOG_ERR, "app"),
    level.Error, level.Emergency,
)

// All levels → file (no level filter = all levels)
stack.PushWithLevels(writer.NewDefaultFileWriter("/var/log/app.log"))

log.SetWriter(stack)

Filters

Filters let you intercept, modify, or suppress log entries before they are formatted and written. Filters are chainable — global filters execute before module-level filters.

Global Filter

Applied to all modules:

log.AddGlobalFilter(func(next filter.Filter) filter.Filter {
    return func(evt event.Event) {
        // Add a field to every log entry
        evt.Fields.CustomFields["request_id"] = "abc-123"

        // Call next(evt) to continue the chain
        // Omitting this call suppresses the log entirely
        next(evt)
    }
})

Module Filter

Applied to a specific module:

logger := log.Module("app.payment")
logger.AddFilter(func(next filter.Filter) filter.Filter {
    return func(evt event.Event) {
        // Suppress debug logs for this module
        if evt.Level == level.Debug {
            return
        }
        next(evt)
    }
})

Built-in Filters

WithStacktrace — automatically attach stack traces for specified log levels:

import "github.com/mylxsw/asteria/filter"

log.AddGlobalFilter(filter.WithStacktrace(level.Error, level.Critical, level.Emergency))

EmergencyExit — terminate the process after logging an Emergency level message:

log.AddGlobalFilter(filter.EmergencyExit(1)) // exit code 1

Global Fields

Attach fields to every log entry across all modules — useful for request IDs, trace IDs, etc.:

log.GlobalFields(func(c event.Fields) {
    c.GlobalFields["hostname"] = "web-01"
    c.GlobalFields["env"] = "production"
})

Lifecycle Management

// Reopen all log files (useful for log rotation with SIGHUP)
log.ReOpenAll()

// Close all loggers gracefully
log.CloseAll()

// Reopen/close a specific module
log.Module("app").ReOpen()
log.Module("app").Close()

Complete Example

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/mylxsw/asteria/filter"
    "github.com/mylxsw/asteria/formatter"
    "github.com/mylxsw/asteria/level"
    "github.com/mylxsw/asteria/log"
    "github.com/mylxsw/asteria/writer"
)

var logger = log.Module("app.main")

func main() {
    // Use JSON formatter for production
    log.All().LogFormatter(formatter.NewJSONFormatter())

    // Rotating daily log files
    fw := writer.NewDefaultRotatingFileWriter(context.TODO(), func(le level.Level, module string) string {
        return fmt.Sprintf("/var/log/app.%s.log", time.Now().Format("20060102"))
    })
    log.All().LogWriter(fw)

    // Set minimum log level to Info (suppress Debug)
    log.All().LogLevel(level.Info)

    // Attach stack traces to errors
    log.AddGlobalFilter(filter.WithStacktrace(level.Error))

    // Auto-exit on emergency
    log.AddGlobalFilter(filter.EmergencyExit(1))

    // Log with context
    logger.WithFields(log.Fields{
        "user_id": 123,
        "action":  "purchase",
    }).Info("order placed successfully")

    // Don't forget to close loggers on shutdown
    defer log.CloseAll()
}

Architecture

asteria/
├── log/           Core API — Logger interface, module management, context logging
├── level/         8 severity levels (Emergency → Debug)
├── formatter/     Output formatters (Text, JSON, JSONWithTime)
├── writer/        Output writers (Stdout, File, RotatingFile, Syslog, Stream, Stack)
├── filter/        Filter chain (WithStacktrace, EmergencyExit)
├── event/         Log event and fields data structures
├── color/         Terminal color utilities
└── misc/          Internal helpers (caller info, module name abbreviation)

License

MIT — Copyright (c) 2026 mylxsw