boulder/log/log.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)
}