1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
|
// Tideland Go Library - Logger
//
// Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany
//
// All rights reserved. Use of this source code is governed
// by the new BSD license.
package logger
//--------------------
// IMPORTS
//--------------------
import (
"fmt"
"io"
"log"
"os"
"path"
"runtime"
"strings"
"sync"
"time"
)
//--------------------
// TYPES AND TYPE FUNCTIONS
//--------------------
// LogLevel describes the chosen log level between
// debug and critical.
type LogLevel int
// Log levels to control the logging output.
const (
LevelDebug LogLevel = iota
LevelInfo
LevelWarning
LevelError
LevelCritical
LevelFatal
)
// FatalExiterFunc defines a functions that will be called
// in case of a Fatalf call.
type FatalExiterFunc func()
// OsFatalExiter exits the application with os.Exit and
// the return code -1.
func OsFatalExiter() {
os.Exit(-1)
}
// PanicFatalExiter exits the application with a panic.
func PanicFatalExiter() {
panic("program aborted after fatal situation, see log")
}
// FilterFunc allows to filter the output of the logging. Filters
// have to return true if the received entry shall be filtered and
// not output.
type FilterFunc func(level LogLevel, info, msg string) bool
//--------------------
// LOG CONTROL
//--------------------
// Log control variables.
var (
logMux sync.RWMutex
logLevel LogLevel = LevelInfo
logBackend Logger = NewStandardLogger(os.Stdout)
logFatalExiter FatalExiterFunc = OsFatalExiter
logFilter FilterFunc
)
// Level returns the current log level.
func Level() LogLevel {
logMux.Lock()
defer logMux.Unlock()
return logLevel
}
// SetLevel switches to a new log level and returns
// the current one.
func SetLevel(level LogLevel) LogLevel {
logMux.Lock()
defer logMux.Unlock()
current := logLevel
switch {
case level <= LevelDebug:
logLevel = LevelDebug
case level >= LevelFatal:
logLevel = LevelFatal
default:
logLevel = level
}
return current
}
// SetLevelString switches to a new log level passed as
// string. Accepted are the values "debug", "info", "warning"
// "error", "critical", and "fatal". The passed string will
// be set to lower-case. The function is intended to be used
// when then log level is read out of a configuration.
func SetLevelString(levelstr string) LogLevel {
logMux.Lock()
defer logMux.Unlock()
current := logLevel
switch strings.ToLower(levelstr) {
case "debug":
logLevel = LevelDebug
case "info":
logLevel = LevelInfo
case "warning":
logLevel = LevelWarning
case "error":
logLevel = LevelError
case "critical":
logLevel = LevelCritical
case "fatal":
logLevel = LevelFatal
}
return current
}
// SetLogger sets a new logger.
func SetLogger(l Logger) Logger {
logMux.Lock()
defer logMux.Unlock()
old := logBackend
logBackend = l
return old
}
// SetFatalExiter sets the fatal exiter function and
// returns the current one.
func SetFatalExiter(fef FatalExiterFunc) FatalExiterFunc {
logMux.Lock()
defer logMux.Unlock()
current := logFatalExiter
logFatalExiter = fef
return current
}
// SetFilter sets the global output filter and returns
// the current one.
func SetFilter(ff FilterFunc) FilterFunc {
logMux.Lock()
defer logMux.Unlock()
current := logFilter
logFilter = ff
return current
}
// UnsetFilter removes the global output folter and
// returns the current one.
func UnsetFilter() FilterFunc {
logMux.Lock()
defer logMux.Unlock()
current := logFilter
logFilter = nil
return current
}
//--------------------
// LOGGING
//--------------------
// Debugf logs a message at debug level.
func Debugf(format string, args ...interface{}) {
logMux.RLock()
defer logMux.RUnlock()
if logLevel <= LevelDebug {
info := retrieveCallInfo().verboseFormat()
msg := fmt.Sprintf(format, args...)
if shallLog(LevelDebug, info, msg) {
logBackend.Debug(info, msg)
}
}
}
// Infof logs a message at info level.
func Infof(format string, args ...interface{}) {
logMux.RLock()
defer logMux.RUnlock()
if logLevel <= LevelInfo {
info := retrieveCallInfo().shortFormat()
msg := fmt.Sprintf(format, args...)
if shallLog(LevelInfo, info, msg) {
logBackend.Info(info, msg)
}
}
}
// Warningf logs a message at warning level.
func Warningf(format string, args ...interface{}) {
logMux.RLock()
defer logMux.RUnlock()
if logLevel <= LevelWarning {
info := retrieveCallInfo().shortFormat()
msg := fmt.Sprintf(format, args...)
if shallLog(LevelWarning, info, msg) {
logBackend.Warning(info, msg)
}
}
}
// Errorf logs a message at error level.
func Errorf(format string, args ...interface{}) {
logMux.RLock()
defer logMux.RUnlock()
if logLevel <= LevelError {
info := retrieveCallInfo().shortFormat()
msg := fmt.Sprintf(format, args...)
if shallLog(LevelError, info, msg) {
logBackend.Error(info, msg)
}
}
}
// Criticalf logs a message at critical level.
func Criticalf(format string, args ...interface{}) {
logMux.RLock()
defer logMux.RUnlock()
if logLevel <= LevelCritical {
info := retrieveCallInfo().verboseFormat()
msg := fmt.Sprintf(format, args...)
if shallLog(LevelCritical, info, msg) {
logBackend.Critical(info, msg)
}
}
}
// Fatalf logs a message independent of any level. After
// logging the message the functions calls the fatal exiter
// function, which by default means exiting the application
// with error code -1.
func Fatalf(format string, args ...interface{}) {
logMux.RLock()
defer logMux.RUnlock()
info := retrieveCallInfo().verboseFormat()
msg := fmt.Sprintf(format, args...)
logBackend.Fatal(info, msg)
logFatalExiter()
}
//--------------------
// LOGGER INTERFACE
//--------------------
// Logger is the interface for different logger backends.
type Logger interface {
// Debug logs a debugging message.
Debug(info, msg string)
// Info logs an informational message.
Info(info, msg string)
// Warning logs a warning message.
Warning(info, msg string)
// Error logs an error message.
Error(info, msg string)
// Critical logs a critical error message.
Critical(info, msg string)
// Fatal logs a fatal error message.
Fatal(info, msg string)
}
// defaultTimeFormat controls how the timestamp of the standard
// logger is printed by default.
const defaultTimeFormat = "2006-01-02 15:04:05 Z07:00"
//--------------------
// STANDARD LOGGER
//--------------------
// standardLogger is a simple logger writing to the given writer. Beside
// the output it doesn't handle the levels differently.
type standardLogger struct {
mutex sync.Mutex
out io.Writer
timeFormat string
}
// NewTimeformatLogger creates a logger writing to the passed
// output and with the time formatted with the passed time format.
func NewTimeformatLogger(out io.Writer, timeFormat string) Logger {
return &standardLogger{
out: out,
timeFormat: timeFormat,
}
}
// NewStandardLogger creates the standard logger writing
// to the passed output.
func NewStandardLogger(out io.Writer) Logger {
return NewTimeformatLogger(out, defaultTimeFormat)
}
// Debug implements Logger.
func (sl *standardLogger) Debug(info, msg string) {
sl.writeLog("DEBUG", info, msg)
}
// Info implements Logger.
func (sl *standardLogger) Info(info, msg string) {
sl.writeLog("INFO", info, msg)
}
// Warning implements Logger.
func (sl *standardLogger) Warning(info, msg string) {
sl.writeLog("WARNING", info, msg)
}
// Error implements Logger.
func (sl *standardLogger) Error(info, msg string) {
sl.writeLog("ERROR", info, msg)
}
// Critical implements Logger.
func (sl *standardLogger) Critical(info, msg string) {
sl.writeLog("CRITICAL", info, msg)
}
// Fatal implements Logger.
func (sl *standardLogger) Fatal(info, msg string) {
sl.writeLog("FATAL", info, msg)
}
// writeLog writes the log output.
func (sl *standardLogger) writeLog(level, info, msg string) {
sl.mutex.Lock()
defer sl.mutex.Unlock()
io.WriteString(sl.out, time.Now().Format(sl.timeFormat))
io.WriteString(sl.out, " [")
io.WriteString(sl.out, level)
io.WriteString(sl.out, "] ")
io.WriteString(sl.out, info)
io.WriteString(sl.out, " ")
io.WriteString(sl.out, msg)
io.WriteString(sl.out, "\n")
}
// goLogger just uses the standard go log package.
type goLogger struct{}
// NewGoLogger returns a logger implementation using the
// Go log package.
func NewGoLogger() Logger {
return &goLogger{}
}
// Debug is specified on the Logger interface.
func (gl *goLogger) Debug(info, msg string) {
log.Println("[DEBUG]", info, msg)
}
// Info is specified on the Logger interface.
func (gl *goLogger) Info(info, msg string) {
log.Println("[INFO]", info, msg)
}
// Warning is specified on the Logger interface.
func (gl *goLogger) Warning(info, msg string) {
log.Println("[WARNING]", info, msg)
}
// Error is specified on the Logger interface.
func (gl *goLogger) Error(info, msg string) {
log.Println("[ERROR]", info, msg)
}
// Critical is specified on the Logger interface.
func (gl *goLogger) Critical(info, msg string) {
log.Println("[CRITICAL]", info, msg)
}
// Fatal is specified on the Logger interface.
func (gl *goLogger) Fatal(info, msg string) {
log.Println("[FATAL]", info, msg)
}
// TestLogger extends the Logger interface with methods to retrieve
// and reset the collected data.
type TestLogger interface {
Logger
// Len returns the number of collected entries.
Len() int
// Entries returns the collected entries.
Entries() []string
// Reset clears the collected entries.
Reset()
}
// testLogger simply collects logs to be evaluated inside of tests.
type testLogger struct {
mux sync.Mutex
entries []string
}
// NewTestLogger returns a special logger for testing.
func NewTestLogger() TestLogger {
return &testLogger{}
}
// Debug implements Logger.
func (tl *testLogger) Debug(info, msg string) {
tl.mux.Lock()
defer tl.mux.Unlock()
entry := fmt.Sprintf("%d [DEBUG] %s %s", time.Now().UnixNano(), info, msg)
tl.entries = append(tl.entries, entry)
}
// Info implements Logger.
func (tl *testLogger) Info(info, msg string) {
tl.mux.Lock()
defer tl.mux.Unlock()
entry := fmt.Sprintf("%d [INFO] %s %s", time.Now().UnixNano(), info, msg)
tl.entries = append(tl.entries, entry)
}
// Warning implements Logger.
func (tl *testLogger) Warning(info, msg string) {
tl.mux.Lock()
defer tl.mux.Unlock()
entry := fmt.Sprintf("%d [WARNING] %s %s", time.Now().UnixNano(), info, msg)
tl.entries = append(tl.entries, entry)
}
// Error implements Logger.
func (tl *testLogger) Error(info, msg string) {
tl.mux.Lock()
defer tl.mux.Unlock()
entry := fmt.Sprintf("%d [ERROR] %s %s", time.Now().UnixNano(), info, msg)
tl.entries = append(tl.entries, entry)
}
// Critical implements Logger.
func (tl *testLogger) Critical(info, msg string) {
tl.mux.Lock()
defer tl.mux.Unlock()
entry := fmt.Sprintf("%d [CRITICAL] %s %s", time.Now().UnixNano(), info, msg)
tl.entries = append(tl.entries, entry)
}
// Fatal implements Logger.
func (tl *testLogger) Fatal(info, msg string) {
tl.mux.Lock()
defer tl.mux.Unlock()
entry := fmt.Sprintf("%d [CRITICAL] %s %s", time.Now().UnixNano(), info, msg)
tl.entries = append(tl.entries, entry)
}
// Len implements TestLogger.
func (tl *testLogger) Len() int {
tl.mux.Lock()
defer tl.mux.Unlock()
return len(tl.entries)
}
// Entries implements TestLogger.
func (tl *testLogger) Entries() []string {
tl.mux.Lock()
defer tl.mux.Unlock()
entries := make([]string, len(tl.entries))
copy(entries, tl.entries)
return entries
}
// Reset implements TestLogger.
func (tl *testLogger) Reset() {
tl.mux.Lock()
defer tl.mux.Unlock()
tl.entries = nil
}
//--------------------
// HELPER
//--------------------
// callInfo bundles the info about the call environment
// when a logging statement occurred.
type callInfo struct {
packageName string
fileName string
funcName string
line int
}
// shortFormat returns a string representation in a short variant.
func (ci *callInfo) shortFormat() string {
return fmt.Sprintf("[%s]", ci.packageName)
}
// verboseFormat returns a string representation in a more verbose variant.
func (ci *callInfo) verboseFormat() string {
return fmt.Sprintf("[%s] (%s:%s:%d)", ci.packageName, ci.fileName, ci.funcName, ci.line)
}
// retrieveCallInfo returns detailed information about
// the call location.
func retrieveCallInfo() *callInfo {
pc, file, line, _ := runtime.Caller(2)
_, fileName := path.Split(file)
parts := strings.Split(runtime.FuncForPC(pc).Name(), ".")
pl := len(parts)
packageName := ""
funcName := parts[pl-1]
if parts[pl-2][0] == '(' {
funcName = parts[pl-2] + "." + funcName
packageName = strings.Join(parts[0:pl-2], ".")
} else {
packageName = strings.Join(parts[0:pl-1], ".")
}
return &callInfo{
packageName: packageName,
fileName: fileName,
funcName: funcName,
line: line,
}
}
// shallLog is used inside the logging functions to check if
// logging is wanted.
func shallLog(level LogLevel, info, msg string) bool {
logMux.RLock()
defer logMux.RUnlock()
if logFilter == nil {
return true
}
return !logFilter(level, info, msg)
}
// EOF
|