mirror of
https://github.com/buger/goreplay.git
synced 2024-04-21 12:32:02 +00:00
592 lines
15 KiB
Go
592 lines
15 KiB
Go
package gou
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
NOLOGGING = -1
|
|
FATAL = 0
|
|
ERROR = 1
|
|
WARN = 2
|
|
INFO = 3
|
|
DEBUG = 4
|
|
)
|
|
|
|
/*
|
|
https://github.com/mewkiz/pkg/tree/master/term
|
|
RED = '\033[0;1;31m'
|
|
GREEN = '\033[0;1;32m'
|
|
YELLOW = '\033[0;1;33m'
|
|
BLUE = '\033[0;1;34m'
|
|
MAGENTA = '\033[0;1;35m'
|
|
CYAN = '\033[0;1;36m'
|
|
WHITE = '\033[0;1;37m'
|
|
DARK_MAGENTA = '\033[0;35m'
|
|
ANSI_RESET = '\033[0m'
|
|
LogColor = map[int]string{FATAL: "\033[0m\033[37m",
|
|
ERROR: "\033[0m\033[31m",
|
|
WARN: "\033[0m\033[33m",
|
|
INFO: "\033[0m\033[32m",
|
|
DEBUG: "\033[0m\033[34m"}
|
|
|
|
\e]PFdedede
|
|
*/
|
|
|
|
var (
|
|
LogLevel int = ERROR
|
|
EMPTY struct{}
|
|
ErrLogLevel int = ERROR
|
|
logger *log.Logger
|
|
customLogger LoggerCustom
|
|
loggerErr *log.Logger
|
|
LogColor = map[int]string{FATAL: "\033[0m\033[37m",
|
|
ERROR: "\033[0m\033[31m",
|
|
WARN: "\033[0m\033[33m",
|
|
INFO: "\033[0m\033[35m",
|
|
DEBUG: "\033[0m\033[34m"}
|
|
LogPrefix = map[int]string{
|
|
FATAL: "[FATAL] ",
|
|
ERROR: "[ERROR] ",
|
|
WARN: "[WARN] ",
|
|
INFO: "[INFO] ",
|
|
DEBUG: "[DEBUG] ",
|
|
}
|
|
logContextKey = "log_prefix"
|
|
escapeNewlines bool = false
|
|
postFix = "" //\033[0m
|
|
LogLevelWords map[string]int = map[string]int{"fatal": 0, "error": 1, "warn": 2, "info": 3, "debug": 4, "none": -1}
|
|
logThrottles = make(map[string]*Throttler)
|
|
throttleMu sync.Mutex
|
|
)
|
|
|
|
// LoggerCustom defines custom interface for logger implementation
|
|
type LoggerCustom interface {
|
|
Log(depth, logLevel int, msg string, fields map[string]interface{})
|
|
}
|
|
|
|
// Setup default logging to Stderr, equivalent to:
|
|
//
|
|
// gou.SetLogger(log.New(os.Stderr, "", log.Ltime|log.Lshortfile), "debug")
|
|
func SetupLogging(lvl string) {
|
|
SetLogger(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds), strings.ToLower(lvl))
|
|
}
|
|
|
|
// Setup default logging to Stderr, equivalent to:
|
|
//
|
|
// gou.SetLogger(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds), level)
|
|
func SetupLoggingLong(lvl string) {
|
|
SetLogger(log.New(os.Stderr, "", log.LstdFlags|log.Llongfile|log.Lmicroseconds), strings.ToLower(lvl))
|
|
}
|
|
|
|
// SetupLoggingFile writes logs to the file object parameter.
|
|
func SetupLoggingFile(f *os.File, lvl string) {
|
|
SetLogger(log.New(f, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds), strings.ToLower(lvl))
|
|
}
|
|
|
|
// SetCustomLogger sets the logger to a custom logger
|
|
func SetCustomLogger(cl LoggerCustom) {
|
|
customLogger = cl
|
|
}
|
|
|
|
// GetCustomLogger returns the custom logger if initialized
|
|
func GetCustomLogger() LoggerCustom {
|
|
return customLogger
|
|
}
|
|
|
|
// Setup colorized output if this is a terminal
|
|
func SetColorIfTerminal() {
|
|
if IsTerminal() {
|
|
SetColorOutput()
|
|
}
|
|
}
|
|
|
|
// Setup colorized output
|
|
func SetColorOutput() {
|
|
for lvl, color := range LogColor {
|
|
LogPrefix[lvl] = color
|
|
}
|
|
postFix = "\033[0m"
|
|
}
|
|
|
|
//Set whether to escape newline characters in log messages
|
|
func SetEscapeNewlines(en bool) {
|
|
escapeNewlines = en
|
|
}
|
|
|
|
// Setup default log output to go to a dev/null
|
|
//
|
|
// log.SetOutput(new(DevNull))
|
|
func DiscardStandardLogger() {
|
|
log.SetOutput(new(DevNull))
|
|
}
|
|
|
|
// you can set a logger, and log level,most common usage is:
|
|
//
|
|
// gou.SetLogger(log.New(os.Stdout, "", log.LstdFlags), "debug")
|
|
//
|
|
// loglevls: debug, info, warn, error, fatal
|
|
// Note, that you can also set a separate Error Log Level
|
|
func SetLogger(l *log.Logger, logLevel string) {
|
|
logger = l
|
|
LogLevelSet(logLevel)
|
|
}
|
|
func GetLogger() *log.Logger {
|
|
return logger
|
|
}
|
|
|
|
// you can set a logger, and log level. this is for errors, and assumes
|
|
// you are logging to Stderr (seperate from stdout above), allowing you to seperate
|
|
// debug&info logging from errors
|
|
//
|
|
// gou.SetLogger(log.New(os.Stderr, "", log.LstdFlags), "debug")
|
|
//
|
|
// loglevls: debug, info, warn, error, fatal
|
|
func SetErrLogger(l *log.Logger, logLevel string) {
|
|
loggerErr = l
|
|
if lvl, ok := LogLevelWords[logLevel]; ok {
|
|
ErrLogLevel = lvl
|
|
}
|
|
}
|
|
func GetErrLogger() *log.Logger {
|
|
return logger
|
|
}
|
|
|
|
// sets the log level from a string
|
|
func LogLevelSet(levelWord string) {
|
|
if lvl, ok := LogLevelWords[levelWord]; ok {
|
|
LogLevel = lvl
|
|
}
|
|
}
|
|
|
|
// NewContext returns a new Context carrying contextual log message
|
|
// that gets prefixed to log statements.
|
|
func NewContext(ctx context.Context, msg string) context.Context {
|
|
return context.WithValue(ctx, logContextKey, msg)
|
|
}
|
|
|
|
// NewContextWrap returns a new Context carrying contextual log message
|
|
// that gets prefixed to log statements.
|
|
func NewContextWrap(ctx context.Context, msg string) context.Context {
|
|
logContext, ok := ctx.Value(logContextKey).(string)
|
|
if ok {
|
|
return context.WithValue(ctx, logContextKey, fmt.Sprintf("%s %s", logContext, msg))
|
|
}
|
|
return context.WithValue(ctx, logContextKey, msg)
|
|
}
|
|
|
|
// FromContext extracts the Log Context prefix from context
|
|
func FromContext(ctx context.Context) string {
|
|
logContext, _ := ctx.Value(logContextKey).(string)
|
|
return logContext
|
|
}
|
|
|
|
// Log at debug level
|
|
func Debug(v ...interface{}) {
|
|
if LogLevel >= 4 {
|
|
DoLog(3, DEBUG, fmt.Sprint(v...))
|
|
}
|
|
}
|
|
|
|
// Debug log formatted
|
|
func Debugf(format string, v ...interface{}) {
|
|
if LogLevel >= 4 {
|
|
DoLog(3, DEBUG, fmt.Sprintf(format, v...))
|
|
}
|
|
}
|
|
|
|
// Debug log formatted context writer
|
|
func DebugCtx(ctx context.Context, format string, v ...interface{}) {
|
|
if LogLevel >= 4 {
|
|
lc := FromContext(ctx)
|
|
if len(lc) > 0 {
|
|
format = fmt.Sprintf("%s %s", lc, format)
|
|
}
|
|
DoLog(3, DEBUG, fmt.Sprintf(format, v...))
|
|
}
|
|
}
|
|
|
|
func DebugT(lineCt int) {
|
|
if LogLevel >= 4 {
|
|
DoLog(3, DEBUG, fmt.Sprint("\n", PrettyStack(lineCt)))
|
|
}
|
|
}
|
|
|
|
// Log at info level
|
|
func Info(v ...interface{}) {
|
|
if LogLevel >= 3 {
|
|
DoLog(3, INFO, fmt.Sprint(v...))
|
|
}
|
|
}
|
|
|
|
// info log formatted
|
|
func Infof(format string, v ...interface{}) {
|
|
if LogLevel >= 3 {
|
|
DoLog(3, INFO, fmt.Sprintf(format, v...))
|
|
}
|
|
}
|
|
|
|
// Info log formatted context writer
|
|
func InfoCtx(ctx context.Context, format string, v ...interface{}) {
|
|
if LogLevel >= 3 {
|
|
lc := FromContext(ctx)
|
|
if len(lc) > 0 {
|
|
format = fmt.Sprintf("%s %s", lc, format)
|
|
}
|
|
DoLog(3, INFO, fmt.Sprintf(format, v...))
|
|
}
|
|
}
|
|
|
|
// Info Trace
|
|
func InfoT(lineCt int) {
|
|
if LogLevel >= 3 {
|
|
DoLog(3, INFO, fmt.Sprint("\n", PrettyStack(lineCt)))
|
|
}
|
|
}
|
|
|
|
// Log at warn level
|
|
func Warn(v ...interface{}) {
|
|
if LogLevel >= 2 {
|
|
DoLog(3, WARN, fmt.Sprint(v...))
|
|
}
|
|
}
|
|
|
|
// Warn log formatted
|
|
func Warnf(format string, v ...interface{}) {
|
|
if LogLevel >= 2 {
|
|
DoLog(3, WARN, fmt.Sprintf(format, v...))
|
|
}
|
|
}
|
|
|
|
// Warn log formatted context writer
|
|
func WarnCtx(ctx context.Context, format string, v ...interface{}) {
|
|
if LogLevel >= 2 {
|
|
lc := FromContext(ctx)
|
|
if len(lc) > 0 {
|
|
format = fmt.Sprintf("%s %s", lc, format)
|
|
}
|
|
DoLog(3, WARN, fmt.Sprintf(format, v...))
|
|
}
|
|
}
|
|
|
|
// Warn Trace
|
|
func WarnT(lineCt int) {
|
|
if LogLevel >= 2 {
|
|
DoLog(3, WARN, fmt.Sprint("\n", PrettyStack(lineCt)))
|
|
}
|
|
}
|
|
|
|
// Log at error level
|
|
func Error(v ...interface{}) {
|
|
if LogLevel >= 1 {
|
|
DoLog(3, ERROR, fmt.Sprint(v...))
|
|
}
|
|
}
|
|
|
|
// Error log formatted
|
|
func Errorf(format string, v ...interface{}) {
|
|
if LogLevel >= 1 {
|
|
DoLog(3, ERROR, fmt.Sprintf(format, v...))
|
|
}
|
|
}
|
|
|
|
// Error log formatted context writer
|
|
func ErrorCtx(ctx context.Context, format string, v ...interface{}) {
|
|
if LogLevel >= 1 {
|
|
lc := FromContext(ctx)
|
|
if len(lc) > 0 {
|
|
format = fmt.Sprintf("%s %s", lc, format)
|
|
}
|
|
DoLog(3, ERROR, fmt.Sprintf(format, v...))
|
|
}
|
|
}
|
|
|
|
// Log this error, and return error object
|
|
func LogErrorf(format string, v ...interface{}) error {
|
|
err := fmt.Errorf(format, v...)
|
|
if LogLevel >= 1 {
|
|
DoLog(3, ERROR, err.Error())
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Log to logger if setup
|
|
//
|
|
// Log(ERROR, "message")
|
|
func Log(logLvl int, v ...interface{}) {
|
|
if LogLevel >= logLvl {
|
|
DoLog(3, logLvl, fmt.Sprint(v...))
|
|
}
|
|
}
|
|
|
|
// Log to logger if setup, grab a stack trace and add that as well
|
|
//
|
|
// u.LogTracef(u.ERROR, "message %s", varx)
|
|
//
|
|
func LogTracef(logLvl int, format string, v ...interface{}) {
|
|
if LogLevel >= logLvl {
|
|
// grab a stack trace
|
|
stackBuf := make([]byte, 6000)
|
|
stackBufLen := runtime.Stack(stackBuf, false)
|
|
stackTraceStr := string(stackBuf[0:stackBufLen])
|
|
parts := strings.Split(stackTraceStr, "\n")
|
|
if len(parts) > 1 {
|
|
v = append(v, strings.Join(parts[3:], "\n"))
|
|
}
|
|
DoLog(3, logLvl, fmt.Sprintf(format+"\n%v", v...))
|
|
}
|
|
}
|
|
|
|
// Log to logger if setup, grab a stack trace and add that as well
|
|
//
|
|
// u.LogTracef(u.ERROR, "message %s", varx)
|
|
//
|
|
func LogTraceDf(logLvl, lineCt int, format string, v ...interface{}) {
|
|
if LogLevel >= logLvl {
|
|
// grab a stack trace
|
|
stackBuf := make([]byte, 6000)
|
|
stackBufLen := runtime.Stack(stackBuf, false)
|
|
stackTraceStr := string(stackBuf[0:stackBufLen])
|
|
parts := strings.Split(stackTraceStr, "\n")
|
|
if len(parts) > 1 {
|
|
if (len(parts) - 3) > lineCt {
|
|
parts = parts[3 : 3+lineCt]
|
|
parts2 := make([]string, 0, len(parts)/2)
|
|
for i := 1; i < len(parts); i = i + 2 {
|
|
parts2 = append(parts2, parts[i])
|
|
}
|
|
v = append(v, strings.Join(parts2, "\n"))
|
|
//v = append(v, strings.Join(parts[3:3+lineCt], "\n"))
|
|
} else {
|
|
v = append(v, strings.Join(parts[3:], "\n"))
|
|
}
|
|
}
|
|
DoLog(3, logLvl, fmt.Sprintf(format+"\n%v", v...))
|
|
}
|
|
}
|
|
|
|
// PrettyStack is a helper to pull stack-trace, prettify it.
|
|
func PrettyStack(lineCt int) string {
|
|
stackBuf := make([]byte, 10000)
|
|
stackBufLen := runtime.Stack(stackBuf, false)
|
|
stackTraceStr := string(stackBuf[0:stackBufLen])
|
|
parts := strings.Split(stackTraceStr, "\n")
|
|
if len(parts) > 3 {
|
|
parts = parts[2:]
|
|
parts2 := make([]string, 0, len(parts)/2)
|
|
for i := 3; i < len(parts)-1; i++ {
|
|
if !strings.HasSuffix(parts[i], ")") && !strings.HasPrefix(parts[i], "/usr/local") {
|
|
parts2 = append(parts2, parts[i])
|
|
}
|
|
}
|
|
if len(parts2) > lineCt {
|
|
return strings.Join(parts2[0:lineCt], "\n")
|
|
}
|
|
return strings.Join(parts2, "\n")
|
|
}
|
|
return stackTraceStr
|
|
}
|
|
|
|
// Throttle logging based on key, such that key would never occur more than
|
|
// @limit times per hour
|
|
//
|
|
// LogThrottleKey(u.ERROR, 1,"error_that_happens_a_lot" "message %s", varx)
|
|
//
|
|
func LogThrottleKey(logLvl, limit int, key, format string, v ...interface{}) {
|
|
if LogLevel < logLvl {
|
|
return
|
|
}
|
|
|
|
doLogThrottle(4, logLvl, limit, key, fmt.Sprintf(format, v...))
|
|
}
|
|
|
|
// LogThrottleKeyCtx log formatted context writer
|
|
func LogThrottleKeyCtx(ctx context.Context, logLvl, limit int, key, format string, v ...interface{}) {
|
|
if LogLevel < logLvl {
|
|
return
|
|
}
|
|
|
|
lc := FromContext(ctx)
|
|
if len(lc) > 0 {
|
|
format = fmt.Sprintf("%s %s", lc, format)
|
|
}
|
|
doLogThrottle(4, logLvl, limit, key, fmt.Sprintf(format, v...))
|
|
}
|
|
|
|
// LogThrottleD logging based on @format as a key, such that key would never occur more than
|
|
// @limit times per hour
|
|
//
|
|
// LogThrottle(u.ERROR, 1, "message %s", varx)
|
|
//
|
|
func LogThrottle(logLvl, limit int, format string, v ...interface{}) {
|
|
if LogLevel < logLvl {
|
|
return
|
|
}
|
|
doLogThrottle(4, logLvl, limit, format, fmt.Sprintf(format, v...))
|
|
}
|
|
|
|
// LogThrottleCtx log formatted context writer, limits to @limit/hour.
|
|
func LogThrottleCtx(ctx context.Context, logLvl, limit int, format string, v ...interface{}) {
|
|
if LogLevel < logLvl {
|
|
return
|
|
}
|
|
|
|
lc := FromContext(ctx)
|
|
if len(lc) > 0 {
|
|
format = fmt.Sprintf("%s %s", lc, format)
|
|
}
|
|
doLogThrottle(4, logLvl, limit, format, fmt.Sprintf(format, v...))
|
|
}
|
|
|
|
// LogThrottleD Throttle logging based on @format as a key, such that key would never
|
|
// occur more than @limit times per hour
|
|
//
|
|
// LogThrottleD(5, u.ERROR, 1, "message %s", varx)
|
|
//
|
|
func LogThrottleD(depth, logLvl, limit int, format string, v ...interface{}) {
|
|
if LogLevel < logLvl {
|
|
return
|
|
}
|
|
doLogThrottle(depth, logLvl, limit, format, fmt.Sprintf(format, v...))
|
|
}
|
|
|
|
func doLogThrottle(depth, logLvl, limit int, key, msg string) {
|
|
if LogLevel < logLvl {
|
|
return
|
|
}
|
|
throttleMu.Lock()
|
|
th, ok := logThrottles[key]
|
|
if !ok {
|
|
th = NewThrottler(limit, 3600*time.Second)
|
|
logThrottles[key] = th
|
|
}
|
|
skip, _ := th.Throttle()
|
|
throttleMu.Unlock()
|
|
if skip {
|
|
return
|
|
}
|
|
|
|
DoLog(depth, logLvl, msg)
|
|
}
|
|
|
|
// Logf to logger if setup
|
|
// Logf(ERROR, "message %d", 20)
|
|
func Logf(logLvl int, format string, v ...interface{}) {
|
|
if LogLevel >= logLvl {
|
|
DoLog(3, logLvl, fmt.Sprintf(format, v...))
|
|
}
|
|
}
|
|
|
|
func LogFieldsf(logLvl int, fields map[string]interface{}, format string, v ...interface{}) {
|
|
if LogLevel >= logLvl {
|
|
DoLogFields(3, logLvl, fmt.Sprintf(format, v...), fields)
|
|
}
|
|
}
|
|
|
|
// Log to logger if setup
|
|
// LogP(ERROR, "prefix", "message", anyItems, youWant)
|
|
func LogP(logLvl int, prefix string, v ...interface{}) {
|
|
if ErrLogLevel >= logLvl && loggerErr != nil {
|
|
loggerErr.Output(3, prefix+LogPrefix[logLvl]+fmt.Sprint(v...)+postFix)
|
|
} else if LogLevel >= logLvl && logger != nil {
|
|
logger.Output(3, prefix+LogPrefix[logLvl]+fmt.Sprint(v...)+postFix)
|
|
}
|
|
}
|
|
|
|
// Log to logger if setup with a prefix
|
|
// LogPf(ERROR, "prefix", "formatString %s %v", anyItems, youWant)
|
|
func LogPf(logLvl int, prefix string, format string, v ...interface{}) {
|
|
if ErrLogLevel >= logLvl && loggerErr != nil {
|
|
loggerErr.Output(3, prefix+LogPrefix[logLvl]+fmt.Sprintf(format, v...)+postFix)
|
|
} else if LogLevel >= logLvl && logger != nil {
|
|
logger.Output(3, prefix+LogPrefix[logLvl]+fmt.Sprintf(format, v...)+postFix)
|
|
}
|
|
}
|
|
|
|
// When you want to use the log short filename flag, and want to use
|
|
// the lower level logging functions (say from an *Assert* type function)
|
|
// you need to modify the stack depth:
|
|
//
|
|
// func init() {}
|
|
// SetLogger(log.New(os.Stderr, "", log.Ltime|log.Lshortfile|log.Lmicroseconds), lvl)
|
|
// }
|
|
//
|
|
// func assert(t *testing.T, myData) {
|
|
// // we want log line to show line that called this assert, not this line
|
|
// LogD(5, DEBUG, v...)
|
|
// }
|
|
func LogD(depth int, logLvl int, v ...interface{}) {
|
|
if LogLevel >= logLvl {
|
|
DoLog(depth, logLvl, fmt.Sprint(v...))
|
|
}
|
|
}
|
|
|
|
// Low level log with depth , level, message and logger
|
|
func DoLog(depth, logLvl int, msg string) {
|
|
DoLogFields(depth+1, logLvl, msg, nil)
|
|
}
|
|
|
|
// LogCtx Low level log with depth , level, message and logger
|
|
func LogCtx(ctx context.Context, depth, logLvl int, format string, v ...interface{}) {
|
|
if LogLevel >= logLvl {
|
|
msg := fmt.Sprintf(format, v...)
|
|
lc := FromContext(ctx)
|
|
if len(lc) > 0 {
|
|
msg = fmt.Sprintf("%s %s", lc, msg)
|
|
}
|
|
DoLog(depth+1, logLvl, msg)
|
|
}
|
|
}
|
|
|
|
// DoLogFields allows the inclusion of additional context for logrus logs
|
|
// file and line number are included in the fields by default
|
|
func DoLogFields(depth, logLvl int, msg string, fields map[string]interface{}) {
|
|
|
|
if escapeNewlines {
|
|
msg = EscapeNewlines(msg)
|
|
}
|
|
|
|
if customLogger == nil {
|
|
// Use standard logger
|
|
if ErrLogLevel >= logLvl && loggerErr != nil {
|
|
loggerErr.Output(depth, LogPrefix[logLvl]+msg+postFix)
|
|
} else if LogLevel >= logLvl && logger != nil {
|
|
logger.Output(depth, LogPrefix[logLvl]+msg+postFix)
|
|
}
|
|
} else {
|
|
customLogger.Log(depth, logLvl, msg, fields)
|
|
}
|
|
}
|
|
|
|
type winsize struct {
|
|
Row uint16
|
|
Col uint16
|
|
Xpixel uint16
|
|
Ypixel uint16
|
|
}
|
|
|
|
const (
|
|
_TIOCGWINSZ = 0x5413 // OSX 1074295912
|
|
)
|
|
|
|
// http://play.golang.org/p/5LIA41Iqfp
|
|
// Dummy discard, satisfies io.Writer without importing io or os.
|
|
type DevNull struct{}
|
|
|
|
func (DevNull) Write(p []byte) (int, error) {
|
|
return len(p), nil
|
|
}
|
|
|
|
// Replace standard newline characters with escaped newlines so long msgs will
|
|
// remain one line.
|
|
func EscapeNewlines(str string) string {
|
|
return strings.Replace(str, "\n", "\\n", -1)
|
|
}
|