Skip to content

Instantly share code, notes, and snippets.

@fawkesley
Created May 22, 2022 16:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fawkesley/d51a93a8a51d86bd410022185d39a92c to your computer and use it in GitHub Desktop.
Save fawkesley/d51a93a8a51d86bd410022185d39a92c to your computer and use it in GitHub Desktop.
Golang logger with custom colour support
package logger
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
)
var mutex sync.Mutex // ensures atomic writes; protects the following fields
var f *os.File // handle to open log file
var callerColours map[string]string
// SetCallerLogColours maps code filenames (e.g. `main` for `main.go`, `sort` for `sort.go`)
// to a custom colour code when using INFO log statements from that go file.
func SetCallerLogColours(colours map[string]string) {
callerColours = colours
}
// Errorf prints a nicely formatted error to stderr
func Errorf(format string, args ...interface{}) {
logLine := formatLogLine(format, args...)
write(brightred("ERR " + " " + logLine))
}
// Warnf prints a nicely formatted warning to stderr
func Warnf(format string, args ...interface{}) {
logLine := formatLogLine(format, args...)
write(yellow("WARN" + " " + logLine))
}
// Infof prints a nicely formatted info line to stderr
func Infof(format string, args ...interface{}) {
logLine := formatLogLine(format, args...)
_, fn, _, _ := runtime.Caller(1)
fn = basename(fn)
if colour, ok := callerColours[fn]; ok {
write(customColour("INFO "+logLine, colour))
} else {
write(blue("INFO " + logLine))
}
}
// Debugf prints a nicely formatted debug line to stderr
func Debugf(format string, args ...interface{}) {
logLine := formatLogLine(format, args...)
write("----" + " " + logLine)
}
func init() {
isodate := time.Now().Format("2006-01-02")
for i := 0; i < 100; i++ {
filename := fmt.Sprintf("log/%s_%.2d.log", isodate, i)
if !fileExists(filename) {
var err error
if f, err = os.Create(filename); err != nil {
panic(err)
}
break
}
}
if f == nil {
panic("couldn't find an unused log filename")
}
}
func fileExists(filename string) bool {
if _, err := os.Stat(filename); err == nil {
// `filename` exists
return true
} else if errors.Is(err, os.ErrNotExist) {
return false
// `filename` does *not* exist
} else {
// Schrodinger: file may or may not exist. See err for details.
// Therefore, do *NOT* use !os.IsNotExist(err) to test for file existence
panic(err)
}
}
func write(msg string) {
mutex.Lock()
defer mutex.Unlock()
msg = fmt.Sprintf("%s %s", time.Now().Format("15:04:05.000"), msg)
msg = strings.TrimSuffix(msg, "\n") + "\n"
os.Stderr.WriteString(msg)
f.WriteString(msg)
}
func formatLogLine(format string, args ...interface{}) string {
_, filename, lineNumber, _ := runtime.Caller(2)
msg := fmt.Sprintf(format, args...)
return fmt.Sprintf("%s:%-3d %s", formatFilename(filename), lineNumber, msg)
}
func formatFilename(filename string) string {
bn := basename(filename)
if len(bn) > 6 {
bn = bn[:5] + "…"
}
return fmt.Sprintf("%6s", bn)
}
func basename(filename string) string {
return strings.TrimSuffix(filepath.Base(filename), filepath.Ext(filename))
}
func customColour(message, terminalCode string) string {
return terminalCode + message + reset
}
func red(message string) string {
return FgRed + message + reset
}
func brightred(message string) string {
return FgBright + FgRed + message + reset
}
func yellow(message string) string {
return FgYellow + message + reset
}
func cyan(message string) string {
return FgCyan + message + reset
}
func brightcyan(message string) string {
return FgBright + FgCyan + message + reset
}
func magenta(message string) string {
return FgMagenta + message + reset
}
func brightmagenta(message string) string {
return FgBright + FgMagenta + message + reset
}
func blue(message string) string {
return FgBlue + message + reset
}
func brightblue(message string) string {
return FgBright + FgBlue + message + reset
}
const (
reset = "\x1b[0m"
// FgBright makes a foreground colour bold/bright
FgBright = "\x1b[1m"
// FgRed sets the foreground text colour to red
FgRed = "\x1b[31m"
// FgYellow sets the foreground text colour to red
FgYellow = "\x1b[33m"
// FgBlue sets the foreground text colour to red
FgBlue = "\x1b[34m"
// FgMagenta sets the foreground text colour to red
FgMagenta = "\x1b[35m"
// FgCyan sets the foreground text colour to red
FgCyan = "\x1b[36m"
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment