log.go 11.5 KB
Newer Older
Aaron Raddon's avatar
Aaron Raddon committed
1 2 3 4 5
package gou

import (
	"fmt"
	"log"
ubuntu's avatar
ubuntu committed
6
	"os"
7
	"runtime"
ubuntu's avatar
ubuntu committed
8
	"strings"
Aaron Raddon's avatar
Aaron Raddon committed
9
	"sync"
10
	"time"
Aaron Raddon's avatar
Aaron Raddon committed
11 12 13 14 15 16 17 18 19 20 21 22
)

const (
	NOLOGGING = -1
	FATAL     = 0
	ERROR     = 1
	WARN      = 2
	INFO      = 3
	DEBUG     = 4
)

/*
23
https://github.com/mewkiz/pkg/tree/master/term
Aaron Raddon's avatar
Aaron Raddon committed
24 25 26 27 28 29 30 31 32
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'
Aaron Raddon's avatar
Aaron Raddon committed
33 34 35 36 37 38 39
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
Aaron Raddon's avatar
Aaron Raddon committed
40 41 42
*/

var (
43
	LogLevel    int = ERROR
Aaron Raddon's avatar
Aaron Raddon committed
44
	EMPTY       struct{}
45 46 47
	ErrLogLevel int = ERROR
	logger      *log.Logger
	loggerErr   *log.Logger
48
	LogColor    = map[int]string{FATAL: "\033[0m\033[37m",
Aaron Raddon's avatar
Aaron Raddon committed
49 50 51 52
		ERROR: "\033[0m\033[31m",
		WARN:  "\033[0m\033[33m",
		INFO:  "\033[0m\033[35m",
		DEBUG: "\033[0m\033[34m"}
53 54 55 56 57 58 59
	LogPrefix = map[int]string{
		FATAL: "[FATAL] ",
		ERROR: "[ERROR] ",
		WARN:  "[WARN] ",
		INFO:  "[INFO] ",
		DEBUG: "[DEBUG] ",
	}
60
	escapeNewlines bool           = false
61 62 63 64
	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
Aaron Raddon's avatar
Aaron Raddon committed
65 66
)

ubuntu's avatar
ubuntu committed
67
// Setup default logging to Stderr, equivalent to:
ubuntu's avatar
ubuntu committed
68
//
ubuntu's avatar
ubuntu committed
69
//	gou.SetLogger(log.New(os.Stderr, "", log.Ltime|log.Lshortfile), "debug")
ubuntu's avatar
ubuntu committed
70
func SetupLogging(lvl string) {
71
	SetLogger(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds), strings.ToLower(lvl))
ubuntu's avatar
ubuntu committed
72 73
}

Aaron Raddon's avatar
Aaron Raddon committed
74 75 76 77 78 79 80
// 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))
}

ubuntu's avatar
ubuntu committed
81 82 83 84 85 86 87 88 89 90 91 92 93
// 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"
ubuntu's avatar
ubuntu committed
94 95
}

96
//Set whether to escape newline characters in log messages
Josh Roppo's avatar
Josh Roppo committed
97
func SetEscapeNewlines(en bool) {
98 99 100
	escapeNewlines = en
}

Aaron Raddon's avatar
Aaron Raddon committed
101 102 103 104 105 106 107
// Setup default log output to go to a dev/null
//
//	log.SetOutput(new(DevNull))
func DiscardStandardLogger() {
	log.SetOutput(new(DevNull))
}

Aaron Raddon's avatar
Aaron Raddon committed
108 109
// you can set a logger, and log level,most common usage is:
//
110
//	gou.SetLogger(log.New(os.Stdout, "", log.LstdFlags), "debug")
Aaron Raddon's avatar
Aaron Raddon committed
111 112
//
//  loglevls:   debug, info, warn, error, fatal
Shawn Smith's avatar
Shawn Smith committed
113
// Note, that you can also set a separate Error Log Level
Aaron Raddon's avatar
Aaron Raddon committed
114 115 116 117
func SetLogger(l *log.Logger, logLevel string) {
	logger = l
	LogLevelSet(logLevel)
}
Aaron Raddon's avatar
Aaron Raddon committed
118 119 120
func GetLogger() *log.Logger {
	return logger
}
Aaron Raddon's avatar
Aaron Raddon committed
121

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
// 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
}

