Documentation ¶
Overview ¶
Package xlog provides a structured Logger implemented in two different strategies: synchronous and asynchronous. The logs can be formatted in JSON, logfmt, custom text format.
Index ¶
- Constants
- Variables
- func AppendNoValue(keyValues []any) []any
- func NewSyncWriter(w io.Writer) io.Writer
- func StackErr(err error) string
- type AsyncLogger
- func (logger *AsyncLogger) Close() error
- func (logger *AsyncLogger) Critical(keyValues ...any)
- func (logger *AsyncLogger) Debug(keyValues ...any)
- func (logger *AsyncLogger) Error(keyValues ...any)
- func (logger *AsyncLogger) Info(keyValues ...any)
- func (logger *AsyncLogger) Log(keyValues ...any)
- func (logger *AsyncLogger) Warn(keyValues ...any)
- type AsyncLoggerOption
- type BufferedWriter
- type BufferedWriterOption
- type CommonOpts
- type ErrorHandler
- type Formatter
- type Level
- type LevelProvider
- type Logger
- type MockLogger
- func (mock *MockLogger) Close() error
- func (mock *MockLogger) CloseCallsCount() int
- func (mock *MockLogger) Critical(keyValues ...any)
- func (mock *MockLogger) Debug(keyValues ...any)
- func (mock *MockLogger) Error(keyValues ...any)
- func (mock *MockLogger) Info(keyValues ...any)
- func (mock *MockLogger) Log(keyValues ...any)
- func (mock *MockLogger) LogCallsCount(lvl Level) int
- func (mock *MockLogger) SetCloseError(closeErr error)
- func (mock *MockLogger) SetLogCallback(lvl Level, callback func(keyValues ...any))
- func (mock *MockLogger) Warn(keyValues ...any)
- type MultiLogger
- func (logger *MultiLogger) Close() error
- func (logger *MultiLogger) Critical(keyValues ...any)
- func (logger *MultiLogger) Debug(keyValues ...any)
- func (logger *MultiLogger) Error(keyValues ...any)
- func (logger *MultiLogger) Info(keyValues ...any)
- func (logger *MultiLogger) Log(keyValues ...any)
- func (logger *MultiLogger) Warn(keyValues ...any)
- type NopLogger
- type Provider
- type SyncLogger
- func (logger *SyncLogger) Close() error
- func (logger *SyncLogger) Critical(keyValues ...any)
- func (logger *SyncLogger) Debug(keyValues ...any)
- func (logger *SyncLogger) Error(keyValues ...any)
- func (logger *SyncLogger) Info(keyValues ...any)
- func (logger *SyncLogger) Log(keyValues ...any)
- func (logger *SyncLogger) Warn(keyValues ...any)
- type SyncLoggerOption
- type SyslogLevelProvider
Examples ¶
Constants ¶
const ErrorKey = "err"
ErrorKey represents the key under which an error resides. For a Logger.Error call usually you'll want to log an error. You are not obliged to use this key.
const MessageKey = "msg"
MessageKey represents the key under which the main message resides. You are not obliged to use this key.
const SyslogPrefixCee = "@cee:"
SyslogPrefixCee defines the @cee prefix for structured logging in syslog. See: http://cee.mitre.org/language/1.0-beta1/clt.html#appendix-1-cee-over-syslog-transport-mapping .
Variables ¶
var ErrNotSyslogWriter = errors.New("the writer should be a *syslog.Writer")
ErrNotSyslogWriter is the error returned in case the writer is not syslog specific.
var SentryFormatter = func(formatter Formatter, hub *sentry.Hub, opts *CommonOpts) Formatter { var ( mu sync.Mutex sentryLevelMap = map[Level]sentry.Level{ LevelDebug: sentry.LevelDebug, LevelInfo: sentry.LevelInfo, LevelWarning: sentry.LevelWarning, LevelError: sentry.LevelError, LevelCritical: sentry.LevelFatal, LevelNone: sentry.Level(""), } labeledLevels = flipLevelLabels(opts.LevelLabels) ) return func(_ io.Writer, keyValues []any) error { keyValues = AppendNoValue(keyValues) buf := bufPool.Get().(*bytes.Buffer) buf.Reset() defer bufPool.Put(buf) if err := formatter(buf, keyValues); err != nil { return err } sentryLevel := sentryLevelMap[extractLevel(labeledLevels, opts.LevelKey, keyValues)] mu.Lock() defer mu.Unlock() hub.Scope().SetLevel(sentryLevel) _ = hub.CaptureMessage(buf.String()) return nil } }
SentryFormatter is a decorator which sends another formatter 's output to Sentry. The writer from the Logger should be io.Discard, as it uses internally a bytes.Buffer.
var SyslogFormatter = func( formatter Formatter, syslogLevelProvider SyslogLevelProvider, prefix string, ) Formatter { return func(w io.Writer, keyValues []any) error { sw, ok := w.(syslogWriter) if !ok { return ErrNotSyslogWriter } keyValues = AppendNoValue(keyValues) buf := bufPool.Get().(*bytes.Buffer) buf.Reset() defer bufPool.Put(buf) if prefix != "" { _, _ = buf.WriteString(prefix) } if err := formatter(buf, keyValues); err != nil { return err } syslogLevel := syslogLevelProvider(keyValues) switch syslogLevel { case syslog.LOG_EMERG: return sw.Emerg(buf.String()) case syslog.LOG_ALERT: return sw.Alert(buf.String()) case syslog.LOG_CRIT: return sw.Crit(buf.String()) case syslog.LOG_ERR: return sw.Err(buf.String()) case syslog.LOG_WARNING: return sw.Warning(buf.String()) case syslog.LOG_NOTICE: return sw.Notice(buf.String()) case syslog.LOG_INFO: return sw.Info(buf.String()) case syslog.LOG_DEBUG: return sw.Debug(buf.String()) default: _, err := sw.Write(buf.Bytes()) return err } } }
SyslogFormatter is a decorator which writes another formatter 's output to system syslog. The second param is a function that knows to return a syslog level for the current log. You can use NewDefaultSyslogLevelProvider / NewExtractFromKeySyslogLevelProvider or custom provider (maybe you want to support other syslog levels - for example nothing stops you from doing this: logger.Log("lvl","NOTICE", ...) and map also "NOTICE" to syslog.LOG_NOTICE). The third param is a prefix to be written with each log. You'll pass here empty string or SyslogPrefixCee.
var TextFormatter = func(opts *CommonOpts) Formatter { return func(w io.Writer, keyValues []any) error { keyValues = AppendNoValue(keyValues) var ( time, level, source, msg string finalOutBuf, extraInfoBuf bytes.Buffer key, value any ) finalOutBuf.Grow(64) extraInfoBuf.Grow(64) for idx := 0; idx < len(keyValues); idx += 2 { key = keyValues[idx] value = keyValues[idx+1] switch key { case opts.LevelKey: level = stringify(value) case opts.TimeKey: time = stringify(value) case opts.SourceKey: source = stringify(value) case MessageKey: msg = stringify(value) default: _, _ = extraInfoBuf.WriteString(stringify(key)) _ = extraInfoBuf.WriteByte('=') _, _ = extraInfoBuf.WriteString(stringify(value)) _ = extraInfoBuf.WriteByte(' ') } } appendTextFinalOutput(&finalOutBuf, []byte(time)) appendTextFinalOutput(&finalOutBuf, []byte(source)) appendTextFinalOutput(&finalOutBuf, []byte(level)) appendTextFinalOutput(&finalOutBuf, []byte(msg)) finalOut := append(finalOutBuf.Bytes(), extraInfoBuf.Bytes()...) finalOut[len(finalOut)-1] = '\n' _, err := w.Write(finalOut) return err } }
TextFormatter provides a more human friendly custom format. This formatter does not comply with any kind of well known standard. It can be used for example for local dev environment. Example of output: "TIME SOURCE LEVEL MESSAGE KEY1=VALUE1 KEY2=VALUE2 ...".
Functions ¶
func AppendNoValue ¶
AppendNoValue is a safety function which adds a "*NoValue*" at the end of keyValues slice in case it is odd.
func NewSyncWriter ¶
NewSyncWriter instantiates a new Writer decorated with a mutex, making is safe for concurrent use by multiple goroutines.
Example ¶
package main import ( "bytes" "fmt" "log" "strconv" "sync" "github.com/actforgood/xlog" ) func main() { // In this example we wrap a bytes.Buffer (Writer) // with a SyncWriter in order for Write to be // concurrent safe. inMemoryWriter := new(bytes.Buffer) // writer := inMemoryWriter // you can enable this line instead of SyncWriter to check race conditions. writer := xlog.NewSyncWriter(inMemoryWriter) text := "Lorem ipsum dolor sit amet, consectetur adipiscing elit." wg := sync.WaitGroup{} // perform 5 concurrent writes. wg.Add(5) for i := 0; i < 5; i++ { go func(threadNo int) { defer wg.Done() threadNoStr := strconv.FormatInt(int64(threadNo+1), 10) _, err := writer.Write([]byte(text + threadNoStr + "\n")) if err != nil { log.Printf("[%d] write error: %v\n", threadNo, err) } }(i) } wg.Wait() checkOutput := inMemoryWriter.Bytes() fmt.Print(string(checkOutput)) }
Output: Lorem ipsum dolor sit amet, consectetur adipiscing elit.1 Lorem ipsum dolor sit amet, consectetur adipiscing elit.4 Lorem ipsum dolor sit amet, consectetur adipiscing elit.3 Lorem ipsum dolor sit amet, consectetur adipiscing elit.5 Lorem ipsum dolor sit amet, consectetur adipiscing elit.2
Types ¶
type AsyncLogger ¶
type AsyncLogger struct {
// contains filtered or unexported fields
}
AsyncLogger is a Logger which writes logs asynchronously. Note: if used in a concurrent context, log writes are concurrent safe if only one worker is configured to process the logs. Otherwise, log writes are not concurrent safe, unless the writer is concurrent safe. See also NewSyncWriter and AsyncLoggerWithWorkersNo on this matter.
Example ¶
package main import ( "io" "os" "github.com/actforgood/xlog" ) func main() { // In this example we create a (async)logger that writes // logs to standard output. opts := xlog.NewCommonOpts() opts.MinLevel = xlog.FixedLevelProvider(xlog.LevelNone) opts.AdditionalKeyValues = []any{ "appName", "demo", "env", "dev", } opts.Time = func() any { // mock time for output check return "2022-03-16T16:01:20Z" } opts.Source = xlog.SourceProvider(4, 1) // keep only filename for output check logger := xlog.NewAsyncLogger( os.Stdout, xlog.AsyncLoggerWithOptions(opts), xlog.AsyncLoggerWithWorkersNo(2), // since workers no > 1, we expect output to be unordered. ) defer logger.Close() logger.Log(xlog.MessageKey, "Hello World", "year", 2022) logger.Debug(xlog.MessageKey, "Hello World", "year", 2022) logger.Info(xlog.MessageKey, "Hello World", "year", 2022) logger.Warn(xlog.MessageKey, "Hello World", "year", 2022) logger.Error(xlog.MessageKey, "Could not read file", xlog.ErrorKey, io.ErrUnexpectedEOF, "file", "/some/file") logger.Critical(xlog.MessageKey, "DB connection is down") }
Output: {"appName":"demo","date":"2022-03-16T16:01:20Z","env":"dev","msg":"Hello World","src":"/logger_async_test.go:43","year":2022} {"appName":"demo","date":"2022-03-16T16:01:20Z","env":"dev","lvl":"DEBUG","msg":"Hello World","src":"/logger_async_test.go:44","year":2022} {"appName":"demo","date":"2022-03-16T16:01:20Z","env":"dev","lvl":"INFO","msg":"Hello World","src":"/logger_async_test.go:45","year":2022} {"appName":"demo","date":"2022-03-16T16:01:20Z","env":"dev","lvl":"WARN","msg":"Hello World","src":"/logger_async_test.go:46","year":2022} {"appName":"demo","date":"2022-03-16T16:01:20Z","env":"dev","err":"unexpected EOF","file":"/some/file","lvl":"ERROR","msg":"Could not read file","src":"/logger_async_test.go:47"} {"appName":"demo","date":"2022-03-16T16:01:20Z","env":"dev","lvl":"CRITICAL","msg":"DB connection is down","src":"/logger_async_test.go:48"}
func NewAsyncLogger ¶
func NewAsyncLogger(w io.Writer, opts ...AsyncLoggerOption) *AsyncLogger
NewAsyncLogger instantiates a new logger object that writes logs asynchronously. First param is a Writer where logs are written to. Example: os.Stdout, a custom opened os.File, an in memory strings.Buffer, etc. Second param is/are function option(s) through which you can customize the logger. Check for AsyncLoggerWith* options.
func (*AsyncLogger) Close ¶
func (logger *AsyncLogger) Close() error
Close nicely closes logger. You should call it to make sure all logs have been processed (for example at your application shutdown). Once called, any further call to any of the logging methods will be ignored.
func (*AsyncLogger) Critical ¶
func (logger *AsyncLogger) Critical(keyValues ...any)
Critical logs application component unavailable, fatal events.
func (*AsyncLogger) Debug ¶
func (logger *AsyncLogger) Debug(keyValues ...any)
Debug logs detailed debug information.
func (*AsyncLogger) Error ¶
func (logger *AsyncLogger) Error(keyValues ...any)
Error logs runtime errors that should typically be logged and monitored.
func (*AsyncLogger) Info ¶
func (logger *AsyncLogger) Info(keyValues ...any)
Info logs interesting events. Example: User logs in, SQL logs.
func (*AsyncLogger) Warn ¶
func (logger *AsyncLogger) Warn(keyValues ...any)
Warn logs exceptional occurrences that are not errors. Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.
type AsyncLoggerOption ¶
type AsyncLoggerOption func(*AsyncLogger)
AsyncLoggerOption defines optional function for configuring an async logger.
func AsyncLoggerWithChannelSize ¶
func AsyncLoggerWithChannelSize(logsChanSize uint16) AsyncLoggerOption
AsyncLoggerWithChannelSize sets internal buffered channel's size, through which logs are processed. If not called, defaults to a 256 size. Note: logging blocks if internal logs channel is full (the rate of producing messages is much higher than consuming one). Using AsyncLoggerWithChannelSize to set a higher value to increase throughput in such case can be helpful.
func AsyncLoggerWithFormatter ¶
func AsyncLoggerWithFormatter(formatter Formatter) AsyncLoggerOption
AsyncLoggerWithFormatter sets desired formatter for the logs. The JSON formatter is used by default.
func AsyncLoggerWithOptions ¶
func AsyncLoggerWithOptions(opts *CommonOpts) AsyncLoggerOption
AsyncLoggerWithOptions sets the common options. A NewCommonOpts is used by default.
func AsyncLoggerWithWorkersNo ¶
func AsyncLoggerWithWorkersNo(workersNo uint16) AsyncLoggerOption
AsyncLoggerWithWorkersNo sets the no. of workers to process internal logs channel. If not called, defaults to 1. Note: logging blocks if internal logs channel is full (the rate of producing messages is more high than consuming one). Using AsyncLoggerWithWorkersNo to increase number of workers might be helpful. Note: having a value greater than 1 implies that logs may not necessarily be written in their timestamp order. Example: goroutine #1 reads a log entry with timestamp sec :00.999 and goroutine #2 reads a log entry with timestamp sec :01.000 in the same time, but goroutine #2 is the first one to write its log entry. Note: having a value greater than 1 implies that the underlying writer is concurrent safe for Writes.
type BufferedWriter ¶
type BufferedWriter struct {
// contains filtered or unexported fields
}
BufferedWriter decorates an io.Writer so that written bytes are buffered. It is concurrent safe to use. It has the capability of auto-flushing the buffer, time interval based.
func NewBufferedWriter ¶
func NewBufferedWriter(w io.Writer, opts ...BufferedWriterOption) *BufferedWriter
NewBufferedWriter instantiates a new buffered writer.
func (*BufferedWriter) Stop ¶
func (bw *BufferedWriter) Stop()
Stop marks the writer as stopped. You should call it to make sure all data have been processed. Once called any further Write will be ignored.
type BufferedWriterOption ¶
type BufferedWriterOption func(*BufferedWriter)
BufferedWriterOption defines optional function for configuring a buffered writer.
func BufferedWriterWithFlushInterval ¶
func BufferedWriterWithFlushInterval(flushInterval time.Duration) BufferedWriterOption
BufferedWriterWithFlushInterval sets desired auto-flush interval. 10s is used by default. Pass a value <=0 if you want to disable the interval based auto-flush.
func BufferedWriterWithSize ¶
func BufferedWriterWithSize(bufSize int) BufferedWriterOption
BufferedWriterWithSize sets desired buffer size. 4Kb is used by default.
type CommonOpts ¶
type CommonOpts struct { // MinLevel is a function that returns the minimum level // allowed to be logged. // By default, is set to return warning level. MinLevel LevelProvider // MaxLevel is a function that returns the maximum level // allowed to be logged. // By default, is set to return error level. MaxLevel LevelProvider // LevelLabels is a map which defines for each level // its string representation. // By default, "CRITICAL", "ERROR", "WARN", "INFO", "DEBUG" labels are used. LevelLabels map[Level]string // LevelKey is the key under which level is found. // By default, is set to "lvl". LevelKey string // TimeKey is the key under which the current log's time is found. // By default, is set to "date". TimeKey string // Time is a function that returns the time to be logged. // By default, is set to UTC time formatted as RFC3339Nano. Time Provider // SourceKey is the key under which caller filename and line are found. // It can be set to an empty string if you want to disable this information // from logs. // By default, is set to "src". SourceKey string // Source is a provider that returns the source where the log occurred // in the call stack. Source Provider // AdditionalKeyValues holds additional key-values that will be stored // with each log. // Example: you may want to log your application version or name or // environment (dev/stage/production/...), etc. // The value can be a Provider for dynamically retrieve a value at runtime. AdditionalKeyValues []any // ErrHandler callback to process errors that occurred during logging. // By design, the logger contract does not return errors from its methods // as you most probably use it for this purpose, to log an error, and // if an error rises in this process what can you do? // You may want to log the error with the standard logger if it // suits your needs for example. // Source of errors might come from IO errors / formatting errors. // By default, is set to a no-op ErrorHandler which disregards the error. ErrHandler ErrorHandler }
CommonOpts is a struct holding common configurations for a logger.
func NewCommonOpts ¶
func NewCommonOpts() *CommonOpts
NewCommonOpts instantiates a default configured CommonOpts object. You can start customization of fields from this object.
func (*CommonOpts) BetweenMinMax ¶
func (opts *CommonOpts) BetweenMinMax(lvl Level) bool
BetweenMinMax returns true if passed level is found in [MinLevel, MaxLevel] interval, false otherwise.
func (*CommonOpts) WithDefaultKeyValues ¶
func (opts *CommonOpts) WithDefaultKeyValues(lvl Level, keyValues ...any) []any
WithDefaultKeyValues returns keyValues enriched with default ones.
type ErrorHandler ¶
ErrorHandler is a callback to handle internal logging errors. It accepts as 1st param the internal logging error. It accepts as 2nd param the key-values log entry on which error occurred.
var NopErrorHandler ErrorHandler = func(_ error, _ []any) {}
NopErrorHandler is "no-op" error handler for any error occurred during log process. It simply ignores the error.
type Formatter ¶
Formatter writes the provided key-values in a given format. Returns error in case something goes wrong.
var JSONFormatter Formatter = func(w io.Writer, keyValues []any) error { keyValues = AppendNoValue(keyValues) keyValueMap := make(map[string]any, len(keyValues)/2) for idx := 0; idx < len(keyValues); idx += 2 { keyValueMap[stringify(keyValues[idx])] = valueForJSON(keyValues[idx+1]) } encoder := json.NewEncoder(w) encoder.SetEscapeHTML(false) return encoder.Encode(keyValueMap) }
JSONFormatter serializes key-values in JSON format and writes the resulted JSON to the writer. It returns error if a serialization/writing problem is encountered.
var LogfmtFormatter Formatter = func(w io.Writer, keyValues []any) error { keyValues = AppendNoValue(keyValues) enc := logfmtEncoderPool.Get().(*logfmtEncoder) enc.Reset() defer logfmtEncoderPool.Put(enc) if err := enc.Encode(keyValues...); err != nil { return err } if _, err := w.Write(enc.buf.Bytes()); err != nil { return err } return nil }
LogfmtFormatter serializes key-values in logfmt format and writes the resulted bytes to the writer. It returns error if a serialization/writing problem is encountered. More about logfmt can be found here: https://brandur.org/logfmt .
type Level ¶
type Level byte
Level of logging.
const ( // LevelNone represents no level. // Is used internally for Log() method. LevelNone Level = 0 // LevelDebug is the level for debug logs. LevelDebug Level = 10 // LevelInfo is the level for info logs. LevelInfo Level = 20 // LevelWarning is the level for warning logs. LevelWarning Level = 30 // LevelError is the level for error logs. LevelError Level = 40 // LevelCritical is the level for critical logs. LevelCritical Level = 50 )
type LevelProvider ¶
type LevelProvider func() Level
LevelProvider is a function that provides at runtime the min/max level allowed to be logged.
func EnvLevelProvider ¶
func EnvLevelProvider(envLvlKey string, defaultLvl Level, levelLabels map[Level]string) LevelProvider
EnvLevelProvider provides a level read from OS's ENV at each call. If the level environment key is not found, or value stored in it is invalid, default provided level is returned. As it is called on each log, you may change during application run the underlying env without restarting the app, and new configured value will be used in place, if suitable.
func FixedLevelProvider ¶
func FixedLevelProvider(lvl Level) LevelProvider
FixedLevelProvider provides a fixed Level returned at each call.
type Logger ¶
type Logger interface { io.Closer // Critical logs application component unavailable, fatal events. Critical(keyValues ...any) // Error logs runtime errors that // should typically be logged and monitored. Error(keyValues ...any) // Warn logs exceptional occurrences that are not errors. // Example: Use of deprecated APIs, poor use of an API, // undesirable things that are not necessarily wrong. Warn(keyValues ...any) // Info logs interesting events. // Example: User logs in, SQL logs. Info(keyValues ...any) // Debug logs detailed debug information. Debug(keyValues ...any) // Log logs arbitrary data. Log(keyValues ...any) }
Logger provides prototype for logging with different levels. It is designed to accept variadic parameters useful for a structured logger.
type MockLogger ¶
type MockLogger struct {
// contains filtered or unexported fields
}
MockLogger is a mock for xlog.Logger contract, to be used in UT.
func (*MockLogger) CloseCallsCount ¶
func (mock *MockLogger) CloseCallsCount() int
CloseCallsCount returns the no. of times Close was called.
func (*MockLogger) Critical ¶
func (mock *MockLogger) Critical(keyValues ...any)
Critical mock logic.
func (*MockLogger) LogCallsCount ¶
func (mock *MockLogger) LogCallsCount(lvl Level) int
LogCallsCount returns the no. of times Critical/Error/Warn/Info/Debug/Log was called. Differentiate methods calls count by passing appropriate level.
func (*MockLogger) SetCloseError ¶
func (mock *MockLogger) SetCloseError(closeErr error)
SetCloseError sets the error to be returned by the Close method.
func (*MockLogger) SetLogCallback ¶
func (mock *MockLogger) SetLogCallback( lvl Level, callback func(keyValues ...any), )
SetLogCallback sets the callback to be executed inside Error/Warn/Info/Debug/Log. You can make assertions upon passed parameter(s) this way.
type MultiLogger ¶
type MultiLogger struct {
// contains filtered or unexported fields
}
MultiLogger is a composite Logger capable of logging to multiple loggers.
Example (LogToStdOutAndCustomFile) ¶
package main import ( "os" "github.com/actforgood/xlog" ) func main() { // In this example we create a (multi)logger that writes // logs to standard output, and to a file. stdOutLgrOpts := xlog.NewCommonOpts() stdOutLgrOpts.MinLevel = xlog.FixedLevelProvider(xlog.LevelNone) stdOutLgrOpts.Time = func() any { // mock time for output check return "2022-03-15T16:01:20Z" } stdOutLgrOpts.Source = xlog.SourceProvider(5, 1) // keep only filename for output check stdOutLgr := xlog.NewSyncLogger( os.Stdout, xlog.SyncLoggerWithOptions(stdOutLgrOpts), ) fileLgrOpts := xlog.NewCommonOpts() fileLgrOpts.MinLevel = xlog.FixedLevelProvider(xlog.LevelNone) fileLgrOpts.Time = func() any { // mock time for output check return "2022-03-15T16:01:20Z" } fileLgrOpts.Source = xlog.SourceProvider(5, 1) // keep only filename for output check f, err := os.CreateTemp("", "x.log-*") // you will have your well known path defined if err != nil { panic(err) } fileLgr := xlog.NewSyncLogger( f, xlog.SyncLoggerWithOptions(fileLgrOpts), ) logger := xlog.NewMultiLogger(stdOutLgr, fileLgr) defer func() { _ = logger.Close() _ = f.Close() _ = os.Remove(f.Name()) // you won't remove the file }() logger.Debug("msg", "I get written to standard output and to a file") }
Output: {"date":"2022-03-15T16:01:20Z","lvl":"DEBUG","msg":"I get written to standard output and to a file","src":"/logger_multi_test.go:91"}
Example (SplitMessagesByLevel) ¶
package main import ( "os" "github.com/actforgood/xlog" ) func main() { // In this example we create a (multi)logger that writes // debug and info logs to standard output and // warning, error and critical logs to error output. errLgrOpts := xlog.NewCommonOpts() errLgrOpts.MinLevel = xlog.FixedLevelProvider(xlog.LevelWarning) errLgrOpts.Time = func() any { // mock time for output check return "2022-03-20T16:01:20Z" } errLgrOpts.Source = xlog.SourceProvider(5, 1) // keep only filename for output check errLgr := xlog.NewSyncLogger( os.Stderr, xlog.SyncLoggerWithOptions(errLgrOpts), ) dbgLgrOpts := xlog.NewCommonOpts() dbgLgrOpts.MinLevel = xlog.FixedLevelProvider(xlog.LevelDebug) dbgLgrOpts.MaxLevel = xlog.FixedLevelProvider(xlog.LevelInfo) dbgLgrOpts.Time = func() any { // mock time for output check return "2022-03-20T16:01:20Z" } dbgLgrOpts.Source = xlog.SourceProvider(5, 1) // keep only filename for output check dbgLgr := xlog.NewSyncLogger( os.Stdout, xlog.SyncLoggerWithOptions(dbgLgrOpts), ) logger := xlog.NewMultiLogger(errLgr, dbgLgr) defer logger.Close() logger.Debug("msg", "I get written to standard output") logger.Error("msg", "I get written to standard error") }
Output: {"date":"2022-03-20T16:01:20Z","lvl":"DEBUG","msg":"I get written to standard output","src":"/logger_multi_test.go:47"}
func NewMultiLogger ¶
func NewMultiLogger(loggers ...Logger) *MultiLogger
NewMultiLogger instantiates a new multi logger object. Accepts the loggers multi-logger handles.
func (*MultiLogger) Close ¶
func (logger *MultiLogger) Close() error
Close performs clean up actions, closes resources, avoids memory leaks, etc. Make sure to call it at your application shutdown for example.
func (*MultiLogger) Critical ¶
func (logger *MultiLogger) Critical(keyValues ...any)
Critical logs application component unavailable, fatal events.
func (*MultiLogger) Debug ¶
func (logger *MultiLogger) Debug(keyValues ...any)
Debug logs detailed debug information.
func (*MultiLogger) Error ¶
func (logger *MultiLogger) Error(keyValues ...any)
Error logs runtime errors that should typically be logged and monitored.
func (*MultiLogger) Info ¶
func (logger *MultiLogger) Info(keyValues ...any)
Info logs interesting events. Example: User logs in, SQL logs.
func (*MultiLogger) Log ¶
func (logger *MultiLogger) Log(keyValues ...any)
Log logs arbitrarily data.
func (*MultiLogger) Warn ¶
func (logger *MultiLogger) Warn(keyValues ...any)
Warn logs exceptional occurrences that are not errors. Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.
type NopLogger ¶
type NopLogger struct{}
NopLogger is a no-operation Logger which does nothing. It simply ignores any log.
type Provider ¶
type Provider func() any
Provider is a function that returns at runtime a value.
func LocalTimeProvider ¶
LocalTimeProvider is a formatted current local time provider.
func SourceProvider ¶
SourceProvider is a file and line from call stack First param is the number of frames to skip in the call stack. Second param is number of directories to skip from file name backwards to root dir (0 means full path is returned).
func UTCTimeProvider ¶
UTCTimeProvider is a formatted current UTC time provider.
type SyncLogger ¶
type SyncLogger struct {
// contains filtered or unexported fields
}
SyncLogger is a Logger which writes logs synchronously. It just calls underlying writer with each log call. Note: if used in a concurrent context, log writes are not concurrent safe, unless the writer is concurrent safe. See also NewSyncWriter on this matter.
Example ¶
package main import ( "io" "os" "github.com/actforgood/xlog" ) func main() { // In this example we create a (sync)logger that writes // logs to standard output. opts := xlog.NewCommonOpts() opts.MinLevel = xlog.FixedLevelProvider(xlog.LevelNone) opts.AdditionalKeyValues = []any{ "appName", "demo", "env", "dev", } opts.Time = func() any { // mock time for output check return "2022-03-14T16:01:20Z" } opts.Source = xlog.SourceProvider(4, 1) // keep only filename for output check logger := xlog.NewSyncLogger( os.Stdout, xlog.SyncLoggerWithOptions(opts), ) defer logger.Close() logger.Log(xlog.MessageKey, "Hello World", "year", 2022) logger.Debug(xlog.MessageKey, "Hello World", "year", 2022) logger.Info(xlog.MessageKey, "Hello World", "year", 2022) logger.Warn(xlog.MessageKey, "Hello World", "year", 2022) logger.Error(xlog.MessageKey, "Could not read file", xlog.ErrorKey, io.ErrUnexpectedEOF, "file", "/some/file") logger.Critical(xlog.MessageKey, "DB connection is down") }
Output: {"appName":"demo","date":"2022-03-14T16:01:20Z","env":"dev","msg":"Hello World","src":"/logger_sync_test.go:42","year":2022} {"appName":"demo","date":"2022-03-14T16:01:20Z","env":"dev","lvl":"DEBUG","msg":"Hello World","src":"/logger_sync_test.go:43","year":2022} {"appName":"demo","date":"2022-03-14T16:01:20Z","env":"dev","lvl":"INFO","msg":"Hello World","src":"/logger_sync_test.go:44","year":2022} {"appName":"demo","date":"2022-03-14T16:01:20Z","env":"dev","lvl":"WARN","msg":"Hello World","src":"/logger_sync_test.go:45","year":2022} {"appName":"demo","date":"2022-03-14T16:01:20Z","env":"dev","err":"unexpected EOF","file":"/some/file","lvl":"ERROR","msg":"Could not read file","src":"/logger_sync_test.go:46"} {"appName":"demo","date":"2022-03-14T16:01:20Z","env":"dev","lvl":"CRITICAL","msg":"DB connection is down","src":"/logger_sync_test.go:47"}
Example (DevLogger) ¶
package main import ( "os" "github.com/actforgood/xlog" ) func main() { // In this example we create a "dev", colorized levels, logger based on TextFormatter. opts := xlog.NewCommonOpts() opts.MinLevel = xlog.FixedLevelProvider(xlog.LevelNone) opts.Time = func() any { // mock time for output check return "2022-03-14T16:01:20Z" } opts.Source = xlog.SourceProvider(4, 1) // keep only filename for output check opts.LevelLabels = map[xlog.Level]string{ // we nicely colorize the levels xlog.LevelDebug: "\033[0;34mDEBUG\033[0m", // blue xlog.LevelInfo: "\033[0;36mINFO\033[0m", // cyan xlog.LevelWarning: "\033[0;33mWARN\033[0m", // yellow xlog.LevelError: "\033[0;31mERROR\033[0m", // red xlog.LevelCritical: "\033[0;31mCRITICAL\033[0m", // red } logger := xlog.NewSyncLogger( os.Stdout, xlog.SyncLoggerWithOptions(opts), xlog.SyncLoggerWithFormatter(xlog.TextFormatter(opts)), ) defer logger.Close() logger.Debug(xlog.MessageKey, "Hello World", "year", 2022) }
Output: 2022-03-14T16:01:20Z /formatter_text_test.go:41 �[0;34mDEBUG�[0m Hello World year=2022
Example (WithLogfmt) ¶
package main import ( "os" "github.com/actforgood/xlog" ) func main() { // In this example we create a SyncLogger that writes logs // in logfmt format. opts := xlog.NewCommonOpts() opts.MinLevel = xlog.FixedLevelProvider(xlog.LevelDebug) opts.AdditionalKeyValues = []any{ "appName", "demo", "env", "dev", } opts.Time = func() any { // mock time for output check return "2022-04-12T16:01:20Z" } opts.Source = xlog.SourceProvider(4, 1) // keep only filename for output check logger := xlog.NewSyncLogger( os.Stdout, xlog.SyncLoggerWithOptions(opts), xlog.SyncLoggerWithFormatter(xlog.LogfmtFormatter), ) defer logger.Close() logger.Info(xlog.MessageKey, "Hello World", "year", 2022) }
Output: date=2022-04-12T16:01:20Z lvl=INFO src=/formatter_logfmt_test.go:43 appName=demo env=dev msg="Hello World" year=2022
Example (WithSentry) ¶
// In this example we create a SyncLogger that sends logs to Sentry. err := sentry.Init(sentry.ClientOptions{ Dsn: "https://examplePublicKey@o0.ingest.sentry.io/0", Environment: "dev", Release: "v1.4.0", }) if err != nil { panic(err) } sentry.ConfigureScope(func(scope *sentry.Scope) { scope.SetTag("appName", "demo") }) opts := xlog.NewCommonOpts() opts.MinLevel = xlog.FixedLevelProvider(xlog.LevelDebug) logger := xlog.NewSyncLogger( io.Discard, xlog.SyncLoggerWithOptions(opts), xlog.SyncLoggerWithFormatter(xlog.SentryFormatter( xlog.JSONFormatter, sentry.CurrentHub().Clone(), opts, )), ) defer func() { _ = logger.Close() _ = sentry.Flush(2 * time.Second) }() logger.Info(xlog.MessageKey, "Hello World", "year", 2022)
Output:
Example (WithSyslog) ¶
package main import ( "log/syslog" "github.com/actforgood/xlog" ) func main() { // In this example we create a SyncLogger that logs to syslog. syslogWriter, err := syslog.Dial("", "", syslog.LOG_ERR, "demo") if err != nil { panic(err) } opts := xlog.NewCommonOpts() opts.MinLevel = xlog.FixedLevelProvider(xlog.LevelDebug) logger := xlog.NewSyncLogger( syslogWriter, xlog.SyncLoggerWithFormatter(xlog.SyslogFormatter( xlog.JSONFormatter, xlog.NewDefaultSyslogLevelProvider(opts), "", )), xlog.SyncLoggerWithOptions(opts), ) defer func() { _ = logger.Close() _ = syslogWriter.Close() }() logger.Info(xlog.MessageKey, "Hello World", "year", 2022) // sudo tail -f /var/log/syslog // Apr 1 03:03:16 bogdan-Aspire-5755G demo[7572]: {"date":"2022-04-01T00:03:16.209891806Z","lvl":"INFO","msg":"Hello World","src":"/home/bogdan/work/go/xlog/formatter_decorator_syslog_test.go:45","year":2022} }
Output:
Example (WithSyslogSupportingAllSyslogLevels) ¶
package main import ( "log/syslog" "github.com/actforgood/xlog" ) func main() { // In this example we create a SyncLogger that logs to syslog, // supporting all syslog levels. We can log extra syslog // levels with Log() method. This is just an example, based on "lvl" // key and Log() method. syslogWriter, err := syslog.Dial("", "", syslog.LOG_ERR, "demo") if err != nil { panic(err) } opts := xlog.NewCommonOpts() opts.MinLevel = xlog.FixedLevelProvider(xlog.LevelNone) // need LevelNone to be able to use Log(). allSyslogLevelsMap := map[any]syslog.Priority{ // we define all levels map. // default xlog levels/labels "CRITICAL": syslog.LOG_CRIT, "ERROR": syslog.LOG_ERR, "WARN": syslog.LOG_WARNING, "INFO": syslog.LOG_INFO, "DEBUG": syslog.LOG_DEBUG, // define extra syslog levels which will be logged with Log() "EMERG": syslog.LOG_EMERG, "ALERT": syslog.LOG_EMERG, "NOTICE": syslog.LOG_NOTICE, } logger := xlog.NewSyncLogger( syslogWriter, xlog.SyncLoggerWithFormatter(xlog.SyslogFormatter( xlog.JSONFormatter, xlog.NewExtractFromKeySyslogLevelProvider( // we set syslog level provider opts.LevelKey, allSyslogLevelsMap, ), "", )), xlog.SyncLoggerWithOptions(opts), ) defer func() { _ = logger.Close() _ = syslogWriter.Close() }() // log xlog levels through dedicated APIs as usual. logger.Info(xlog.MessageKey, "Hello World", "year", 2022) // log extra syslog levels through Log(). logger.Log("lvl", "NOTICE", xlog.MessageKey, "Hello World", "year", 2022) }
Output:
func NewSyncLogger ¶
func NewSyncLogger(w io.Writer, opts ...SyncLoggerOption) *SyncLogger
NewSyncLogger instantiates a new logger object that writes logs synchronously. First param is a Writer where logs are written to. Example: os.Stdout, a custom opened os.File, an in memory strings.Buffer, etc. Second param is/are function option(s) through which you can customize the logger. Check for SyncLoggerWith* options.
func (*SyncLogger) Close ¶
func (logger *SyncLogger) Close() error
Close performs clean up actions, closes resources, avoids memory leaks, etc. Make sure to call it at your application shutdown for example.
func (*SyncLogger) Critical ¶
func (logger *SyncLogger) Critical(keyValues ...any)
Critical logs application component unavailable, fatal events.
func (*SyncLogger) Debug ¶
func (logger *SyncLogger) Debug(keyValues ...any)
Debug logs detailed debug information.
func (*SyncLogger) Error ¶
func (logger *SyncLogger) Error(keyValues ...any)
Error logs runtime errors that should typically be logged and monitored.
func (*SyncLogger) Info ¶
func (logger *SyncLogger) Info(keyValues ...any)
Info logs interesting events. Example: User logs in, SQL logs.
func (*SyncLogger) Warn ¶
func (logger *SyncLogger) Warn(keyValues ...any)
Warn logs exceptional occurrences that are not errors. Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.
type SyncLoggerOption ¶
type SyncLoggerOption func(*SyncLogger)
SyncLoggerOption defines optional function for configuring a sync logger.
func SyncLoggerWithFormatter ¶
func SyncLoggerWithFormatter(formatter Formatter) SyncLoggerOption
SyncLoggerWithFormatter sets desired formatter. The JSON formatter is used by default.
func SyncLoggerWithOptions ¶
func SyncLoggerWithOptions(opts *CommonOpts) SyncLoggerOption
SyncLoggerWithOptions sets the common options. A NewCommonOpts is used by default.
type SyslogLevelProvider ¶
SyslogLevelProvider is a function that extracts the syslog level.
func NewDefaultSyslogLevelProvider ¶
func NewDefaultSyslogLevelProvider(opts *CommonOpts) SyslogLevelProvider
NewDefaultSyslogLevelProvider returns a SyslogLevelProvider that maps xlog default Levels to their appropriate syslog Levels.
func NewExtractFromKeySyslogLevelProvider ¶
func NewExtractFromKeySyslogLevelProvider( key string, syslogLevels map[any]syslog.Priority, ) SyslogLevelProvider
NewExtractFromKeySyslogLevelProvider extracts the value of given key as first param and returns the syslog level from provided map as second param.
Source Files ¶
- common_options.go
- doc.go
- formatter.go
- formatter_decorator_sentry.go
- formatter_decorator_syslog.go
- formatter_json.go
- formatter_logfmt.go
- formatter_text.go
- level.go
- logger.go
- logger_async.go
- logger_async_options.go
- logger_mock.go
- logger_multi.go
- logger_nop.go
- logger_sync.go
- logger_sync_options.go
- writer_buffered.go
- writer_sync.go