239 lines
5.9 KiB
Go
239 lines
5.9 KiB
Go
package log
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log/syslog"
|
|
"os"
|
|
"path"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/jmhodges/clock"
|
|
)
|
|
|
|
// A Logger logs messages with explicit priority levels. It is
|
|
// implemented by a logging back-end as provided by New() or
|
|
// NewMock().
|
|
type Logger interface {
|
|
Err(m string)
|
|
Warning(m string)
|
|
Info(m string)
|
|
Debug(m string)
|
|
AuditPanic()
|
|
AuditInfo(string)
|
|
AuditObject(string, interface{})
|
|
AuditErr(string)
|
|
}
|
|
|
|
// impl implements Logger.
|
|
type impl struct {
|
|
w writer
|
|
}
|
|
|
|
// singleton defines the object of a Singleton pattern
|
|
type singleton struct {
|
|
once sync.Once
|
|
log Logger
|
|
}
|
|
|
|
// _Singleton is the single impl entity in memory
|
|
var _Singleton singleton
|
|
|
|
// The constant used to identify audit-specific messages
|
|
const auditTag = "[AUDIT]"
|
|
|
|
// New returns a new Logger that uses the given syslog.Writer as a backend.
|
|
func New(log *syslog.Writer, stdoutLogLevel int, syslogLogLevel int) (Logger, error) {
|
|
if log == nil {
|
|
return nil, errors.New("Attempted to use a nil System Logger.")
|
|
}
|
|
return &impl{
|
|
&bothWriter{log, stdoutLogLevel, syslogLogLevel, clock.Default()},
|
|
}, nil
|
|
}
|
|
|
|
// initialize should only be used in unit tests.
|
|
func initialize() {
|
|
// defaultPriority is never used because we always use specific priority-based
|
|
// logging methods.
|
|
const defaultPriority = syslog.LOG_INFO | syslog.LOG_LOCAL0
|
|
syslogger, err := syslog.Dial("", "", defaultPriority, "test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
logger, err := New(syslogger, int(syslog.LOG_DEBUG), int(syslog.LOG_DEBUG))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
_ = Set(logger)
|
|
}
|
|
|
|
// Set configures the singleton Logger. This method
|
|
// must only be called once, and before calling Get the
|
|
// first time.
|
|
func Set(logger Logger) (err error) {
|
|
if _Singleton.log != nil {
|
|
err = errors.New("You may not call Set after it has already been implicitly or explicitly set.")
|
|
_Singleton.log.Warning(err.Error())
|
|
} else {
|
|
_Singleton.log = logger
|
|
}
|
|
return
|
|
}
|
|
|
|
// Get obtains the singleton Logger. If Set has not been called first, this
|
|
// method initializes with basic defaults. The basic defaults cannot error, and
|
|
// subsequent access to an already-set Logger also cannot error, so this method is
|
|
// error-safe.
|
|
func Get() Logger {
|
|
_Singleton.once.Do(func() {
|
|
if _Singleton.log == nil {
|
|
initialize()
|
|
}
|
|
})
|
|
|
|
return _Singleton.log
|
|
}
|
|
|
|
type writer interface {
|
|
logAtLevel(syslog.Priority, string)
|
|
}
|
|
|
|
// bothWriter implements writer and writes to both syslog and stdout.
|
|
type bothWriter struct {
|
|
*syslog.Writer
|
|
stdoutLevel int
|
|
syslogLevel int
|
|
clk clock.Clock
|
|
}
|
|
|
|
// Log the provided message at the appropriate level, writing to
|
|
// both stdout and the Logger
|
|
func (w *bothWriter) logAtLevel(level syslog.Priority, msg string) {
|
|
var prefix string
|
|
var err error
|
|
|
|
const red = "\033[31m\033[1m"
|
|
const yellow = "\033[33m"
|
|
|
|
switch syslogAllowed := int(level) <= w.syslogLevel; level {
|
|
case syslog.LOG_ERR:
|
|
if syslogAllowed {
|
|
err = w.Err(msg)
|
|
}
|
|
prefix = red + "E"
|
|
case syslog.LOG_WARNING:
|
|
if syslogAllowed {
|
|
err = w.Warning(msg)
|
|
}
|
|
prefix = yellow + "W"
|
|
case syslog.LOG_INFO:
|
|
if syslogAllowed {
|
|
err = w.Info(msg)
|
|
}
|
|
prefix = "I"
|
|
case syslog.LOG_DEBUG:
|
|
if syslogAllowed {
|
|
err = w.Debug(msg)
|
|
}
|
|
prefix = "D"
|
|
default:
|
|
err = w.Err(fmt.Sprintf("%s (unknown logging level: %d)", msg, int(level)))
|
|
}
|
|
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Failed to write to syslog: %s (%s)\n", msg, err)
|
|
}
|
|
|
|
var reset string
|
|
if strings.HasPrefix(prefix, "\033") {
|
|
reset = "\033[0m"
|
|
}
|
|
|
|
if int(level) <= w.stdoutLevel {
|
|
fmt.Printf("%s%s %s %s%s\n",
|
|
prefix,
|
|
w.clk.Now().Format("150405"),
|
|
path.Base(os.Args[0]),
|
|
msg,
|
|
reset)
|
|
}
|
|
}
|
|
|
|
func (log *impl) auditAtLevel(level syslog.Priority, msg string) {
|
|
text := fmt.Sprintf("%s %s", auditTag, msg)
|
|
log.w.logAtLevel(level, text)
|
|
}
|
|
|
|
// Return short format caller info for panic events, skipping to before the
|
|
// panic handler.
|
|
func caller(level int) string {
|
|
_, file, line, _ := runtime.Caller(level)
|
|
splits := strings.Split(file, "/")
|
|
filename := splits[len(splits)-1]
|
|
return fmt.Sprintf("%s:%d:", filename, line)
|
|
}
|
|
|
|
// AuditPanic catches panicking executables. This method should be added
|
|
// in a defer statement as early as possible
|
|
func (log *impl) AuditPanic() {
|
|
if err := recover(); err != nil {
|
|
buf := make([]byte, 8192)
|
|
log.AuditErr(fmt.Sprintf("Panic caused by err: %s", err))
|
|
|
|
runtime.Stack(buf, false)
|
|
log.AuditErr(fmt.Sprintf("Stack Trace (Current frame) %s", buf))
|
|
|
|
runtime.Stack(buf, true)
|
|
log.Warning(fmt.Sprintf("Stack Trace (All frames): %s", buf))
|
|
}
|
|
}
|
|
|
|
// Err level messages are always marked with the audit tag, for special handling
|
|
// at the upstream system logger.
|
|
func (log *impl) Err(msg string) {
|
|
log.auditAtLevel(syslog.LOG_ERR, msg)
|
|
}
|
|
|
|
// Warning level messages pass through normally.
|
|
func (log *impl) Warning(msg string) {
|
|
log.w.logAtLevel(syslog.LOG_WARNING, msg)
|
|
}
|
|
|
|
// Info level messages pass through normally.
|
|
func (log *impl) Info(msg string) {
|
|
log.w.logAtLevel(syslog.LOG_INFO, msg)
|
|
}
|
|
|
|
// Debug level messages pass through normally.
|
|
func (log *impl) Debug(msg string) {
|
|
log.w.logAtLevel(syslog.LOG_DEBUG, msg)
|
|
}
|
|
|
|
// AuditInfo sends an INFO-severity message that is prefixed with the
|
|
// audit tag, for special handling at the upstream system logger.
|
|
func (log *impl) AuditInfo(msg string) {
|
|
log.auditAtLevel(syslog.LOG_INFO, msg)
|
|
}
|
|
|
|
// AuditObject sends an INFO-severity JSON-serialized object message that is prefixed
|
|
// with the audit tag, for special handling at the upstream system logger.
|
|
func (log *impl) AuditObject(msg string, obj interface{}) {
|
|
jsonObj, err := json.Marshal(obj)
|
|
if err != nil {
|
|
log.auditAtLevel(syslog.LOG_ERR, fmt.Sprintf("Object could not be serialized to JSON. Raw: %+v", obj))
|
|
return
|
|
}
|
|
|
|
log.auditAtLevel(syslog.LOG_INFO, fmt.Sprintf("%s JSON=%s", msg, jsonObj))
|
|
}
|
|
|
|
// AuditErr can format an error for auditing; it does so at ERR level.
|
|
func (log *impl) AuditErr(msg string) {
|
|
log.auditAtLevel(syslog.LOG_ERR, msg)
|
|
}
|