rlog is a small, fast logger for services and command-line tools that need
bounded asynchronous delivery of unstructured text logs. It deliberately
targets a narrow set of use cases instead of providing a configurable logging
framework.
go get github.com/gavriva/rlogInfoF and the other capital-F methods use
github.com/gavriva/format, which is pulled
in as a module dependency.
package main
import "github.com/gavriva/rlog"
func main() {
file := rlog.NewBufferedSink(
3000,
rlog.NewFileSink("service", 100e6, 3),
false,
)
console := rlog.NewBufferedSink(
200,
rlog.NewConsoleSink(),
true,
)
log := rlog.NewLogger(rlog.NewMultiSink(
file,
console,
rlog.LevelDebug,
rlog.LevelWarn,
), true)
defer log.Close()
log.InfoF("listening on {}:{}", "127.0.0.1", 8080)
}Each terminal sink has its own BufferedSink. This allows the file and console
workers to consume their queues independently.
Each level has three forms:
log.Infof("connected to %s in %v", host, elapsed) // fmt.Printf syntax
log.InfoF("connected to {} in {}", host, elapsed) // format.Format syntax
log.Info(func(dst []byte) []byte { // direct append path
dst = append(dst, "connected to "...)
dst = append(dst, host...)
return dst
})The package also exposes the same methods through a global default logger. Use
SetDefaultLogger to install it and rlog.Close() during shutdown.
SetDefaultLogger closes the previous logger and panics when passed nil or
the logger that is already current. Package-level Err reports only the current
default logger; replacing or closing it forgets errors from the previous one.
FileSink and ConsoleSink are sequential terminal sinks. Concurrent logging
requires a BufferedSink before every terminal sink. MultiSink does not
serialize its destinations, so the recommended topology is:
Logger
└─ MultiSink
├─ BufferedSink ─ FileSink
└─ BufferedSink ─ ConsoleSink
BufferedSink owns a bounded queue and a single downstream worker. When its
queue is full, dropWhenFull=false blocks the producer and
dropWhenFull=true drops the new record. It also flushes downstream
periodically. Logger.Flush waits for records queued before the flush request;
Logger.Close drains the queue and closes downstream.
MultiSink routes a record independently to destinations whose minimum level
is satisfied. A record below both thresholds is discarded.
DropSink discards all records and is useful for disabling logging without
special cases in application code.
NewFileSink(baseName, maxSize, maxFiles) writes to baseName.log and rotates
older files to baseName.log.1, baseName.log.2, and so on. maxFiles includes
the active file; use 1 to disable rotation. maxSize is clamped to the range
10 MB through 100 GB. An empty baseName derives the name from the executable.
Opening is lazy. An open, write, or flush failure stores the first error,
reports it once to standard error, stops repeated I/O attempts for five
seconds, and then retries when another record arrives. Records received during
the backoff are discarded. A failure while removing or renaming rotated files
permanently disables the sink. Failure to create or inspect the new active file
after rotation does the same. Rotation errors are not retried automatically.
Logger.Err() returns the first stored sink error, including through buffered
and multi-sink compositions, and remains non-nil after successful I/O recovery.
ConsoleSink writes colored output to standard output when attached to a
terminal. Console output is best effort: write errors are intentionally ignored
and are not returned by Logger.Err().
NewEventTimeLogger returns a logger and a timestamp setter. It is intended for
replaying or processing events whose source time should be written instead of
wall-clock time. Until the setter is called, records use the Unix epoch. The
setter may run concurrently with logging.
The package includes three opinionated default-logger presets:
EnableDefaultLoggerForUtilitylogs debug and above to a blocking file queue, and audit and above to a dropping console queue.EnableDefaultLoggerForServiceuses the same file policy and emits warn and above to the console.EnableDefaultLoggerForLogServeruses only buffered file output.
The hot paths reuse records and byte buffers. The append-callback API avoids
general-purpose formatting, while the capital-F API uses
github.com/gavriva/format. Run the repository benchmarks on the target
machine:
go test -run '^$' -bench . -benchmem ./...rlog intentionally does not provide structured fields, configuration files,
arbitrary user-defined sinks, hooks, or a general logging abstraction. Sink
implementations and the pooled record protocol remain private so the package
can keep its contracts small and its hot paths predictable.