commonlog

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 3, 2023 License: Apache-2.0 Imports: 5 Imported by: 96

README

CommonLog

License Go Reference Go Report Card

A common Go API for structured and unstructured logging with support for pluggable backends and sinks.

Supported backends (you can log to these APIs):

Supported sinks (you can capture logs from these APIs):

The main design goal is to unite your logging and allow you to change its backend at runtime. For example, you can choose to log to stderr by default and switch to journald when running as a systemd service. Sinks allow you to use your selected backend with imported 3rd-party libraries when they use different logging APIs. This design goal is inspired by SLF4J, and we must lament that the Go ecosystem ended up with the same logging challenges that have existed for years in the Java ecosystem.

A secondary design goal is to provide a home for a full-featured unstructured logging library, which we call the "simple" backend. It supports rich, customizable formatting, including ANSI coloring when logging to a terminal (even on Windows). So, CommonLog is useful if you're just looking for a straightforward logging solution right now that will allow you to change the technology in the future.

Note that efficiency and performance are not in themselves design goals for CommonLog, and indeed there is always some overhead involved in wrapper and sink implementations. For example, using zerolog directly involves no allocations, but using it via CommonLog will add allocations. If you want zero allocation you must use zerolog directly. Sinks can be especially inefficient because they may have to rely on capturing and parsing of log text. Programming is all about tradeoffs: CommonLog provides compatibility and flexibility at the cost of some efficiency and performance. However, as always, beware of premature optimization. How you are storing or transmitting your log messages is likely the biggest factor in your optimization narrative.

API features:

  • Fine-grained control over verbosity via hierarchical log names. For example, "engine.parser.background" inherits from "engine.parser", which in turn inherits from "engine". The empty name is the root of the hierarchy. Each name's default verbosity is that of its parent, which you can then override with commonlog.SetMaxLevel().
  • Support for call stack depth. This can be used by a backend (for example, klog) to find out where in the code the logging happened.
  • No need to create logger objects. The true API entrypoint is the global function commonlog.NewMessage(), which you provide with a name and a level. The default logger type is just a convenient wrapper around it that provides the familiar unstructured functions.
  • The unstructured commonlog.Logger type is an interface, allowing you to more easily switch implementations per use without having to introduce a whole backend. For example, you can assign the commonlog.MOCK_LOGGER to disable a logger without changing the rest of your implementation. Compare with Go's built-in Logger type, which frustratingly is a struct rather than an interface.

Basic Usage

The easiest way to plug in a backend is to anonymously import the correct sub-package into your program's main package:

import (
    _ "github.com/tliron/commonlog/simple"
)

This should enable the backend with sensible defaults. Specifically it will log to stderr with verbosity at the "notice" max level.

Example of structured logging:

import (
    "github.com/tliron/commonlog"
    _ "github.com/tliron/commonlog/simple"
    "github.com/tliron/kutil/util"
)

func main() {
    if m := commonlog.NewMessage([]string{"engine", "parser"}, commonlog.Error, 0); m != nil {
        m.Set("message", "Hello world!").Set("myfloat", 10.2).Send()
    }
    util.Exit(0)
}

Note that commonlog.NewMessage() will return nil if the message cannot be created, for example if the message level is higher than the max level for that name.

Set can accept any key and value, but two keys are recognized by the API:

  • message: The main description of the message. This is the key used by unstructured logging.
  • scope: An optional identifier that can be used to group messages, making them easier to filter (e.g. by grep). Backends may handle this specially. Unstructured backends may, for example, add it as a bracketed prefix for messages.

Also note that calling util.Exit(0) to exit your program is not absolutely necessary, however it's good practice because it makes sure to flush buffered log messages for some backends.

Example of unstructured logging:

import (
    "github.com/tliron/commonlog"
    _ "github.com/tliron/commonlog/simple"
    "github.com/tliron/kutil/util"
)

var log = commonlog.GetLogger("engine.parser")

func main() {
    log.Errorf("Hello %s!", "world")
    util.Exit(0)
}

Use conditional logging to optimize for costly unstructured message creation, e.g.:

if log.AllowLevel(commonlog.Debug) {
    log.Debugf("Status is: %s", getStatusFromDatabase())
}

The scope logger can be used to automatically set the "scope" key for another logger. It automatically detects nesting, in which case it appends the new scope separated by a ".", e.g.:

var log = commonlog.GetLogger("engine.parser")
var validationLog = commonlog.NewScopeLogger(log, "validation")
var syntaxLog = commonlog.NewScopeLogger(validationLog, "syntax")

func main() {
    // Name is "engine.parser" and scope is "validation.syntax"
    syntaxLog.Errorf("Hello %s!", "world")
    ...
}

Configuration

All backends can be configured via a common API. For example, to increase verbosity and write to a file:

func main() {
    path := "myapp.log"
    commonlog.Configure(1, &path)
    ...
}

Backends may also have their own (non-common) configuration APIs related to their specific features.

You can set the max level (verbosity) using either the global API or a logger. For example, here is a way to make all logging verbose by default, except for one name:

func init() {
    commonlog.SetMaxLevel(nil, commonlog.Debug) // nil = the root
    commonlog.SetMaxLevel([]string{"engine", "parser"}, commonlog.Error)
}

Note that descendents of "engine.parser", e.g. "engine.parser.analysis", would inherit the "engine.parser" log levels rather than the root's. Here's the same effect using unstructured loggers:

var rootLog = commonlog.GetLogger("")
var parserLog = commonlog.GetLogger("engine.parser")

func init() {
    rootLog.SetMaxLevel(commonlog.Debug)
    parserLog.SetMaxLevel(commonlog.Error)
}

It's important to note that the configuration APIs are not thread safe. This includes Configure() and SetMaxLevel(). Thus, make sure to get all your configuration done before you start sending log messages. A good place for this is init() or main() functions.

Color

For the simple backend you must explicitly attempt to enable ANSI color if desired. Note that if it's unsupported by the terminal then no ANSI codes will be sent (unless you force it via terminal.EnableColor(true)):

import (
    "github.com/tliron/commonlog"
    _ "github.com/tliron/commonlog/simple"
    "github.com/tliron/kutil/terminal"
    "github.com/tliron/kutil/util"
)

