logging is now compatible with klog.

See https://git.kyleisom.net/lib/libklogger
This commit is contained in:
Kyle Isom 2016-04-01 15:08:55 -07:00
parent 419f23d655
commit d1452f54c0
8 changed files with 442 additions and 487 deletions

14
logging/console_logger.go Normal file
View File

@ -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)}
}

15
logging/doc.go Normal file
View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

73
logging/file.go Normal file
View File

@ -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
}

View File

@ -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"

View File

@ -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() {}

55
logging/log_test.go Normal file
View File

@ -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")
}