xlog

package module
v0.5.2 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2020 License: MIT Imports: 11 Imported by: 0

README

General

xlog is a library for easily logging into multiple endpoints, such as files, stdout, stderr, syslog, etc at the same time. Configure logger(s) once, and then write to several log endpoints according to defined rules with one function call. Flexible configuration of loggers allows controlling which recorders will be used in different cases.

Overview

LogRecorder objects represent log endpoints and provide write function and other methods to correctly interact with them. Recorders connect to one or more Logger objects which control them and determinate which log messages they should write and when. Basically all what recorder do it is listening signals from connected loggers and write the specific log messages when WRITE signal received.

Logger objects unite several recorders to correspond to some context. You can specify bitmasks for each connected recorder to control which recorders will be used for each severity (log level). Every logger also has a list of default recorders which will be used by default for writing if a custom list is not specified at the write function call.

Usage

🛈 Use godoc to get complete package documentation.

For example, we want to write logs to the stdout and 2 log files. Files should be used as primary endpoints: one for the info messages and one for the errors; stdout gonna be optional endpoint (will use it manually when we need).

First, you should create LogRecorder objects for necessary endpoints. Usually, you will need one recorder per endpoint. Also, we need Logger object to control recorders. If you have several contexts in your app, you probably may need several loggers.

Create and activate recorders:

// ...

stdoutRecorder  := xlog.NewIoDirectRecorder(os.Stdout)
infFileRecorder := xlog.NewIoDirectRecorder(hFileInfo)
errFileRecorder := xlog.NewIoDirectRecorder(hFileError)

go stdoutRecorder.Listen()
go infFileRecorder.Listen()
go errFileRecorder.Listen()

// defer func() { stdoutRecorder.Intrf().ChCtl  <- xlog.SignalStop() }()
// defer func() { intFileRecorder.Intrf().ChCtl <- xlog.SignalStop() }()
// defer func() { errFileRecorder.Intrf().ChCtl <- xlog.SignalStop() }()

OR you can use the SpawnXXX functions (that's the recommended way).

stdoutRecorder  := xlog.SpawnIoDirectRecorder(os.Stdout)
infFileRecorder := xlog.SpawnIoDirectRecorder(hFileInfo)
errFileRecorder := xlog.SpawnIoDirectRecorder(hFileError)

Declare recorder IDs which will be used in the logger to access the recorders.

recStdout := xlog.RecorderID("rec-stdout")
recInfo   := xlog.RecorderID("rec-finfo")
recErr    := xlog.RecorderID("rec-ferr")

Create Logger and connect recorders:

logger := xlog.NewLogger()
_ = logger.RegisterRecorder(recStdout, stdoutRecorder.Intrf())
_ = logger.RegisterRecorder(recInfo,  infFileRecorder.Intrf())
_ = logger.RegisterRecorder(recErr,   errFileRecorder.Intrf())

Logger.RegisterRecorder() registers recorder in the logger and bind it to the given ID. Logger can interact only with registered recorders.

Configure the logger:

_ = logger.DefaultsSet([]RecorderID{recInfo, recErr})
_ = logger.SetSeverityMask(recInfo, xlog.SeverityMinor | xlog.Warning) // Notice | Info | Warning
_ = logger.SetSeverityMask(recErr,  xlog.SeverityMajor) // Warning | Emerg | Alert | Critical | Error

Here we set infFileRecorder and errFileRecorder as default recorders for the logger. These recorders will be used by default unless otherwise specified. So, to write into the stdoutRecorder, we need manually pass recStdout id at the Logger.WriteMsg() call. Also, we set severity masks for the recorders: errFileRecorder will write only "bad" severities ignoring messages with Notice, Info and Debug flags; infFileRecorder opposite will write only Info, Notice and Warning (optional) messages. This allows the recorder to automatically distribute messages to the correct recorders, you need to specify only severity flag for the message.

And finally, initialise it:

if err := logger.Initialise(); err != nil {
    os.Exit(1)
} else {
    defer func() { // at exit
        logger.Close()
    }()
}

To log something just call Logger.Write(). This function receives severity and attribute flags as first parameter and message with arguments as second and further (like fmt.Printf). Logger.Write() always use default recorders of the logger.

logger.Write(xlog.Info, "my message %d", 123)     // will be send to the infFileRecorder
logger.Write(xlog.Error, "something went wrong")  // will be send to the errFileRecorder
logger.Write(xlog.Warning, "imp. notification")   // will be send to both file recorders
logger.Write(xlog.Debug, "some debug info")       // this message will be ignored

If you have a long operation and you want to handle it with a single log message, you can use LogMsg to construct a complex message. In this case, you should use Logger.WriteMsg() instead.

msg := xlog.NewLogMsg().
    SetFlags(xlog.Info).
    Setf("message header\n")
// ... do something
msg.Addf("  op1: OK\n")
// .. do something
msg.Addf("  op2: OK")
msg.UpdateTime()

logger.WriteMsg(nil, msg)

To manually select which recorders (of a specific logger) should be used to handle a message, you should use Logger.WriteMsg() with specified list of recorders IDs as the first argument. Otherwise (nil argument), logger will use default recorders.

// write only to the stdout recorder (registered earlier as recStdout)
logger.WriteMsg([]xlog.RecorderID{recStdout}, xlog.Message("my message %d", 123))

// send Critical message to all recorders
recAll := []xlog.RecorderID{recStdout, recInfo, recErr}
logger.WriteMsg(recAll, xlog.Message("my message").SetFlags(xlog.Critical))

xlog.Message("msg") is equivalent to xlog.NewMsg().Setf("msg")

Some advanced features

Safe initialisation

Logger.Initialise() can receive an optional parameter - list of recorder objects. If it specified the function will use it to call LogRecorder.IsListening() functions to ensure that recorders are ready to receive the signals. Because if some of the recorders are not listening, initialisation call may lock the goroutine. Besides if parameter xlog.cfgAutoStartListening is enabled, the function can call a listener by self and continue without an error.

So, if you don't use SpawnXXX functions, this way is recommended:

l := xlog.NewLogger()
var gRecorders xlog.ListOfRecorders
r1 := xlog.NewIoDirectRecorder(os.Stdout)
r2 := xlog.NewSyslogRecorder("my-prefix")
go r1.Listen()
go r2.Listen()
gRecorders.Add(r1)
gRecorders.Add(r2)

l.RegisterRecorder("REC-1", r1.Intrf())
l.RegisterRecorder("REC-2", r2.Intrf())

if err := l.Initialise(gRecorders); err != nil {
    if err == xlog.ErrNotListening { ... }
}
Handling write errors

Default recorders just skip write signal in case of error to do not lock a caller goroutine. It means that you will not be notified if the error happens. So, if you need to handle msg write errors, you can attach external channel to receive error messages (it's errors from the internal write function only).

First, you need to create a receiver: it must be an async channel chan error with a sufficient buffer size so as not to block the recorder. You should provide the proper usage of this channel(s). If you use a single handler for all your recorders, you have to make sure that it will not be closed or locked while recorders use it.

To set a channel as an error handler for the default recorder you need to send SigSetErrChan control signal. To construct this signal use SignalSetErrChan function. To drop the channel for the logger send SigDropErrChan signal using SignalDropErrChan function.

chErr := make(chan error, 256)
go func() {
    for msg := range chErr {
        if msg == nil { continue } // unreachable
        fmt.Printf("RECORDER ERROR: %s\n", msg.Error())
    }
}

r := xlog.NewIoDirectRecorder(os.Stdout, "my-prefix")
r.Intrf().ChCtl <- xlog.SignalSetErrChan(chErr)
runtime.Gosched()

// ...

r.Intrf().ChCtl <- xlog.SignalDropErrChan()
time.Sleep(time.Second)
close(chErr)

...

Log message formatters & custom flags

You can control the recorder's output by custom format functions. Recorder will call a formatter before the writing to construct a final output string. This functions should implement the interface: type FormatFunc func(*xlog.LogMsg) string. To set format function call LogRecorder.FormatFunc().

For example, to get colored output, you can do this:

r := xlog.NewIoDirectRecorder(os.Stdout).FormatFunc( func(msg *xlog.LogMsg) string {
    // drop attributes, get severity flags only
    sev := msg.GetFlags() &^ xlog.SeverityShadowMask

    // Logger.WriteMsg ensures that several severity flags
    // issue is not possible here; we can use switch here
    switch (sev) {
    case xlog.Emerg:
        return fmt.Sprintf("\x1b[30;41m%s\x1b[0m", msg.GetContent())
    case xlog.Alert:
        return fmt.Sprintf("\x1b[30;41m%s\x1b[0m", msg.GetContent())
    case xlog.Critical:
        return fmt.Sprintf("\x1b[30;41m%s\x1b[0m", msg.GetContent())
    case xlog.Error:
        return fmt.Sprintf("\x1b[31m%s\x1b[0m", msg.GetContent())
    case xlog.Warning:
        return fmt.Sprintf("\x1b[33m%s\x1b[0m", msg.GetContent())
    case xlog.Notice:
        return fmt.Sprintf("\x1b[1m%s\x1b[0m", msg.GetContent())
    default:
        return msg.GetContent()
    }
})

Furthermore, LogMsg has Data field which can be used to pass any kind additional information into the formatter:

type payload struct {
  a string
  b int
}

func formatter(msg *xlog.LogMsg) string {
    var extra string
    if data, ok := msg.Data.(payload); ok {
        extra = fmt.Sprintf(" with %v", data)
    }
    return msg.GetContent() + extra
}

// ...

msg := xlog.NewLogMsg().Setf("message")
msg.Data = payload{"extra", 83485}
logger.WriteMsg(nil, msg)

Besides 10 default flags (8 severities and 2 attributes) custom flags are available. You can declare em like this:

var MySeverity1 xlog.MsgFlagT = xlog.CustomB1
var MySeverity1 xlog.MsgFlagT = xlog.CustomB2

var MyAttribute1 xlog.MsgFlagT = xlog.CustomB3
var MyAttribute2 xlog.MsgFlagT = xlog.CustomB4

With custom flags and message formatters you can realise extra functionality without creating a custom recorder.

Custom recorders

TL;DW
Docs coming soon. For now, you can use rec_direct.go as an example.

Documentation

Index

Constants

View Source
const (
	SigInit  signalType = "SIG_INIT"
	SigClose signalType = "SIG_CLOSE"
	SigStop  signalType = "SIG_STOP"

	SigSetErrChan  signalType = "SIG_SET_ERR"
	SigSetDbgChan  signalType = "SIG_SET_DBG"
	SigDropErrChan signalType = "SIG_DROP_ERR"
	SigDropDbgChan signalType = "SIG_GROP_DBG"
)
View Source
const After ssDirection = false
View Source
const Before ssDirection = true

Variables

View Source
var CfgAutoStartListening bool_s = bool_s{/* contains filtered or unexported fields */}

If true, Initialise function with passed 'objects' argument will start listeners by self for not-listening recorders.

default value: true
View Source
var CfgGlobalDisable bool_s = bool_s{/* contains filtered or unexported fields */}

If true, all Logger methods will be skipped.

default value: false
View Source
var ErrNoRecorders = errors.New("xlog: the logger has no registered recorders")

ErrNoRecorders returns when user tries to write to the empty logger.

View Source
var ErrNotInitialised = errors.New("xlog: not initialised")

ErrNotInitialised returns when you try to write in the uninitialised logger.

View Source
var ErrNotListening error = errors.New("xlog: recorder is not listening")

ErrNotListening returns when Logger tries to send a signal to recorder which is not ready to receive signals.

View Source
var ErrNotWhereToWrite = errors.New("xlog: " +
	"the logger has no default recorders, " +
	"but custom recorders are not specified")

ErrNotWhereToWrite returns when a user tries to write to the logger without configured default recorders with unspecified custom recorders field (nil).

View Source
var ErrWrongFlagValue = errors.New("xlog: wrong flag value")

ErrWrongFlagValue returns when some function detects a wrong flag value.

View Source
var ErrWrongParameter = errors.New("xlog: wrong parameter")

ErrWrongParameter returns when passed parameter is incorrect e.g. RecorderID("").

View Source
var ErrWrongRecorderID = errors.New("xlog: wrong recorder id")

ErrWrongRecorderID returns when recorder id can not be found in the recorder list or if the new id already used in the logger object.

Functions

func DbgMsg added in v0.3.0

func DbgMsg(recorderXID xid.ID, format string, args ...interface{}) debugMessage

func IoDirectDefaultFormatter added in v0.1.5

func IoDirectDefaultFormatter(msg *LogMsg) string

func NewDebugLogger added in v0.3.0

func NewDebugLogger(writer io.Writer) *debugLogger

func NewIoDirectRecorder

func NewIoDirectRecorder(
	writer io.Writer, prefix ...string,
) *ioDirectRecorder

NewIoDirectRecorder allocates and returns a new I/O direct recorder (recorder which represents endpoint as io.Writer).

func NewSyslogRecorder

func NewSyslogRecorder(prefix string) *syslogRecorder

NewSyslogRecorder allocates and returns a new syslog recorder.

func SignalClose added in v0.3.0

func SignalClose() controlSignal

func SignalDropDbgChan added in v0.4.1

func SignalDropDbgChan() controlSignal

func SignalDropErrChan added in v0.4.1

func SignalDropErrChan() controlSignal

func SignalInit added in v0.3.0

func SignalInit(chErr chan error) controlSignal

func SignalSetDbgChan added in v0.4.1

func SignalSetDbgChan(chDbg chan<- debugMessage) controlSignal

func SignalSetErrChan added in v0.4.1

func SignalSetErrChan(chErr chan<- error) controlSignal

func SignalStop added in v0.3.0

func SignalStop() controlSignal

func SpawnIoDirectRecorder added in v0.4.1

func SpawnIoDirectRecorder(
	writer io.Writer, prefix ...string,
) *ioDirectRecorder

SpawnIoDirectRecorder creates recorder and starts a listener.

func SpawnSyslogRecorder added in v0.4.1

func SpawnSyslogRecorder(prefix string) *syslogRecorder

SpawnSyslogRecorder creates recorder and starts a listener.

Types

type BatchResult added in v0.1.5

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

BatchResult is designed to accumulate the status (success and failure) of several operations. It is used as a partial error when some operations complete successfully and some do not. It is mainly used in processing lists of recorders, for example during initialisation ops.

func (BatchResult) Error added in v0.1.5

func (br BatchResult) Error() string

func (*BatchResult) Fail added in v0.1.5

func (br *BatchResult) Fail(rec RecorderID, err error) *BatchResult

func (BatchResult) GetErrors added in v0.4.1

func (br BatchResult) GetErrors() map[RecorderID]error

func (BatchResult) GetSuccessful added in v0.4.1

func (br BatchResult) GetSuccessful() []RecorderID

func (*BatchResult) OK added in v0.1.5

func (br *BatchResult) OK(rec RecorderID) *BatchResult

func (*BatchResult) SetMsg added in v0.1.5

func (br *BatchResult) SetMsg(msgFmt string, msgArgs ...interface{}) *BatchResult

type FormatFunc

type FormatFunc func(*LogMsg) string

FormatFunc is an interface for the recorder's format function. This function handles the log message object and returns final output string.

type InternalError added in v0.1.5

type InternalError struct {
	Err  error
	File string
	Func string
	Line int
}

This error used for critical situations caused by wrong package usage (by user). The operations cannot be done with the wrong data, may cause data damage or panics (in this function call).

func (InternalError) Error added in v0.1.5

func (e InternalError) Error() string

type ListOfRecorders added in v0.5.0

type ListOfRecorders []LogRecorder

func (*ListOfRecorders) Add added in v0.5.0

func (list *ListOfRecorders) Add(rec LogRecorder)

func (ListOfRecorders) FindByID added in v0.5.0

func (list ListOfRecorders) FindByID(id xid.ID) LogRecorder

type LogMsg

type LogMsg struct {
	Data interface{} // extra data
	// contains filtered or unexported fields
}

LogMsg represents a log message. It contains message data, flags, time and extra data for non-default handling.

func Message added in v0.1.1

func Message(msgFmt string, msgArgs ...interface{}) *LogMsg

Message builds and returns simple message with default severity (0).

func NewLogMsg

func NewLogMsg() *LogMsg

NewLogMsg allocates and returns a new LogMsg.

func (*LogMsg) Addf added in v0.1.5

func (LM *LogMsg) Addf(msgFmt string, msgArgs ...interface{}) *LogMsg

Addf attaches new string to the end of the existing message text.

func (*LogMsg) Addfn added in v0.3.0

func (LM *LogMsg) Addfn(msgFmt string, msgArgs ...interface{}) *LogMsg

Addfn adds new string to the existing message text as a new line.

func (*LogMsg) GetContent added in v0.1.1

func (LM *LogMsg) GetContent() string

func (*LogMsg) GetFlags added in v0.2.1

func (LM *LogMsg) GetFlags() MsgFlagT

func (*LogMsg) GetTime added in v0.1.1

func (LM *LogMsg) GetTime() time.Time

func (*LogMsg) SetFlags added in v0.2.1

func (LM *LogMsg) SetFlags(flags MsgFlagT) *LogMsg

SetFlags sets severity and arrtibute flags for the message.

func (*LogMsg) Setf added in v0.1.5

func (LM *LogMsg) Setf(msgFmt string, msgArgs ...interface{}) *LogMsg

Setf resets current message text and sets the given string.

func (*LogMsg) UpdateTime added in v0.1.1

func (LM *LogMsg) UpdateTime() *LogMsg

UpdateTime updates message's time to current time.

type LogRecorder added in v0.4.1

type LogRecorder interface {
	Listen()
	IsListening() bool
	Intrf() RecorderInterface
	GetID() xid.ID
}

LogRecorder is an interface for the log endpoint recorder. These types should provide write functions and other methods to correctly interact with a logging destination objects (e.g. files, streams etc).

type Logger

type Logger struct {
	sync.RWMutex
	// contains filtered or unexported fields
}

func NewLogger added in v0.1.3

func NewLogger() *Logger

NewLogger allocates and returns new logger.

func (*Logger) ChangeSeverityOrder added in v0.1.5

func (L *Logger) ChangeSeverityOrder(
	recorder RecorderID, srcFlag MsgFlagT, dir ssDirection, trgFlag MsgFlagT,
) error

ChangeSeverityOrder changes severity order for the specified recorder. It takes specified flag and moves it before/after the target flag position.

Only custom flags can be moved (currently disabled).

func (*Logger) Close

func (L *Logger) Close()

Close disconnects (sends a close signal) all registered recorders and sets the 'uninitialised' state for the logger. Meanwhile, it does not unregister (remove from the logger) recorders.

func (*Logger) DefaultsAdd added in v0.4.0

func (L *Logger) DefaultsAdd(recorders []RecorderID) error

DefaultsAdd adds given recorders into the default list of the logger.

func (*Logger) DefaultsRemove added in v0.4.0

func (L *Logger) DefaultsRemove(recorders []RecorderID) error

DefaultsRemove removes given recorders form defaults in this logger.

func (*Logger) DefaultsSet added in v0.4.0

func (L *Logger) DefaultsSet(recorders []RecorderID) error

DefaultsSet sets given recorders as default for this logger.

func (*Logger) Initialise

func (L *Logger) Initialise(objects ...ListOfRecorders) error

Initialise sends an initialisation signal to each registered recorder.

func (*Logger) NumberOfRecorders added in v0.1.5

func (L *Logger) NumberOfRecorders() int

func (*Logger) RegisterRecorder

func (L *Logger) RegisterRecorder(
	id RecorderID,
	intrf RecorderInterface,
	asDefault ...bool,
) error

RegisterRecorder registers the recorder in the logger with the given id. This function receives optional parameter 'asDefault', which says whether the need to set it as default recorder. If the optional parameter is not specified, it will have a true value.

func (*Logger) SetSeverityMask

func (L *Logger) SetSeverityMask(recorder RecorderID, flags MsgFlagT) error

SetSeverityMask sets which severities allowed for the given recorder in this logger.

func (*Logger) UnregisterRecorder added in v0.2.2

func (L *Logger) UnregisterRecorder(id RecorderID) error

UnregisterRecorder disconnects specified recorder from the logger (sends a close signal) and removes recorder interface from the logger.

func (*Logger) Write

func (L *Logger) Write(flags MsgFlagT, msgFmt string, msgArgs ...interface{}) error

Write builds the message with format line and specified message flags, then calls WriteMsg. It allows avoiding calling fmt.Sprintf() function and LogMsg's functions directly, it wraps all of it.

Returns nil in case of success otherwise returns an error.

func (*Logger) WriteMsg

func (L *Logger) WriteMsg(recorders []RecorderID, msg *LogMsg) error

WriteMsg send write signal with given message to the specified recorders. If custom recorders are not specified, uses default recorders of this logger.

Returns nil on success and error on fail.

type MsgFlagT added in v0.2.1

type MsgFlagT uint16

MsgFlagT type represents messages bit flags for severities and attributes.

const (
	Emerg    MsgFlagT = 0x01 // 0000 0000 0000 0001
	Alert    MsgFlagT = 0x02 // 0000 0000 0000 0010
	Critical MsgFlagT = 0x04 // 0000 0000 0000 0100
	Error    MsgFlagT = 0x08 // 0000 0000 0000 1000
	Warning  MsgFlagT = 0x10 // 0000 0000 0001 0000
	Notice   MsgFlagT = 0x20 // 0000 0000 0010 0000
	Info     MsgFlagT = 0x40 // 0000 0000 0100 0000
	Debug    MsgFlagT = 0x80 // 0000 0000 1000 0000

	CustomB1 MsgFlagT = 0x1000 // 0001 0000 0000 0000
	CustomB2 MsgFlagT = 0x2000 // 0010 0000 0000 0000
)
const (
	StackTrace      MsgFlagT = 0x100 // 0000 0001 0000 0000
	StackTraceShort MsgFlagT = 0x800 // 0000 1000 0000 0000

	CustomB3 MsgFlagT = 0x4000 // 0100 0000 0000 0000
	CustomB4 MsgFlagT = 0x8000 // 1000 0000 0000 0000
)
const (
	SeverityAll     MsgFlagT = 0x30FF // Default | Custom
	SeverityMajor   MsgFlagT = 0x001F // Major = Emerg | Alert | Critical | Error | Warning
	SeverityMinor   MsgFlagT = 0x00E0 // Minor = Notice | Info
	SeverityDefault MsgFlagT = 0x00FF // Default = Major | Minor | Debug
	SeverityCustom  MsgFlagT = 0x3000 // Custom = CustomB1 | CustomB2
)
const AttributeShadowMask MsgFlagT = 0x30FF

bit-reset (reversed) mask for attribute flags

const SeverityShadowMask MsgFlagT = 0xCF00

bit-reset (reversed) mask for severity flags

func (MsgFlagT) String added in v0.2.1

func (f MsgFlagT) String() string

Returns string with severity name in text format. For unexpected flags returns string with hexadecimal code.

type RecorderID

type RecorderID string

RecorderID is an identifier used in Logger functions to select recorders.

type RecorderInterface added in v0.4.0

type RecorderInterface struct {
	ChCtl chan<- controlSignal
	ChMsg chan<- LogMsg
	// contains filtered or unexported fields
}

RecorderInterface structure represents interface channels of the recorder.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL