logging is now compatible with klog.
See https://git.kyleisom.net/lib/libklogger
This commit is contained in:
parent
419f23d655
commit
d1452f54c0
|
@ -0,0 +1,14 @@
|
|||
package logging
|
||||
|
||||
import "os"
|
||||
|
||||
// Console is a Logger that writes to the console. It must be
|
||||
// constructed with a call to NewConsole.
|
||||
type Console struct {
|
||||
*LogWriter
|
||||
}
|
||||
|
||||
// NewConsole returns a new console logger.
|
||||
func NewConsole() *Console {
|
||||
return &Console{LogWriter: NewLogWriter(os.Stdout, os.Stderr)}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
// Package logging implements attribute-based logging. Log entries
|
||||
// consist of timestamps, an actor and event string, and a mapping of
|
||||
// string key-value attribute pairs. For example,
|
||||
//
|
||||
// log.Error("serialiser", "failed to open file",
|
||||
// map[string]string{
|
||||
// "error": err.Error(),
|
||||
// "path": "data.bin",
|
||||
// })
|
||||
//
|
||||
// This produces the output message
|
||||
//
|
||||
// [2016-04-01T15:04:30-0700] [ERROR] [actor:serialiser event:failed to open file] error=is a directory path=data.bin
|
||||
//
|
||||
package logging
|
|
@ -1,75 +1,43 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/kisom/goutils/logging"
|
||||
"github.com/kisom/testio"
|
||||
)
|
||||
|
||||
var log = logging.Init()
|
||||
var olog, _ = logging.New("subsystem #42", logging.LevelNotice)
|
||||
var log = logging.NewConsole()
|
||||
var olog = logging.NewConsole()
|
||||
|
||||
func main() {
|
||||
exampleNewWriters()
|
||||
log.Notice("Hello, world.")
|
||||
log.Warning("this program is about to end")
|
||||
log.Info("example", "Hello, world.", nil)
|
||||
log.Warn("example", "this program is about to end", nil)
|
||||
|
||||
log.SetLevel(logging.LevelDebug)
|
||||
log.Debug("hello world")
|
||||
log.SetLevel(logging.LevelNotice)
|
||||
log.Critical("example", "screaming into the void", nil)
|
||||
olog.Critical("other", "can anyone hear me?", nil)
|
||||
|
||||
olog.Print("now online")
|
||||
logging.Suppress("olog")
|
||||
olog.Print("extraneous information")
|
||||
log.Warn("example", "but not for long", nil)
|
||||
|
||||
logging.Enable("olog")
|
||||
olog.Print("relevant now")
|
||||
log.Info("example", "fare thee well", nil)
|
||||
olog.Info("other", "all good journeys must come to an end",
|
||||
map[string]string{"when": time.Now().String()})
|
||||
|
||||
logging.SuppressAll()
|
||||
log.Alert("screaming into the void")
|
||||
olog.Critical("can anyone hear me?")
|
||||
|
||||
log.Enable()
|
||||
log.Notice("i'm baaack")
|
||||
log.Suppress()
|
||||
log.Warning("but not for long")
|
||||
|
||||
logging.EnableAll()
|
||||
log.Notice("fare thee well")
|
||||
olog.Print("all good journeys must come to an end")
|
||||
log.Info("example", "filelog test", nil)
|
||||
exampleNewFromFile()
|
||||
}
|
||||
|
||||
func exampleNewWriters() {
|
||||
o := testio.NewBufCloser(nil)
|
||||
e := testio.NewBufCloser(nil)
|
||||
|
||||
wlog, _ := logging.NewFromWriters("writers", logging.DefaultLevel, o, e)
|
||||
wlog.Notice("hello, world")
|
||||
wlog.Notice("some more things happening")
|
||||
wlog.Warning("something suspicious has happened")
|
||||
wlog.Alert("pick up that can, Citizen!")
|
||||
|
||||
fmt.Println("--- BEGIN OUT ---")
|
||||
fmt.Printf("%s", o.Bytes())
|
||||
fmt.Println("--- END OUT ---")
|
||||
|
||||
fmt.Println("--- BEGIN ERR ---")
|
||||
fmt.Printf("%s", e.Bytes())
|
||||
fmt.Println("--- END ERR ---")
|
||||
os.Remove("example.log")
|
||||
os.Remove("example.err")
|
||||
}
|
||||
|
||||
func exampleNewFromFile() {
|
||||
flog, err := logging.NewFromFile("file logger", logging.LevelNotice,
|
||||
"example.log", "example.err", true)
|
||||
flog, err := logging.NewSplitFile("example.log", "example.err", true)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open logger: %v", err)
|
||||
log.Fatal("filelog", "failed to open logger",
|
||||
map[string]string{"error": err.Error()})
|
||||
}
|
||||
defer flog.Close()
|
||||
|
||||
flog.Notice("hello, world")
|
||||
flog.Notice("some more things happening")
|
||||
flog.Warning("something suspicious has happened")
|
||||
flog.Alert("pick up that can, Citizen!")
|
||||
flog.Info("filelog", "hello, world", nil)
|
||||
flog.Info("filelog", "some more things happening", nil)
|
||||
flog.Warn("filelog", "something suspicious has happened", nil)
|
||||
flog.Critical("filelog", "pick up that can, Citizen!", nil)
|
||||
}
|
||||
|
|
|
@ -1,44 +1,37 @@
|
|||
package logging_test
|
||||
|
||||
import "github.com/kisom/goutils/logging"
|
||||
import (
|
||||
"time"
|
||||
|
||||
var log = logging.Init()
|
||||
var olog, _ = logging.New("subsystem #42", logging.LevelNotice)
|
||||
"github.com/kisom/goutils/logging"
|
||||
)
|
||||
|
||||
var log = logging.NewConsole()
|
||||
var olog = logging.NewConsole()
|
||||
|
||||
func Example() {
|
||||
log.Notice("Hello, world.")
|
||||
log.Warning("this program is about to end")
|
||||
log.Info("example", "Hello, world.", nil)
|
||||
log.Warn("example", "this program is about to end", nil)
|
||||
|
||||
olog.Print("now online")
|
||||
logging.Suppress("olog")
|
||||
olog.Print("extraneous information")
|
||||
log.Critical("example", "screaming into the void", nil)
|
||||
olog.Critical("other", "can anyone hear me?", nil)
|
||||
|
||||
logging.Enable("olog")
|
||||
olog.Print("relevant now")
|
||||
log.Warn("example", "but not for long", nil)
|
||||
|
||||
logging.SuppressAll()
|
||||
log.Alert("screaming into the void")
|
||||
olog.Critical("can anyone hear me?")
|
||||
|
||||
log.Enable()
|
||||
log.Notice("i'm baaack")
|
||||
log.Suppress()
|
||||
log.Warning("but not for long")
|
||||
|
||||
logging.EnableAll()
|
||||
log.Notice("fare thee well")
|
||||
olog.Print("all good journeys must come to an end")
|
||||
log.Info("example", "fare thee well", nil)
|
||||
olog.Info("example", "all good journeys must come to an end",
|
||||
map[string]string{"when": time.Now().String()})
|
||||
}
|
||||
|
||||
func ExampleNewFromFile() {
|
||||
log, err := logging.NewFromFile("file logger", logging.LevelNotice,
|
||||
"example.log", "example.err", true)
|
||||
flog, err := logging.NewSplitFile("example.log", "example.err", true)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open logger: %v", err)
|
||||
log.Fatal("filelog", "failed to open logger",
|
||||
map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
log.Notice("hello, world")
|
||||
log.Notice("some more things happening")
|
||||
log.Warning("something suspicious has happened")
|
||||
log.Alert("pick up that can, Citizen!")
|
||||
flog.Info("filelog", "hello, world", nil)
|
||||
flog.Info("filelog", "some more things happening", nil)
|
||||
flog.Warn("filelog", "something suspicious has happened", nil)
|
||||
flog.Critical("filelog", "pick up that can, Citizen!", nil)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package logging
|
||||
|
||||
import "os"
|
||||
|
||||
// File writes its logs to file.
|
||||
type File struct {
|
||||
fo, fe *os.File
|
||||
*LogWriter
|
||||
}
|
||||
|
||||
func (fl *File) Close() {
|
||||
fl.fo.Close()
|
||||
if fl.fe != nil {
|
||||
fl.fe.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// NewFile creates a new Logger that writes all logs to the file
|
||||
// specified by path. If overwrite is specified, the log file will be
|
||||
// truncated before writing. Otherwise, the log file will be appended
|
||||
// to.
|
||||
func NewFile(path string, overwrite bool) (*File, error) {
|
||||
fl := new(File)
|
||||
|
||||
var err error
|
||||
|
||||
if overwrite {
|
||||
fl.fo, err = os.Create(path)
|
||||
} else {
|
||||
fl.fo, err = os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0644)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fl.LogWriter = NewLogWriter(fl.fo, fl.fo)
|
||||
return fl, nil
|
||||
}
|
||||
|
||||
// NewSplitFile creates a new Logger that writes debug and information
|
||||
// messages to the output file, and warning and higher messages to the
|
||||
// error file. If overwrite is specified, the log files will be
|
||||
// truncated before writing.
|
||||
func NewSplitFile(outpath, errpath string, overwrite bool) (*File, error) {
|
||||
fl := new(File)
|
||||
|
||||
var err error
|
||||
|
||||
if overwrite {
|
||||
fl.fo, err = os.Create(outpath)
|
||||
} else {
|
||||
fl.fo, err = os.OpenFile(outpath, os.O_WRONLY|os.O_APPEND, 0644)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if overwrite {
|
||||
fl.fe, err = os.Create(errpath)
|
||||
} else {
|
||||
fl.fe, err = os.OpenFile(errpath, os.O_WRONLY|os.O_APPEND, 0644)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fl.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fl.LogWriter = NewLogWriter(fl.fo, fl.fe)
|
||||
return fl, nil
|
||||
}
|
|
@ -1,12 +1,5 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Level represents a logging level.
|
||||
type Level uint8
|
||||
|
||||
|
@ -14,15 +7,11 @@ type Level uint8
|
|||
const (
|
||||
// LevelDebug are debug output useful during program testing
|
||||
// and debugging.
|
||||
LevelDebug = iota
|
||||
LevelDebug = 1 << iota
|
||||
|
||||
// LevelInfo is used for informational messages.
|
||||
LevelInfo
|
||||
|
||||
// LevelNotice is for messages that are normal but
|
||||
// significant.
|
||||
LevelNotice
|
||||
|
||||
// LevelWarning is for messages that are warning conditions:
|
||||
// they're not indicative of a failure, but of a situation
|
||||
// that may lead to a failure later.
|
||||
|
@ -35,15 +24,13 @@ const (
|
|||
// LevelCritical are messages for critical conditions.
|
||||
LevelCritical
|
||||
|
||||
// LevelAlert are for messages indicating that action
|
||||
// must be taken immediately.
|
||||
LevelAlert
|
||||
|
||||
// LevelFatal messages are akin to syslog's LOG_EMERG: the
|
||||
// system is unusable and cannot continue execution.
|
||||
LevelFatal
|
||||
)
|
||||
|
||||
const DefaultLevel = LevelInfo
|
||||
|
||||
// Cheap integer to fixed-width decimal ASCII. Give a negative width
|
||||
// to avoid zero-padding. (From log/log.go in the standard library).
|
||||
func itoa(i int, wid int) string {
|
||||
|
@ -70,168 +57,13 @@ func writeToOut(level Level) bool {
|
|||
}
|
||||
|
||||
var levelPrefix = [...]string{
|
||||
LevelDebug: "[DEBUG] ",
|
||||
LevelInfo: "[INFO] ",
|
||||
LevelNotice: "[NOTICE] ",
|
||||
LevelWarning: "[WARNING] ",
|
||||
LevelError: "[ERROR] ",
|
||||
LevelCritical: "[CRITICAL] ",
|
||||
LevelAlert: "[ALERT] ",
|
||||
LevelFatal: "[FATAL] ",
|
||||
LevelDebug: "DEBUG",
|
||||
LevelInfo: "INFO",
|
||||
LevelWarning: "WARNING",
|
||||
LevelError: "ERROR",
|
||||
LevelCritical: "CRITICAL",
|
||||
LevelFatal: "FATAL",
|
||||
}
|
||||
|
||||
// DateFormat contains the default date format string used by the logger.
|
||||
var DateFormat = "2006-01-02T15:03:04-0700"
|
||||
|
||||
func (l *Logger) outputf(level Level, format string, v []interface{}) {
|
||||
if !l.Enabled() {
|
||||
return
|
||||
}
|
||||
|
||||
if level >= l.level {
|
||||
domain := l.domain
|
||||
if level == LevelDebug {
|
||||
_, file, line, ok := runtime.Caller(2)
|
||||
if ok {
|
||||
domain += " " + file + ":" + itoa(line, -1)
|
||||
}
|
||||
}
|
||||
|
||||
format = fmt.Sprintf("%s %s: %s%s\n",
|
||||
time.Now().Format(DateFormat),
|
||||
domain, levelPrefix[level], format)
|
||||
if writeToOut(level) {
|
||||
fmt.Fprintf(l.out, format, v...)
|
||||
} else {
|
||||
fmt.Fprintf(l.err, format, v...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) output(level Level, v []interface{}) {
|
||||
if !l.Enabled() {
|
||||
return
|
||||
}
|
||||
|
||||
if level >= l.level {
|
||||
domain := l.domain
|
||||
if level == LevelDebug {
|
||||
_, file, line, ok := runtime.Caller(2)
|
||||
if ok {
|
||||
domain += " " + file + ":" + itoa(line, -1)
|
||||
}
|
||||
}
|
||||
|
||||
format := fmt.Sprintf("%s %s: %s",
|
||||
time.Now().Format(DateFormat),
|
||||
domain, levelPrefix[level])
|
||||
if writeToOut(level) {
|
||||
fmt.Fprintf(l.out, format)
|
||||
fmt.Fprintln(l.out, v...)
|
||||
} else {
|
||||
fmt.Fprintf(l.err, format)
|
||||
fmt.Fprintln(l.err, v...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fatalf logs a formatted message at the "fatal" level and then exits. The
|
||||
// arguments are handled in the same manner as fmt.Printf.
|
||||
func (l *Logger) Fatalf(format string, v ...interface{}) {
|
||||
l.outputf(LevelFatal, format, v)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatal logs its arguments at the "fatal" level and then exits.
|
||||
func (l *Logger) Fatal(v ...interface{}) {
|
||||
l.output(LevelFatal, v)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Alertf logs a formatted message at the "alert" level. The
|
||||
// arguments are handled in the same manner as fmt.Printf.
|
||||
func (l *Logger) Alertf(format string, v ...interface{}) {
|
||||
l.outputf(LevelAlert, format, v)
|
||||
}
|
||||
|
||||
// Alert logs its arguments at the "alert" level.
|
||||
func (l *Logger) Alert(v ...interface{}) {
|
||||
l.output(LevelAlert, v)
|
||||
}
|
||||
|
||||
// Criticalf logs a formatted message at the "critical" level. The
|
||||
// arguments are handled in the same manner as fmt.Printf.
|
||||
func (l *Logger) Criticalf(format string, v ...interface{}) {
|
||||
l.outputf(LevelCritical, format, v)
|
||||
}
|
||||
|
||||
// Critical logs its arguments at the "critical" level.
|
||||
func (l *Logger) Critical(v ...interface{}) {
|
||||
l.output(LevelCritical, v)
|
||||
}
|
||||
|
||||
// Errorf logs a formatted message at the "error" level. The arguments
|
||||
// are handled in the same manner as fmt.Printf.
|
||||
func (l *Logger) Errorf(format string, v ...interface{}) {
|
||||
l.outputf(LevelError, format, v)
|
||||
}
|
||||
|
||||
// Error logs its arguments at the "error" level.
|
||||
func (l *Logger) Error(v ...interface{}) {
|
||||
l.output(LevelError, v)
|
||||
}
|
||||
|
||||
// Warningf logs a formatted message at the "warning" level. The
|
||||
// arguments are handled in the same manner as fmt.Printf.
|
||||
func (l *Logger) Warningf(format string, v ...interface{}) {
|
||||
l.outputf(LevelWarning, format, v)
|
||||
}
|
||||
|
||||
// Warning logs its arguments at the "warning" level.
|
||||
func (l *Logger) Warning(v ...interface{}) {
|
||||
l.output(LevelWarning, v)
|
||||
}
|
||||
|
||||
// Noticef logs a formatted message at the "notice" level. The arguments
|
||||
// are handled in the same manner as fmt.Printf.
|
||||
func (l *Logger) Noticef(format string, v ...interface{}) {
|
||||
l.outputf(LevelNotice, format, v)
|
||||
}
|
||||
|
||||
// Notice logs its arguments at the "notice" level.
|
||||
func (l *Logger) Notice(v ...interface{}) {
|
||||
l.output(LevelNotice, v)
|
||||
}
|
||||
|
||||
// Infof logs a formatted message at the "info" level. The arguments
|
||||
// are handled in the same manner as fmt.Printf.
|
||||
func (l *Logger) Infof(format string, v ...interface{}) {
|
||||
l.outputf(LevelInfo, format, v)
|
||||
}
|
||||
|
||||
// Info logs its arguments at the "info" level.
|
||||
func (l *Logger) Info(v ...interface{}) {
|
||||
l.output(LevelInfo, v)
|
||||
}
|
||||
|
||||
// Debugf logs a formatted message at the "debug" level. The arguments
|
||||
// are handled in the same manner as fmt.Printf. Note that debug
|
||||
// logging will print the current
|
||||
func (l *Logger) Debugf(format string, v ...interface{}) {
|
||||
l.outputf(LevelDebug, format, v)
|
||||
}
|
||||
|
||||
// Debug logs its arguments at the "debug" level.
|
||||
func (l *Logger) Debug(v ...interface{}) {
|
||||
l.output(LevelDebug, v)
|
||||
}
|
||||
|
||||
// Printf prints a formatted message at the default level.
|
||||
func (l *Logger) Printf(format string, v ...interface{}) {
|
||||
l.outputf(DefaultLevel, format, v)
|
||||
}
|
||||
|
||||
// Print prints its arguments at the default level.
|
||||
func (l *Logger) Print(v ...interface{}) {
|
||||
l.output(DefaultLevel, v)
|
||||
}
|
||||
const DateFormat = "2006-01-02T15:03:04-0700"
|
||||
|
|
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() {}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// A list of implementations that should be tested.
|
||||
var implementations []Logger
|
||||
|
||||
func init() {
|
||||
lw := NewLogWriter(&bytes.Buffer{}, nil)
|
||||
cw := NewConsole()
|
||||
|
||||
implementations = append(implementations, lw)
|
||||
implementations = append(implementations, cw)
|
||||
}
|
||||
|
||||
func TestFileSetup(t *testing.T) {
|
||||
fw1, err := NewFile("fw1.log", true)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create new file logger: %v", err)
|
||||
}
|
||||
|
||||
fw2, err := NewSplitFile("fw2.log", "fw2.err", true)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create new split file logger: %v", err)
|
||||
}
|
||||
|
||||
implementations = append(implementations, fw1)
|
||||
implementations = append(implementations, fw2)
|
||||
}
|
||||
|
||||
func TestImplementations(t *testing.T) {
|
||||
for _, l := range implementations {
|
||||
l.Info("TestImplementations", "Info message",
|
||||
map[string]string{"type": fmt.Sprintf("%T", l)})
|
||||
l.Warn("TestImplementations", "Warning message",
|
||||
map[string]string{"type": fmt.Sprintf("%T", l)})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloseLoggers(t *testing.T) {
|
||||
for _, l := range implementations {
|
||||
l.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDestroyLogFiles(t *testing.T) {
|
||||
os.Remove("fw1.log")
|
||||
os.Remove("fw2.log")
|
||||
os.Remove("fw2.err")
|
||||
}
|
Loading…
Reference in New Issue