Files
2019-05-13 20:38:06 +03:00

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)
}