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 package main
import ( import (
"fmt" "os"
"time"
"github.com/kisom/goutils/logging" "github.com/kisom/goutils/logging"
"github.com/kisom/testio"
) )
var log = logging.Init() var log = logging.NewConsole()
var olog, _ = logging.New("subsystem #42", logging.LevelNotice) var olog = logging.NewConsole()
func main() { func main() {
exampleNewWriters() log.Info("example", "Hello, world.", nil)
log.Notice("Hello, world.") log.Warn("example", "this program is about to end", nil)
log.Warning("this program is about to end")
log.SetLevel(logging.LevelDebug) log.Critical("example", "screaming into the void", nil)
log.Debug("hello world") olog.Critical("other", "can anyone hear me?", nil)
log.SetLevel(logging.LevelNotice)
olog.Print("now online") log.Warn("example", "but not for long", nil)
logging.Suppress("olog")
olog.Print("extraneous information")
logging.Enable("olog") log.Info("example", "fare thee well", nil)
olog.Print("relevant now") olog.Info("other", "all good journeys must come to an end",
map[string]string{"when": time.Now().String()})
logging.SuppressAll() log.Info("example", "filelog test", nil)
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")
exampleNewFromFile() exampleNewFromFile()
} os.Remove("example.log")
os.Remove("example.err")
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 ---")
} }
func exampleNewFromFile() { func exampleNewFromFile() {
flog, err := logging.NewFromFile("file logger", logging.LevelNotice, flog, err := logging.NewSplitFile("example.log", "example.err", true)
"example.log", "example.err", true)
if err != nil { 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.Info("filelog", "hello, world", nil)
flog.Notice("some more things happening") flog.Info("filelog", "some more things happening", nil)
flog.Warning("something suspicious has happened") flog.Warn("filelog", "something suspicious has happened", nil)
flog.Alert("pick up that can, Citizen!") flog.Critical("filelog", "pick up that can, Citizen!", nil)
} }

View File

@ -1,44 +1,37 @@
package logging_test package logging_test
import "github.com/kisom/goutils/logging" import (
"time"
var log = logging.Init() "github.com/kisom/goutils/logging"
var olog, _ = logging.New("subsystem #42", logging.LevelNotice) )
var log = logging.NewConsole()
var olog = logging.NewConsole()
func Example() { func Example() {
log.Notice("Hello, world.") log.Info("example", "Hello, world.", nil)
log.Warning("this program is about to end") log.Warn("example", "this program is about to end", nil)
olog.Print("now online") log.Critical("example", "screaming into the void", nil)
logging.Suppress("olog") olog.Critical("other", "can anyone hear me?", nil)
olog.Print("extraneous information")
logging.Enable("olog") log.Warn("example", "but not for long", nil)
olog.Print("relevant now")
logging.SuppressAll() log.Info("example", "fare thee well", nil)
log.Alert("screaming into the void") olog.Info("example", "all good journeys must come to an end",
olog.Critical("can anyone hear me?") map[string]string{"when": time.Now().String()})
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")
} }
func ExampleNewFromFile() { func ExampleNewFromFile() {
log, err := logging.NewFromFile("file logger", logging.LevelNotice, flog, err := logging.NewSplitFile("example.log", "example.err", true)
"example.log", "example.err", true)
if err != nil { 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") flog.Info("filelog", "hello, world", nil)
log.Notice("some more things happening") flog.Info("filelog", "some more things happening", nil)
log.Warning("something suspicious has happened") flog.Warn("filelog", "something suspicious has happened", nil)
log.Alert("pick up that can, Citizen!") 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 package logging
import (
"fmt"
"os"
"runtime"
"time"
)
// A Level represents a logging level. // A Level represents a logging level.
type Level uint8 type Level uint8
@ -14,15 +7,11 @@ type Level uint8
const ( const (
// LevelDebug are debug output useful during program testing // LevelDebug are debug output useful during program testing
// and debugging. // and debugging.
LevelDebug = iota LevelDebug = 1 << iota
// LevelInfo is used for informational messages. // LevelInfo is used for informational messages.
LevelInfo LevelInfo
// LevelNotice is for messages that are normal but
// significant.
LevelNotice
// LevelWarning is for messages that are warning conditions: // LevelWarning is for messages that are warning conditions:
// they're not indicative of a failure, but of a situation // they're not indicative of a failure, but of a situation
// that may lead to a failure later. // that may lead to a failure later.
@ -35,15 +24,13 @@ const (
// LevelCritical are messages for critical conditions. // LevelCritical are messages for critical conditions.
LevelCritical LevelCritical
// LevelAlert are for messages indicating that action
// must be taken immediately.
LevelAlert
// LevelFatal messages are akin to syslog's LOG_EMERG: the // LevelFatal messages are akin to syslog's LOG_EMERG: the
// system is unusable and cannot continue execution. // system is unusable and cannot continue execution.
LevelFatal LevelFatal
) )
const DefaultLevel = LevelInfo
// Cheap integer to fixed-width decimal ASCII. Give a negative width // Cheap integer to fixed-width decimal ASCII. Give a negative width
// to avoid zero-padding. (From log/log.go in the standard library). // to avoid zero-padding. (From log/log.go in the standard library).
func itoa(i int, wid int) string { func itoa(i int, wid int) string {
@ -70,168 +57,13 @@ func writeToOut(level Level) bool {
} }
var levelPrefix = [...]string{ var levelPrefix = [...]string{
LevelDebug: "[DEBUG] ", LevelDebug: "DEBUG",
LevelInfo: "[INFO] ", LevelInfo: "INFO",
LevelNotice: "[NOTICE] ", LevelWarning: "WARNING",
LevelWarning: "[WARNING] ", LevelError: "ERROR",
LevelError: "[ERROR] ", LevelCritical: "CRITICAL",
LevelCritical: "[CRITICAL] ", LevelFatal: "FATAL",
LevelAlert: "[ALERT] ",
LevelFatal: "[FATAL] ",
} }
// DateFormat contains the default date format string used by the logger. // DateFormat contains the default date format string used by the logger.
var DateFormat = "2006-01-02T15:03:04-0700" const 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)
}

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 package logging
import ( import (
"fmt"
"io" "io"
"os" "os"
"path/filepath" "time"
"strings"
"sync"
"github.com/kisom/goutils/mwc"
) )
var logConfig = struct { // Logger provides a standardised logging interface.
registered map[string]*Logger type Logger interface {
lock *sync.Mutex // SetLevel sets the minimum log level.
}{ SetLevel(Level)
registered: map[string]*Logger{},
lock: new(sync.Mutex), // 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. // A LogWriter is a Logger that operates on an io.Writer.
func SetLevel(level Level) { type LogWriter struct {
logConfig.lock.Lock() wo, we io.Writer
defer logConfig.lock.Unlock() lvl Level
state error
snl bool // suppress newline
}
for _, l := range logConfig.registered { // NewLogWriter takes an output writer (wo) and an error writer (we),
l.SetLevel(level) // 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. func (lw *LogWriter) output(w io.Writer, lvl Level, actor, event string, attrs map[string]string) {
const DefaultLevel = LevelNotice t := time.Now().Format(DateFormat)
fmt.Fprintf(w, "[%s] [%s] [actor:%s event:%s]", t, levelPrefix[lvl], actor, event)
// Init returns a new default logger. The domain is set to the for k, v := range attrs {
// program's name, and the default logging level is used. fmt.Fprintf(w, " %s=%s", k, v)
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
} }
return l.err.Close() if !lw.snl {
} fmt.Fprintf(w, "\n")
// 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()
} }
} }
// SuppressPrefix suppress logs whose domain is prefixed with the // Debug emits a debug-level message. These are only used during
// prefix. // development or if a deployed system repeatedly sees abnormal
func SuppressPrefix(prefix string) { // errors.
logConfig.lock.Lock() //
defer logConfig.lock.Unlock() // Actor specifies the component emitting the message; event indicates
for domain, l := range logConfig.registered { // the event that caused the log message to be emitted. attrs is a map
if strings.HasPrefix(domain, prefix) { // of key-value string pairs that can be used to provide additional
l.Suppress() // 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. // Info emits an informational message. This is a normal log message
func SuppressAll() { // that is used to deliver information, such as recording
logConfig.lock.Lock() // requests. Ops teams are never paged for informational
defer logConfig.lock.Unlock() // messages. This is the default log level.
for _, l := range logConfig.registered { //
l.Suppress() // 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. // Warn emits a warning message. An example of this is a bad request
func Enable(domain string) { // sent to a server. This isn't an error on the part of the program,
logConfig.lock.Lock() // but it may be indicative of other things. Like errors, the ops team
defer logConfig.lock.Unlock() // shouldn't be paged for errors, but a page might be triggered if a
l, ok := logConfig.registered[domain] // certain threshold of warnings is reached (which is typically much
if ok { // higher than errors). For example, repeated warnings might be a sign
l.Enable() // 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. // Error emits an error message. A single error doesn't require an ops
func EnablePrefix(prefix string) { // team to be paged, but repeated errors should often trigger a page
logConfig.lock.Lock() // based on threshold triggers. An example is a network failure: it
defer logConfig.lock.Unlock() // might be a transient failure (these do happen), but most of the
for domain, l := range logConfig.registered { // time it's self-correcting.
if strings.HasPrefix(domain, prefix) { //
l.Enable() // 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. // Critical emits a message indicating a critical condition. The
func EnableAll() { // error, if uncorrected, is likely to cause a fatal condition
logConfig.lock.Lock() // shortly. An example is running out of disk space. This is
defer logConfig.lock.Unlock() // something that the ops team should get paged for.
for _, l := range logConfig.registered { //
l.Enable() // 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 // Fatal emits a message indicating that the system is in an unsuable
// and below and standard error for levels above Notice. If a logger // state, and cannot continue to run. The program will exit with exit
// with the same domain exists, the logger will set its level to level // code 1.
// and return the logger; in this case, the registered return value //
// will be true. // Actor specifies the component emitting the message; event indicates
func New(domain string, level Level) (l *Logger, registered bool) { // the event that caused the log message to be emitted. attrs is a map
logConfig.lock.Lock() // of key-value string pairs that can be used to provide additional
defer logConfig.lock.Unlock() // information.
func (lw *LogWriter) Fatal(actor, event string, attrs map[string]string) {
l = logConfig.registered[domain] if lw.lvl > LevelFatal {
if l != nil { return
l.SetLevel(level)
return l, true
} }
lw.output(lw.we, LevelFatal, actor, event, attrs)
l = &Logger{ os.Exit(1)
domain: domain,
level: level,
out: os.Stdout,
err: os.Stderr,
lock: new(sync.Mutex),
}
l.Enable()
logConfig.registered[domain] = l
return l, false
} }
// NewFromWriters returns a new logger that writes to the w io.WriteCloser // Fatal emits a message indicating that the system is in an unsuable
// for Notice and below and to the e io.WriteCloser for levels above // state, and cannot continue to run. The program will exit with the
// Notice. If e is nil, w will be used. If a logger with the same // exit code speicfied in the exitcode argument.
// domain exists, the logger will set its level to level and return //
// the logger; in this case, the registered return value will be true. // Actor specifies the component emitting the message; event indicates
func NewFromWriters(domain string, level Level, w, e io.WriteCloser) (l *Logger, registered bool) { // the event that caused the log message to be emitted. attrs is a map
logConfig.lock.Lock() // of key-value string pairs that can be used to provide additional
defer logConfig.lock.Unlock() // information.
func (lw *LogWriter) FatalCode(exitcode int, actor, event string, attrs map[string]string) {
l = logConfig.registered[domain] if lw.lvl > LevelFatal {
if l != nil { return
l.SetLevel(level)
return l, true
} }
lw.output(lw.we, LevelFatal, actor, event, attrs)
if w == nil { os.Exit(exitcode)
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
} }
// NewFromFile returns a new logger that opens the files for writing. If // Fatal emits a message indicating that the system is in an unsuable
// multiplex is true, output will be multiplexed to standard output // state, and cannot continue to run. The program will not exit; it is
// and standard error as well. // assumed that the caller has some final clean up to perform.
func NewFromFile(domain string, level Level, outFile, errFile string, multiplex bool, flags int) (*Logger, error) { //
l := &Logger{ // Actor specifies the component emitting the message; event indicates
domain: domain, // the event that caused the log message to be emitted. attrs is a map
level: level, // of key-value string pairs that can be used to provide additional
lock: new(sync.Mutex), // information.
func (lw *LogWriter) FatalNoDie(actor, event string, attrs map[string]string) {
if lw.lvl > LevelFatal {
return
} }
lw.output(lw.we, LevelFatal, actor, event, attrs)
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
} }
// Enable allows output from the logger. // Good returns true if the logger is healthy.
func (l *Logger) Enable() { func (lw *LogWriter) Good() bool {
l.lock.Lock() return lw.state == nil
defer l.lock.Unlock()
l.enabled = true
} }
// Enabled returns true if the logger is enabled. // Status returns an error value from the logger if it isn't healthy,
func (l *Logger) Enabled() bool { // or nil if the logger is healthy.
return l.enabled func (lw *LogWriter) Status() error {
return lw.state
} }
// Suppress ignores output from the logger. // SetLevel changes the log level.
func (l *Logger) Suppress() { func (lw *LogWriter) SetLevel(l Level) {
l.lock.Lock() lw.lvl = l
defer l.lock.Unlock()
l.enabled = false
} }
// Domain returns the domain of the logger. // Close is a no-op that satisfies the Logger interface.
func (l *Logger) Domain() string { func (lw *LogWriter) Close() {}
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
}

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