SNekLog
猫- add threshold-aware LogLevel constructors - add Logger.SetThresholdMode and SameThreshold - preserve legacy SameLevel behavior for compatibility - extend tests for threshold mode and compatibility - update README and release notes for v2.2.0
SNekLog (ScuroNeko Logger)
Small structured logger for Go with text and JSON output, multiple writers, and optional traceback metadata.
Russian version: README_ru.md
Features
- Fan out the same record to multiple destinations.
- Text and JSON writers.
stdout, files, and arbitrary externalio.Writervalues.- Optional timestamps for text output.
- Compact traceback metadata for text writers and full traceback slices for JSON.
- Message replacement for masking secrets or normalizing output.
- Explicit ownership rules for writer closing.
Installation
go get git.scuroneko.dev/scuroneko/sneklog/v2
Quick start
package main
import (
"log"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
func main() {
logger := sneklog.NewLogger().
SetName("API").
SetLevel(sneklog.DEBUG).
AddReplacer("SOME_SECRET", "<redacted>")
text := logger.CreateTextStdoutWriter()
jsonFile, err := logger.CreateJsonFileWriter("logs/app.json")
if err != nil {
log.Fatal(err)
}
logger.AddWriters(text, jsonFile)
logger.Infoln("service started")
logger.Warnln("cache miss")
logger.Errorln("request failed")
logger.Debugln("debug details")
logger.Infoln("token", "SOME_SECRET")
if err := logger.Close(); err != nil {
log.Fatal(err)
}
}
Defaults
NewLogger() starts with:
Prefix("LOG")Level(sneklog.FATAL)JsonPretty(false)- text formatter:
sneklog.DefaultTextFormatter - JSON formatter:
sneklog.DefaultJsonFormatter
CreateLogger() is still available for backward compatibility.
Important: with the current level ordering, Level(sneklog.FATAL) allows INFO, WARN, ERROR, and FATAL, but not DEBUG. Use Level(sneklog.DEBUG) to enable every level.
If you want classic threshold filtering instead, call SetThresholdMode(true) and
configure levels with NewThresholdLogLevel(...) or
NewThresholdLogLevelWithColors(...).
Writers and ownership
Logger.Close() only closes writers created by the logger itself:
CreateTextFileWriter(...)CreateJsonFileWriter(...)
The following writers remain owned by the caller and are not closed by Logger.Close():
CreateTextWriter(existingWriter)CreateJsonWriter(existingWriter)CreateTextStdoutWriter()CreateJsonStdoutWriter()
This makes it safe to plug in bytes.Buffer, network writers, and other externally managed resources.
Output formats
Text writers render records like:
2026-03-17T14:05:09+03:00 info API: service started
Use SetFormatter on a writer to customize timestamps, traceback fields, colors, and message layout.
JSON writers emit objects with this shape:
{
"time": "2026-03-17T14:05:09.123456789+03:00",
"level": "info",
"prefix": "API",
"message": "service started",
"traceback": [
{
"method": "main",
"filename": "main.go",
"line": 27,
"signature": "main.main",
"fullPath": "/path/to/main.go"
}
]
}
When JsonPretty(true) is enabled, JSON is indented.
Custom levels and colors
You can define your own levels and assign ANSI, 256-color, or RGB colors, plus text attributes such as Bold or Italic.
httpDelete := sneklog.NewLogLevel(0, "delete")
httpDelete.SetBackgroundRGB(128, 0, 0)
httpDelete.AddAttribute(sneklog.Italic).AddAttribute(sneklog.Bold)
httpCache := sneklog.NewLogLevel(0, "cache")
httpCache.SetForeground256Color(214)
Because LogLevel setters mutate the level in place, call them on a variable, not on a temporary value returned by NewLogLevel(...).
Short forms such as SetFgColor and SetBgColor remain available for backward compatibility.
Common severities also have dedicated helpers:
access := sneklog.NewInfoLogLevelWithColors("access", sneklog.FgCyan, sneklog.BgNone)
audit := sneklog.NewWarnLogLevel("audit")
These helpers keep the legacy severity index for backward compatibility and also assign default threshold values:
INFO:th=10WARN:th=20ERROR:th=30FATAL:th=40DEBUG:th=0
To define custom threshold-based levels explicitly:
trace := sneklog.NewThresholdLogLevelWithColors(4, 5, "trace", sneklog.FgHiBlack, sneklog.BgNone)
audit := sneklog.NewThresholdLogLevel(1, 25, "audit")
logger := sneklog.NewLogger().
SetLevel(audit).
SetThresholdMode(true)
logger.Print(trace, "verbose trace") // filtered out
logger.Print(audit, "audit event") // allowed
HTTP method-specific predefined levels are available out of the box:
level := sneklog.LogLevelForMethod(http.MethodPost)
logger.Print(level, "POST /users")
When comparing levels:
- use
SameLevelif only the legacy severity index matters; - use
SameThresholdif only threshold filtering matters; - use
Equalif the full configuration, including threshold, colors, and attributes, must match.
Message replacement
AddReplacer(old, new) replaces matching text in every message before the
record reaches any writer. Replacement rules are applied in the order they are
added.
logger := sneklog.CreateLogger().
Level(sneklog.DEBUG).
AddReplacer("SOME_SECRET", "<redacted>").
AddReplacer("user@example.com", "<email>")
logger.Infoln("login token:", "SOME_SECRET")
This writes <redacted> instead of SOME_SECRET in both text and JSON output.
An empty old value is ignored.
API summary
Info,Warn,Error,Debug, andFatalaccept a list of values.Infof,Warnf,Errorf,Debugf, andFatalfusefmt.Sprintf.Printf(level, format, args...)formats a message for an explicitLogLevel.- The
*lnmethods preserve newline semantics, which is useful forstdout, Docker, and line-based collectors. Fatal,Fatalf, andFatallncallos.Exit(1)after writing the message.AddReplacermasks or rewrites message text before records are sent to writers.
Traceback behavior
- Text writers use the nearest user stack frame.
- JSON writers receive the full traceback slice.
- Internal
sneklogframes andruntimeframes are filtered out.
Repository example
See examples/main.go.
License
This project is licensed under GNU GPLv3. See LICENSE.