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
package shared

import (
	"fmt"
	"io"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"
)

type ILogger interface {
	Debug(msg interface{}, keyvals ...interface{})
	Info(msg interface{}, keyvals ...interface{})
	Warn(msg interface{}, keyvals ...interface{})
	Error(msg interface{}, keyvals ...interface{})
	Printf(format string, args ...interface{})
	Debugf(format string, args ...interface{})
	Infof(format string, args ...interface{})
	Warnf(format string, args ...interface{})
	Errorf(format string, args ...interface{})
}

type logLevel int

const (
	levelDebug logLevel = iota
	levelInfo
	levelWarn
	levelError
)

// Logger is a minimal stdlib-based logger that always writes to a single
// io.Writer (stderr in production). It replaces the previous charmbracelet/log
// dependency, which pulled in a large terminal-styling dependency tree that is
// pointless for a service whose logs go to the systemd journal.
type Logger struct {
	mu    sync.Mutex
	out   io.Writer
	level logLevel
}

// NewLogger creates a logger writing to out, filtering by the given level
// string ("Debug", "Info", "Warn", "Error"; anything else means Error).
func NewLogger(out io.Writer, level string) *Logger {
	l := levelError
	switch level {
	case "Debug":
		l = levelDebug
	case "Info":
		l = levelInfo
	case "Warn":
		l = levelWarn
	case "Error":
		l = levelError
	}
	return &Logger{out: out, level: l}
}

func (l *Logger) log(lvl logLevel, tag string, msg string, keyvals ...interface{}) {
	if lvl < l.level {
		return
	}
	ts := time.Now().Format("2006-01-02 15:04:05.000")
	caller := callerLocation(3)
	var b strings.Builder
	b.WriteString(ts)
	b.WriteByte(' ')
	if tag != "" {
		b.WriteString(tag)
		b.WriteByte(' ')
	}
	if caller != "" {
		b.WriteByte('<')
		b.WriteString(caller)
		b.WriteString("> ")
	}
	b.WriteString(msg)
	for i := 0; i+1 < len(keyvals); i += 2 {
		fmt.Fprintf(&b, " %v=%v", keyvals[i], keyvals[i+1])
	}
	b.WriteByte('\n')

	l.mu.Lock()
	defer l.mu.Unlock()
	_, _ = io.WriteString(l.out, b.String())
}

func callerLocation(skip int) string {
	_, file, line, ok := runtime.Caller(skip)
	if !ok {
		return ""
	}
	// Show <pkgdir/file.go:line> to match the previous log format closely.
	dir := filepath.Base(filepath.Dir(file))
	return fmt.Sprintf("%s/%s:%d", dir, filepath.Base(file), line)
}

func toStr(msg interface{}) string { return fmt.Sprint(msg) }

func (l *Logger) Debug(msg interface{}, keyvals ...interface{}) {
	l.log(levelDebug, "DEBU", toStr(msg), keyvals...)
}
func (l *Logger) Info(msg interface{}, keyvals ...interface{}) {
	l.log(levelInfo, "INFO", toStr(msg), keyvals...)
}
func (l *Logger) Warn(msg interface{}, keyvals ...interface{}) {
	l.log(levelWarn, "WARN", toStr(msg), keyvals...)
}
func (l *Logger) Error(msg interface{}, keyvals ...interface{}) {
	l.log(levelError, "ERRO", toStr(msg), keyvals...)
}

// Printf logs unconditionally (like the previous logger's Printf).
func (l *Logger) Printf(format string, args ...interface{}) {
	l.log(levelError, "", fmt.Sprintf(format, args...))
}
func (l *Logger) Debugf(format string, args ...interface{}) {
	l.log(levelDebug, "DEBU", fmt.Sprintf(format, args...))
}
func (l *Logger) Infof(format string, args ...interface{}) {
	l.log(levelInfo, "INFO", fmt.Sprintf(format, args...))
}
func (l *Logger) Warnf(format string, args ...interface{}) {
	l.log(levelWarn, "WARN", fmt.Sprintf(format, args...))
}
func (l *Logger) Errorf(format string, args ...interface{}) {
	l.log(levelError, "ERRO", fmt.Sprintf(format, args...))
}