328 lines
8.7 KiB
Go
328 lines
8.7 KiB
Go
|
// Package log provides support for logging to stdout, stderr and file.
|
||
|
package log
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"context"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"os/signal"
|
||
|
"path"
|
||
|
"syscall"
|
||
|
"time"
|
||
|
|
||
|
"github.com/pengsrc/go-shared/check"
|
||
|
"github.com/pengsrc/go-shared/reopen"
|
||
|
)
|
||
|
|
||
|
// Logger presents a logger.
|
||
|
// The only way to initialize a logger is using the convention construct
|
||
|
// functions like NewLogger().
|
||
|
type Logger struct {
|
||
|
level Level
|
||
|
lw LevelWriter
|
||
|
|
||
|
// Interested context keys.
|
||
|
ctxKeys []interface{}
|
||
|
ctxKeysMap map[interface{}]string
|
||
|
|
||
|
// isCallerEnabled sets whether to annotating logs with the calling
|
||
|
// function's file name and line number. By default, all logs are annotated.
|
||
|
isCallerEnabled bool
|
||
|
}
|
||
|
|
||
|
// GetLevel get the log level string.
|
||
|
func (l *Logger) GetLevel() string {
|
||
|
return l.level.String()
|
||
|
}
|
||
|
|
||
|
// SetLevel sets the log level.
|
||
|
// Valid levels are "debug", "info", "warn", "error", and "fatal".
|
||
|
func (l *Logger) SetLevel(level string) (err error) {
|
||
|
levelFlag, err := ParseLevel(level)
|
||
|
if err == nil {
|
||
|
l.level = levelFlag
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// SetInterestContextKeys sets the contexts keys that the logger should be
|
||
|
// interested in. Value of the interested context key will extract and print as
|
||
|
// newEvent filed.
|
||
|
func (l *Logger) SetInterestContextKeys(keys []interface{}) {
|
||
|
l.ctxKeys = keys
|
||
|
|
||
|
l.ctxKeysMap = make(map[interface{}]string)
|
||
|
for _, key := range l.ctxKeys {
|
||
|
l.ctxKeysMap[key] = fmt.Sprintf("%v", key)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// SetCallerFlag sets whether to annotating logs with the caller.
|
||
|
func (l *Logger) SetCallerFlag(isEnabled bool) {
|
||
|
l.isCallerEnabled = isEnabled
|
||
|
}
|
||
|
|
||
|
// Flush writes buffered logs.
|
||
|
func (l *Logger) Flush() {
|
||
|
if flusher, ok := l.lw.(Flusher); ok {
|
||
|
flusher.Flush()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Fatal logs a message with severity FATAL followed by a call to os.Exit(1).
|
||
|
func (l *Logger) Fatal(ctx context.Context, v ...interface{}) {
|
||
|
l.event(ctx, FatalLevel).write("", v...)
|
||
|
}
|
||
|
|
||
|
// Panic logs a message with severity PANIC followed by a call to panic().
|
||
|
func (l *Logger) Panic(ctx context.Context, v ...interface{}) {
|
||
|
l.event(ctx, PanicLevel).write("", v...)
|
||
|
}
|
||
|
|
||
|
// Error logs a message with severity ERROR.
|
||
|
func (l *Logger) Error(ctx context.Context, v ...interface{}) {
|
||
|
l.event(ctx, ErrorLevel).write("", v...)
|
||
|
}
|
||
|
|
||
|
// Warn logs a message with severity WARN.
|
||
|
func (l *Logger) Warn(ctx context.Context, v ...interface{}) {
|
||
|
l.event(ctx, WarnLevel).write("", v...)
|
||
|
}
|
||
|
|
||
|
// Info logs a message with severity INFO.
|
||
|
func (l *Logger) Info(ctx context.Context, v ...interface{}) {
|
||
|
l.event(ctx, InfoLevel).write("", v...)
|
||
|
}
|
||
|
|
||
|
// Debug logs a message with severity DEBUG.
|
||
|
func (l *Logger) Debug(ctx context.Context, v ...interface{}) {
|
||
|
l.event(ctx, DebugLevel).write("", v...)
|
||
|
}
|
||
|
|
||
|
// Fatalf logs a message with severity FATAL in format followed by a call to
|
||
|
// os.Exit(1).
|
||
|
func (l *Logger) Fatalf(ctx context.Context, format string, v ...interface{}) {
|
||
|
l.event(ctx, FatalLevel).write(format, v...)
|
||
|
}
|
||
|
|
||
|
// Panicf logs a message with severity PANIC in format followed by a call to
|
||
|
// panic().
|
||
|
func (l *Logger) Panicf(ctx context.Context, format string, v ...interface{}) {
|
||
|
l.event(ctx, PanicLevel).write(format, v...)
|
||
|
}
|
||
|
|
||
|
// Errorf logs a message with severity ERROR in format.
|
||
|
func (l *Logger) Errorf(ctx context.Context, format string, v ...interface{}) {
|
||
|
l.event(ctx, ErrorLevel).write(format, v...)
|
||
|
}
|
||
|
|
||
|
// Warnf logs a message with severity WARN in format.
|
||
|
func (l *Logger) Warnf(ctx context.Context, format string, v ...interface{}) {
|
||
|
l.event(ctx, WarnLevel).write(format, v...)
|
||
|
}
|
||
|
|
||
|
// Infof logs a message with severity INFO in format.
|
||
|
func (l *Logger) Infof(ctx context.Context, format string, v ...interface{}) {
|
||
|
l.event(ctx, InfoLevel).write(format, v...)
|
||
|
}
|
||
|
|
||
|
// Debugf logs a message with severity DEBUG in format.
|
||
|
func (l *Logger) Debugf(ctx context.Context, format string, v ...interface{}) {
|
||
|
l.event(ctx, DebugLevel).write(format, v...)
|
||
|
}
|
||
|
|
||
|
// FatalEvent returns a log event with severity FATAL.
|
||
|
func (l *Logger) FatalEvent(ctx context.Context) *Event {
|
||
|
return l.event(ctx, FatalLevel)
|
||
|
}
|
||
|
|
||
|
// PanicEvent returns a log event with severity PANIC.
|
||
|
func (l *Logger) PanicEvent(ctx context.Context) *Event {
|
||
|
return l.event(ctx, PanicLevel)
|
||
|
}
|
||
|
|
||
|
// ErrorEvent returns a log event with severity ERROR.
|
||
|
func (l *Logger) ErrorEvent(ctx context.Context) *Event {
|
||
|
return l.event(ctx, ErrorLevel)
|
||
|
}
|
||
|
|
||
|
// WarnEvent returns a log event with severity WARN.
|
||
|
func (l *Logger) WarnEvent(ctx context.Context) *Event {
|
||
|
return l.event(ctx, WarnLevel)
|
||
|
}
|
||
|
|
||
|
// InfoEvent returns a log event with severity INFO.
|
||
|
func (l *Logger) InfoEvent(ctx context.Context) *Event {
|
||
|
return l.event(ctx, InfoLevel)
|
||
|
}
|
||
|
|
||
|
// DebugEvent returns a log event with severity DEBUG.
|
||
|
func (l *Logger) DebugEvent(ctx context.Context) *Event {
|
||
|
return l.event(ctx, DebugLevel)
|
||
|
}
|
||
|
|
||
|
func (l *Logger) event(ctx context.Context, level Level) (e *Event) {
|
||
|
var ctxKeys *[]interface{}
|
||
|
var ctxKeysMap *map[interface{}]string
|
||
|
|
||
|
if len(l.ctxKeys) > 0 {
|
||
|
ctxKeys = &l.ctxKeys
|
||
|
}
|
||
|
if len(l.ctxKeysMap) > 0 {
|
||
|
ctxKeysMap = &l.ctxKeysMap
|
||
|
}
|
||
|
|
||
|
return newEvent(ctx, ctxKeys, ctxKeysMap, level, l.lw, level <= l.level, l.isCallerEnabled)
|
||
|
}
|
||
|
|
||
|
// NewLogger creates a new logger for given out and level, and the level is
|
||
|
// optional.
|
||
|
func NewLogger(out io.Writer, level ...string) (*Logger, error) {
|
||
|
return NewLoggerWithError(out, nil, level...)
|
||
|
}
|
||
|
|
||
|
// NewLoggerWithError creates a new logger for given out, err out, level, and the
|
||
|
// err out can be nil, and the level is optional.
|
||
|
func NewLoggerWithError(out, errOut io.Writer, level ...string) (l *Logger, err error) {
|
||
|
if out == nil {
|
||
|
return nil, errors.New("logger output must specified")
|
||
|
}
|
||
|
|
||
|
sw := &StandardWriter{w: out, ew: errOut, pid: os.Getpid()}
|
||
|
l = &Logger{lw: sw}
|
||
|
|
||
|
if len(level) == 1 {
|
||
|
if err = l.SetLevel(level[0]); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return l, nil
|
||
|
}
|
||
|
|
||
|
// NewTerminalLogger creates a logger that write into terminal.
|
||
|
func NewTerminalLogger(level ...string) (*Logger, error) {
|
||
|
return NewLogger(os.Stdout, level...)
|
||
|
}
|
||
|
|
||
|
// NewBufferedTerminalLogger creates a buffered logger that write into terminal.
|
||
|
func NewBufferedTerminalLogger(level ...string) (*Logger, error) {
|
||
|
return NewLogger(bufio.NewWriter(os.Stdout), level...)
|
||
|
}
|
||
|
|
||
|
// NewFileLogger creates a logger that write into file.
|
||
|
func NewFileLogger(filePath string, level ...string) (*Logger, error) {
|
||
|
return NewFileLoggerWithError(filePath, "", level...)
|
||
|
}
|
||
|
|
||
|
// NewFileLoggerWithError creates a logger that write into files.
|
||
|
func NewFileLoggerWithError(filePath, errFilePath string, level ...string) (*Logger, error) {
|
||
|
if err := check.Dir(path.Dir(filePath)); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if errFilePath != "" {
|
||
|
if err := check.Dir(path.Dir(errFilePath)); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
out, err := reopen.NewFileWriter(filePath)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
var errOut *reopen.FileWriter
|
||
|
if errFilePath != "" {
|
||
|
errOut, err = reopen.NewFileWriter(errFilePath)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
c := make(chan os.Signal)
|
||
|
go func() {
|
||
|
for {
|
||
|
select {
|
||
|
case <-c:
|
||
|
out.Reopen()
|
||
|
if errOut != nil {
|
||
|
errOut.Reopen()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
signal.Notify(c, syscall.SIGHUP)
|
||
|
|
||
|
if errOut == nil {
|
||
|
return NewLoggerWithError(out, nil, level...)
|
||
|
}
|
||
|
return NewLoggerWithError(out, errOut, level...)
|
||
|
}
|
||
|
|
||
|
// NewBufferedFileLogger creates a logger that write into file with buffer.
|
||
|
// The flushSeconds's unit is second.
|
||
|
func NewBufferedFileLogger(filePath string, flushInterval int, level ...string) (*Logger, error) {
|
||
|
return NewBufferedFileLoggerWithError(filePath, "", flushInterval, level...)
|
||
|
}
|
||
|
|
||
|
// NewBufferedFileLoggerWithError creates a logger that write into files with buffer.
|
||
|
// The flushSeconds's unit is second.
|
||
|
func NewBufferedFileLoggerWithError(filePath, errFilePath string, flushInterval int, level ...string) (*Logger, error) {
|
||
|
if err := check.Dir(path.Dir(filePath)); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if errFilePath != "" {
|
||
|
if err := check.Dir(path.Dir(errFilePath)); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if flushInterval == 0 {
|
||
|
flushInterval = 10
|
||
|
}
|
||
|
|
||
|
out, err := reopen.NewFileWriter(filePath)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
var errOut *reopen.FileWriter
|
||
|
if errFilePath != "" {
|
||
|
errOut, err = reopen.NewFileWriter(errFilePath)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bufferedOut := reopen.NewBufferedFileWriter(out)
|
||
|
var bufferedErrOut *reopen.BufferedFileWriter
|
||
|
if errOut != nil {
|
||
|
bufferedErrOut = reopen.NewBufferedFileWriter(errOut)
|
||
|
}
|
||
|
|
||
|
c := make(chan os.Signal)
|
||
|
go func() {
|
||
|
for {
|
||
|
select {
|
||
|
case <-c:
|
||
|
bufferedOut.Reopen()
|
||
|
if bufferedErrOut != nil {
|
||
|
bufferedErrOut.Reopen()
|
||
|
}
|
||
|
case <-time.After(time.Duration(flushInterval) * time.Second):
|
||
|
bufferedOut.Flush()
|
||
|
if bufferedErrOut != nil {
|
||
|
bufferedErrOut.Flush()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
signal.Notify(c, syscall.SIGHUP)
|
||
|
|
||
|
if bufferedErrOut == nil {
|
||
|
return NewLoggerWithError(bufferedOut, nil, level...)
|
||
|
}
|
||
|
return NewLoggerWithError(bufferedOut, bufferedErrOut, level...)
|
||
|
}
|