Aaron Raddon's avatar
Aaron Raddon committed
139 140 141 142 143 144 145 146 147
// sets the log level from a string
func LogLevelSet(levelWord string) {
	if lvl, ok := LogLevelWords[levelWord]; ok {
		LogLevel = lvl
	}
}

// Log at debug level
func Debug(v ...interface{}) {
148 149
	if LogLevel >= 4 {
		DoLog(3, DEBUG, fmt.Sprint(v...))
Aaron Raddon's avatar
Aaron Raddon committed
150 151 152
	}
}

153
// Debug log formatted
Aaron Raddon's avatar
Aaron Raddon committed
154 155
func Debugf(format string, v ...interface{}) {
	if LogLevel >= 4 {
156
		DoLog(3, DEBUG, fmt.Sprintf(format, v...))
Aaron Raddon's avatar
Aaron Raddon committed
157 158 159
	}
}

Aaron Raddon's avatar
Aaron Raddon committed
160 161
func DebugT(lineCt int) {
	if LogLevel >= 4 {
Aaron Raddon's avatar
Aaron Raddon committed
162
		DoLog(3, DEBUG, fmt.Sprint("\n", PrettyStack(lineCt)))
Aaron Raddon's avatar
Aaron Raddon committed
163 164 165
	}
}

166 167
// Log at info level
func Info(v ...interface{}) {
Aaron Raddon's avatar
Aaron Raddon committed
168
	if LogLevel >= 3 {
169 170 171 172 173 174
		DoLog(3, INFO, fmt.Sprint(v...))
	}
}

// info log formatted
func Infof(format string, v ...interface{}) {
Aaron Raddon's avatar
Aaron Raddon committed
175
	if LogLevel >= 3 {
176 177 178 179
		DoLog(3, INFO, fmt.Sprintf(format, v...))
	}
}

Aaron Raddon's avatar
Aaron Raddon committed
180 181 182
// Info Trace
func InfoT(lineCt int) {
	if LogLevel >= 3 {
Aaron Raddon's avatar
Aaron Raddon committed
183
		DoLog(3, INFO, fmt.Sprint("\n", PrettyStack(lineCt)))
Aaron Raddon's avatar
Aaron Raddon committed
184 185 186
	}
}

187 188
// Log at warn level
func Warn(v ...interface{}) {
Aaron Raddon's avatar
Aaron Raddon committed
189
	if LogLevel >= 2 {
190 191 192 193 194 195
		DoLog(3, WARN, fmt.Sprint(v...))
	}
}

// Debug log formatted
func Warnf(format string, v ...interface{}) {
Aaron Raddon's avatar
Aaron Raddon committed
196
	if LogLevel >= 2 {
197 198 199 200
		DoLog(3, WARN, fmt.Sprintf(format, v...))
	}
}

Aaron Raddon's avatar
Aaron Raddon committed
201 202 203
// Warn Trace
func WarnT(lineCt int) {
	if LogLevel >= 2 {
Aaron Raddon's avatar
Aaron Raddon committed
204
		DoLog(3, WARN, fmt.Sprint("\n", PrettyStack(lineCt)))
Aaron Raddon's avatar
Aaron Raddon committed
205 206 207
	}
}

208 209
// Log at error level
func Error(v ...interface{}) {
Aaron Raddon's avatar
Aaron Raddon committed
210
	if LogLevel >= 1 {
211 212 213 214 215 216
		DoLog(3, ERROR, fmt.Sprint(v...))
	}
}

// Error log formatted
func Errorf(format string, v ...interface{}) {
Aaron Raddon's avatar
Aaron Raddon committed
217
	if LogLevel >= 1 {
218 219 220 221
		DoLog(3, ERROR, fmt.Sprintf(format, v...))
	}
}

222 223 224 225 226 227 228 229 230
// 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
}

Aaron Raddon's avatar
Aaron Raddon committed
231 232 233 234
// Log to logger if setup
//    Log(ERROR, "message")
func Log(logLvl int, v ...interface{}) {
	if LogLevel >= logLvl {
235
		DoLog(3, logLvl, fmt.Sprint(v...))
Aaron Raddon's avatar
Aaron Raddon committed
236 237 238
	}
}

239 240 241 242 243 244 245
// 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
246
		stackBuf := make([]byte, 6000)
247 248
		stackBufLen := runtime.Stack(stackBuf, false)
		stackTraceStr := string(stackBuf[0:stackBufLen])
249 250
		parts := strings.Split(stackTraceStr, "\n")
		if len(parts) > 1 {
Shawn Smith's avatar
Shawn Smith committed
251
			v = append(v, strings.Join(parts[3:], "\n"))
252
		}
253
		DoLog(3, logLvl, fmt.Sprintf(format+"\n%v", v...))
254 255 256
	}
}

257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
// 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 {
Shawn Smith's avatar
Shawn Smith committed
278
				v = append(v, strings.Join(parts[3:], "\n"))
279 280 281 282 283 284
			}
		}
		DoLog(3, logLvl, fmt.Sprintf(format+"\n%v", v...))
	}
}

Aaron Raddon's avatar
Aaron Raddon committed
285
func PrettyStack(lineCt int) string {
Aaron Raddon's avatar
Aaron Raddon committed
286
	stackBuf := make([]byte, 10000)
Aaron Raddon's avatar
Aaron Raddon committed
287 288 289 290 291 292
	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)
Aaron Raddon's avatar
Aaron Raddon committed
293
		for i := 3; i < len(parts)-1; i++ {
Aaron Raddon's avatar
Aaron Raddon committed
294 295 296 297 298 299 300 301 302 303 304 305
			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
}

Aaron Raddon's avatar
Aaron Raddon committed
306
// Throttle logging based on key, such that key would never occur more than
307
//   @limit times per hour
Aaron Raddon's avatar
Aaron Raddon committed
308 309 310 311 312 313 314 315
//
//    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 {
		throttleMu.Lock()
		th, ok := logThrottles[key]
		if !ok {
316
			th = NewThrottler(limit, 3600*time.Second)
Aaron Raddon's avatar
Aaron Raddon committed
317 318
			logThrottles[key] = th
		}
319 320
		skip, throttleCount := th.Throttle()
		if skip {
Aaron Raddon's avatar
Aaron Raddon committed
321 322 323 324
			throttleMu.Unlock()
			return
		}
		throttleMu.Unlock()
325 326

		if throttleCount > 0 {
327
			format = fmt.Sprintf("%s LogsThrottled[%d]", format, throttleCount)
328
		}
329
		DoLog(3, logLvl, fmt.Sprintf(format, v...))
Aaron Raddon's avatar
Aaron Raddon committed
330 331 332
	}
}

333
// Throttle logging based on @format as a key, such that key would never occur more than
334
//   @limit times per hour
335 336 337 338 339 340 341 342
//
//    LogThrottle(u.ERROR, 1, "message %s", varx)
//
func LogThrottle(logLvl, limit int, format string, v ...interface{}) {
	if LogLevel >= logLvl {
		throttleMu.Lock()
		th, ok := logThrottles[format]
		if !ok {
343
			th = NewThrottler(limit, 3600*time.Second)
344 345
			logThrottles[format] = th
		}
346 347 348
		var throttleCount int32
		skip, throttleCount := th.Throttle()
		if skip {
349 350 351 352
			throttleMu.Unlock()
			return
		}
		throttleMu.Unlock()
353 354

		if throttleCount > 0 {
355
			format = fmt.Sprintf("%s LogsThrottled[%d]", format, throttleCount)
356
		}
357
		DoLog(3, logLvl, fmt.Sprintf(format, v...))
358 359 360
	}
}

Aaron Raddon's avatar
Aaron Raddon committed
361 362 363 364 365 366 367 368 369 370 371 372 373
// 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 {
		throttleMu.Lock()
		th, ok := logThrottles[format]
		if !ok {
			th = NewThrottler(limit, 3600*time.Second)
			logThrottles[format] = th
		}
