Skip to content

Instantly share code, notes, and snippets.

@jo-makar
Last active May 11, 2020 18:00
Show Gist options
  • Save jo-makar/3f2b1dd6b4bb2f9d72208e980dea7210 to your computer and use it in GitHub Desktop.
Save jo-makar/3f2b1dd6b4bb2f9d72208e980dea7210 to your computer and use it in GitHub Desktop.
Logging framework
package logger
import (
"fmt"
"io"
"path"
"runtime"
"strings"
"sync"
"time"
)
const (
TraceLevel = iota
DebugLevel
InfoLevel
WarningLevel
ErrorLevel
PanicLevel
)
var levels = map[int]string{
TraceLevel: "trace",
DebugLevel: "debug",
InfoLevel: "info",
WarningLevel: "warning",
ErrorLevel: "error",
PanicLevel: "panic",
}
type Logger struct {
Level int
Format []string
Writer io.Writer
mux sync.Mutex
}
func (logger *Logger) log(level int, format string, values ...interface{}) string {
logger.mux.Lock()
defer logger.mux.Unlock()
now := time.Now()
if _, ok := levels[level]; !ok {
panic(fmt.Sprintf("unknown level %d", level))
}
pc, file, line, ok := runtime.Caller(2)
if !ok {
panic("unknown caller: runtime.Caller() !ok")
}
funcobj := runtime.FuncForPC(pc)
if funcobj == nil {
panic("unknown caller: runtime.FuncForPC() nil")
}
t := strings.Split(funcobj.Name(), ".")
if len(t) == 1 {
panic(fmt.Sprintf("unexpected pkg/func name %q", funcobj.Name()))
}
pkgname := strings.Join(t[:len(t)-1], ".")
funcname := t[len(t)-1]
formatters := map[string]func()string{
"date": func() string { return now.Format("2006/01/02") },
"time": func() string { return now.Format("15:04:05") },
"time_ms": func() string { return now.Format("15:04:05") + fmt.Sprintf(".%03d", now.Nanosecond() / 1000000) },
"time_us": func() string { return now.Format("15:04:05") + fmt.Sprintf(".%06d", now.Nanosecond() / 1000) },
"level": func() string { return levels[level] },
"name": func() string { return funcobj.Name() },
"pkg": func() string { return pkgname },
"func": func() string { return funcname },
"path": func() string { return fmt.Sprintf("%s:%d", file, line) },
"file": func() string { return fmt.Sprintf("%s:%d", path.Base(file), line) },
}
var builder strings.Builder
for _, f := range logger.Format {
prefix := ""
if builder.Len() > 0 {
prefix = " "
}
if formatter, ok := formatters[f]; ok {
if _, err := builder.WriteString(prefix + formatter()); err != nil {
panic(err)
}
} else {
panic(fmt.Sprintf("unsupported formatter %q", f))
}
}
if _, err := fmt.Fprintf(&builder, ": " + format, values...); err != nil {
panic(err)
}
entry := builder.String()
if !strings.HasSuffix(entry, "\n") {
entry += "\n"
}
if _, err := logger.Writer.Write([]byte(entry)); err != nil {
panic(err)
}
return entry
}
func (l *Logger) Trace(f string, v ...interface{}) {
if l.Level <= TraceLevel {
l.log(TraceLevel, f, v...)
}
}
func (l *Logger) Debug(f string, v ...interface{}) {
if l.Level <= DebugLevel {
l.log(DebugLevel, f, v...)
}
}
func (l *Logger) Info(f string, v ...interface{}) {
if l.Level <= InfoLevel {
l.log(InfoLevel, f, v...)
}
}
func (l *Logger) Warning(f string, v ...interface{}) {
if l.Level <= WarningLevel {
l.log(WarningLevel, f, v...)
}
}
func (l *Logger) Error(f string, v ...interface{}) {
if l.Level <= ErrorLevel {
l.log(ErrorLevel, f, v...)
}
}
func (l *Logger) Panic(f string, v ...interface{}) {
s := l.log(PanicLevel, f, v...)
panic(s)
}
package main
import (
"logger"
"os"
)
func main() {
logger := logger.Logger{
Level: logger.InfoLevel,
Format: []string{"date", "time_ms", "level", "file"},
Writer: os.Stderr,
}
logger.Info("example")
logger.Info("another: %d %s %d", 1, "foo", 2)
// Outputs:
// 2020/05/11 13:54:03.816 info main.go:15: example
// 2020/05/11 13:54:03.816 info main.go:16: another: 1 foo 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment