Adding logging and some common functions.
This commit is contained in:
parent
49444c3318
commit
0750c235b6
|
@ -0,0 +1,11 @@
|
||||||
|
package lib
|
||||||
|
|
||||||
|
// Various constants used throughout the tools.
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ExitSuccess is the successful exit status.
|
||||||
|
ExitSuccess = 0
|
||||||
|
|
||||||
|
// ExitFailure is the failing exit status.
|
||||||
|
ExitFailure = 1
|
||||||
|
)
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Package lib contains functions useful for most programs.
|
||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var progname = filepath.Base(os.Args[0])
|
||||||
|
|
||||||
|
// Warnx displays a formatted error message to standard error, à la
|
||||||
|
// warnx(3).
|
||||||
|
func Warnx(format string, a ...interface{}) (int, error) {
|
||||||
|
format = fmt.Sprintf("[%s] %s", progname, format)
|
||||||
|
format += "\n"
|
||||||
|
return fmt.Fprintf(os.Stderr, format, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn displays a formatted error message to standard output,
|
||||||
|
// appending the error string, à la warn(3).
|
||||||
|
func Warn(err error, format string, a ...interface{}) (int, error) {
|
||||||
|
format = fmt.Sprintf("[%s] %s", progname, format)
|
||||||
|
format += ": %v\n"
|
||||||
|
a = append(a, err)
|
||||||
|
return fmt.Fprintf(os.Stderr, format, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errx displays a formatted error message to standard error and exits
|
||||||
|
// with the status code from `exit`, à la errx(3).
|
||||||
|
func Errx(exit int, format string, a ...interface{}) {
|
||||||
|
format = fmt.Sprintf("[%s] %s", progname, format)
|
||||||
|
format += "\n"
|
||||||
|
fmt.Fprintf(os.Stderr, format, a...)
|
||||||
|
os.Exit(exit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err displays a formatting error message to standard error,
|
||||||
|
// appending the error string, and exits with the status code from
|
||||||
|
// `exit`, à la err(3).
|
||||||
|
func Err(exit int, err error, format string, a ...interface{}) {
|
||||||
|
format = fmt.Sprintf("[%s] %s", progname, format)
|
||||||
|
format += ": %v\n"
|
||||||
|
a = append(a, err)
|
||||||
|
fmt.Fprintf(os.Stderr, format, a...)
|
||||||
|
os.Exit(exit)
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/kisom/goutils/logging"
|
||||||
|
|
||||||
|
var log = logging.Init()
|
||||||
|
var olog = logging.New("subsystem #42", logging.LevelNotice)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.Notice("Hello, world.")
|
||||||
|
log.Warning("this program is about to end")
|
||||||
|
|
||||||
|
olog.Print("now online")
|
||||||
|
logging.Suppress("olog")
|
||||||
|
olog.Print("extraneous information")
|
||||||
|
|
||||||
|
logging.Enable("olog")
|
||||||
|
olog.Print("relevant now")
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
|
@ -0,0 +1,200 @@
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Level represents a logging level.
|
||||||
|
type Level uint8
|
||||||
|
|
||||||
|
// The following constants represent logging levels in increasing levels of seriousness.
|
||||||
|
const (
|
||||||
|
// LevelDebug are debug output useful during program testing
|
||||||
|
// and debugging.
|
||||||
|
LevelDebug = 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.
|
||||||
|
LevelWarning
|
||||||
|
|
||||||
|
// LevelError is for messages indicating an error of some
|
||||||
|
// kind.
|
||||||
|
LevelError
|
||||||
|
|
||||||
|
// 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
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeToOut(level Level) bool {
|
||||||
|
if level < LevelWarning {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelPrefix = [...]string{
|
||||||
|
LevelDebug: "[DEBUG] ",
|
||||||
|
LevelInfo: "[INFO] ",
|
||||||
|
LevelNotice: "[NOTICE] ",
|
||||||
|
LevelWarning: "[WARNING] ",
|
||||||
|
LevelError: "[ERROR] ",
|
||||||
|
LevelCritical: "[CRITICAL] ",
|
||||||
|
LevelAlert: "[ALERT] ",
|
||||||
|
LevelFatal: "[FATAL] ",
|
||||||
|
}
|
||||||
|
|
||||||
|
var DateFormat = "2006-01-02T15:03:04-0700"
|
||||||
|
|
||||||
|
func (l *Logger) outputf(level Level, format string, v []interface{}) {
|
||||||
|
if !logConfig.registered[l.domain] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if level >= l.level {
|
||||||
|
format = fmt.Sprintf("%s %s: %s%s\n",
|
||||||
|
time.Now().Format(DateFormat),
|
||||||
|
l.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 !logConfig.registered[l.domain] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if level >= l.level {
|
||||||
|
format := fmt.Sprintf("%s %s: %s",
|
||||||
|
time.Now().Format(DateFormat),
|
||||||
|
l.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.
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
// Package logging is an adaptation of the CFSSL logging library. It
|
||||||
|
// operates on domains, which are components for which logging can be
|
||||||
|
// selectively enabled or disabled. It also differentiates between
|
||||||
|
// normal messages (which are sent to standard output) and errors,
|
||||||
|
// which are sent to standard error.
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/kisom/goutils/mwc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logConfig = struct {
|
||||||
|
registered map[string]bool
|
||||||
|
lock *sync.Mutex
|
||||||
|
}{
|
||||||
|
registered: map[string]bool{},
|
||||||
|
lock: new(sync.Mutex),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return New(filepath.Base(os.Args[0]), DefaultLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Logger writes logs on behalf of a particular domain at a certain
|
||||||
|
// level.
|
||||||
|
type Logger struct {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suppress ignores logs from a specific domain.
|
||||||
|
func Suppress(domain string) {
|
||||||
|
logConfig.lock.Lock()
|
||||||
|
defer logConfig.lock.Unlock()
|
||||||
|
logConfig.registered[domain] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuppressAll suppresses all logging output.
|
||||||
|
func SuppressAll() {
|
||||||
|
logConfig.lock.Lock()
|
||||||
|
defer logConfig.lock.Unlock()
|
||||||
|
for domain := range logConfig.registered {
|
||||||
|
logConfig.registered[domain] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable enables logs from a specific domain.
|
||||||
|
func Enable(domain string) {
|
||||||
|
logConfig.lock.Lock()
|
||||||
|
defer logConfig.lock.Unlock()
|
||||||
|
logConfig.registered[domain] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableAll enables all domains.
|
||||||
|
func EnableAll() {
|
||||||
|
logConfig.lock.Lock()
|
||||||
|
defer logConfig.lock.Unlock()
|
||||||
|
for domain := range logConfig.registered {
|
||||||
|
logConfig.registered[domain] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new logger that writes to standard output for Notice
|
||||||
|
// and below and standard error for levels above Notice.
|
||||||
|
func New(domain string, level Level) *Logger {
|
||||||
|
l := &Logger{
|
||||||
|
domain: domain,
|
||||||
|
level: level,
|
||||||
|
out: os.Stdout,
|
||||||
|
err: os.Stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
Enable(domain)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriters 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.
|
||||||
|
func NewWriters(domain string, level Level, w, e io.WriteCloser) *Logger {
|
||||||
|
if e == nil {
|
||||||
|
e = w
|
||||||
|
}
|
||||||
|
|
||||||
|
l := &Logger{
|
||||||
|
domain: domain,
|
||||||
|
level: level,
|
||||||
|
out: w,
|
||||||
|
err: e,
|
||||||
|
}
|
||||||
|
|
||||||
|
Enable(domain)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFile 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 NewFile(domain string, level Level, outFile, errFile string, multiplex bool) (*Logger, error) {
|
||||||
|
l := &Logger{
|
||||||
|
domain: domain,
|
||||||
|
level: level,
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
l.out, err = os.OpenFile(outFile, os.O_APPEND|os.O_CREATE, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.err, err = os.OpenFile(errFile, os.O_APPEND|os.O_CREATE, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if multiplex {
|
||||||
|
l.out = mwc.MultiWriteCloser(l.out, os.Stdout)
|
||||||
|
l.err = mwc.MultiWriteCloser(l.err, os.Stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
Enable(domain)
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable allows output from the logger.
|
||||||
|
func (l *Logger) Enable() {
|
||||||
|
Enable(l.domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suppress ignores output from the logger.
|
||||||
|
func (l *Logger) Suppress() {
|
||||||
|
Suppress(l.domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.level = level
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Package mwc implements MultiWriteClosers.
|
||||||
|
package mwc
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type mwc struct {
|
||||||
|
wcs []io.WriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements the Writer interface.
|
||||||
|
func (t *mwc) Write(p []byte) (n int, err error) {
|
||||||
|
for _, w := range t.wcs {
|
||||||
|
n, err = w.Write(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n != len(p) {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements the Closer interface.
|
||||||
|
func (t *mwc) Close() error {
|
||||||
|
for _, wc := range t.wcs {
|
||||||
|
err := wc.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiWriteClose creates a WriteCloser that duplicates its writes to
|
||||||
|
// all the provided writers, similar to the Unix tee(1) command.
|
||||||
|
func MultiWriteCloser(wc ...io.WriteCloser) io.WriteCloser {
|
||||||
|
wcs := make([]io.WriteCloser, len(wc))
|
||||||
|
copy(wcs, wc)
|
||||||
|
return &mwc{wcs}
|
||||||
|
}
|
Loading…
Reference in New Issue