logging is now compatible with klog.
See https://git.kyleisom.net/lib/libklogger
This commit is contained in:
457
logging/log.go
457
logging/log.go
@@ -1,274 +1,279 @@
|
||||
// Package logging provides domain-based logging in the same style as
|
||||
// sylog. Domains are some name for which logging can be selectively
|
||||
// enabled or disabled. Logging also differentiates between normal
|
||||
// messages (which are sent to standard output) and errors, which are
|
||||
// sent to standard error; debug messages will also include the file
|
||||
// and line number.
|
||||
//
|
||||
// Domains are intended for identifying logging subystems. A domain
|
||||
// can be suppressed with Suppress, and re-enabled with Enable. There
|
||||
// are prefixed versions of these as well.
|
||||
//
|
||||
// Packages (e.g. those meant to be imported by programs) using the
|
||||
// loggers here should observe a few etiquette guides:
|
||||
//
|
||||
// 1. A package should never suppress or enable other loggers except
|
||||
// via an exported function that should be called by the end user.
|
||||
//
|
||||
// 2. A package should never call the global `SetLevel`; this is
|
||||
// reserved for the end user.
|
||||
//
|
||||
// 3. Packages should use consistent, sane domains: preferably,
|
||||
// related packages should use an unsurprising common prefix in their
|
||||
// domains.
|
||||
//
|
||||
// This package was adapted from the CFSSL logging code.
|
||||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/kisom/goutils/mwc"
|
||||
"time"
|
||||
)
|
||||
|
||||
var logConfig = struct {
|
||||
registered map[string]*Logger
|
||||
lock *sync.Mutex
|
||||
}{
|
||||
registered: map[string]*Logger{},
|
||||
lock: new(sync.Mutex),
|
||||
// Logger provides a standardised logging interface.
|
||||
type Logger interface {
|
||||
// SetLevel sets the minimum log level.
|
||||
SetLevel(Level)
|
||||
|
||||
// Good returns true if the Logger is healthy.
|
||||
Good() bool
|
||||
|
||||
// Status returns an error corresponding to the logger's state;
|
||||
// if it's healthy (e.g. Good() returns true), Error will
|
||||
// return nil.
|
||||
Status() error
|
||||
|
||||
// Close gives the Logger the opportunity to perform any cleanup.
|
||||
Close()
|
||||
|
||||
// Log messages consist of four components:
|
||||
//
|
||||
// 1. The **level** attaches a notion of priority to the log message.
|
||||
// Several log levels are available:
|
||||
//
|
||||
// + FATAL (32): the system is in an unsuable state, and cannot
|
||||
// continue to run. Most of the logging for this will cause the
|
||||
// program to exit with an error code.
|
||||
// + CRITICAL (16): critical conditions. The error, if uncorrected, is
|
||||
// likely to cause a fatal condition shortly. An example is running
|
||||
// out of disk space. This is something that the ops team should get
|
||||
// paged for.
|
||||
// + ERROR (8): error conditions. A single error doesn't require an
|
||||
// ops team to be paged, but repeated errors should often trigger a
|
||||
// page based on threshold triggers. An example is a network
|
||||
// failure: it might be a transient failure (these do happen), but
|
||||
// most of the time it's self-correcting.
|
||||
// + WARNING (4): warning conditions. An example of this is a bad
|
||||
// request sent to a server. This isn't an error on the part of the
|
||||
// program, but it may be indicative of other things. Like errors,
|
||||
// the ops team shouldn't be paged for errors, but a page might be
|
||||
// triggered if a certain threshold of warnings is reached (which is
|
||||
// typically much higher than errors). For example, repeated
|
||||
// warnings might be a sign that the system is under attack.
|
||||
// + INFO (2): informational message. This is a normal log message
|
||||
// that is used to deliver information, such as recording
|
||||
// requests. Ops teams are never paged for informational
|
||||
// messages. This is the default log level.
|
||||
// + DEBUG (1): debug-level message. These are only used during
|
||||
// development or if a deployed system repeatedly sees abnormal
|
||||
// errors.
|
||||
//
|
||||
// The numeric values indicate the priority of a given level.
|
||||
//
|
||||
// 2. The **actor** is used to specify which component is generating
|
||||
// the log message. This could be the program name, or it could be
|
||||
// a specific component inside the system.
|
||||
//
|
||||
// 3. The **event** is a short message indicating what happened. This is
|
||||
// most like the traditional log message.
|
||||
//
|
||||
// 4. The **attributes** are an optional set of key-value string pairs that
|
||||
// provide additional information.
|
||||
//
|
||||
// Additionally, each log message has an associated timestamp. For the
|
||||
// text-based logs, this is "%FT%T%z"; for the binary logs, this is a
|
||||
// 64-bit Unix timestamp. An example text-based timestamp might look like ::
|
||||
//
|
||||
// [2016-03-27T20:59:27-0700] [INFO] [actor:server event:request received] client=192.168.2.5 request-size=839
|
||||
//
|
||||
// Note that this is organised in a manner that facilitates parsing::
|
||||
//
|
||||
// /\[(\d{4}-\d{3}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{4})\] \[(\w+\)]\) \[actor:(.+?) event:(.+?)\]/
|
||||
//
|
||||
// will cover the header:
|
||||
//
|
||||
// + ``$1`` contains the timestamp
|
||||
// + ``$2`` contains the level
|
||||
// + ``$3`` contains the actor
|
||||
// + ``$4`` contains the event
|
||||
Debug(actor, event string, attrs map[string]string)
|
||||
Info(actor, event string, attrs map[string]string)
|
||||
Warn(actor, event string, attrs map[string]string)
|
||||
Error(actor, event string, attrs map[string]string)
|
||||
Critical(actor, event string, attrs map[string]string)
|
||||
Fatal(actor, event string, attrs map[string]string)
|
||||
FatalCode(exitcode int, actor, event string, attrs map[string]string)
|
||||
FatalNoDie(actor, event string, attrs map[string]string)
|
||||
}
|
||||
|
||||
// SetLevel sets the logging level for all loggers.
|
||||
func SetLevel(level Level) {
|
||||
logConfig.lock.Lock()
|
||||
defer logConfig.lock.Unlock()
|
||||
// A LogWriter is a Logger that operates on an io.Writer.
|
||||
type LogWriter struct {
|
||||
wo, we io.Writer
|
||||
lvl Level
|
||||
state error
|
||||
snl bool // suppress newline
|
||||
}
|
||||
|
||||
for _, l := range logConfig.registered {
|
||||
l.SetLevel(level)
|
||||
// NewLogWriter takes an output writer (wo) and an error writer (we),
|
||||
// and produces a new Logger. If the error writer is nil, error logs
|
||||
// will be multiplexed onto the output writer.
|
||||
func NewLogWriter(wo, we io.Writer) *LogWriter {
|
||||
if we == nil {
|
||||
we = wo
|
||||
}
|
||||
|
||||
return &LogWriter{
|
||||
wo: wo,
|
||||
we: we,
|
||||
lvl: DefaultLevel,
|
||||
state: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultLevel defaults to the notice level of logging.
|
||||
const DefaultLevel = LevelNotice
|
||||
|
||||
// Init returns a new default logger. The domain is set to the
|
||||
// program's name, and the default logging level is used.
|
||||
func Init() *Logger {
|
||||
l, _ := New(filepath.Base(os.Args[0]), DefaultLevel)
|
||||
return l
|
||||
}
|
||||
|
||||
// A Logger writes logs on behalf of a particular domain at a certain
|
||||
// level.
|
||||
type Logger struct {
|
||||
enabled bool
|
||||
lock *sync.Mutex
|
||||
domain string
|
||||
level Level
|
||||
out io.WriteCloser
|
||||
err io.WriteCloser
|
||||
}
|
||||
|
||||
// Close closes the log's writers and suppresses the logger.
|
||||
func (l *Logger) Close() error {
|
||||
Suppress(l.domain)
|
||||
err := l.out.Close()
|
||||
if err != nil {
|
||||
return nil
|
||||
func (lw *LogWriter) output(w io.Writer, lvl Level, actor, event string, attrs map[string]string) {
|
||||
t := time.Now().Format(DateFormat)
|
||||
fmt.Fprintf(w, "[%s] [%s] [actor:%s event:%s]", t, levelPrefix[lvl], actor, event)
|
||||
for k, v := range attrs {
|
||||
fmt.Fprintf(w, " %s=%s", k, v)
|
||||
}
|
||||
|
||||
return l.err.Close()
|
||||
}
|
||||
|
||||
// Suppress ignores logs from a specific domain.
|
||||
func Suppress(domain string) {
|
||||
logConfig.lock.Lock()
|
||||
defer logConfig.lock.Unlock()
|
||||
l, ok := logConfig.registered[domain]
|
||||
if ok {
|
||||
l.Suppress()
|
||||
if !lw.snl {
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
// SuppressPrefix suppress logs whose domain is prefixed with the
|
||||
// prefix.
|
||||
func SuppressPrefix(prefix string) {
|
||||
logConfig.lock.Lock()
|
||||
defer logConfig.lock.Unlock()
|
||||
for domain, l := range logConfig.registered {
|
||||
if strings.HasPrefix(domain, prefix) {
|
||||
l.Suppress()
|
||||
}
|
||||
// Debug emits a debug-level message. These are only used during
|
||||
// development or if a deployed system repeatedly sees abnormal
|
||||
// errors.
|
||||
//
|
||||
// Actor specifies the component emitting the message; event indicates
|
||||
// the event that caused the log message to be emitted. attrs is a map
|
||||
// of key-value string pairs that can be used to provide additional
|
||||
// information.
|
||||
func (lw *LogWriter) Debug(actor, event string, attrs map[string]string) {
|
||||
if lw.lvl > LevelDebug {
|
||||
return
|
||||
}
|
||||
lw.output(lw.wo, LevelDebug, actor, event, attrs)
|
||||
}
|
||||
|
||||
// SuppressAll suppresses all logging output.
|
||||
func SuppressAll() {
|
||||
logConfig.lock.Lock()
|
||||
defer logConfig.lock.Unlock()
|
||||
for _, l := range logConfig.registered {
|
||||
l.Suppress()
|
||||
// Info emits an informational message. This is a normal log message
|
||||
// that is used to deliver information, such as recording
|
||||
// requests. Ops teams are never paged for informational
|
||||
// messages. This is the default log level.
|
||||
//
|
||||
// Actor specifies the component emitting the message; event indicates
|
||||
// the event that caused the log message to be emitted. attrs is a map
|
||||
// of key-value string pairs that can be used to provide additional
|
||||
// information.
|
||||
func (lw *LogWriter) Info(actor, event string, attrs map[string]string) {
|
||||
if lw.lvl > LevelInfo {
|
||||
return
|
||||
}
|
||||
lw.output(lw.wo, LevelInfo, actor, event, attrs)
|
||||
}
|
||||
|
||||
// Enable enables logs from a specific domain.
|
||||
func Enable(domain string) {
|
||||
logConfig.lock.Lock()
|
||||
defer logConfig.lock.Unlock()
|
||||
l, ok := logConfig.registered[domain]
|
||||
if ok {
|
||||
l.Enable()
|
||||
// Warn emits a warning message. An example of this is a bad request
|
||||
// sent to a server. This isn't an error on the part of the program,
|
||||
// but it may be indicative of other things. Like errors, the ops team
|
||||
// shouldn't be paged for errors, but a page might be triggered if a
|
||||
// certain threshold of warnings is reached (which is typically much
|
||||
// higher than errors). For example, repeated warnings might be a sign
|
||||
// that the system is under attack.
|
||||
//
|
||||
// Actor specifies the component emitting the message; event indicates
|
||||
// the event that caused the log message to be emitted. attrs is a map
|
||||
// of key-value string pairs that can be used to provide additional
|
||||
// information.
|
||||
func (lw *LogWriter) Warn(actor, event string, attrs map[string]string) {
|
||||
if lw.lvl > LevelWarning {
|
||||
return
|
||||
}
|
||||
lw.output(lw.we, LevelWarning, actor, event, attrs)
|
||||
}
|
||||
|
||||
// EnablePrefix enables logs whose domain is prefixed with prefix.
|
||||
func EnablePrefix(prefix string) {
|
||||
logConfig.lock.Lock()
|
||||
defer logConfig.lock.Unlock()
|
||||
for domain, l := range logConfig.registered {
|
||||
if strings.HasPrefix(domain, prefix) {
|
||||
l.Enable()
|
||||
}
|
||||
// Error emits an error message. A single error doesn't require an ops
|
||||
// team to be paged, but repeated errors should often trigger a page
|
||||
// based on threshold triggers. An example is a network failure: it
|
||||
// might be a transient failure (these do happen), but most of the
|
||||
// time it's self-correcting.
|
||||
//
|
||||
// Actor specifies the component emitting the message; event indicates
|
||||
// the event that caused the log message to be emitted. attrs is a map
|
||||
// of key-value string pairs that can be used to provide additional
|
||||
// information.
|
||||
func (lw *LogWriter) Error(actor, event string, attrs map[string]string) {
|
||||
if lw.lvl > LevelError {
|
||||
return
|
||||
}
|
||||
lw.output(lw.we, LevelError, actor, event, attrs)
|
||||
}
|
||||
|
||||
// EnableAll enables all domains.
|
||||
func EnableAll() {
|
||||
logConfig.lock.Lock()
|
||||
defer logConfig.lock.Unlock()
|
||||
for _, l := range logConfig.registered {
|
||||
l.Enable()
|
||||
// Critical emits a message indicating a critical condition. The
|
||||
// error, if uncorrected, is likely to cause a fatal condition
|
||||
// shortly. An example is running out of disk space. This is
|
||||
// something that the ops team should get paged for.
|
||||
//
|
||||
// Actor specifies the component emitting the message; event indicates
|
||||
// the event that caused the log message to be emitted. attrs is a map
|
||||
// of key-value string pairs that can be used to provide additional
|
||||
// information.
|
||||
func (lw *LogWriter) Critical(actor, event string, attrs map[string]string) {
|
||||
if lw.lvl > LevelCritical {
|
||||
return
|
||||
}
|
||||
lw.output(lw.we, LevelCritical, actor, event, attrs)
|
||||
}
|
||||
|
||||
// New returns a new logger that writes to standard output for Notice
|
||||
// and below and standard error for levels above Notice. If a logger
|
||||
// with the same domain exists, the logger will set its level to level
|
||||
// and return the logger; in this case, the registered return value
|
||||
// will be true.
|
||||
func New(domain string, level Level) (l *Logger, registered bool) {
|
||||
logConfig.lock.Lock()
|
||||
defer logConfig.lock.Unlock()
|
||||
|
||||
l = logConfig.registered[domain]
|
||||
if l != nil {
|
||||
l.SetLevel(level)
|
||||
return l, true
|
||||
// Fatal emits a message indicating that the system is in an unsuable
|
||||
// state, and cannot continue to run. The program will exit with exit
|
||||
// code 1.
|
||||
//
|
||||
// Actor specifies the component emitting the message; event indicates
|
||||
// the event that caused the log message to be emitted. attrs is a map
|
||||
// of key-value string pairs that can be used to provide additional
|
||||
// information.
|
||||
func (lw *LogWriter) Fatal(actor, event string, attrs map[string]string) {
|
||||
if lw.lvl > LevelFatal {
|
||||
return
|
||||
}
|
||||
|
||||
l = &Logger{
|
||||
domain: domain,
|
||||
level: level,
|
||||
out: os.Stdout,
|
||||
err: os.Stderr,
|
||||
lock: new(sync.Mutex),
|
||||
}
|
||||
|
||||
l.Enable()
|
||||
logConfig.registered[domain] = l
|
||||
return l, false
|
||||
lw.output(lw.we, LevelFatal, actor, event, attrs)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// NewFromWriters returns a new logger that writes to the w io.WriteCloser
|
||||
// for Notice and below and to the e io.WriteCloser for levels above
|
||||
// Notice. If e is nil, w will be used. If a logger with the same
|
||||
// domain exists, the logger will set its level to level and return
|
||||
// the logger; in this case, the registered return value will be true.
|
||||
func NewFromWriters(domain string, level Level, w, e io.WriteCloser) (l *Logger, registered bool) {
|
||||
logConfig.lock.Lock()
|
||||
defer logConfig.lock.Unlock()
|
||||
|
||||
l = logConfig.registered[domain]
|
||||
if l != nil {
|
||||
l.SetLevel(level)
|
||||
return l, true
|
||||
// Fatal emits a message indicating that the system is in an unsuable
|
||||
// state, and cannot continue to run. The program will exit with the
|
||||
// exit code speicfied in the exitcode argument.
|
||||
//
|
||||
// Actor specifies the component emitting the message; event indicates
|
||||
// the event that caused the log message to be emitted. attrs is a map
|
||||
// of key-value string pairs that can be used to provide additional
|
||||
// information.
|
||||
func (lw *LogWriter) FatalCode(exitcode int, actor, event string, attrs map[string]string) {
|
||||
if lw.lvl > LevelFatal {
|
||||
return
|
||||
}
|
||||
|
||||
if w == nil {
|
||||
w = os.Stdout
|
||||
}
|
||||
|
||||
if e == nil {
|
||||
e = w
|
||||
}
|
||||
|
||||
l = &Logger{
|
||||
domain: domain,
|
||||
level: level,
|
||||
out: w,
|
||||
err: e,
|
||||
lock: new(sync.Mutex),
|
||||
}
|
||||
|
||||
l.Enable()
|
||||
logConfig.registered[domain] = l
|
||||
return l, false
|
||||
lw.output(lw.we, LevelFatal, actor, event, attrs)
|
||||
os.Exit(exitcode)
|
||||
}
|
||||
|
||||
// NewFromFile returns a new logger that opens the files for writing. If
|
||||
// multiplex is true, output will be multiplexed to standard output
|
||||
// and standard error as well.
|
||||
func NewFromFile(domain string, level Level, outFile, errFile string, multiplex bool, flags int) (*Logger, error) {
|
||||
l := &Logger{
|
||||
domain: domain,
|
||||
level: level,
|
||||
lock: new(sync.Mutex),
|
||||
// Fatal emits a message indicating that the system is in an unsuable
|
||||
// state, and cannot continue to run. The program will not exit; it is
|
||||
// assumed that the caller has some final clean up to perform.
|
||||
//
|
||||
// Actor specifies the component emitting the message; event indicates
|
||||
// the event that caused the log message to be emitted. attrs is a map
|
||||
// of key-value string pairs that can be used to provide additional
|
||||
// information.
|
||||
func (lw *LogWriter) FatalNoDie(actor, event string, attrs map[string]string) {
|
||||
if lw.lvl > LevelFatal {
|
||||
return
|
||||
}
|
||||
|
||||
outf, err := os.OpenFile(outFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY|flags, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errf, err := os.OpenFile(errFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY|flags, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if multiplex {
|
||||
l.out = mwc.MultiWriteCloser(outf, os.Stdout)
|
||||
l.err = mwc.MultiWriteCloser(errf, os.Stderr)
|
||||
} else {
|
||||
l.out = outf
|
||||
l.err = errf
|
||||
}
|
||||
|
||||
Enable(domain)
|
||||
return l, nil
|
||||
lw.output(lw.we, LevelFatal, actor, event, attrs)
|
||||
}
|
||||
|
||||
// Enable allows output from the logger.
|
||||
func (l *Logger) Enable() {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
l.enabled = true
|
||||
// Good returns true if the logger is healthy.
|
||||
func (lw *LogWriter) Good() bool {
|
||||
return lw.state == nil
|
||||
}
|
||||
|
||||
// Enabled returns true if the logger is enabled.
|
||||
func (l *Logger) Enabled() bool {
|
||||
return l.enabled
|
||||
// Status returns an error value from the logger if it isn't healthy,
|
||||
// or nil if the logger is healthy.
|
||||
func (lw *LogWriter) Status() error {
|
||||
return lw.state
|
||||
}
|
||||
|
||||
// Suppress ignores output from the logger.
|
||||
func (l *Logger) Suppress() {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
l.enabled = false
|
||||
// SetLevel changes the log level.
|
||||
func (lw *LogWriter) SetLevel(l Level) {
|
||||
lw.lvl = l
|
||||
}
|
||||
|
||||
// Domain returns the domain of the logger.
|
||||
func (l *Logger) Domain() string {
|
||||
return l.domain
|
||||
}
|
||||
|
||||
// SetLevel changes the level of the logger.
|
||||
func (l *Logger) SetLevel(level Level) {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
l.level = level
|
||||
}
|
||||
// Close is a no-op that satisfies the Logger interface.
|
||||
func (lw *LogWriter) Close() {}
|
||||
|
||||
Reference in New Issue
Block a user