blob: ae4644b5515167ecf2dbf572e8e949818a934d68 [file] [log] [blame]
// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package log exports logging primitives that log to stderr and also to Google
// Cloud Logging.
package log // import "upspin.io/log"
import (
"bytes"
"fmt"
"io"
"log"
"os"
"sync"
)
// Logger is the interface for logging messages.
type Logger interface {
// Printf writes a formated message to the log.
Printf(format string, v ...interface{})
// Print writes a message to the log.
Print(v ...interface{})
// Println writes a line to the log.
Println(v ...interface{})
// Fatal writes a message to the log and aborts.
Fatal(v ...interface{})
// Fatalf writes a formated message to the log and aborts.
Fatalf(format string, v ...interface{})
}
// Level represents the level of logging.
type Level int
// Different levels of logging.
const (
DebugLevel Level = iota
InfoLevel
ErrorLevel
DisabledLevel
)
// ExternalLogger describes a service that processes logs.
type ExternalLogger interface {
Log(Level, string)
Flush()
}
// The set of default loggers for each log level.
var (
Debug = &logger{DebugLevel}
Info = &logger{InfoLevel}
Error = &logger{ErrorLevel}
)
type globalState struct {
currentLevel Level
defaultLogger Logger
external ExternalLogger
}
var (
mu sync.RWMutex
state = globalState{
currentLevel: InfoLevel,
defaultLogger: newDefaultLogger(os.Stderr),
}
)
func globals() globalState {
mu.RLock()
defer mu.RUnlock()
return state
}
func newDefaultLogger(w io.Writer) Logger {
return log.New(w, "", log.Ldate|log.Ltime|log.LUTC|log.Lmicroseconds)
}
// logBridge augments the Logger type with the io.Writer interface enabling
// NewStdLogger to connect Go's standard library logger to the logger provided
// by this package.
type logBridge struct {
Logger
}
// Write parses the standard logging line (configured with log.Lshortfile) and
// passes its message component to the logger provided by this package.
func (lb logBridge) Write(b []byte) (n int, err error) {
var message string
// Split "f.go:42: message" into "f.go", "42", and "message".
parts := bytes.SplitN(b, []byte{':'}, 3)
if len(parts) != 3 || len(parts[0]) < 1 || len(parts[2]) < 1 {
message = fmt.Sprintf("bad log format: %s", b)
} else {
message = string(parts[2][1:]) // Skip leading space.
}
lb.Print(message)
return len(b), nil
}
// NewStdLogger creates a *log.Logger ("log" is from the Go standard library)
// that forwards messages to the provided upspin logger using a logBridge. The
// standard logger is configured with log.Lshortfile, this log line
// format which is parsed to extract the log message (skipping the filename,
// line number) to forward it to the provided upspin logger.
func NewStdLogger(l Logger) *log.Logger {
lb := logBridge{l}
return log.New(lb, "", log.Lshortfile)
}
// Register connects an ExternalLogger to the default logger. This may only be
// called once.
func Register(e ExternalLogger) {
mu.Lock()
defer mu.Unlock()
if state.external != nil {
panic("cannot register second external logger")
}
state.external = e
}
// SetOutput sets the default loggers to write to w.
// If w is nil, the default loggers are disabled.
func SetOutput(w io.Writer) {
mu.Lock()
defer mu.Unlock()
if w == nil {
state.defaultLogger = nil
} else {
state.defaultLogger = newDefaultLogger(w)
}
}
type logger struct {
level Level
}
var _ Logger = (*logger)(nil)
// Printf writes a formatted message to the log.
func (l *logger) Printf(format string, v ...interface{}) {
g := globals()
if l.level < g.currentLevel {
return // Don't log at lower levels.
}
if g.external != nil {
g.external.Log(l.level, fmt.Sprintf(format, v...))
}
if g.defaultLogger != nil {
g.defaultLogger.Printf(format, v...)
}
}
// Print writes a message to the log.
func (l *logger) Print(v ...interface{}) {
g := globals()
if l.level < g.currentLevel {
return // Don't log at lower levels.
}
if g.external != nil {
g.external.Log(l.level, fmt.Sprint(v...))
}
if g.defaultLogger != nil {
g.defaultLogger.Print(v...)
}
}
// Println writes a line to the log.
func (l *logger) Println(v ...interface{}) {
g := globals()
if l.level < g.currentLevel {
return // Don't log at lower levels.
}
if g.external != nil {
g.external.Log(l.level, fmt.Sprintln(v...))
}
if g.defaultLogger != nil {
g.defaultLogger.Println(v...)
}
}
// Fatal writes a message to the log and aborts, regardless of the current log level.
func (l *logger) Fatal(v ...interface{}) {
g := globals()
if g.external != nil {
g.external.Log(l.level, fmt.Sprint(v...))
// Make sure we get the Fatal recorded.
g.external.Flush()
// Fall through to ensure we record it locally too.
}
if g.defaultLogger != nil {
g.defaultLogger.Fatal(v...)
} else {
log.Fatal(v...)
}
}
// Fatalf writes a formatted message to the log and aborts, regardless of the
// current log level.
func (l *logger) Fatalf(format string, v ...interface{}) {
g := globals()
if g.external != nil {
g.external.Log(l.level, fmt.Sprintf(format, v...))
// Make sure we get the Fatal recorded.
g.external.Flush()
// Fall through to ensure we record it locally too.
}
if g.defaultLogger != nil {
g.defaultLogger.Fatalf(format, v...)
} else {
log.Fatalf(format, v...)
}
}
// Flush implements ExternalLogger.
func (l *logger) Flush() {
Flush()
}
// String returns the name of the logger.
func (l *logger) String() string {
return toString(l.level)
}
func toString(level Level) string {
switch level {
case InfoLevel:
return "info"
case DebugLevel:
return "debug"
case ErrorLevel:
return "error"
case DisabledLevel:
return "disabled"
}
return "unknown"
}
func toLevel(level string) (Level, error) {
switch level {
case "info":
return InfoLevel, nil
case "debug":
return DebugLevel, nil
case "error":
return ErrorLevel, nil
case "disabled":
return DisabledLevel, nil
}
return DisabledLevel, fmt.Errorf("invalid log level %q", level)
}
// GetLevel returns the current logging level.
func GetLevel() string {
g := globals()
return toString(g.currentLevel)
}
// SetLevel sets the current level of logging.
func SetLevel(level string) error {
l, err := toLevel(level)
if err != nil {
return err
}
mu.Lock()
state.currentLevel = l
mu.Unlock()
return nil
}
// At returns whether the level will be logged currently.
func At(level string) bool {
g := globals()
l, err := toLevel(level)
if err != nil {
return false
}
return g.currentLevel <= l
}
// Printf writes a formatted message to the log.
func Printf(format string, v ...interface{}) {
Info.Printf(format, v...)
}
// Print writes a message to the log.
func Print(v ...interface{}) {
Info.Print(v...)
}
// Println writes a line to the log.
func Println(v ...interface{}) {
Info.Println(v...)
}
// Fatal writes a message to the log and aborts.
func Fatal(v ...interface{}) {
Info.Fatal(v...)
}
// Fatalf writes a formatted message to the log and aborts.
func Fatalf(format string, v ...interface{}) {
Info.Fatalf(format, v...)
}
// Flush flushes the external logger, if any.
func Flush() {
g := globals()
if g.external != nil {
g.external.Flush()
}
}