123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- package log
- import (
- "encoding/json"
- "fmt"
- "heckel.io/ntfy/v2/util"
- "log"
- "os"
- "sort"
- "strings"
- "time"
- )
- const (
- fieldTag = "tag"
- fieldError = "error"
- fieldTimeTaken = "time_taken_ms"
- fieldExitCode = "exit_code"
- tagStdLog = "stdlog"
- )
- // Event represents a single log event
- type Event struct {
- Timestamp string `json:"time"`
- Level Level `json:"level"`
- Message string `json:"message"`
- time time.Time
- contexters []Contexter
- fields Context
- }
- // newEvent creates a new log event
- //
- // 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.
- func newEvent() *Event {
- return &Event{
- time: time.Now(),
- }
- }
- // Fatal logs the event as FATAL, and exits the program with exit code 1
- func (e *Event) Fatal(message string, v ...any) {
- e.Field(fieldExitCode, 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) *Event {
- return e.Log(ErrorLevel, message, v...)
- }
- // Warn logs the event with log level warn
- func (e *Event) Warn(message string, v ...any) *Event {
- return e.Log(WarnLevel, message, v...)
- }
- // Info logs the event with log level info
- func (e *Event) Info(message string, v ...any) *Event {
- return e.Log(InfoLevel, message, v...)
- }
- // Debug logs the event with log level debug
- func (e *Event) Debug(message string, v ...any) *Event {
- return e.Log(DebugLevel, message, v...)
- }
- // Trace logs the event with log level trace
- func (e *Event) Trace(message string, v ...any) *Event {
- return e.Log(TraceLevel, message, v...)
- }
- // Tag adds a "tag" field to the log event
- func (e *Event) Tag(tag string) *Event {
- return e.Field(fieldTag, tag)
- }
- // Time sets the time field
- func (e *Event) Time(t time.Time) *Event {
- e.time = t
- return e
- }
- // 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())
- }
- // Err adds an "error" field to the log event
- func (e *Event) Err(err error) *Event {
- if err == nil {
- return e
- } else if c, ok := err.(Contexter); ok {
- return e.With(c)
- }
- return e.Field(fieldError, err.Error())
- }
- // Field adds a custom field and value to the log event
- func (e *Event) Field(key string, value any) *Event {
- if e.fields == nil {
- e.fields = make(Context)
- }
- e.fields[key] = value
- return e
- }
- // 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
- }
- // Fields adds a map of fields to the log event
- func (e *Event) Fields(fields Context) *Event {
- if e.fields == nil {
- e.fields = make(Context)
- }
- 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 {
- if e.contexters == nil {
- e.contexters = contexters
- } else {
- e.contexters = append(e.contexters, contexters...)
- }
- return e
- }
- // 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
- // 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 {
- appliedContexters := e.maybeApplyContexters()
- if !e.Loggable(l) {
- return ""
- }
- e.Message = fmt.Sprintf(message, v...)
- e.Level = l
- e.Timestamp = util.FormatTime(e.time)
- if !appliedContexters {
- e.applyContexters()
- }
- if CurrentFormat() == JSONFormat {
- return e.JSON()
- }
- return e.String()
- }
- // 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 {
- if m := e.Render(l, message, v...); m != "" {
- log.Println(m)
- }
- return e
- }
- // 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) globalLevelWithOverride() Level {
- mu.RLock()
- l, ov := level, overrides
- mu.RUnlock()
- if e.fields == nil {
- return l
- }
- for field, fieldOverrides := range ov {
- value, exists := e.fields[field]
- if exists {
- for _, o := range fieldOverrides {
- if o.value == "" || o.value == value || o.value == fmt.Sprintf("%v", value) {
- return o.level
- }
- }
- }
- }
- return l
- }
- func (e *Event) maybeApplyContexters() bool {
- mu.RLock()
- hasOverrides := len(overrides) > 0
- mu.RUnlock()
- if hasOverrides {
- e.applyContexters()
- }
- return hasOverrides // = applied
- }
- func (e *Event) applyContexters() {
- for _, c := range e.contexters {
- e.Fields(c.Context())
- }
- }
|