galog.go (488 lines of code) (raw):
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package galog
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"text/template"
"time"
)
// Config is the interface used to bridge behavior configurations
// between the log framework and the backend implementation.
type Config interface {
// QueueSize returns the max size of the queue for a given backend.
QueueSize() int
// SetQueueSize sets the max size of the queue for a given backend.
SetQueueSize(size int)
// SetFormat sets the log format of the specified level for a given backend.
SetFormat(level Level, format string)
// Format returns the log format of the specified level from a given backen.
Format(level Level) string
}
// FormatMap wraps the level <-> format map type.
type FormatMap map[Level]string
// backendConfig is a common implementation of Config interface, more
// "sophisticated" backends may want to have their own implementation,
// for a generic and simple use case backendConfig should suffice.
type backendConfig struct {
// queueSize is the log record/entry queue max size. If greater than 0
// the queue will be rotated/collected when it hits queueSize; The queue
// size will be unlimited otherwise.
queueSize int
// formatMap maps the log format for a given log level.
// See [Config]'s Format() for more info.
formatMap FormatMap
}
// Backend defines the interface of a backend implementation.
type Backend interface {
// ID returns the backend's implementation ID.
ID() string
// Log is the entry point with the Backend implementation, the log
// framework will use Log() to forward the logging entry to the backend.
Log(entry *LogEntry) error
// Config returns the backend configuration interface implementation.
Config() Config
// Shutdown performs shutdown operations for the backend service.
// The operations should include flushing the backend's backing log storage
// and closing the backend's logging service if applicable.
Shutdown(ctx context.Context) error
}
// LogEntry describes a log record.
type LogEntry struct {
// Level is the log level of the log record/entry.
Level Level
// File is the file name of the log caller.
File string
// Line is the file's line of the log caller.
Line int
// Function is the function name of the log caller.
Function string
// When is the time when this log record/entry was created.
When time.Time
// Message is the formated final log message.
Message string
// Prefix is a string/tag prefixed to the log message.
Prefix string
}
// BackendQueue wraps all the entry queueing control objects.
type BackendQueue struct {
// entries is the slice of queued log entries.
entries []*LogEntry
// entriesMutex protects access to entries variable. There will be two go
// routine accessing entries, one processing the entries being enqueued with a
// channel and another handling the periodic processing of enqueued entries.
entriesMutex sync.Mutex
// cancel is the channel used to cancel the queue handing go routine
// of a given backend.
cancel chan bool
// bus is the channel used to enqueue a new log record/entry.
bus chan *LogEntry
// ticker is a timer used to periodically process pending queue.
ticker *time.Ticker
// tickerFrequency is the frequency of the ticker.
tickerFrequency time.Duration
// backend points to the actual backend object.
backend Backend
}
// logger is the backing implementation of the logging facilities.
type logger struct {
// currentLevel is the min log level currently set.
currentLevel Level
// queues packs the registered backend control data (indexed by backend ID).
queues map[string]*BackendQueue
// queuesMutex protects/syncs access to queues mapping. This mutex makes sure
// it's thread safe to RegisterBackend() and UnregisterBackend() new backend's
// queues as well as changing configurations such as SetQueueRetryFrequency().
queuesMutex sync.Mutex
// retryFrequency is the frequency for the log queue timed processing retry.
retryFrequency time.Duration
// verbosity is the verbosity level set for the logger.
verbosity int
// Prefix is a string/tag prefixed to the log message.
prefix string
// exitFunc is the exit function called on behalf Fatal and Fatalf calls.
exitFunc func()
}
// Verbose is the verbosity controlled log operations.
type Verbose bool
var (
// defaultLogger is the default logger used by the application.
defaultLogger *logger
// defaultRetryFrequency is the default frequency for the log queue timed
// processing retry.
defaultRetryFrequency = time.Millisecond * 10
// FatalLevel is the log level definition for Fatal severity.
FatalLevel = Level{0, "FATAL"}
// ErrorLevel is the log level definition for Error severity.
ErrorLevel = Level{1, "ERROR"}
// WarningLevel is the log level definition for Warning severity.
WarningLevel = Level{2, "WARNING"}
// InfoLevel is the log level definition for Info severity.
InfoLevel = Level{3, "INFO"}
// DebugLevel is the log level definition for Deug severity.
DebugLevel = Level{4, "DEBUG"}
// allLevels is the list of all supported log levels.
allLevels = []Level{FatalLevel, ErrorLevel, WarningLevel, InfoLevel, DebugLevel}
)
const (
// fallbackFormat is used when a backend didn't provide the level <-> format
// mapping.
fallbackFormat = `{{.When.Format "2006-01-02T15:04:05.0000Z07:00"}} [{{.Level}}]: {{.Message}}`
)
// newLogger allocates and configures a new logger based on default behavior
// descriptors.
func newLogger(exitFunc func()) *logger {
return &logger{
currentLevel: WarningLevel,
queues: make(map[string]*BackendQueue),
retryFrequency: defaultRetryFrequency,
exitFunc: exitFunc,
}
}
// init initializes the default logger.
func init() {
defaultLogger = newLogger(func() { os.Exit(1) })
}
// Level wraps id and description of a log level.
type Level struct {
// level is the log level numeric id.
level int
// tag is the tag to be displayed when writing the log.
tag string
}
// String returns the string representation of a log level.
func (level Level) String() string {
return level.tag
}
// ParseLevel returns the log level object for a given level id. In case of
// invalid level id, an error is returned.
func ParseLevel(level int) (Level, error) {
for _, lvl := range allLevels[1:] {
if lvl.level == level {
return lvl, nil
}
}
return Level{level: level, tag: "INVALID"}, fmt.Errorf("invalid log level: %d", level)
}
// ValidLevels returns a string representation of all the valid log levels, the
// only exception is the Fatal level as it's a special internal level.
func ValidLevels() string {
var levels []string
// Iterate over all valid levels skipping fatal.
for _, lvl := range allLevels[1:] {
levels = append(levels, fmt.Sprintf("%s(%d)", lvl.tag, lvl.level))
}
return strings.Join(levels, ", ")
}
// SetLevel sets the current log level.
func SetLevel(level Level) {
defaultLogger.SetLevel(level)
}
// CurrentLevel returns the default logger current log level.
func CurrentLevel() Level {
return defaultLogger.CurrentLevel()
}
// RegisterBackend inserts/registers a backend implementation. This function is
// thread safe and can be called from any goroutine in the program.
func RegisterBackend(ctx context.Context, backend Backend) {
defaultLogger.RegisterBackend(ctx, backend)
}
// RegisteredBackendIDs returns the list of registered backend IDs.
func RegisteredBackendIDs() []string {
return defaultLogger.RegisteredBackendIDs()
}
// UnregisterBackend removes/unregisters a backend implementation. This function
// is thread safe and can be called from any goroutine in the program.
func UnregisterBackend(backend Backend) {
defaultLogger.UnregisterBackend(backend)
}
// Shutdown shuts down the default logger.
func Shutdown(timeout time.Duration) {
defaultLogger.Shutdown(timeout)
}
// SetPrefix sets the prefix to be used for the log message. When present the
// default backend's formats will prefix the provided string to all log
// messages.
//
// In cases where multiple backends are writing to the same backing
// storage (i.e. same file or the same syslog's ident) the prefix adds a
// meaningful context of the log originator.
func SetPrefix(prefix string) {
defaultLogger.SetPrefix(prefix)
}
// SetQueueRetryFrequency sets the frequency which the log queue will be
// processed. This function is thread safe and can be called from any goroutine
// in the program.
func SetQueueRetryFrequency(frequency time.Duration) {
defaultLogger.SetQueueRetryFrequency(frequency)
}
// SetMinVerbosity sets the min verbosity to be assumed/used across the V()
// calls.
func SetMinVerbosity(v int) {
defaultLogger.SetMinVerbosity(v)
}
// MinVerbosity returns an integer representing the currently min verbosity set.
func MinVerbosity() int {
return defaultLogger.MinVerbosity()
}
// QueueRetryFrequency returns the currently log queue processing frequency.
func QueueRetryFrequency() time.Duration {
return defaultLogger.QueueRetryFrequency()
}
// V returns a boolean of type Verbose, it's value is true if the requested
// level is less or equal to the min verbosity value (i.e. by passing -V flag).
// The returned value implements the logging functions such as Debug(),
// Debugf(), Info(), Infof() etc.
func V(v int) Verbose {
return defaultLogger.V(v)
}
// Debug logs to the DEBUG log. Arguments are handled in the manner of
// fmt.Print; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context.
func Debug(args ...any) {
defaultLogger.Debug(args...)
}
// Debugf logs to the DEBUG log. Arguments are handled in the manner of
// fmt.Printf; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context.
func Debugf(format string, args ...any) {
defaultLogger.Debugf(format, args...)
}
// Info logs to the INFO log. Arguments are handled in the manner of fmt.Print;
// New line adding is handled by each backend and will be added when it makes
// sense considering the backend context.
func Info(args ...any) {
defaultLogger.Info(args...)
}
// Infof logs to the INFO log. Arguments are handled in the manner of
// fmt.Printf; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context.
func Infof(format string, args ...any) {
defaultLogger.Infof(format, args...)
}
// Warn logs to the WARNING log. Arguments are handled in the manner of
// fmt.Print; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context.
func Warn(args ...any) {
defaultLogger.Warn(args...)
}
// Warnf logs to the WARNING log. Arguments are handled in the manner of
// fmt.Printf; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context.
func Warnf(format string, args ...any) {
defaultLogger.Warnf(format, args...)
}
// Error logs to the ERROR log. Arguments are handled in the manner of
// fmt.Print; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context.
func Error(args ...any) {
defaultLogger.Error(args...)
}
// Errorf logs to the ERROR log. Arguments are handled in the manner of
// fmt.Printf; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context.
func Errorf(format string, args ...any) {
defaultLogger.Errorf(format, args...)
}
// Fatal logs to the FATAL log. Arguments are handled in the manner of
// fmt.Print; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context. The Fatal function also
// exists the program with os.Exit(1).
//
// Since it's calling os.Exit() the fatal functions will also Shutdown() the
// logger specifying the queue retry frequency as the timeout
// (see [SetQueueRetryFrequency]).
func Fatal(args ...any) {
defaultLogger.Fatal(args...)
}
// Fatalf logs to the FATAL log. Arguments are handled in the manner of
// fmt.Printf; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context. The Fatal function also
// exists the program with os.Exit(1).
//
// Since it's calling os.Exit() the fatal functions will also Shutdown() the
// logger specifying the queue retry frequency as the timeout
// (see [SetQueueRetryFrequency]).
func Fatalf(format string, args ...any) {
defaultLogger.Fatalf(format, args...)
}
// Debug logs to the DEBUG log assumed that the Verbosity v is smaller or equal
// to the configured min verbosity (see [V] for more). Arguments are handled in
// the manner of fmt.Print; New line adding is handled by each backend and will
// be added when it makes sense considering the backend context.
func (v Verbose) Debug(args ...any) {
defaultLogger.DebugV(v, args...)
}
// Debugf logs to the DEBUG log assumed that the Verbosity v is smaller or equal
// to the configured min verbosity (see [V] for more). Arguments are handled in
// the manner of fmt.Printf; New line adding is handled by each backend and will
// be added when it makes sense considering the backend context.
func (v Verbose) Debugf(format string, args ...any) {
defaultLogger.DebugfV(v, format, args...)
}
// Info logs to the INFO log assumed that the Verbosity v is smaller or equal to
// the configured min verbosity (see [V] for more). Arguments are handled in the
// manner of fmt.Print; New line adding is handled by each backend and will be
// added when it makes sense considering the backend context.
func (v Verbose) Info(args ...any) {
defaultLogger.InfoV(v, args...)
}
// Infof logs to the INFO log assumed that the Verbosity v is smaller or equal
// to the configured min verbosity (see [V] for more). Arguments are handled in
// the manner of fmt.Printf; New line adding is handled by each backend and will
// be added when it makes sense considering the backend context.
func (v Verbose) Infof(format string, args ...any) {
defaultLogger.InfofV(v, format, args...)
}
// Warn logs to the WARNING log assumed that the Verbosity v is smaller or equal
// to the configured min verbosity (see [V] for more). Arguments are handled in
// the manner of fmt.Print; New line adding is handled by each backend and will
// be added when it makes sense considering the backend context.
func (v Verbose) Warn(args ...any) {
defaultLogger.WarnV(v, args...)
}
// Warnf logs to the WARNING log assumed that the Verbosity v is smaller or
// equal to the configured min verbosity (see [V] for more). Arguments are
// handled in the manner of fmt.Printf; New line adding is handled by each
// backend and will be added when it makes sense considering the backend
// context.
func (v Verbose) Warnf(format string, args ...any) {
defaultLogger.WarnfV(v, format, args...)
}
// Error logs to the ERROR log assumed that the Verbosity v is smaller or equal
// to the configured min verbosity (see [V] for more). Arguments are handled in
// the manner of fmt.Print; New line adding is handled by each backend and will
// be added when it makes sense considering the backend context.
func (v Verbose) Error(args ...any) {
defaultLogger.ErrorV(v, args...)
}
// Errorf logs to the ERROR log assumed that the Verbosity v is smaller or
// equal to the configured min verbosity (see [V] for more). Arguments are
// handled in the manner of fmt.Printf; New line adding is handled by each
// backend and will be added when it makes sense considering the backend
// context.
func (v Verbose) Errorf(format string, args ...any) {
defaultLogger.ErrorfV(v, format, args...)
}
// newEntry sets up the log entry for each logging call.
func newEntry(level Level, prefix string, msg string) *LogEntry {
pc, file, line, _ := runtime.Caller(3)
return &LogEntry{
Level: level,
When: time.Now(),
File: filepath.Base(file),
Line: line,
Function: runtime.FuncForPC(pc).Name(),
Message: msg,
Prefix: prefix,
}
}
// Format processes a template provided in format and return it as a string.
func (en *LogEntry) Format(format string) (string, error) {
tmpl, err := template.New("").Parse(format)
if err != nil {
return "", fmt.Errorf("failed to parse template: %w", err)
}
buffer := new(strings.Builder)
if err := tmpl.Execute(buffer, en); err != nil {
return "", fmt.Errorf("failed to execute template: %w", err)
}
return buffer.String(), nil
}
// newBackendConfig allocates and initializes a new backendConfig instance.
func newBackendConfig(queueSize int) *backendConfig {
return &backendConfig{
queueSize: queueSize,
formatMap: make(FormatMap),
}
}
// QueueSize returns the queueSize set to the configuration.
func (bc *backendConfig) QueueSize() int {
return bc.queueSize
}
// SetQueueSize sets the size as the queueSize of the configuration. When a
// backend's queue size reaches its limit the oldest messages will start to be
// dropped one-by-one, the queue processing is asynchronous and
// thread safe - it runs on its own goroutine and synchronizes with the callers
// go routine using a channel. Resetting the queue size is also thread safe.
func (bc *backendConfig) SetQueueSize(size int) {
bc.queueSize = size
}
// SetFormat adds level and format to the format mapping.
func (bc *backendConfig) SetFormat(level Level, format string) {
bc.formatMap[level] = format
}
// Format returns the format configured to a given level. If n format is not
// found for the requested level the closest format will be used, i.e:
//
// - if Error is found in the mapping, Info is not defined, and level is Info
// the format of Error will be returned.
//
// If no format could be found, meaning, the backend has never defined a valid
// format configuration, a default fallbackFormat is returned.
func (bc *backendConfig) Format(level Level) string {
if format, found := bc.formatMap[level]; found {
return format
}
for i := level.level; i >= 0; i-- {
curr := allLevels[i]
if format, found := bc.formatMap[curr]; found {
return format
}
}
for i := level.level; i < len(allLevels); i++ {
curr := allLevels[i]
if format, found := bc.formatMap[curr]; found {
return format
}
}
return fallbackFormat
}
// SetPrefix sets the prefix to be used for the log message. When present the
// default backend's formats will prefix the provided string to all log
// messages.
//
// In cases where multiple backends are writing to the same backing
// storage (i.e. same file or the same syslog's ident) the prefix adds a
// meaningful context of the log originator.
func (lg *logger) SetPrefix(prefix string) {
lg.prefix = prefix
}
// SetQueueRetryFrequency sets the frequency which the log queue will be
// processed.
func (lg *logger) SetQueueRetryFrequency(frequency time.Duration) {
lg.retryFrequency = frequency
lg.queuesMutex.Lock()
for _, backend := range lg.queues {
if backend.tickerFrequency != frequency {
backend.tickerFrequency = frequency
backend.ticker.Reset(frequency)
}
}
lg.queuesMutex.Unlock()
}
// QueueRetryFrequency returns the currently set log queue processing frequency.
func (lg *logger) QueueRetryFrequency() time.Duration {
return lg.retryFrequency
}
// UnregisterBackend removes/unregisters a backend implementation.
func (lg *logger) UnregisterBackend(backend Backend) {
lg.queuesMutex.Lock()
defer lg.queuesMutex.Unlock()
if queue, found := lg.queues[backend.ID()]; found {
delete(lg.queues, backend.ID())
queue.ticker.Stop()
queue.cancel <- true
}
}
// Shutdown shuts down the logger. It unregisters all previously registered
// backends. Tries to flush out any pending enqueued entries for a period
// provided by the caller with timeout parameter.
//
// Any pending write in the backing storage of a backend will be flushed - the
// backend implementation's Flush() operation will be called.
//
// After calling Shutdown() all logging calls (i.e. Errorf(), Infof()) will be
// no-op (as no backends will left registered after that).
func (lg *logger) Shutdown(timeout time.Duration) {
var backends []*BackendQueue
lg.queuesMutex.Lock()
for _, queue := range lg.queues {
backends = append(backends, queue)
}
lg.queuesMutex.Unlock()
// Equivalent to unsubscribe, make sure we stop accepting new entries.
for _, queue := range backends {
lg.UnregisterBackend(queue.backend)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
// Flush any pending writes.
for _, queue := range backends {
if ctx.Err() != nil {
break
}
lg.flushEnqueuedEntries(ctx, queue)
queue.backend.Shutdown(ctx)
}
}
// RegisterBackend inserts/registers a backend implementation.
func (lg *logger) RegisterBackend(ctx context.Context, backend Backend) {
backendQueue := &BackendQueue{
cancel: make(chan bool),
bus: make(chan *LogEntry),
tickerFrequency: lg.retryFrequency,
ticker: time.NewTicker(lg.retryFrequency),
backend: backend,
}
lg.queuesMutex.Lock()
lg.queues[backend.ID()] = backendQueue
lg.queuesMutex.Unlock()
go lg.runBackend(ctx, backend, backendQueue)
}
// RegisteredBackendIDs returns the list of registered backend IDs.
func (lg *logger) RegisteredBackendIDs() []string {
lg.queuesMutex.Lock()
defer lg.queuesMutex.Unlock()
var backendIDs []string
for _, queue := range lg.queues {
backendIDs = append(backendIDs, queue.backend.ID())
}
return backendIDs
}
// runBackend runs the go routine receiving signals to write a log from a
// backend or ultimately enqueuing it. Each registered backend will have its own
// enqueuing managing go routine.
func (lg *logger) runBackend(ctx context.Context, backend Backend, bq *BackendQueue) {
// enqueue guarantees the max size of the entry queue.
enqueue := func(force bool, config Config, item *LogEntry) bool {
bq.entriesMutex.Lock()
defer bq.entriesMutex.Unlock()
if !force && len(bq.entries) == 0 {
return false
}
if config == nil || config.QueueSize() == 0 {
return false
}
bq.entries = append(bq.entries, item)
queueSize := config.QueueSize()
// If we've reached the queue limit we remove the oldest entry from the
// queue.
if queueSize > 0 && len(bq.entries) > queueSize {
bq.entries = bq.entries[1:]
}
return true
}
var wg sync.WaitGroup
wg.Add(1)
// Runs the the periodical processing of the queue.
go func() {
defer wg.Done()
for {
select {
// Context cancelation handling.
case <-ctx.Done():
bq.ticker.Stop()
return
// Periodically queue processing.
case <-bq.ticker.C:
lg.flushEnqueuedEntries(ctx, bq)
}
}
}()
wg.Add(1)
// Runs the entry enqueueing and backend unregistration.
go func() {
defer wg.Done()
for {
select {
// Context cancelation handling.
case <-ctx.Done():
return
// Backend unregistering handling.
case <-bq.cancel:
return
// Entry enqueueing handling.
case entry := <-bq.bus:
if enqueue(false, backend.Config(), entry) {
continue
}
if err := backend.Log(entry); err != nil {
enqueue(true, backend.Config(), entry)
}
}
}
}()
wg.Wait()
}
// flushEnqueuedEntries try to write any pending entries from the queue. It
// will attempt to write until the context is canceled otherwise it runs until
// all the queue is processed.
func (lg *logger) flushEnqueuedEntries(ctx context.Context, bq *BackendQueue) {
var success int
bq.entriesMutex.Lock()
defer bq.entriesMutex.Unlock()
for _, curr := range bq.entries {
if ctx.Err() != nil {
break
}
if err := bq.backend.Log(curr); err != nil {
break
}
success++
}
if len(bq.entries) > 0 && success > 0 {
bq.entries = bq.entries[success:]
}
}
// log is the bridging point between the logging functions and the
// queue management layer. Each backend will have their own queue.
// If the log entry level is not higher than the currently set level
// the entry is ignored.
func (lg *logger) log(entry *LogEntry) {
if lg.currentLevel.level < entry.Level.level {
return
}
lg.queuesMutex.Lock()
defer lg.queuesMutex.Unlock()
for _, curr := range lg.queues {
curr.bus <- entry
}
}
// SetLevel sets the current log level of lg.
func (lg *logger) SetLevel(level Level) {
lg.currentLevel = level
}
// CurrentLevel returns the current log level of lg.
func (lg *logger) CurrentLevel() Level {
return lg.currentLevel
}
// Debug logs to the DEBUG log. Arguments are handled in the manner of
// fmt.Print; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context.
func (lg *logger) Debug(args ...any) {
lg.log(newEntry(DebugLevel, lg.prefix, fmt.Sprint(args...)))
}
// Debugf logs to the DEBUG log. Arguments are handled in the manner of
// fmt.Printf; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context.
func (lg *logger) Debugf(format string, args ...any) {
lg.log(newEntry(DebugLevel, lg.prefix, fmt.Sprintf(format, args...)))
}
// Info logs to the INFO log. Arguments are handled in the manner of fmt.Print;
// New line adding is handled by each backend and will be added when it makes
// sense considering the backend context.
func (lg *logger) Info(args ...any) {
lg.log(newEntry(InfoLevel, lg.prefix, fmt.Sprint(args...)))
}
// Infof logs to the INFO log. Arguments are handled in the manner of
// fmt.Printf; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context.
func (lg *logger) Infof(format string, args ...any) {
lg.log(newEntry(InfoLevel, lg.prefix, fmt.Sprintf(format, args...)))
}
// Warn logs to the WARNING log. Arguments are handled in the manner of
// fmt.Print; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context.
func (lg *logger) Warn(args ...any) {
lg.log(newEntry(WarningLevel, lg.prefix, fmt.Sprint(args...)))
}
// Warnf logs to the WARNING log. Arguments are handled in the manner of
// fmt.Printf; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context.
func (lg *logger) Warnf(format string, args ...any) {
lg.log(newEntry(WarningLevel, lg.prefix, fmt.Sprintf(format, args...)))
}
// Error logs to the ERROR log. Arguments are handled in the manner of
// fmt.Print; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context.
func (lg *logger) Error(args ...any) {
lg.log(newEntry(ErrorLevel, lg.prefix, fmt.Sprint(args...)))
}
// Errorf logs to the ERROR log. Arguments are handled in the manner of
// fmt.Printf; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context.
func (lg *logger) Errorf(format string, args ...any) {
lg.log(newEntry(ErrorLevel, lg.prefix, fmt.Sprintf(format, args...)))
}
// Fatal logs to the FATAL log. Arguments are handled in the manner of
// fmt.Print; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context. The Fatal function also
// exists the program with os.Exit(1).
//
// Since it's calling os.Exit() the fatal functions will also Shutdown() the
// logger specifying the queue retry frequency as the timeout
// (see [SetQueueRetryFrequency]).
func (lg *logger) Fatal(args ...any) {
lg.log(newEntry(FatalLevel, lg.prefix, fmt.Sprint(args...)))
lg.Shutdown(lg.retryFrequency)
lg.exitFunc()
}
// Fatalf logs to the FATAL log. Arguments are handled in the manner of
// fmt.Printf; New line adding is handled by each backend and will be added when
// it makes sense considering the backend context. The Fatal function also
// exists the program with os.Exit(1).
//
// Since it's calling os.Exit() the fatal functions will also Shutdown() the
// logger specifying the queue retry frequency as the timeout
// (see [SetQueueRetryFrequency]).
func (lg *logger) Fatalf(format string, args ...any) {
lg.log(newEntry(FatalLevel, lg.prefix, fmt.Sprintf(format, args...)))
lg.Shutdown(lg.retryFrequency)
lg.exitFunc()
}
// SetMinVerbosity sets the min verbosity to be assumed/used across the V()
// calls.
func (lg *logger) SetMinVerbosity(v int) {
lg.verbosity = v
}
// V returns a boolean of type Verbose, it's value is true if the requested
// level is less or equal to the min verbosity value (i.e. by passing -V flag).
// The returned value implements the logging functions such as Debug(),
// Debugf(), Info(), Infof() etc.
func (lg *logger) V(v int) Verbose {
if v <= lg.verbosity {
return true
}
return false
}
// MinVerbosity returns an integer representing the currently min verbosity set.
func (lg *logger) MinVerbosity() int {
return defaultLogger.verbosity
}
// DebugV logs to the DEBUG log assumed that the Verbosity v is smaller or equal
// to the configured min verbosity (see [V] for more). Arguments are handled in
// the manner of fmt.Print; New line adding is handled by each backend and will
// be added when it makes sense considering the backend context.
func (lg *logger) DebugV(v Verbose, args ...any) {
if v {
lg.log(newEntry(DebugLevel, lg.prefix, fmt.Sprint(args...)))
}
}
// DebugfV logs to the DEBUG log assumed that the Verbosity v is smaller or
// equal to the configured min verbosity (see [V] for more). Arguments are
// handled in the manner of fmt.Printf; New line adding is handled by each
// backend and will be added when it makes sense considering the backend
// context.
func (lg *logger) DebugfV(v Verbose, format string, args ...any) {
if v {
lg.log(newEntry(DebugLevel, lg.prefix, fmt.Sprintf(format, args...)))
}
}
// InfoV logs to the INFO log assumed that the Verbosity v is smaller or equal
// to the configured min verbosity (see [V] for more). Arguments are handled in
// the manner of fmt.Print; New line adding is handled by each backend and will
// be added when it makes sense considering the backend context.
func (lg *logger) InfoV(v Verbose, args ...any) {
if v {
lg.log(newEntry(InfoLevel, lg.prefix, fmt.Sprint(args...)))
}
}
// InfofV logs to the INFO log assumed that the Verbosity v is smaller or equal
// to the configured min verbosity (see [V] for more). Arguments are handled in
// the manner of fmt.Printf; New line adding is handled by each backend and will
// be added when it makes sense considering the backend context.
func (lg *logger) InfofV(v Verbose, format string, args ...any) {
if v {
lg.log(newEntry(InfoLevel, lg.prefix, fmt.Sprintf(format, args...)))
}
}
// WarnV logs to the WARNING log assumed that the Verbosity v is smaller or
// equal to the configured min verbosity (see [V] for more). Arguments are
// handled in the manner of fmt.Print; New line adding is handled by each
// backend and will be added when it makes sense considering the backend
// context.
func (lg *logger) WarnV(v Verbose, args ...any) {
if v {
lg.log(newEntry(WarningLevel, lg.prefix, fmt.Sprint(args...)))
}
}
// WarnfV logs to the WARNING log assumed that the Verbosity v is smaller or
// equal to the configured min verbosity (see [V] for more). Arguments are
// handled in the manner of fmt.Printf; New line adding is handled by each
// backend and will be added when it makes sense considering the backend
// context.
func (lg *logger) WarnfV(v Verbose, format string, args ...any) {
if v {
lg.log(newEntry(WarningLevel, lg.prefix, fmt.Sprintf(format, args...)))
}
}
// ErrorV logs to the ERROR log assumed that the Verbosity v is smaller or
// equal to the configured min verbosity (see [V] for more). Arguments are
// handled in the manner of fmt.Print; New line adding is handled by each
// backend and will be added when it makes sense considering the backend
// context.
func (lg *logger) ErrorV(v Verbose, args ...any) {
if v {
lg.log(newEntry(ErrorLevel, lg.prefix, fmt.Sprint(args...)))
}
}
// ErrorfV logs to the ERROR log assumed that the Verbosity v is smaller or
// equal to the configured min verbosity (see [V] for more). Arguments are
// handled in the manner of fmt.Printf; New line adding is handled by each
// backend and will be added when it makes sense considering the backend
// context.
func (lg *logger) ErrorfV(v Verbose, format string, args ...any) {
if v {
lg.log(newEntry(ErrorLevel, lg.prefix, fmt.Sprintf(format, args...)))
}
}