269 строки
6.9 KiB
Go
269 строки
6.9 KiB
Go
// Copyright 2017 Microsoft. All rights reserved.
|
|
// MIT License
|
|
|
|
package log
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"sync"
|
|
)
|
|
|
|
// Log level
|
|
const (
|
|
LevelAlert = iota
|
|
LevelError
|
|
LevelWarning
|
|
LevelInfo
|
|
LevelDebug
|
|
)
|
|
|
|
// Log target
|
|
const (
|
|
TargetStderr = iota
|
|
TargetSyslog
|
|
TargetLogfile
|
|
TargetStdout
|
|
TargetStdOutAndLogFile
|
|
)
|
|
|
|
const (
|
|
// Log file properties.
|
|
logPrefix = ""
|
|
logFileExtension = ".log"
|
|
logFilePerm = os.FileMode(0o664)
|
|
|
|
// Log file rotation default limits, in bytes.
|
|
maxLogFileSize = 5 * 1024 * 1024
|
|
maxLogFileCount = 8
|
|
rotationCheckFrq = 8
|
|
)
|
|
|
|
// Logger object
|
|
type Logger struct {
|
|
l *log.Logger
|
|
out io.WriteCloser
|
|
name string
|
|
level int
|
|
target int
|
|
maxFileSize int
|
|
maxFileCount int
|
|
callCount int
|
|
directory string
|
|
mutex *sync.Mutex
|
|
}
|
|
|
|
var pid = os.Getpid()
|
|
|
|
// NewLoggerE creates a new Logger and surfaces any errors encountered during
|
|
// the process. The returned logger is guaranteed to be safe to use when a
|
|
// non-nil error is returned, but may have undesired behavior. Callers should
|
|
// treat the logger as nil under error conditions unless necessary for
|
|
// backwards compatibility reasons.
|
|
func NewLoggerE(name string, level, target int, logDir string) (*Logger, error) {
|
|
logger := &Logger{
|
|
l: log.New(io.Discard, logPrefix, log.LstdFlags),
|
|
name: name,
|
|
level: level,
|
|
directory: logDir,
|
|
maxFileSize: maxLogFileSize,
|
|
maxFileCount: maxLogFileCount,
|
|
mutex: &sync.Mutex{},
|
|
}
|
|
|
|
err := logger.SetTarget(target)
|
|
if err != nil {
|
|
// we *do* want to return the logger here for backwards compatibility
|
|
return logger, fmt.Errorf("setting log target: %w", err)
|
|
}
|
|
return logger, nil
|
|
}
|
|
|
|
// NewLogger creates a new Logger.
|
|
//
|
|
// Deprecated: use NewLoggerE instead
|
|
func NewLogger(name string, level, target int, logDir string) *Logger {
|
|
logger, err := NewLoggerE(name, level, target, logDir)
|
|
if err != nil {
|
|
// ideally this would be returned to the caller, but this API is depended
|
|
// on by unknown parties. Given at this point we have an unusable (but
|
|
// safe) logger, we log to stderr with the standard library in hopes that
|
|
// an operator will see the error and be able to take corrective action
|
|
log.Println("error initializing logger: err:", err)
|
|
}
|
|
|
|
return logger
|
|
}
|
|
|
|
// SetName sets the log name.
|
|
func (logger *Logger) SetName(name string) {
|
|
logger.name = name
|
|
}
|
|
|
|
// SetLevel sets the log chattiness.
|
|
func (logger *Logger) SetLevel(level int) {
|
|
logger.level = level
|
|
}
|
|
|
|
// SetLogFileLimits sets the log file limits.
|
|
func (logger *Logger) SetLogFileLimits(maxFileSize int, maxFileCount int) {
|
|
logger.maxFileSize = maxFileSize
|
|
logger.maxFileCount = maxFileCount
|
|
}
|
|
|
|
// Close closes the log stream.
|
|
func (logger *Logger) Close() {
|
|
if logger.out != nil {
|
|
logger.out.Close()
|
|
}
|
|
}
|
|
|
|
// SetTargetLogDirectory sets the directory location where logs should be stored along with the target
|
|
func (logger *Logger) SetTargetLogDirectory(target int, logDirectory string) error {
|
|
logger.directory = logDirectory
|
|
return logger.SetTarget(target)
|
|
}
|
|
|
|
// GetLogDirectory gets the directory location where logs should be stored.
|
|
func (logger *Logger) GetLogDirectory() string {
|
|
if logger.directory != "" {
|
|
return logger.directory
|
|
}
|
|
|
|
return LogPath
|
|
}
|
|
|
|
// GetLogFileName returns the full log file name.
|
|
func (logger *Logger) getLogFileName() string {
|
|
var logFileName string
|
|
|
|
if logger.directory != "" {
|
|
logFileName = path.Join(logger.directory, logger.name+logFileExtension)
|
|
} else {
|
|
logFileName = LogPath + logger.name + logFileExtension
|
|
}
|
|
|
|
return logFileName
|
|
}
|
|
|
|
// todo: handle errors and use atomic file rotation
|
|
// Rotate checks the active log file size and rotates log files if necessary.
|
|
func (logger *Logger) rotate() {
|
|
// Return if target is not a log file.
|
|
if (logger.target != TargetLogfile && logger.target != TargetStdOutAndLogFile) || logger.out == nil {
|
|
return
|
|
}
|
|
|
|
fileName := logger.getLogFileName()
|
|
fileInfo, err := os.Stat(fileName)
|
|
if err != nil {
|
|
logger.Logf("[log] Failed to query log file info %+v.", err)
|
|
return
|
|
}
|
|
|
|
// Rotate if size limit is reached.
|
|
if fileInfo.Size() >= int64(logger.maxFileSize) {
|
|
logger.out.Close()
|
|
var fn1, fn2 string
|
|
|
|
// Rotate log files, keeping the last maxFileCount files.
|
|
for n := logger.maxFileCount - 1; n >= 0; n-- {
|
|
fn2 = fn1
|
|
if n == 0 {
|
|
fn1 = fileName
|
|
} else {
|
|
fn1 = fmt.Sprintf("%v.%v", fileName, n)
|
|
}
|
|
if fn2 != "" {
|
|
os.Rename(fn1, fn2)
|
|
}
|
|
}
|
|
|
|
// Create a new log file.
|
|
logger.SetTarget(logger.target)
|
|
}
|
|
}
|
|
|
|
// Request logs a structured request.
|
|
func (logger *Logger) Request(tag string, request interface{}, err error) {
|
|
if err == nil {
|
|
logger.Printf("[%s] Received %T %+v.", tag, request, request)
|
|
} else {
|
|
logger.Errorf("[%s] Failed to decode %T %+v %s.", tag, request, request, err.Error())
|
|
}
|
|
}
|
|
|
|
// Response logs a structured response.
|
|
func (logger *Logger) Response(tag string, response interface{}, returnCode int, returnStr string, err error) {
|
|
if err == nil && returnCode == 0 {
|
|
logger.Printf("[%s] Sent %T %+v.", tag, response, response)
|
|
} else if err != nil {
|
|
logger.Errorf("[%s] Code:%s, %+v %s.", tag, returnStr, response, err.Error())
|
|
} else {
|
|
logger.Errorf("[%s] Code:%s, %+v.", tag, returnStr, response)
|
|
}
|
|
}
|
|
|
|
// ResponseEx logs a structured response and the request associate with it.
|
|
func (logger *Logger) ResponseEx(tag string, request interface{}, response interface{}, returnCode int, returnStr string, err error) {
|
|
if err == nil && returnCode == 0 {
|
|
logger.Printf("[%s] Sent %T %+v %T %+v.", tag, request, request, response, response)
|
|
} else if err != nil {
|
|
logger.Errorf("[%s] Code:%s, %+v, %+v %s.", tag, returnStr, request, response, err.Error())
|
|
} else {
|
|
logger.Errorf("[%s] Code:%s, %+v, %+v.", tag, returnStr, request, response)
|
|
}
|
|
}
|
|
|
|
// logf logs a formatted string.
|
|
func (logger *Logger) logf(format string, args ...interface{}) {
|
|
if logger.callCount%rotationCheckFrq == 0 {
|
|
logger.rotate()
|
|
}
|
|
format = fmt.Sprintf("[%v] %s", pid, format)
|
|
logger.callCount++
|
|
logger.l.Printf(format, args...)
|
|
}
|
|
|
|
// Logf wraps logf.
|
|
func (logger *Logger) Logf(format string, args ...interface{}) {
|
|
logger.mutex.Lock()
|
|
logger.logf(format, args...)
|
|
logger.mutex.Unlock()
|
|
}
|
|
|
|
// Printf logs a formatted string at info level.
|
|
func (logger *Logger) Printf(format string, args ...interface{}) {
|
|
if logger.level < LevelInfo {
|
|
return
|
|
}
|
|
|
|
logger.Logf(format, args...)
|
|
}
|
|
|
|
// Debugf logs a formatted string at info level.
|
|
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
|
if logger.level < LevelDebug {
|
|
return
|
|
}
|
|
|
|
logger.Logf(format, args...)
|
|
}
|
|
|
|
// Errorf logs a formatted string at info level and sends the string to TelemetryBuffer.
|
|
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
|
logger.Logf(format, args...)
|
|
}
|
|
|
|
// Warnf logs a formatted string at warninglevel
|
|
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
|
if logger.level < LevelWarning {
|
|
return
|
|
}
|
|
|
|
logger.Logf(format, args...)
|
|
}
|