374 375
		skip, throttleCount := th.Throttle()
		if skip {
Aaron Raddon's avatar
Aaron Raddon committed
376 377 378 379
			throttleMu.Unlock()
			return
		}
		throttleMu.Unlock()
380 381

		format = fmt.Sprintf("Log Throttled[%d] %s", throttleCount, format)
Aaron Raddon's avatar
Aaron Raddon committed
382 383 384 385
		DoLog(depth, logLvl, fmt.Sprintf(format, v...))
	}
}

Aaron Raddon's avatar
Aaron Raddon committed
386
// Log to logger if setup
387 388 389 390
//    Logf(ERROR, "message %d", 20)
func Logf(logLvl int, format string, v ...interface{}) {
	if LogLevel >= logLvl {
		DoLog(3, logLvl, fmt.Sprintf(format, v...))
Aaron Raddon's avatar
Aaron Raddon committed
391 392 393
	}
}

Aaron Raddon's avatar
Aaron Raddon committed
394
// Log to logger if setup
395 396 397
//    LogP(ERROR, "prefix", "message", anyItems, youWant)
func LogP(logLvl int, prefix string, v ...interface{}) {
	if ErrLogLevel >= logLvl && loggerErr != nil {
398
		loggerErr.Output(3, prefix+LogPrefix[logLvl]+fmt.Sprint(v...)+postFix)
399
	} else if LogLevel >= logLvl && logger != nil {
400
		logger.Output(3, prefix+LogPrefix[logLvl]+fmt.Sprint(v...)+postFix)
Aaron Raddon's avatar
Aaron Raddon committed
401 402 403
	}
}

404
// Log to logger if setup with a prefix
Aaron Raddon's avatar
Aaron Raddon committed
405 406
//    LogPf(ERROR, "prefix", "formatString %s %v", anyItems, youWant)
func LogPf(logLvl int, prefix string, format string, v ...interface{}) {
407
	if ErrLogLevel >= logLvl && loggerErr != nil {
408
		loggerErr.Output(3, prefix+LogPrefix[logLvl]+fmt.Sprintf(format, v...)+postFix)
409
	} else if LogLevel >= logLvl && logger != nil {
410
		logger.Output(3, prefix+LogPrefix[logLvl]+fmt.Sprintf(format, v...)+postFix)
Aaron Raddon's avatar
Aaron Raddon committed
411 412 413
	}
}

414
// When you want to use the log short filename flag, and want to use
415
// the lower level logging functions (say from an *Assert* type function)
Aaron Raddon's avatar
Aaron Raddon committed
416 417
// you need to modify the stack depth:
//
418 419 420
//     func init() {}
// 	       SetLogger(log.New(os.Stderr, "", log.Ltime|log.Lshortfile|log.Lmicroseconds), lvl)
//     }
421
//
422 423 424 425
//     func assert(t *testing.T, myData) {
//         // we want log line to show line that called this assert, not this line
//         LogD(5, DEBUG, v...)
//     }
Aaron Raddon's avatar
Aaron Raddon committed
426 427
func LogD(depth int, logLvl int, v ...interface{}) {
	if LogLevel >= logLvl {
428
		DoLog(depth, logLvl, fmt.Sprint(v...))
Aaron Raddon's avatar
Aaron Raddon committed
429 430 431 432
	}
}

// Low level log with depth , level, message and logger
433
func DoLog(depth, logLvl int, msg string) {
434 435 436
	if escapeNewlines {
		msg = EscapeNewlines(msg)
	}
437
	if ErrLogLevel >= logLvl && loggerErr != nil {
438
		loggerErr.Output(depth, LogPrefix[logLvl]+msg+postFix)
439
	} else if LogLevel >= logLvl && logger != nil {
440 441 442 443 444 445 446 447 448 449 450 451 452 453
		logger.Output(depth, LogPrefix[logLvl]+msg+postFix)
	}
}

type winsize struct {
	Row    uint16
	Col    uint16
	Xpixel uint16
	Ypixel uint16
}

const (
	_TIOCGWINSZ = 0x5413 // OSX 1074295912
)
Aaron Raddon's avatar
Aaron Raddon committed
454 455 456 457 458 459 460 461

//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
}
462 463 464 465 466 467

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