blob: 6d36bb98c4ece8385eae1d0fedac27b854189a96 [file] [log] [blame]
// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
// Package log4go provides level-based and highly configurable logging.
//
// Enhanced Logging
//
// This is inspired by the logging functionality in Java. Essentially, you create a Logger
// object and create output filters for it. You can send whatever you want to the Logger,
// and it will filter that based on your settings and send it to the outputs. This way, you
// can put as much debug code in your program as you want, and when you're done you can filter
// out the mundane messages so only the important ones show up.
//
// Utility functions are provided to make life easier. Here is some example code to get started:
//
// log := log4go.NewLogger()
// log.AddFilter("stdout", log4go.DEBUG, log4go.NewConsoleLogWriter())
// log.AddFilter("log", log4go.FINE, log4go.NewFileLogWriter("example.log", true))
// log.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02"))
//
// The first two lines can be combined with the utility NewDefaultLogger:
//
// log := log4go.NewDefaultLogger(log4go.DEBUG)
// log.AddFilter("log", log4go.FINE, log4go.NewFileLogWriter("example.log", true))
// log.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02"))
//
// Usage notes:
// - The ConsoleLogWriter does not display the source of the message to standard
// output, but the FileLogWriter does.
// - The utility functions (Info, Debug, Warn, etc) derive their source from the
// calling function, and this incurs extra overhead.
//
// Changes from 2.0:
// - The external interface has remained mostly stable, but a lot of the
// internals have been changed, so if you depended on any of this or created
// your own LogWriter, then you will probably have to update your code. In
// particular, Logger is now a map and ConsoleLogWriter is now a channel
// behind-the-scenes, and the LogWrite method no longer has return values.
//
// Future work: (please let me know if you think I should work on any of these particularly)
// - Log file rotation
// - Logging configuration files ala log4j
// - Have the ability to remove filters?
// - Have GetInfoChannel, GetDebugChannel, etc return a chan string that allows
// for another method of logging
// - Add an XML filter type
package log4go
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
)
// Version information
const (
L4G_VERSION = "log4go-v3.1.0"
L4G_MAJOR = 3
L4G_MINOR = 1
L4G_BUILD = 0
)
/****** Constants ******/
const (
DEFAULT_CALLER_LEVEL = 2
DEFAULT_LOG_BUFSIZE = 4096
)
var logCallerLevel = DEFAULT_CALLER_LEVEL
func SetCallerLevel(level int) {
logCallerLevel = DEFAULT_CALLER_LEVEL + level
}
// These are the integer logging levels used by the logger
type Level int
const (
FINEST Level = iota
FINE
DEBUG
TRACE
INFO
WARNING
ERROR
CRITICAL
)
// Logging level strings
var (
levelStrings = [...]string{"FNST", "FINE", "DEBG", "TRAC", "INFO", "WARN", "EROR", "CRIT"}
)
func (l Level) String() string {
if l < 0 || int(l) > len(levelStrings) {
return "UNKNOWN"
}
return levelStrings[int(l)]
}
/****** Variables ******/
var (
// LogBufferLength specifies how many log messages a particular log4go
// logger can buffer at a time before writing them.
LogBufferLength = 128
SocketLogBufferLength = 8192
SockFailWaitTimeout time.Duration = 1e8 // 100ms
)
/****** LogWriter ******/
// This is an interface for anything that should be able to write logs
type LogWriter interface {
// This will be called to log a LogRecord message.
LogWrite(rec *LogRecord)
// This should clean up anything lingering about the LogWriter, as it is called before
// the LogWriter is removed. LogWrite should not be called after Close.
Close()
}
/****** Logger ******/
// A Filter represents the log level below which no log records are written to
// the associated LogWriter.
type Filter struct {
Level Level
LogWriter
}
// string可以认为是Filter{logger对象}的logger name
type FilterMap map[string]*Filter
// A Logger represents a collection of Filters through which log messages are
// written.
type Logger struct {
FilterMap
minLevel Level // 所有filter中最低的log level
sync.Once
}
// Create a new logger.
//
// DEPRECATED: Use make(Logger) instead.
func NewLogger() Logger {
// os.Stderr.WriteString("warning: use of deprecated NewLogger\n")
return Logger{FilterMap: make(FilterMap), minLevel: DEBUG}
}
// Create a new logger with a "stdout" filter configured to send log messages at
// or above lvl to standard output.
//
// DEPRECATED: use NewDefaultLogger instead.
func NewConsoleLogger(lvl Level) Logger {
// return Logger{
// "stdout": &Filter{lvl, NewConsoleLogWriter()},
// }
os.Stderr.WriteString("warning: use of deprecated NewConsoleLogger\n")
var logger = Logger{FilterMap: make(FilterMap), minLevel: DEBUG}
logger.FilterMap["stdout"] = &Filter{lvl, NewConsoleLogWriter(false)}
return logger
}
// Create a new logger with a "stdout" filter configured to send log messages at
// or above lvl to standard output.
func NewDefaultLogger(lvl Level) Logger {
// return Logger{
// "stdout": &Filter{lvl, NewConsoleLogWriter()},
// }
var logger = Logger{FilterMap: make(FilterMap), minLevel: lvl}
logger.FilterMap["stdout"] = &Filter{lvl, NewConsoleLogWriter(false)}
return logger
}
func (log Logger) SetAsDefaultLogger() Logger {
Global = log
return log
}
// Closes all log writers in preparation for exiting the program or a
// reconfiguration of logging. Calling this is not really imperative, unless
// you want to guarantee that all log messages are written. Close removes
// all filters (and thus all LogWriters) from the logger.
func (log Logger) Close() {
log.Once.Do(func() {
m := log.FilterMap
log.FilterMap = nil
if m != nil {
// Close all open loggers
for n := range m {
m[n].Close()
delete(m, n)
}
}
})
}
// Add a new LogWriter to the Logger which will only log messages at lvl or
// higher. This function should not be called from multiple goroutines.
// Returns the logger for chaining.
// func (log *Logger) AddFilter(name string, lvl Level, writer LogWriter) Logger {
func (log *Logger) AddFilter(name string, lvl Level, writer LogWriter) {
log.FilterMap[name] = &Filter{lvl, writer}
if lvl < log.minLevel {
log.minLevel = lvl
}
// return *log
}
/******* Logging *******/
// Send a formatted log message internally
func (log Logger) intLogf(lvl Level, format string, args ...interface{}) {
// Determine if any logging will be done
// skip := true
// for _, filt := range log.FilterMap {
// if lvl >= filt.Level {
// skip = false
// break
// }
// }
// if skip {
// return
// }
if lvl < log.minLevel {
return
}
// Determine caller func
pc, fileName, lineno, ok := runtime.Caller(logCallerLevel)
src := ""
if ok {
// 此处不输出filename是有道理的,因为finename会附带有文件路径,这回导致log prefix非常长, for example:
// [2016/09/21 14:16:39 CST] [WARN] (github.com/AlexStocks/goext/src/log.TestNewLogger: \
// C:/Users/AlexStocks/share/test/golang/lib/src/github.com/AlexStocks/goext/src/log/log_test.go:28) warning msg: 0
src = fmt.Sprintf("%s:%s:%d", filepath.Base(fileName), runtime.FuncForPC(pc).Name(), lineno)
}
msg := format
if len(args) > 0 {
msg = fmt.Sprintf(format, args...)
}
// Make the log record
rec := &LogRecord{
Level: lvl,
Created: time.Now(),
Source: src,
Message: msg,
}
// Dispatch the logs
// 根据level,把log内容输出到各个logger
for _, filt := range log.FilterMap {
if lvl < filt.Level { // log4go若在log message已经拼写完毕后再输出判断level是否合适,这会导致效率非常低
continue
}
filt.LogWrite(rec)
}
}
// Send a closure log message internally
func (log Logger) intLogc(lvl Level, closure func() string) {
// // Determine if any logging will be done
// skip := true
// for _, filt := range log {
// if lvl >= filt.Level {
// skip = false
// break
// }
// }
// if skip {
// return
// }
if lvl < log.minLevel {
return
}
// Determine caller func
pc, fileName, lineno, ok := runtime.Caller(logCallerLevel)
src := ""
if ok {
src = fmt.Sprintf("%s:%s:%d", filepath.Base(fileName), runtime.FuncForPC(pc).Name(), lineno)
}
// Make the log record
rec := &LogRecord{
Level: lvl,
Created: time.Now(),
Source: src,
Message: closure(),
}
// Dispatch the logs
for _, filt := range log.FilterMap {
if lvl < filt.Level {
continue
}
filt.LogWrite(rec)
}
}
// Send a log message with manual level, source, and message.
func (log Logger) Log(lvl Level, source, message string) {
// Determine if any logging will be done
// skip := true
// for _, filt := range log {
// if lvl >= filt.Level {
// skip = false
// break
// }
// }
// if skip {
// return
// }
if lvl < log.minLevel {
return
}
// Make the log record
rec := &LogRecord{
Level: lvl,
Created: time.Now(),
Source: source,
Message: message,
}
// Dispatch the logs
for _, filt := range log.FilterMap {
if lvl < filt.Level {
continue
}
filt.LogWrite(rec)
}
}
// Logf logs a formatted log message at the given log level, using the caller as
// its source.
func (log Logger) Logf(lvl Level, format string, args ...interface{}) {
log.intLogf(lvl, format, args...)
}
// Logc logs a string returned by the closure at the given log level, using the caller as
// its source. If no log message would be written, the closure is never called.
func (log Logger) Logc(lvl Level, closure func() string) {
log.intLogc(lvl, closure)
}
// Finest logs a message at the finest log level.
// See Debug for an explanation of the arguments.
func (log Logger) Finest(arg0 interface{}, args ...interface{}) {
const (
lvl = FINEST
)
switch first := arg0.(type) {
case string:
// Use the string as a format string
log.intLogf(lvl, first, args...)
case func() string:
// Log the closure (no other arguments used)
log.intLogc(lvl, first)
default:
// Build a format string so that it will be similar to Sprint
log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
}
}
// Fine logs a message at the fine log level.
// See Debug for an explanation of the arguments.
func (log Logger) Fine(arg0 interface{}, args ...interface{}) {
const (
lvl = FINE
)
switch first := arg0.(type) {
case string:
// Use the string as a format string
log.intLogf(lvl, first, args...)
case func() string:
// Log the closure (no other arguments used)
log.intLogc(lvl, first)
default:
// Build a format string so that it will be similar to Sprint
log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
}
}
// Debug is a utility method for debug log messages.
// The behavior of Debug depends on the first argument:
// - arg0 is a string
// When given a string as the first argument, this behaves like Logf but with
// the DEBUG log level: the first argument is interpreted as a format for the
// latter arguments.
// - arg0 is a func()string
// When given a closure of type func()string, this logs the string returned by
// the closure iff it will be logged. The closure runs at most one time.
// - arg0 is interface{}
// When given anything else, the log message will be each of the arguments
// formatted with %v and separated by spaces (ala Sprint).
func (log Logger) Debug(arg0 interface{}, args ...interface{}) {
const (
lvl = DEBUG
)
switch first := arg0.(type) {
case string:
// Use the string as a format string
log.intLogf(lvl, first, args...)
case func() string:
// Log the closure (no other arguments used)
log.intLogc(lvl, first)
default:
// Build a format string so that it will be similar to Sprint
log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
}
}
// Trace logs a message at the trace log level.
// See Debug for an explanation of the arguments.
func (log Logger) Trace(arg0 interface{}, args ...interface{}) {
const (
lvl = TRACE
)
switch first := arg0.(type) {
case string:
// Use the string as a format string
log.intLogf(lvl, first, args...)
case func() string:
// Log the closure (no other arguments used)
log.intLogc(lvl, first)
default:
// Build a format string so that it will be similar to Sprint
log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
}
}
// Info logs a message at the info log level.
// See Debug for an explanation of the arguments.
func (log Logger) Info(arg0 interface{}, args ...interface{}) {
const (
lvl = INFO
)
switch first := arg0.(type) {
case string:
// Use the string as a format string
log.intLogf(lvl, first, args...)
case func() string:
// Log the closure (no other arguments used)
log.intLogc(lvl, first)
default:
// Build a format string so that it will be similar to Sprint
log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
}
}
// Warn logs a message at the warning log level and returns the formatted error.
// At the warning level and higher, there is no performance benefit if the
// message is not actually logged, because all formats are processed and all
// closures are executed to format the error message.
// See Debug for further explanation of the arguments.
func (log Logger) Warn(arg0 interface{}, args ...interface{}) error {
const (
lvl = WARNING
)
var msg string
switch first := arg0.(type) {
case string:
// Use the string as a format string
msg = fmt.Sprintf(first, args...)
case func() string:
// Log the closure (no other arguments used)
msg = first()
default:
// Build a format string so that it will be similar to Sprint
msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
}
log.intLogf(lvl, msg)
// return errors.New(msg)
return nil
}
// Error logs a message at the error log level and returns the formatted error,
// See Warn for an explanation of the performance and Debug for an explanation
// of the parameters.
func (log Logger) Error(arg0 interface{}, args ...interface{}) error {
const (
lvl = ERROR
)
var msg string
switch first := arg0.(type) {
case string:
// Use the string as a format string
msg = fmt.Sprintf(first, args...)
case func() string:
// Log the closure (no other arguments used)
msg = first()
default:
// Build a format string so that it will be similar to Sprint
msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
}
log.intLogf(lvl, msg)
// return errors.New(msg)
return nil
}
// Critical logs a message at the critical log level and returns the formatted error,
// See Warn for an explanation of the performance and Debug for an explanation
// of the parameters.
func (log Logger) Critical(arg0 interface{}, args ...interface{}) error {
const (
lvl = CRITICAL
)
var msg string
switch first := arg0.(type) {
case string:
// Use the string as a format string
msg = fmt.Sprintf(first, args...)
case func() string:
// Log the closure (no other arguments used)
msg = first()
default:
// Build a format string so that it will be similar to Sprint
msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
}
log.intLogf(lvl, msg)
// return errors.New(msg)
return nil
}
func (log Logger) Critic(arg0 interface{}, args ...interface{}) error {
return log.Critical(arg0, args...)
}