go/shinylog/shinylog.go

Summary

Maintainability
A
0 mins
Test Coverage
package shinylog

import (
    "fmt"
    "io"
    "log"
    "os"
    "strings"
    "sync"
    "syscall"
)

type ShinyLogger struct {
    mu                  sync.Mutex
    errorLogger         log.Logger
    locationErrorLogger log.Logger
    suppressOutput      bool
    disableColor        bool
}

type loggerOptions struct {
    isError, printNewline, includeLocation bool
}

var errorOptions loggerOptions
var stdoutOptions loggerOptions
var stderrOptions loggerOptions

func init() {
    errorOptions = loggerOptions{isError: true, printNewline: true, includeLocation: true}
    stdoutOptions = loggerOptions{isError: false, printNewline: true, includeLocation: false}
    stderrOptions = loggerOptions{isError: false, printNewline: true, includeLocation: false}
}

func NewShinyLogger(out, err interface {
    io.Writer
}) *ShinyLogger {
    errorLogger := log.New(err, "", 0)
    locationErrorLogger := log.New(err, "", log.Lshortfile)
    var mu sync.Mutex
    return &ShinyLogger{mu, *errorLogger, *locationErrorLogger, false, false}
}

func NewTraceLogger(out interface {
    io.Writer
}) *log.Logger {
    return log.New(out, "", log.Ldate|log.Ltime|log.Lmicroseconds)
}

const (
    red         = "\x1b[31m"
    green       = "\x1b[32m"
    brightgreen = "\x1b[1;32m"
    yellow      = "\x1b[33m"
    blue        = "\x1b[34m"
    magenta     = "\x1b[35m"
    reset       = "\x1b[0m"
)

var dlm sync.RWMutex
var defaultLogger *ShinyLogger = NewShinyLogger(os.Stdout, os.Stderr)
var traceLogger *log.Logger = nil

func DefaultLogger() *ShinyLogger {
    dlm.RLock()
    defer dlm.RUnlock()
    return defaultLogger
}

func SetDefaultLogger(sl *ShinyLogger) {
    dlm.Lock()
    defaultLogger = sl
    dlm.Unlock()
}

func TraceLogger() *log.Logger {
    dlm.RLock()
    defer dlm.RUnlock()
    return traceLogger
}

func SetTraceLogger(sl *log.Logger) {
    dlm.Lock()
    traceLogger = sl
    dlm.Unlock()
}

func Suppress()                           { DefaultLogger().Suppress() }
func DisableColor()                       { DefaultLogger().DisableColor() }
func Colorized(msg string) (printed bool) { return DefaultLogger().Colorized(msg) }
func Error(err error) bool                { return DefaultLogger().Error(err) }
func FatalError(err error)                { DefaultLogger().FatalError(err) }
func FatalErrorString(msg string)         { DefaultLogger().FatalErrorString(msg) }
func ErrorString(msg string) bool         { return DefaultLogger().ErrorString(msg) }
func StdErrorString(msg string) bool      { return DefaultLogger().StdErrorString(msg) }
func Red(msg string) bool                 { return DefaultLogger().Red(msg) }
func Green(msg string) bool               { return DefaultLogger().Green(msg) }
func Brightgreen(msg string) bool         { return DefaultLogger().Brightgreen(msg) }
func Yellow(msg string) bool              { return DefaultLogger().Yellow(msg) }
func Blue(msg string) bool                { return DefaultLogger().Blue(msg) }
func Magenta(msg string) bool             { return DefaultLogger().Magenta(msg) }

func TraceEnabled() bool {
    return TraceLogger() != nil
}

func Trace(format string, v ...interface{}) bool {
    if TraceEnabled() {
        TraceLogger().Printf(format, v...)
        return true
    }
    return false
}

func (l *ShinyLogger) Suppress() {
    l.mu.Lock()
    defer l.mu.Unlock()
    l.suppressOutput = true
}

func (l *ShinyLogger) DisableColor() {
    l.disableColor = true
}

func (l *ShinyLogger) Colorized(msg string) (printed bool) {
    return l.colorized(3, msg, stdoutOptions)
}

func (l *ShinyLogger) ColorizedSansNl(msg string) (printed bool) {
    return l.colorized(3, msg, loggerOptions{isError: false, printNewline: false, includeLocation: false})
}

// If we send SIGTERM rather than explicitly exiting,
// the signal can be handled and the master can clean up.
// This is a workaround for Go not having `atexit` :(.
func terminate() {
    proc, _ := os.FindProcess(os.Getpid())
    proc.Signal(syscall.SIGTERM)
}

func (l *ShinyLogger) FatalErrorString(msg string) {
    l.colorized(3, "{red}"+msg, errorOptions)
    terminate()
}

func (l *ShinyLogger) FatalError(err error) {
    l.colorized(3, "{red}"+err.Error(), errorOptions)
    terminate()
}

func (l *ShinyLogger) Error(err error) bool {
    return l.colorized(3, "{red}"+err.Error(), errorOptions)
}

func (l *ShinyLogger) ErrorString(msg string) bool {
    return l.colorized(3, "{red}"+msg, errorOptions)
}

func (l *ShinyLogger) StdErrorString(msg string) bool {
    return l.colorized(3, "{red}"+msg, stderrOptions)
}

func (l *ShinyLogger) Red(msg string) bool {
    return l.colorized(3, "{red}"+msg, stdoutOptions)
}

func (l *ShinyLogger) Green(msg string) bool {
    return l.colorized(3, "{green}"+msg, stdoutOptions)
}

func (l *ShinyLogger) Brightgreen(msg string) bool {
    return l.colorized(3, "{brightgreen}"+msg, stdoutOptions)
}

func (l *ShinyLogger) Yellow(msg string) bool {
    return l.colorized(3, "{yellow}"+msg, stdoutOptions)
}

func (l *ShinyLogger) Blue(msg string) bool {
    return l.colorized(3, "{blue}"+msg, stdoutOptions)
}

func (l *ShinyLogger) Magenta(msg string) bool {
    return l.colorized(3, "{magenta}"+msg, stdoutOptions)
}

func (l *ShinyLogger) formatColors(msg string) string {
    if l.disableColor {
        msg = strings.Replace(msg, "{red}", "", -1)
        msg = strings.Replace(msg, "{green}", "", -1)
        msg = strings.Replace(msg, "{brightgreen}", "", -1)
        msg = strings.Replace(msg, "{yellow}", "", -1)
        msg = strings.Replace(msg, "{blue}", "", -1)
        msg = strings.Replace(msg, "{magenta}", "", -1)
        msg = strings.Replace(msg, "{reset}", "", -1)
    } else {
        msg = strings.Replace(msg, "{red}", red, -1)
        msg = strings.Replace(msg, "{green}", green, -1)
        msg = strings.Replace(msg, "{brightgreen}", brightgreen, -1)
        msg = strings.Replace(msg, "{yellow}", yellow, -1)
        msg = strings.Replace(msg, "{blue}", blue, -1)
        msg = strings.Replace(msg, "{magenta}", magenta, -1)
        msg = strings.Replace(msg, "{reset}", reset, -1)
    }
    return msg
}

func (l *ShinyLogger) colorized(callDepth int, msg string, options loggerOptions) bool {
    l.mu.Lock()
    defer l.mu.Unlock()

    if !l.suppressOutput {
        msg = l.formatColors(msg)

        if l == DefaultLogger() {
            callDepth++ // this was called through a proxy method
        }
        if options.isError {
            if options.includeLocation {
                l.locationErrorLogger.Output(callDepth, msg+reset)
            } else {
                l.errorLogger.Output(callDepth, msg+reset)
            }
        } else {
            if options.printNewline {
                fmt.Println(msg + reset)
            } else {
                fmt.Print(msg + reset)
            }
        }
    }
    return !l.suppressOutput
}