ntfy/log/event.go

246 lines
6.1 KiB
Go
Raw Permalink Normal View History

2023-02-04 04:21:50 +01:00
package log
import (
"encoding/json"
"fmt"
2023-03-03 19:56:48 +01:00
"heckel.io/ntfy/util"
2023-02-04 04:21:50 +01:00
"log"
"os"
"sort"
"strings"
"time"
)
const (
2023-03-03 19:56:48 +01:00
fieldTag = "tag"
fieldError = "error"
fieldTimeTaken = "time_taken_ms"
fieldExitCode = "exit_code"
tagStdLog = "stdlog"
2023-02-04 04:21:50 +01:00
)
2023-02-06 05:34:27 +01:00
// Event represents a single log event
2023-02-04 04:21:50 +01:00
type Event struct {
2023-02-08 04:10:51 +01:00
Timestamp string `json:"time"`
Level Level `json:"level"`
Message string `json:"message"`
time time.Time
contexters []Contexter
fields Context
2023-02-04 04:21:50 +01:00
}
2023-02-06 05:34:27 +01:00
// newEvent creates a new log event
2023-02-08 04:10:51 +01:00
//
// We delay allocations and processing for efficiency, because most log events
// are never actually rendered, so we don't format the time, or allocate a fields map.
2023-02-04 04:21:50 +01:00
func newEvent() *Event {
return &Event{
2023-02-08 04:10:51 +01:00
time: time.Now(),
2023-02-04 04:21:50 +01:00
}
}
2023-02-06 05:34:27 +01:00
// Fatal logs the event as FATAL, and exits the program with exit code 1
2023-02-04 04:21:50 +01:00
func (e *Event) Fatal(message string, v ...any) {
2023-05-16 20:15:58 +02:00
e.Field(fieldExitCode, 1).Log(FatalLevel, message, v...)
2023-02-06 05:53:24 +01:00
fmt.Fprintf(os.Stderr, message+"\n", v...) // Always output error to stderr
2023-02-04 04:21:50 +01:00
os.Exit(1)
}
2023-02-06 05:34:27 +01:00
// Error logs the event with log level error
2023-05-16 20:15:58 +02:00
func (e *Event) Error(message string, v ...any) *Event {
return e.Log(ErrorLevel, message, v...)
2023-02-04 04:21:50 +01:00
}
2023-02-06 05:34:27 +01:00
// Warn logs the event with log level warn
2023-05-16 20:15:58 +02:00
func (e *Event) Warn(message string, v ...any) *Event {
return e.Log(WarnLevel, message, v...)
2023-02-04 04:21:50 +01:00
}
2023-02-06 05:34:27 +01:00
// Info logs the event with log level info
2023-05-16 20:15:58 +02:00
func (e *Event) Info(message string, v ...any) *Event {
return e.Log(InfoLevel, message, v...)
2023-02-04 04:21:50 +01:00
}
2023-02-06 05:34:27 +01:00
// Debug logs the event with log level debug
2023-05-16 20:15:58 +02:00
func (e *Event) Debug(message string, v ...any) *Event {
return e.Log(DebugLevel, message, v...)
2023-02-04 04:21:50 +01:00
}
2023-02-06 05:34:27 +01:00
// Trace logs the event with log level trace
2023-05-16 20:15:58 +02:00
func (e *Event) Trace(message string, v ...any) *Event {
return e.Log(TraceLevel, message, v...)
2023-02-04 04:21:50 +01:00
}
2023-02-06 05:34:27 +01:00
// Tag adds a "tag" field to the log event
2023-02-04 04:21:50 +01:00
func (e *Event) Tag(tag string) *Event {
return e.Field(fieldTag, tag)
2023-02-04 04:21:50 +01:00
}
2023-02-06 05:34:27 +01:00
// Time sets the time field
2023-02-07 18:02:25 +01:00
func (e *Event) Time(t time.Time) *Event {
2023-02-08 04:10:51 +01:00
e.time = t
2023-02-06 05:34:27 +01:00
return e
}
2023-02-09 21:24:12 +01:00
// Timing runs f and records the time if took to execute it in "time_taken_ms"
func (e *Event) Timing(f func()) *Event {
start := time.Now()
f()
return e.Field(fieldTimeTaken, time.Since(start).Milliseconds())
2023-02-09 21:24:12 +01:00
}
2023-02-06 05:34:27 +01:00
// Err adds an "error" field to the log event
2023-02-04 04:21:50 +01:00
func (e *Event) Err(err error) *Event {
2023-02-08 21:20:44 +01:00
if err == nil {
return e
} else if c, ok := err.(Contexter); ok {
return e.With(c)
2023-02-06 22:01:32 +01:00
}
return e.Field(fieldError, err.Error())
2023-02-04 04:21:50 +01:00
}
2023-02-06 05:34:27 +01:00
// Field adds a custom field and value to the log event
2023-02-04 04:21:50 +01:00
func (e *Event) Field(key string, value any) *Event {
2023-02-08 04:10:51 +01:00
if e.fields == nil {
e.fields = make(Context)
}
2023-02-04 04:21:50 +01:00
e.fields[key] = value
return e
}
2023-05-16 20:15:58 +02:00
// FieldIf adds a custom field and value to the log event if the given level is loggable
func (e *Event) FieldIf(key string, value any, level Level) *Event {
if e.Loggable(level) {
return e.Field(key, value)
}
return e
}
2023-02-06 05:34:27 +01:00
// Fields adds a map of fields to the log event
func (e *Event) Fields(fields Context) *Event {
2023-02-08 04:10:51 +01:00
if e.fields == nil {
e.fields = make(Context)
}
2023-02-04 04:21:50 +01:00
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 Context method
func (e *Event) With(contexters ...Contexter) *Event {
2023-02-08 04:10:51 +01:00
if e.contexters == nil {
e.contexters = contexters
2023-02-08 04:10:51 +01:00
} else {
e.contexters = append(e.contexters, contexters...)
2023-02-04 04:21:50 +01:00
}
return e
}
2023-02-18 03:00:43 +01:00
// Render returns the rendered log event as a string, or an empty string. The event is only rendered,
// if either the global log level is >= l, or if the log level in one of the overrides matches
2023-02-08 04:10:51 +01:00
// the level.
//
// If no overrides are defined (default), the Contexter array is not applied unless the event
// is actually logged. If overrides are defined, then Contexters have to be applied in any case
// to determine if they match. This is super complicated, but required for efficiency.
func (e *Event) Render(l Level, message string, v ...any) string {
2023-02-08 04:10:51 +01:00
appliedContexters := e.maybeApplyContexters()
2023-05-16 20:15:58 +02:00
if !e.Loggable(l) {
return ""
2023-02-08 04:10:51 +01:00
}
2023-02-04 04:21:50 +01:00
e.Message = fmt.Sprintf(message, v...)
e.Level = l
2023-03-03 19:56:48 +01:00
e.Timestamp = util.FormatTime(e.time)
2023-02-08 04:10:51 +01:00
if !appliedContexters {
e.applyContexters()
}
if CurrentFormat() == JSONFormat {
return e.JSON()
2023-02-04 04:21:50 +01:00
}
return e.String()
2023-02-04 04:21:50 +01:00
}
2023-05-16 20:15:58 +02:00
// Log logs the event to the defined output, or does nothing if Render returns an empty string
func (e *Event) Log(l Level, message string, v ...any) *Event {
2023-02-18 03:00:43 +01:00
if m := e.Render(l, message, v...); m != "" {
log.Println(m)
}
2023-05-16 20:15:58 +02:00
return e
2023-02-18 03:00:43 +01:00
}
2023-02-04 04:21:50 +01:00
// 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)
}
2023-02-06 05:34:27 +01:00
// JSON returns the event as a JSON representation
2023-02-04 04:21:50 +01:00
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
}
2023-02-06 05:34:27 +01:00
// String returns the event as a string
2023-02-04 04:21:50 +01:00
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) globalLevelWithOverride() Level {
2023-02-10 01:45:02 +01:00
mu.RLock()
2023-02-04 04:21:50 +01:00
l, ov := level, overrides
2023-02-10 01:45:02 +01:00
mu.RUnlock()
2023-02-08 04:10:51 +01:00
if e.fields == nil {
return l
}
for field, fieldOverrides := range ov {
2023-02-04 04:21:50 +01:00
value, exists := e.fields[field]
2023-02-09 04:57:10 +01:00
if exists {
for _, o := range fieldOverrides {
if o.value == "" || o.value == value || o.value == fmt.Sprintf("%v", value) {
return o.level
}
2023-02-09 04:57:10 +01:00
}
2023-02-04 04:21:50 +01:00
}
}
return l
}
2023-02-08 04:10:51 +01:00
func (e *Event) maybeApplyContexters() bool {
2023-02-10 01:45:02 +01:00
mu.RLock()
2023-02-08 04:10:51 +01:00
hasOverrides := len(overrides) > 0
2023-02-10 01:45:02 +01:00
mu.RUnlock()
2023-02-08 04:10:51 +01:00
if hasOverrides {
e.applyContexters()
}
return hasOverrides // = applied
}
func (e *Event) applyContexters() {
for _, c := range e.contexters {
e.Fields(c.Context())
}
}