Skip to content

mylxsw/asteria

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

59 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Asteria

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

About

Asteria is a logging library for go.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages