1// Copyright 2022 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package slog 6 7import ( 8 "errors" 9 "fmt" 10 "strconv" 11 "strings" 12 "sync/atomic" 13) 14 15// A Level is the importance or severity of a log event. 16// The higher the level, the more important or severe the event. 17type Level int 18 19// Names for common levels. 20// 21// Level numbers are inherently arbitrary, 22// but we picked them to satisfy three constraints. 23// Any system can map them to another numbering scheme if it wishes. 24// 25// First, we wanted the default level to be Info, Since Levels are ints, Info is 26// the default value for int, zero. 27// 28// Second, we wanted to make it easy to use levels to specify logger verbosity. 29// Since a larger level means a more severe event, a logger that accepts events 30// with smaller (or more negative) level means a more verbose logger. Logger 31// verbosity is thus the negation of event severity, and the default verbosity 32// of 0 accepts all events at least as severe as INFO. 33// 34// Third, we wanted some room between levels to accommodate schemes with named 35// levels between ours. For example, Google Cloud Logging defines a Notice level 36// between Info and Warn. Since there are only a few of these intermediate 37// levels, the gap between the numbers need not be large. Our gap of 4 matches 38// OpenTelemetry's mapping. Subtracting 9 from an OpenTelemetry level in the 39// DEBUG, INFO, WARN and ERROR ranges converts it to the corresponding slog 40// Level range. OpenTelemetry also has the names TRACE and FATAL, which slog 41// does not. But those OpenTelemetry levels can still be represented as slog 42// Levels by using the appropriate integers. 43const ( 44 LevelDebug Level = -4 45 LevelInfo Level = 0 46 LevelWarn Level = 4 47 LevelError Level = 8 48) 49 50// String returns a name for the level. 51// If the level has a name, then that name 52// in uppercase is returned. 53// If the level is between named values, then 54// an integer is appended to the uppercased name. 55// Examples: 56// 57// LevelWarn.String() => "WARN" 58// (LevelInfo+2).String() => "INFO+2" 59func (l Level) String() string { 60 str := func(base string, val Level) string { 61 if val == 0 { 62 return base 63 } 64 return fmt.Sprintf("%s%+d", base, val) 65 } 66 67 switch { 68 case l < LevelInfo: 69 return str("DEBUG", l-LevelDebug) 70 case l < LevelWarn: 71 return str("INFO", l-LevelInfo) 72 case l < LevelError: 73 return str("WARN", l-LevelWarn) 74 default: 75 return str("ERROR", l-LevelError) 76 } 77} 78 79// MarshalJSON implements [encoding/json.Marshaler] 80// by quoting the output of [Level.String]. 81func (l Level) MarshalJSON() ([]byte, error) { 82 // AppendQuote is sufficient for JSON-encoding all Level strings. 83 // They don't contain any runes that would produce invalid JSON 84 // when escaped. 85 return strconv.AppendQuote(nil, l.String()), nil 86} 87 88// UnmarshalJSON implements [encoding/json.Unmarshaler] 89// It accepts any string produced by [Level.MarshalJSON], 90// ignoring case. 91// It also accepts numeric offsets that would result in a different string on 92// output. For example, "Error-8" would marshal as "INFO". 93func (l *Level) UnmarshalJSON(data []byte) error { 94 s, err := strconv.Unquote(string(data)) 95 if err != nil { 96 return err 97 } 98 return l.parse(s) 99} 100 101// MarshalText implements [encoding.TextMarshaler] 102// by calling [Level.String]. 103func (l Level) MarshalText() ([]byte, error) { 104 return []byte(l.String()), nil 105} 106 107// UnmarshalText implements [encoding.TextUnmarshaler]. 108// It accepts any string produced by [Level.MarshalText], 109// ignoring case. 110// It also accepts numeric offsets that would result in a different string on 111// output. For example, "Error-8" would marshal as "INFO". 112func (l *Level) UnmarshalText(data []byte) error { 113 return l.parse(string(data)) 114} 115 116func (l *Level) parse(s string) (err error) { 117 defer func() { 118 if err != nil { 119 err = fmt.Errorf("slog: level string %q: %w", s, err) 120 } 121 }() 122 123 name := s 124 offset := 0 125 if i := strings.IndexAny(s, "+-"); i >= 0 { 126 name = s[:i] 127 offset, err = strconv.Atoi(s[i:]) 128 if err != nil { 129 return err 130 } 131 } 132 switch strings.ToUpper(name) { 133 case "DEBUG": 134 *l = LevelDebug 135 case "INFO": 136 *l = LevelInfo 137 case "WARN": 138 *l = LevelWarn 139 case "ERROR": 140 *l = LevelError 141 default: 142 return errors.New("unknown name") 143 } 144 *l += Level(offset) 145 return nil 146} 147 148// Level returns the receiver. 149// It implements [Leveler]. 150func (l Level) Level() Level { return l } 151 152// A LevelVar is a [Level] variable, to allow a [Handler] level to change 153// dynamically. 154// It implements [Leveler] as well as a Set method, 155// and it is safe for use by multiple goroutines. 156// The zero LevelVar corresponds to [LevelInfo]. 157type LevelVar struct { 158 val atomic.Int64 159} 160 161// Level returns v's level. 162func (v *LevelVar) Level() Level { 163 return Level(int(v.val.Load())) 164} 165 166// Set sets v's level to l. 167func (v *LevelVar) Set(l Level) { 168 v.val.Store(int64(l)) 169} 170 171func (v *LevelVar) String() string { 172 return fmt.Sprintf("LevelVar(%s)", v.Level()) 173} 174 175// MarshalText implements [encoding.TextMarshaler] 176// by calling [Level.MarshalText]. 177func (v *LevelVar) MarshalText() ([]byte, error) { 178 return v.Level().MarshalText() 179} 180 181// UnmarshalText implements [encoding.TextUnmarshaler] 182// by calling [Level.UnmarshalText]. 183func (v *LevelVar) UnmarshalText(data []byte) error { 184 var l Level 185 if err := l.UnmarshalText(data); err != nil { 186 return err 187 } 188 v.Set(l) 189 return nil 190} 191 192// A Leveler provides a [Level] value. 193// 194// As Level itself implements Leveler, clients typically supply 195// a Level value wherever a Leveler is needed, such as in [HandlerOptions]. 196// Clients who need to vary the level dynamically can provide a more complex 197// Leveler implementation such as *[LevelVar]. 198type Leveler interface { 199 Level() Level 200} 201