Adding logging and some common functions.

This commit is contained in:
Kyle 2015-09-22 03:27:14 -07:00
parent 49444c3318
commit 0750c235b6
6 changed files with 496 additions and 0 deletions

11
lib/defs.go Normal file
View File

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

47
lib/lib.go Normal file
View File

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

31
logging/example/logger.go Normal file
View File

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

200
logging/levels.go Normal file
View File

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

165
logging/log.go Normal file
View File

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

42
mwc/mwc.go Normal file
View File

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