ntfy/log/event.go

174 lines
3.9 KiB
Go

package log
import (
"encoding/json"
"fmt"
"log"
"os"
"sort"
"strings"
"time"
)
const (
tagField = "tag"
errorField = "error"
)
// Event represents a single log event
type Event struct {
Timestamp int64 `json:"time"`
Level Level `json:"level"`
Message string `json:"message"`
fields Context
}
// newEvent creates a new log event
func newEvent() *Event {
return &Event{
Timestamp: time.Now().UnixMilli(),
fields: make(Context),
}
}
// Fatal logs the event as FATAL, and exits the program with exit code 1
func (e *Event) Fatal(message string, v ...any) {
e.Field("exit_code", 1).Log(FatalLevel, message, v...)
fmt.Fprintf(os.Stderr, message+"\n", v...) // Always output error to stderr
os.Exit(1)
}
// Error logs the event with log level error
func (e *Event) Error(message string, v ...any) {
e.Log(ErrorLevel, message, v...)
}
// Warn logs the event with log level warn
func (e *Event) Warn(message string, v ...any) {
e.Log(WarnLevel, message, v...)
}
// Info logs the event with log level info
func (e *Event) Info(message string, v ...any) {
e.Log(InfoLevel, message, v...)
}
// Debug logs the event with log level debug
func (e *Event) Debug(message string, v ...any) {
e.Log(DebugLevel, message, v...)
}
// Trace logs the event with log level trace
func (e *Event) Trace(message string, v ...any) {
e.Log(TraceLevel, message, v...)
}
// Tag adds a "tag" field to the log event
func (e *Event) Tag(tag string) *Event {
e.fields[tagField] = tag
return e
}
// Time sets the time field
func (e *Event) Time(time time.Time) *Event {
e.Timestamp = time.UnixMilli()
return e
}
// Err adds an "error" field to the log event
func (e *Event) Err(err error) *Event {
e.fields[errorField] = err.Error()
return e
}
// Field adds a custom field and value to the log event
func (e *Event) Field(key string, value any) *Event {
e.fields[key] = value
return e
}
// Fields adds a map of fields to the log event
func (e *Event) Fields(fields Context) *Event {
for k, v := range fields {
e.fields[k] = v
}
return e
}
// With adds the fields of the given Contexter structs to the log event by calling their With method
func (e *Event) With(contexts ...Contexter) *Event {
for _, c := range contexts {
e.Fields(c.Context())
}
return e
}
// Log logs a message with the given log level
func (e *Event) Log(l Level, message string, v ...any) {
e.Message = fmt.Sprintf(message, v...)
e.Level = l
if e.shouldPrint() {
if CurrentFormat() == JSONFormat {
log.Println(e.JSON())
} else {
log.Println(e.String())
}
}
}
// Loggable returns true if the given log level is lower or equal to the current log level
func (e *Event) Loggable(l Level) bool {
return e.globalLevelWithOverride() <= l
}
// IsTrace returns true if the current log level is TraceLevel
func (e *Event) IsTrace() bool {
return e.Loggable(TraceLevel)
}
// IsDebug returns true if the current log level is DebugLevel or below
func (e *Event) IsDebug() bool {
return e.Loggable(DebugLevel)
}
// JSON returns the event as a JSON representation
func (e *Event) JSON() string {
b, _ := json.Marshal(e)
s := string(b)
if len(e.fields) > 0 {
b, _ := json.Marshal(e.fields)
s = fmt.Sprintf("{%s,%s}", s[1:len(s)-1], string(b[1:len(b)-1]))
}
return s
}
// String returns the event as a string
func (e *Event) String() string {
if len(e.fields) == 0 {
return fmt.Sprintf("%s %s", e.Level.String(), e.Message)
}
fields := make([]string, 0)
for k, v := range e.fields {
fields = append(fields, fmt.Sprintf("%s=%v", k, v))
}
sort.Strings(fields)
return fmt.Sprintf("%s %s (%s)", e.Level.String(), e.Message, strings.Join(fields, ", "))
}
func (e *Event) shouldPrint() bool {
return e.globalLevelWithOverride() <= e.Level
}
func (e *Event) globalLevelWithOverride() Level {
mu.Lock()
l, ov := level, overrides
mu.Unlock()
for field, override := range ov {
value, exists := e.fields[field]
if exists && value == override.value {
return override.level
}
}
return l
}