func main() {
    cleanup, err := terminal.EnableColor(false)
    util.FailOnError(err)
    if cleanup != nil {
        util.OnExitError(cleanup)
    }
    commonlog.GetLogger("engine.parser").Error("Hello world!") // errors are in red
    util.Exit(0)
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AllowLevel

func AllowLevel(name []string, level Level) bool

func CallAndLogError

func CallAndLogError(f func() error, task string, log Logger)

func Configure

func Configure(verbosity int, path *string)

func GetWriter

func GetWriter() io.Writer

func Initialize added in v0.1.1

func Initialize(verbosity int, path string)

func SetBackend

func SetBackend(backend_ Backend)

func SetMaxLevel

func SetMaxLevel(name []string, level Level)

Types

type Backend

type Backend interface {
	// If "path" is nil will log to stdout, colorized if possible
	// The default "verbosity" 0 will log criticals, errors, warnings, and notices.
	// "verbosity" 1 will add infos. "verbosity" 2 will add debugs.
	// Set "verbostiy" to -1 to disable the log.
	Configure(verbosity int, path *string)
	GetWriter() io.Writer

	NewMessage(name []string, level Level, depth int) Message
	AllowLevel(name []string, level Level) bool
	SetMaxLevel(name []string, level Level)
	GetMaxLevel(name []string) Level
}

type BackendLogger

type BackendLogger struct {
	// contains filtered or unexported fields
}

func NewBackendLogger

func NewBackendLogger(id []string) BackendLogger

func (BackendLogger) AllowLevel

func (self BackendLogger) AllowLevel(level Level) bool

Logger interface

func (BackendLogger) Critical

func (self BackendLogger) Critical(message string)

Logger interface

func (BackendLogger) Criticalf

func (self BackendLogger) Criticalf(format string, values ...any)

Logger interface

func (BackendLogger) Debug

func (self BackendLogger) Debug(message string)

Logger interface

func (BackendLogger) Debugf

func (self BackendLogger) Debugf(format string, values ...any)

Logger interface

func (BackendLogger) Error

func (self BackendLogger) Error(message string)

Logger interface

func (BackendLogger) Errorf

func (self BackendLogger) Errorf(format string, values ...any)

Logger interface

func (BackendLogger) GetMaxLevel

func (self BackendLogger) GetMaxLevel() Level

Logger interface

func (BackendLogger) Info

func (self BackendLogger) Info(message string)

Logger interface

func (BackendLogger) Infof

func (self BackendLogger) Infof(format string, values ...any)

Logger interface

func (BackendLogger) Log

func (self BackendLogger) Log(level Level, depth int, message string)

Logger interface

func (BackendLogger) Logf

func (self BackendLogger) Logf(level Level, depth int, format string, values ...any)

Logger interface

func (BackendLogger) NewMessage

func (self BackendLogger) NewMessage(level Level, depth int) Message

Logger interface

func (BackendLogger) Notice

func (self BackendLogger) Notice(message string)

Logger interface

func (BackendLogger) Noticef

func (self BackendLogger) Noticef(format string, values ...any)

Logger interface

func (BackendLogger) SetMaxLevel

func (self BackendLogger) SetMaxLevel(level Level)

Logger interface

func (BackendLogger) Warning

func (self BackendLogger) Warning(message string)

Logger interface

func (BackendLogger) Warningf

func (self BackendLogger) Warningf(format string, values ...any)

Logger interface

type Hierarchy

type Hierarchy struct {
	// contains filtered or unexported fields
}

func NewMaxLevelHierarchy

func NewMaxLevelHierarchy() *Hierarchy

func (*Hierarchy) AllowLevel

func (self *Hierarchy) AllowLevel(name []string, level Level) bool

func (*Hierarchy) GetMaxLevel

func (self *Hierarchy) GetMaxLevel(name []string) Level

func (*Hierarchy) SetMaxLevel

func (self *Hierarchy) SetMaxLevel(name []string, level Level)

type Level

type Level int
const (
	None     Level = 0
	Critical Level = 1
	Error    Level = 2
	Warning  Level = 3
	Notice   Level = 4
	Info     Level = 5
	Debug    Level = 6
)

func GetMaxLevel

func GetMaxLevel(name []string) Level

func VerbosityToMaxLevel

func VerbosityToMaxLevel(verbosity int) Level

func (Level) String

func (self Level) String() string

fmt.Stringify interface

type Logger

type Logger interface {
	NewMessage(level Level, depth int) Message
	AllowLevel(level Level) bool
	SetMaxLevel(level Level)
	GetMaxLevel() Level

	Log(level Level, depth int, message string)
	Logf(level Level, depth int, format string, values ...any)

	Critical(message string)
	Criticalf(format string, values ...any)
	Error(message string)
	Errorf(format string, values ...any)
	Warning(message string)
	Warningf(format string, values ...any)
	Notice(message string)
	Noticef(format string, values ...any)
	Info(message string)
	Infof(format string, values ...any)
	Debug(message string)
	Debugf(format string, values ...any)
}

func GetLogger

func GetLogger(name string) Logger

func GetLoggerf

func GetLoggerf(format string, values ...any) Logger

type Message

type Message interface {
	Set(key string, value any) Message
	Send()
}

func NewMessage

func NewMessage(name []string, level Level, depth int) Message

type MockLogger

type MockLogger struct{}
var MOCK_LOGGER MockLogger

func (MockLogger) AllowLevel

func (self MockLogger) AllowLevel(level Level) bool

Logger interface

func (MockLogger) Critical

func (self MockLogger) Critical(message string)

Logger interface

func (MockLogger) Criticalf

func (self MockLogger) Criticalf(format string, values ...any)

Logger interface

func (MockLogger) Debug

func (self MockLogger) Debug(message string)

Logger interface

func (MockLogger) Debugf

func (self MockLogger) Debugf(format string, values ...any)

Logger interface

func (MockLogger) Error

func (self MockLogger) Error(message string)

Logger interface

func (MockLogger) Errorf

func (self MockLogger) Errorf(format string, values ...any)

Logger interface

func (MockLogger) GetMaxLevel

func (self MockLogger) GetMaxLevel() Level

Logger interface

func (MockLogger) Info

func (self MockLogger) Info(message string)

Logger interface

func (MockLogger) Infof

func (self MockLogger) Infof(format string, values ...any)

Logger interface

func (MockLogger) Log

func (self MockLogger) Log(level Level, depth int, message string)

Logger interface

func (MockLogger) Logf

func (self MockLogger) Logf(level Level, depth int, format string, values ...any)

Logger interface

func (MockLogger) NewMessage

func (self MockLogger) NewMessage(level Level, depth int) Message

Logger interface

func (MockLogger) Notice

func (self MockLogger) Notice(message string)

Logger interface

func (MockLogger) Noticef

func (self MockLogger) Noticef(format string, values ...any)

Logger interface

func (MockLogger) SetMaxLevel

func (self MockLogger) SetMaxLevel(level Level)

Logger interface

func (MockLogger) Warning

func (self MockLogger) Warning(message string)

Logger interface

func (MockLogger) Warningf

func (self MockLogger) Warningf(format string, values ...any)

Logger interface

type Node

type Node struct {
	// contains filtered or unexported fields
}

func NewNode

func NewNode() *Node

type ScopeLogger

type ScopeLogger struct {
	// contains filtered or unexported fields
}

func NewScopeLogger

func NewScopeLogger(logger Logger, scope string) ScopeLogger

func (ScopeLogger) AllowLevel

func (self ScopeLogger) AllowLevel(level Level) bool

Logger interface

func (ScopeLogger) Critical

func (self ScopeLogger) Critical(message string)

Logger interface

func (ScopeLogger) Criticalf

func (self ScopeLogger) Criticalf(format string, values ...any)

Logger interface

func (ScopeLogger) Debug

func (self ScopeLogger) Debug(message string)

Logger interface

func (ScopeLogger) Debugf

func (self ScopeLogger) Debugf(format string, values ...any)

Logger interface

func (ScopeLogger) Error

func (self ScopeLogger) Error(message string)

Logger interface

func (ScopeLogger) Errorf

func (self ScopeLogger) Errorf(format string, values ...any)

Logger interface

func (ScopeLogger) GetMaxLevel

func (self ScopeLogger) GetMaxLevel() Level

Logger interface

func (ScopeLogger) Info

func (self ScopeLogger) Info(message string)

Logger interface

func (ScopeLogger) Infof

func (self ScopeLogger) Infof(format string, values ...any)

Logger interface

func (ScopeLogger) Log

func (self ScopeLogger) Log(level Level, depth int, message string)

Logger interface

func (ScopeLogger) Logf

func (self ScopeLogger) Logf(level Level, depth int, format string, values ...any)

Logger interface

func (ScopeLogger) NewMessage

func (self ScopeLogger) NewMessage(level Level, depth int) Message

Logger interface

func (ScopeLogger) Notice

func (self ScopeLogger) Notice(message string)

Logger interface

func (ScopeLogger) Noticef

func (self ScopeLogger) Noticef(format string, values ...any)

Logger interface

func (ScopeLogger) SetMaxLevel

func (self ScopeLogger) SetMaxLevel(level Level)

Logger interface

func (ScopeLogger) Warning

func (self ScopeLogger) Warning(message string)

Logger interface

func (ScopeLogger) Warningf

func (self ScopeLogger) Warningf(format string, values ...any)

Logger interface

type SendUnstructuredMessageFunc

type SendUnstructuredMessageFunc func(message string)

type UnstructuredMessage

type UnstructuredMessage struct {
	// contains filtered or unexported fields
}

func (*UnstructuredMessage) Send

func (self *UnstructuredMessage) Send()

func (*UnstructuredMessage) Set

func (self *UnstructuredMessage) Set(key string, value any) Message

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
JackTT - Gopher 🇻🇳