Documentation
¶
Overview ¶
Package log implements a simple logging package with a hierarchical logger tree. Each logger can have children, and configuration changes propagate to descendants. The package is thread-safe; all exported methods except SetNow() are safe for concurrent use.
Each logger has an optional topic. The idea is that this is the main distinction between loggers. Think of the topic as subsystem or part of the code.
Please check out the provided examples to get a better understanding.
A log message consists of a timestamp, a log level, an optional topic, an optional code location and the actual message.
The package defines the following log levels: NOTICE (printed always), PANIC (print and exit 1), ERROR, WARN, INFO, DEBUG, DEBG2, DEBG3, DEBG4, DEBG5
The root logger comes with the folowing properties: log level = WARN (a message logged with a higher level will NOT be printed); no topic; the output is sent to os.Stderr; the timeformat is 2006-01-02 15:04:05.000000; code locations are included in DEBUG and above; the location contains the fully qualified file name and multiline mode is turned off.
At the beginning the only existing logger is the root logger. Derived loggers can be created using New(). Options passed to New() allow to configure the child logger.
Another concept is that of the current logger. It is created using the package level New() function or using the SetCurrent() function. The current logger is used by all package level functions.
All properties of a logger except the topic can be changed after creation. A property change is propagated to all derived loggers recursively. So, if you change the log level of the root logger, this change is inherited by all other loggers.
Example (Lazy) ¶
package main
import (
"github.com/tfoertsch123/log"
"math"
"os"
"time"
)
func main() {
// Use SetNow() to generate a fixed timestamp. This is only needed
// to have this pass as a test so that go doc includes the example.
defer func(orig func() time.Time) { log.SetNow(orig) }(log.SetNow(func() time.Time {
return time.Date(2026, 10, 5, 9, 2, 5, 987654321, time.UTC)
}))
log.SetOutput(os.Stdout)
defer log.Root().Close() // reset/close all loggers
complex_work := func() string {
time.Sleep(100 * time.Millisecond)
return "done"
}
ms := time.Millisecond
// The log level is WARN. So, nothing will be printed.
// Yet it still takes time.
now := time.Now()
log.Infof("complex_work: %s", complex_work())
log.Warnf("complex_work took %vms +/- 20ms",
20*math.Round(float64(time.Now().Sub(now))/float64(20*ms)),
)
now = time.Now()
// Now with lazy execution. This should take almost no time
// since complex_work() is not executed.
log.Infol("complex_work: %s",
log.Lazy(func() interface{} { return complex_work() }))
log.Warnf("complex_work with lazy execution took %vms +/- 20ms",
20*math.Round(float64(time.Now().Sub(now))/float64(20*ms)),
)
// Now let's raise the log level and repeat the previous.
// It should take ~100ms
log.SetLevel(log.INFO)
now = time.Now()
log.Infol("complex_work: %s",
log.Lazy(func() interface{} { return complex_work() }))
log.Warnf("complex_work if actually done took %vms +/- 20ms",
20*math.Round(float64(time.Now().Sub(now))/float64(20*ms)),
)
}
Output: 2026-10-05 09:02:05.987654 WARN complex_work took 100ms +/- 20ms 2026-10-05 09:02:05.987654 WARN complex_work with lazy execution took 0ms +/- 20ms 2026-10-05 09:02:05.987654 NOTICE Setting log level from WARN to INFO 2026-10-05 09:02:05.987654 INFO complex_work: done 2026-10-05 09:02:05.987654 WARN complex_work if actually done took 100ms +/- 20ms
Example (Multiline) ¶
package main
import (
"github.com/tfoertsch123/log"
"os"
"time"
)
func main() {
// Use SetNow() to generate a fixed timestamp. This is only needed
// to have this pass as a test so that go doc includes the example.
defer func(orig func() time.Time) { log.SetNow(orig) }(log.SetNow(func() time.Time {
return time.Date(2026, 10, 5, 9, 2, 5, 987654321, time.UTC)
}))
log.SetOutput(os.Stdout)
defer log.Root().Close() // reset/close all loggers
log.Notice("this\nis a\nmultiline\nmessage")
log.NewC(log.WithMultiLine(true))
log.Notice("this\nis a\nmultiline\nmessage")
mlprefix := ">"
log.NewC(log.WithMultiLinePrefix(&mlprefix))
log.Notice("this\nis a\nmultiline\nmessage")
log.Warn("and another\nmessage")
}
Output: 2026-10-05 09:02 NOTICE this is a multiline message 2026-10-05 09:02 NOTICE this 2026-10-05 09:02 NOTICE is a 2026-10-05 09:02 NOTICE multiline 2026-10-05 09:02 NOTICE message 2026-10-05 09:02 NOTICE this > is a > multiline > message 2026-10-05 09:02 WARN and another > message
Index ¶
- Variables
- func Close()
- func Debg2(m string)
- func Debg2f(f string, p ...interface{})
- func Debg2l(f string, p ...interface{})
- func Debg3(m string)
- func Debg3f(f string, p ...interface{})
- func Debg3l(f string, p ...interface{})
- func Debg4(m string)
- func Debg4f(f string, p ...interface{})
- func Debg4l(f string, p ...interface{})
- func Debg5(m string)
- func Debg5f(f string, p ...interface{})
- func Debg5l(f string, p ...interface{})
- func Debug(m string)
- func Debugf(f string, p ...interface{})
- func Debugl(f string, p ...interface{})
- func Error(m string)
- func Errorf(f string, p ...interface{})
- func Errorl(f string, p ...interface{})
- func GetLocDirectories() int
- func GetMultiLine() bool
- func GetMultiLinePrefix() *string
- func GetOutput() io.Writer
- func GetTimeFmt() string
- func Info(m string)
- func Infof(f string, p ...interface{})
- func Infol(f string, p ...interface{})
- func Log(lvl Level, m string)
- func Logf(lvl Level, f string, p ...interface{})
- func Logl(lvl Level, f string, p ...interface{})
- func Notice(m string)
- func Noticef(f string, p ...interface{})
- func Noticel(f string, p ...interface{})
- func Panic(m string)
- func Panicf(f string, p ...interface{})
- func Panicl(f string, p ...interface{})
- func SetLevel(lvl Level)
- func SetLocDirectories(n int)
- func SetMinLocation(lvl Level)
- func SetMultiLine(ml bool)
- func SetMultiLinePrefix(x *string)
- func SetNow(now_f func() time.Time) func() time.Time
- func SetOutput(out io.Writer)
- func SetTimeFmt(f string)
- func Warn(m string)
- func Warnf(f string, p ...interface{})
- func Warnl(f string, p ...interface{})
- type Deferred
- type Level
- type LogOpts
- type Logger
- func (l *Logger) Close() *Logger
- func (l *Logger) Debg2(m string)
- func (l *Logger) Debg2f(f string, p ...interface{})
- func (l *Logger) Debg2l(f string, p ...interface{})
- func (l *Logger) Debg3(m string)
- func (l *Logger) Debg3f(f string, p ...interface{})
- func (l *Logger) Debg3l(f string, p ...interface{})
- func (l *Logger) Debg4(m string)
- func (l *Logger) Debg4f(f string, p ...interface{})
- func (l *Logger) Debg4l(f string, p ...interface{})
- func (l *Logger) Debg5(m string)
- func (l *Logger) Debg5f(f string, p ...interface{})
- func (l *Logger) Debg5l(f string, p ...interface{})
- func (l *Logger) Debug(m string)
- func (l *Logger) Debugf(f string, p ...interface{})
- func (l *Logger) Debugl(f string, p ...interface{})
- func (l *Logger) Error(m string)
- func (l *Logger) Errorf(f string, p ...interface{})
- func (l *Logger) Errorl(f string, p ...interface{})
- func (l *Logger) GetLevel() Level
- func (l *Logger) GetLocDirectories() int
- func (l *Logger) GetMinLocation() Level
- func (l *Logger) GetMultiLine() bool
- func (l *Logger) GetMultiLinePrefix() *string
- func (l *Logger) GetOutput() io.Writer
- func (l *Logger) GetTimeFmt() string
- func (l *Logger) Info(m string)
- func (l *Logger) Infof(f string, p ...interface{})
- func (l *Logger) Infol(f string, p ...interface{})
- func (l *Logger) IsClosed() bool
- func (l *Logger) IsCurrent() bool
- func (l *Logger) IsRoot() bool
- func (l *Logger) Kids(recursive bool) []*Logger
- func (l *Logger) Log(lvl Level, m string)
- func (l *Logger) Logf(lvl Level, f string, p ...interface{})
- func (l *Logger) Logl(lvl Level, f string, p ...interface{})
- func (l *Logger) New(_opts ...Opt) *Logger
- func (l *Logger) Notice(m string)
- func (l *Logger) Noticef(f string, p ...interface{})
- func (l *Logger) Noticel(f string, p ...interface{})
- func (l *Logger) Panic(m string)
- func (l *Logger) Panicf(f string, p ...interface{})
- func (l *Logger) Panicl(f string, p ...interface{})
- func (l *Logger) SetCurrent()
- func (l *Logger) SetLevel(lvl Level)
- func (l *Logger) SetLocDirectories(n int)
- func (l *Logger) SetMinLocation(lvl Level)
- func (l *Logger) SetMultiLine(ml bool)
- func (l *Logger) SetMultiLinePrefix(x *string)
- func (l *Logger) SetOutput(out io.Writer)
- func (l *Logger) SetTimeFmt(f string)
- func (l *Logger) String() string
- func (l *Logger) Warn(m string)
- func (l *Logger) Warnf(f string, p ...interface{})
- func (l *Logger) Warnl(f string, p ...interface{})
- type Opt
- func ParseURL(s string, hnd func(error)) ([]Opt, error)
- func WithLevel(lv Level) Opt
- func WithLocDirectories(n int) Opt
- func WithMinLocation(lv Level) Opt
- func WithMultiLine(x bool) Opt
- func WithMultiLinePrefix(x *string) Opt
- func WithOutput(out io.Writer) Opt
- func WithOutputFactory(c OutputFactory, hnd OutputFactoryErrorHandler) Opt
- func WithTimeFmt(f string) Opt
- func WithTopic(tp string) Opt
- type OutputFactory
- type OutputFactoryErrorHandler
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidFormat error = errors.New(`invalid format`)
ErrInvalidFormat is an error returned by ParseURL indicating there is something wrong with the URL.
var ErrInvalidLevel error = errors.New(`invalid log level`)
ErrInvalidLevel is returned by ParseLevel when the input string does not match any known log level.
Functions ¶
func Debg2f ¶
func Debg2f(f string, p ...interface{})
Debg2f logs a formatted debug message at level 2 using the current logger.
func Debg2l ¶
func Debg2l(f string, p ...interface{})
Debg2l logs a message with late evaluation at debug level 2 using the current logger.
func Debg3f ¶
func Debg3f(f string, p ...interface{})
Debg3f logs a formatted debug message at level 3 using the current logger.
func Debg3l ¶
func Debg3l(f string, p ...interface{})
Debg3l logs a message with late evaluation at debug level 3 using the current logger.
func Debg4f ¶
func Debg4f(f string, p ...interface{})
Debg4f logs a formatted debug message at level 4 using the current logger.
func Debg4l ¶
func Debg4l(f string, p ...interface{})
Debg4l logs a message with late evaluation at debug level 4 using the current logger.
func Debg5f ¶
func Debg5f(f string, p ...interface{})
Debg5f logs a formatted debug message at level 5 using the current logger.
func Debg5l ¶
func Debg5l(f string, p ...interface{})
Debg5l logs a message with late evaluation at debug level 5 using the current logger.
func Debugf ¶
func Debugf(f string, p ...interface{})
Debugf logs a formatted debug message using the current logger.
func Debugl ¶
func Debugl(f string, p ...interface{})
Debugl logs a message with late evaluation at debug level using the current logger.
func Errorf ¶
func Errorf(f string, p ...interface{})
Errorf logs a formatted error message using the current logger.
func Errorl ¶
func Errorl(f string, p ...interface{})
Errorl logs a message with late evaluation at error level using the current logger.
func GetLocDirectories ¶
func GetLocDirectories() int
GetLocDirectories returns the current logger’s directory component count.
func GetMultiLine ¶
func GetMultiLine() bool
GetMultiLine returns true if multiline mode is on for the current logger.
func GetMultiLinePrefix ¶ added in v0.2.0
func GetMultiLinePrefix() *string
GetMultiLinePrefix returns the MultiLinePrefix for the current logger.
func GetTimeFmt ¶
func GetTimeFmt() string
GetTimeFmt returns the current logger’s time format string.
func Infof ¶
func Infof(f string, p ...interface{})
Infof logs a formatted info message using the current logger.
func Infol ¶
func Infol(f string, p ...interface{})
Infol logs a message with late evaluation at info level using the current logger.
func Noticef ¶
func Noticef(f string, p ...interface{})
Noticef logs a formatted notice message using the current logger.
func Noticel ¶
func Noticel(f string, p ...interface{})
Noticel logs a message with late evaluation at notice level using the current logger.
func Panic ¶
func Panic(m string)
Panic logs a panic message using the current logger and exits with code 1.
func Panicf ¶
func Panicf(f string, p ...interface{})
Panicf logs a formatted panic message using the current logger and exits with code 1.
func Panicl ¶
func Panicl(f string, p ...interface{})
Panicl logs a message with late evaluation at panic level using the current logger and exits with code 1.
func SetLevel ¶
func SetLevel(lvl Level)
SetLevel sets the minimum log level on the current logger and all its descendants.
func SetLocDirectories ¶
func SetLocDirectories(n int)
SetLocDirectories sets the directory component count on the current logger and its descendants.
func SetMinLocation ¶
func SetMinLocation(lvl Level)
SetMinLocation sets the minimum location level on the current logger and its descendants.
func SetMultiLine ¶
func SetMultiLine(ml bool)
SetMultiLine turns multiline mode on or off for the current logger and all its descendants.
func SetMultiLinePrefix ¶ added in v0.2.0
func SetMultiLinePrefix(x *string)
SetMultiLinePrefix sets the MultiLinePrefix for the current logger and all its descendants.
func SetNow ¶
SetNow allows to change this package's notion of now. It's intended to be used mainly for testing and debugging purposes. The default value is time.Now. SetNow is not thread-safe. If you need to modify it from different go routines, make sure to prevent all concurrent logging.
func SetTimeFmt ¶
func SetTimeFmt(f string)
SetTimeFmt sets the time format string on the current logger and its descendants.
Types ¶
type Deferred ¶ added in v0.3.0
type Deferred func() interface{}
Deferred is a type representing a function wrapped for lazy execution. See Logl for more details.
type Level ¶
type Level int8
Level represents a log severity level. Lower values indicate higher severity. The zero value of Level is PANIC. Use ToLevel or ParseLevel to obtain a valid Level.
const ( // NOTICE is a special level that always prints, regardless of the // logger's threshold. It is the only level that can be lower than PANIC. NOTICE Level = iota - 1 // PANIC is the highest severity. Logging at this level will also call // os.Exit(1). PANIC // ERROR indicates an error condition. ERROR // WARN indicates a warning condition. WARN // INFO indicates an informational message. INFO // DEBUG indicates a debug message. DEBUG // DEBG2 is a more verbose debug level. DEBG2 // DEBG3 is an even more verbose debug level. DEBG3 // DEBG4 is a highly verbose debug level. DEBG4 // DEBG5 is the most verbose debug level. DEBG5 )
func GetMinLocation ¶
func GetMinLocation() Level
GetMinLocation returns the current logger’s minimum location level.
func ParseLevel ¶
ParseLevel parses a string into a Level. It accepts case-insensitive names: "notice", "panic", "error", "warn"/"warning", "info", "debug", "debug2"/"debg2", "debug3"/"debg3", "debug4"/"debg4", "debug5"/"debg5", or the numeric strings "0" through "8". Returns ErrInvalidLevel if the string does not match.
type LogOpts ¶
type LogOpts struct {
// contains filtered or unexported fields
}
LogOpts holds optional configuration for creating a new Logger via New.
type Logger ¶
type Logger struct {
// contains filtered or unexported fields
}
Logger represents a node in a hierarchical logger tree.
func L ¶
func L() *Logger
L returns the current package-level logger. The current logger is used by all top-level logging functions (e.g. Log, Debug). It is protected by a global mutex; use this function to obtain a consistent snapshot.
func NewC ¶
New creates and returns a new logger as a child of the current logger and sets it as the new current logger. Shortcut for lg=log.L().New(); lg.SetCurrent()
Example ¶
package main
import (
"github.com/tfoertsch123/log"
"os"
"time"
)
func main() {
// Use SetNow() to generate a fixed timestamp. This is only needed
// to have this pass as a test so that go doc includes the example.
defer func(orig func() time.Time) { log.SetNow(orig) }(log.SetNow(func() time.Time {
return time.Date(2026, 10, 5, 9, 2, 5, 987654321, time.UTC)
}))
// This example uses the current logger.
log.SetLevel(log.DEBUG) // change the log level from WARN to DEBUG
log.SetOutput(os.Stdout) // change the output from Stderr to Stdout
log.SetLocDirectories(1) // we want 1 directory component in the location output
log.Info(`message without a topic`)
log.Debug(`with code location`)
log.Debg2(`not printed`) // because DEBUG < DEBG2
// Create a new logger and make it the current one.
// Everything but the topic is inherited from the root.
log.NewC(log.WithTopic(`TPC`))
log.Debug(`with topic and location`)
// Close the current logger. The root logger becomes current again.
log.Close()
log.Warn(`get back the old logger`)
// Close the root logger. This reinits it.
log.Close()
}
Output: 2026-10-05 09:02:05.987654 INFO message without a topic 2026-10-05 09:02:05.987654 DEBUG (log/exmpl01_log_test.go:21) with code location 2026-10-05 09:02:05.987654 DEBUG [TPC] (log/exmpl01_log_test.go:27) with topic and location 2026-10-05 09:02:05.987654 WARN get back the old logger
func NewK ¶
New creates and returns a new logger as a child of the current logger without making it the current logger. Shortcut for log.L().New().
Example ¶
package main
import (
"github.com/tfoertsch123/log"
"os"
"time"
)
func main() {
// Use SetNow() to generate a fixed timestamp. This is only needed
// to have this pass as a test so that go doc includes the example.
defer func(orig func() time.Time) { log.SetNow(orig) }(log.SetNow(func() time.Time {
return time.Date(2026, 10, 5, 9, 2, 5, 987654321, time.UTC)
}))
var lg1 *log.Logger
var lg2 *log.Logger
fn1 := func() {
lg1.Infof(`info in fn%d`, 1)
lg1.Warnf(`info in fn%d`, 1)
}
fn2 := func() {
lg2.Infof(`info in fn%d`, 2)
lg2.Warnf(`info in fn%d`, 2)
}
// Create 2 loggers with different topics. Both are children of the
// current logger. The current logger does not change. So, they are
// also direct kids of the root logger.
// Since at this point the root and current loggers are the same,
// NewR() and NewK() are basically so too.
lg1 = log.NewK(log.WithTopic(`TOPIC1`))
lg2 = log.NewR(log.WithTopic(`TOPIC2`))
// these changes propagate from the root/current logger to all other
log.SetOutput(os.Stdout)
log.SetTimeFmt("2006-01-02 15:04")
fn1()
fn2()
// change the log level from WARN to INFO for all loggers
log.SetLevel(log.DEBUG)
fn1()
fn2()
}
Output: 2026-10-05 09:02 WARN [TOPIC1] info in fn1 2026-10-05 09:02 WARN [TOPIC2] info in fn2 2026-10-05 09:02 NOTICE Setting log level from WARN to DEBUG 2026-10-05 09:02 INFO [TOPIC1] info in fn1 2026-10-05 09:02 WARN [TOPIC1] info in fn1 2026-10-05 09:02 INFO [TOPIC2] info in fn2 2026-10-05 09:02 WARN [TOPIC2] info in fn2
func NewR ¶
New creates and returns a new logger as a child of the root logger without making it the current logger. Shortcut for log.Root().New().
func Root ¶
func Root() *Logger
Root returns a pointer to the global root logger. The root logger cannot be closed and is always available.
func (*Logger) Close ¶
Close closes the logger and all its descendants. After closing, the logger and its subtree are marked as closed and cannot be used. The returned pointer is the previous parent (or nil for the root). Once a logger has been closed, it will not send any messages anymore. The root logger cannot be closed. If closed, it will be reinitialized with the default values.
func (*Logger) GetLocDirectories ¶
GetLocDirectories returns the number of directory components shown in source locations.
func (*Logger) GetMinLocation ¶
GetMinLocation returns the logger’s current minimum location level.
func (*Logger) GetMultiLine ¶
GetMultiLine returns true if multiline mode is on for this logger.
func (*Logger) GetMultiLinePrefix ¶ added in v0.2.0
GetMultiLinePrefix returns the MultiLinePrefix for this logger.
func (*Logger) GetTimeFmt ¶
GetTimeFmt returns the logger’s time format string.
func (*Logger) IsCurrent ¶
IsCurrent reports whether the logger is the current package-level logger.
func (*Logger) Kids ¶
Kids returns a list of this logger’s direct children. If recursive is true, it also includes all descendants (depth-first).
func (*Logger) Log ¶
Log outputs a message at the given level if it meets the logger’s threshold. The output includes a timestamp, level, optional topic, optional source location, and message. In multiline mode, the message is split into lines and each line is prefixed as if it was a log message on its own.
func (*Logger) Logl ¶
Logl outputs a formatted message similar to Logf with late evaluation of arguments. Arguments that are functions of type Deferred are called to obtain their value.
Example:
fib := func(yield func(uint64) bool) {
var x, y uint64 = 1, 1
for {
if !yield(x) {return}
x, y = y, x+y
}
}
// the sum will only be calculated if loglevel is >= log.DEBUG
logger.Logl(
log.DEBUG,
`sum of first 1000 fibonacci numbers is %v`,
log.Lazy(func() interface{} {
var sum uint64
i := 0
for fb := range fib {
sum += fb
i++
if i >= 1000 {break}
}
return sum
}), // log.Lazy() returns a log.Deferred object
)
func (*Logger) New ¶
New creates a new child logger by copying the current logger’s configuration and applying the given options. The new logger is added to this logger’s children set.
func (*Logger) Panicl ¶
Panicl logs a message with late evaluation at panic level and exits with code 1.
func (*Logger) SetCurrent ¶
func (l *Logger) SetCurrent()
SetCurrent makes this logger the current package-level logger. If l is nil, the root logger becomes current.
func (*Logger) SetLevel ¶
SetLevel sets the minimum log level on the current logger and all its descendants.
func (*Logger) SetLocDirectories ¶
SetLocDirectories sets the number of directory components shown in source locations for this logger and all its descendants.
func (*Logger) SetMinLocation ¶
SetMinLocation sets the minimum log level for printing the caller’s source location on this logger and all its descendants. Levels outside the valid range disable location printing.
func (*Logger) SetMultiLine ¶
SetMultiLine turns multiline mode on or off for this logger and all its descendants.
func (*Logger) SetMultiLinePrefix ¶ added in v0.2.0
SetMultiLinePrefix sets the MultiLinePrefix for this logger and all its descendants.
func (*Logger) SetOutput ¶
SetOutput sets the output writer for this logger and all its descendants.
func (*Logger) SetTimeFmt ¶
SetTimeFmt sets the time format string for this logger and all its descendants.
type Opt ¶
type Opt func(*LogOpts)
Opt is a function type used to apply optional settings to a LogOpts.
func ParseURL ¶ added in v0.2.0
ParseURL parses an URL-like string into a set of options that can be passed to one of the New functions. The URL is defined like so:
the scheme is either omitted or must be "file:"
the user part can be the log level, "INFO@"
the host part is either empty or a single dot to signify a relative path name
if a relative or absolute path name is specified, a rotate object is created as output. ParseURL itself only creates a promise to create such an object when any of the New functions is called. If the actual creation fails, New will return nil. The "hnd" parameter should be used in this case to catch any error.
the host part can also be either "stderr", "stdout" or a number. In that case either *os.Stderr, *os.Stdout or an *os.File connected to the file descriptor passed as the number is used. This allows commands like
program 5>logfile --logurl=//DEBUG@5?multiline=true
Note, in the FD form, the file descriptor will be attached to an *os.File object. Once that object becomes inaccessible and is garbage collected, the file will be closed. If you need the file descriptor elsewhere, dup() it.
Here are some URL examples:
# [rotate] objects with absolute paths file://LEVEL@/absolute/path/to/file?KEY=VALUE&KEY=VALUE... //LEVEL@/absolute/path/to/file?KEY=VALUE&KEY=VALUE... file:///absolute/path/to/file?level=LEVEL&KEY=VALUE&KEY=VALUE... file:/absolute/path/to/file?level=LEVEL&KEY=VALUE&KEY=VALUE... /absolute/path/to/file?level=LEVEL&KEY=VALUE&KEY=VALUE... # [rotate] objects with relative paths file://LEVEL@./relative/path/to/file?KEY=VALUE&KEY=VALUE... //LEVEL@./relative/path/to/file?KEY=VALUE&KEY=VALUE... file://LEVEL@/./relative/path/to/file?KEY=VALUE&KEY=VALUE... //LEVEL@/./relative/path/to/file?KEY=VALUE&KEY=VALUE... file:/./relative/path/to/file?level=LEVEL&KEY=VALUE&KEY=VALUE... /./relative/path/to/file?level=LEVEL&KEY=VALUE&KEY=VALUE... relative/path/to/file?level=LEVEL&KEY=VALUE&KEY=VALUE... # stderr, stdout, fd file://LEVEL@stderr?KEY=VALUE... //LEVEL@stdout?KEY=VALUE... //5?level=LEVEL&KEY=VALUE...
Known query parameters:
- level - corresponds to WithLevel
- timeformat - corresponds to WithTimeFmt
- multiline - corresponds to WithMultiLine. True values can be expressed as "on", "true", "1" or "yes"
- mlprefix - corresponds to WithMultiLinePrefix
- minloc - corresponds to WithMinLocation
- locdirs - corresponds to WithLocDirectories
- topic - corresponds to WithTopic
- maxsize - corresponds to rotate.WithMaxSize
- nbackups - corresponds to rotate.WithNBackups
- append - corresponds to rotate.WithInitialAppend. True values are the same as for "multiline" above
URL parsing fails for various reasons. In particular it fails if any of the log levels cannot be interpreted or the "LEVEL@" part is given but the "level=LEVEL" query parameter specifies a different value (that's ambiguous). It also fails if a query parameter is given multiple times or if an unknown query parameter is given. Query parameters are case-sensitive. Furthermore, it does not make sense to specify a "maxsize" for instance for a non-rotate destination. So, that will also fail.
Example usage:
var err error
opts, err := log.ParseURL(url, func(e error) {err = e})
if err != nil {
// handle ParseURL error
}
lg := log.L().New(append(opts, log.WithTopic("OVERRIDE-TOPIC"))...)
if lg == nil {
// err now contains the error generated by rotate.New()
}
Example (Fd) ¶
package main
import (
"fmt"
"github.com/tfoertsch123/log"
"os"
"syscall"
"time"
)
func main() {
// Use SetNow() to generate a fixed timestamp. This is only needed
// to have this pass as a test so that go doc includes the example.
defer func(orig func() time.Time) { log.SetNow(orig) }(log.SetNow(func() time.Time {
return time.Date(2026, 10, 5, 9, 2, 5, 987654321, time.UTC)
}))
defer log.Root().Close() // reset/close all loggers
// This example demonstrates the use of an open file descriptor as
// the log destination. In Bash, that could be achieved like so:
//
// program 5>logfile --logurl="file://5?timeformat&..."
//
// Here we just dup() stdout. We have to dup() it because the file
// descriptor is internally assigned to an *os.File which has a cleanup
// function attached so that the file will be closed by the next
// run of the garbage collector when the logger becomes unreachable.
fd, err := syscall.Dup(int(os.Stdout.Fd()))
if err != nil {
panic("unexpected error in syscall.Dup()")
}
defer syscall.Close(fd)
url := fmt.Sprintf(
"//%d?"+
"timeformat=2006-01-02%2015%3A04&"+
"locdirs=1&"+
"topic=EXAMPLE&"+
"level=debug",
fd,
)
log.Root().Noticef("Using URL %q", url)
if opts, e := log.ParseURL(url, nil); e == nil {
log.NewC(opts...)
} else {
panic(fmt.Sprintf("ParseURL: %v", e))
}
log.Info("Info Message")
log.Debug("Debug Message") // this is line 51
log.Debg2("Debg2 Message")
log.NewC(log.WithTopic("MAIN"))
log.Info("Info Message")
log.Debug("Debug Message") // this is line 57
log.Debg2("Debg2 Message")
}
Output: 2026-10-05:02 INFO [EXAMPLE] Info Message 2026-10-05:02 DEBUG [EXAMPLE] (log/exmpl07_URL_fd_test.go:51) Debug Message 2026-10-05:02 INFO [MAIN] Info Message 2026-10-05:02 DEBUG [MAIN] (log/exmpl07_URL_fd_test.go:57) Debug Message
Example (Rotate) ¶
package main
import (
"fmt"
"github.com/tfoertsch123/log"
"os"
"path/filepath"
"time"
)
func main() {
// Use SetNow() to generate a fixed timestamp. This is only needed
// to have this pass as a test so that go doc includes the example.
defer func(orig func() time.Time) { log.SetNow(orig) }(log.SetNow(func() time.Time {
return time.Date(2026, 10, 5, 9, 2, 5, 987654321, time.UTC)
}))
// Setup a temp directory for log files
dir, err := os.MkdirTemp("", "Example_ParseURL_rotate")
if err != nil {
panic(err)
}
defer os.RemoveAll(dir) // cleanup
cat := func(fn string) {
data, err := os.ReadFile(filepath.Join(dir, fn))
if err != nil {
panic(err)
}
fmt.Printf("file %s, len %d:\n%s\n", fn, len(data), string(data))
}
log.Root().Noticef("logfile is in %q", dir)
defer log.Root().Close() // reset/close all loggers
logf := "test.log"
url := "//DEBUG@" + dir + "/" + logf + "?" +
"maxsize=100&" +
"nbackups=5&" +
"timeformat=2006-01-02%2015%3A04&" +
"locdirs=1&" +
"topic=EXAMPLE"
if opts, e := log.ParseURL(url, nil); e == nil {
log.NewC(opts...)
} else {
panic(fmt.Sprintf("ParseURL: %v", e))
}
log.Info("Info Message")
log.Debug("Debug Message") // this is line 29
log.Debg2("Debg2 Message")
// give it some time to rotate
time.Sleep(100 * time.Millisecond)
log.NewC(log.WithTopic("MAIN"))
log.Info("Info Message")
log.Debug("Debug Message") // this is line 35
log.Debg2("Debg2 Message")
// We should have triggered a 2nd rotation, maxsize=100
time.Sleep(100 * time.Millisecond)
cat(logf)
cat(logf + "~")
cat(logf + "~2")
}
Output: file test.log, len 0: file test.log~, len 122: 2026-10-05 09:02 INFO [MAIN] Info Message 2026-10-05 09:02 DEBUG [MAIN] (log/exmpl06_URL_rotate_test.go:60) Debug Message file test.log~2, len 128: 2026-10-05 09:02 INFO [EXAMPLE] Info Message 2026-10-05 09:02 DEBUG [EXAMPLE] (log/exmpl06_URL_rotate_test.go:51) Debug Message
Example (Stdout) ¶
package main
import (
"fmt"
"github.com/tfoertsch123/log"
"time"
)
func main() {
// Use SetNow() to generate a fixed timestamp. This is only needed
// to have this pass as a test so that go doc includes the example.
defer func(orig func() time.Time) { log.SetNow(orig) }(log.SetNow(func() time.Time {
return time.Date(2026, 10, 5, 9, 2, 5, 987654321, time.UTC)
}))
defer log.Root().Close() // reset/close all loggers
// This URL sends the log output to Stdout. It configures os.Stdout
// as output. Instead of stdout, stderr can also be used.
url := "//DEBUG@stdout?" +
"timeformat=2006-01-02%2015%3A04&" +
"locdirs=1&" +
"topic=EXAMPLE"
if opts, e := log.ParseURL(url, nil); e == nil {
log.NewC(opts...)
} else {
panic(fmt.Sprintf("ParseURL: %v", e))
}
log.Info("Info Message")
log.Debug("Debug Message") // this is line 31
log.Debg2("Debg2 Message")
log.NewC(log.WithTopic("MAIN"))
log.Info("Info Message")
log.Debug("Debug Message") // this is line 37
log.Debg2("Debg2 Message")
}
Output: 2026-10-05 09:02 INFO [EXAMPLE] Info Message 2026-10-05 09:02 DEBUG [EXAMPLE] (log/exmpl05_URL_stdout_test.go:31) Debug Message 2026-10-05 09:02 INFO [MAIN] Info Message 2026-10-05 09:02 DEBUG [MAIN] (log/exmpl05_URL_stdout_test.go:37) Debug Message
func WithLocDirectories ¶
WithLocDirectories returns an Opt that sets the number of directory components to include in the printed source location. Negative values print the full path.
func WithMinLocation ¶
WithMinLocation returns an Opt that sets the minimum log level for printing the caller’s source location. Levels outside [NOTICE+1, DEBG5] disable location printing.
func WithMultiLine ¶
WithMultiLine returns an Opt that turns on/off multiline mode. In multiline mode, line breaks in the log message are detected and each line is prefixed with the normal line prefix or the MultiLinePrefix if set.
func WithMultiLinePrefix ¶ added in v0.2.0
WithMLPrefix returns an Opt that sets the MultiLinePrefix. This is used in combination with MultiLineMode. By default, the multiline message "this\nis a\nmultiline\nmessage" will be rendered like so:
2026-10-05 09:02 NOTICE this 2026-10-05 09:02 NOTICE is a 2026-10-05 09:02 NOTICE multiline 2026-10-05 09:02 NOTICE message
If the MultiLinePrefix is set to "+", it becomes
2026-10-05 09:02 NOTICE this + is a + multiline + message
The empty string is also accepted. If nil is passed, the default behavior is restored.
func WithOutput ¶
WithOutput returns an Opt that sets the output writer.
func WithOutputFactory ¶ added in v0.2.0
func WithOutputFactory(c OutputFactory, hnd OutputFactoryErrorHandler) Opt
WithOutputFactory returns an Opt that allows delayed creation of the output writer. The main intend of this to work in combination with ParseURL. An OutputFactory is a function returning an io.Writer and an error. The callback function is called as part of [New]. If the returned error is nil, the produced io.writer becomes the output of the new logger. If the error is different from nil, the OutputFactoryErrorHandler is called with the error as the sole parameter. The resulting logger is then nil. If both, WithOutput and WithOutputFactory, are used, WithOutputFactory is ignored.
func WithTimeFmt ¶
WithTimeFmt returns an Opt that sets the time format string. The string is passed to time.Format in order to format the time stamp.
type OutputFactory ¶ added in v0.2.0
type OutputFactoryErrorHandler ¶ added in v0.2.0
type OutputFactoryErrorHandler func(error)