Created
May 22, 2022 16:58
-
-
Save fawkesley/d51a93a8a51d86bd410022185d39a92c to your computer and use it in GitHub Desktop.
Golang logger with custom colour support
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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