118 lines
2.3 KiB
Go
118 lines
2.3 KiB
Go
package util
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type LogLevel string
|
|
|
|
const (
|
|
LevelInfo LogLevel = "info"
|
|
LevelDebug LogLevel = "debug"
|
|
LevelError LogLevel = "error"
|
|
LevelFatal LogLevel = "fatal"
|
|
LevelMsg LogLevel = "msg"
|
|
)
|
|
|
|
type LogMessage struct {
|
|
Level LogLevel
|
|
Time string
|
|
Message any
|
|
}
|
|
|
|
type Logger struct {
|
|
debugEnabled bool
|
|
encoder *json.Encoder
|
|
pool *sync.Pool
|
|
}
|
|
|
|
var logger = NewLogger()
|
|
|
|
func GetLogger() *Logger {
|
|
return logger
|
|
}
|
|
|
|
func NewLogger() *Logger {
|
|
enc := json.NewEncoder(os.Stdout)
|
|
enc.SetEscapeHTML(false)
|
|
|
|
return &Logger{
|
|
encoder: enc,
|
|
pool: &sync.Pool{
|
|
New: func() any {
|
|
return &LogMessage{}
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (l *Logger) SetDebug(enabled bool) {
|
|
if enabled {
|
|
l.debugEnabled = true
|
|
l.Info("Enabling Debug logging for headplane-agent")
|
|
l.Info("Be careful, this will spam a lot of information")
|
|
}
|
|
}
|
|
|
|
func (l *Logger) log(level LogLevel, format string, v ...any) {
|
|
msg := fmt.Sprintf(format, v...)
|
|
timestamp := time.Now().Format(time.RFC3339)
|
|
|
|
// Manually construct compact JSON line for performance
|
|
line := `{"Level":"` + string(level) +
|
|
`","Time":"` + timestamp +
|
|
`","Message":"` + escapeString(msg) + `"}` + "\n"
|
|
|
|
if level == LevelError || level == LevelFatal {
|
|
os.Stderr.WriteString(line)
|
|
}
|
|
|
|
// Always write to stdout but also write to stderr for errors
|
|
os.Stdout.WriteString(line)
|
|
if level == LevelFatal {
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func (l *Logger) Debug(format string, v ...any) {
|
|
if l.debugEnabled {
|
|
l.log(LevelDebug, format, v...)
|
|
}
|
|
}
|
|
|
|
func (l *Logger) Info(format string, v ...any) { l.log(LevelInfo, format, v...) }
|
|
func (l *Logger) Error(format string, v ...any) { l.log(LevelError, format, v...) }
|
|
func (l *Logger) Fatal(format string, v ...any) { l.log(LevelFatal, format, v...) }
|
|
|
|
func (l *Logger) Msg(obj any) {
|
|
entry := l.pool.Get().(*LogMessage)
|
|
defer l.pool.Put(entry)
|
|
|
|
entry.Level = LevelMsg
|
|
entry.Time = time.Now().Format(time.RFC3339)
|
|
entry.Message = obj
|
|
|
|
// Because the encoder is tied to STDOUT we get a message
|
|
_ = l.encoder.Encode(entry)
|
|
|
|
// Reset the entry for reuse
|
|
entry.Level = ""
|
|
entry.Time = ""
|
|
entry.Message = nil
|
|
}
|
|
|
|
func escapeString(s string) string {
|
|
replacer := strings.NewReplacer(
|
|
`"`, `\"`,
|
|
`\`, `\\`,
|
|
"\n", `\n`,
|
|
"\t", `\t`,
|
|
)
|
|
return replacer.Replace(s)
|
|
}
|