Source file src/log/slog/level.go
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 5 package slog 6 7 import ( 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. 17 type 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. 43 const ( 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" 59 func (l Level) String() string { 60 str := func(base string, val Level) string { 61 if val == 0 { 62 return base 63 } 64 sval := strconv.Itoa(int(val)) 65 if val > 0 { 66 sval = "+" + sval 67 } 68 return base + sval 69 } 70 71 switch { 72 case l < LevelInfo: 73 return str("DEBUG", l-LevelDebug) 74 case l < LevelWarn: 75 return str("INFO", l-LevelInfo) 76 case l < LevelError: 77 return str("WARN", l-LevelWarn) 78 default: 79 return str("ERROR", l-LevelError) 80 } 81 } 82 83 // MarshalJSON implements [encoding/json.Marshaler] 84 // by quoting the output of [Level.String]. 85 func (l Level) MarshalJSON() ([]byte, error) { 86 // AppendQuote is sufficient for JSON-encoding all Level strings. 87 // They don't contain any runes that would produce invalid JSON 88 // when escaped. 89 return strconv.AppendQuote(nil, l.String()), nil 90 } 91 92 // UnmarshalJSON implements [encoding/json.Unmarshaler] 93 // It accepts any string produced by [Level.MarshalJSON], 94 // ignoring case. 95 // It also accepts numeric offsets that would result in a different string on 96 // output. For example, "Error-8" would marshal as "INFO". 97 func (l *Level) UnmarshalJSON(data []byte) error { 98 s, err := strconv.Unquote(string(data)) 99 if err != nil { 100 return err 101 } 102 return l.parse(s) 103 } 104 105 // AppendText implements [encoding.TextAppender] 106 // by calling [Level.String]. 107 func (l Level) AppendText(b []byte) ([]byte, error) { 108 return append(b, l.String()...), nil 109 } 110 111 // MarshalText implements [encoding.TextMarshaler] 112 // by calling [Level.AppendText]. 113 func (l Level) MarshalText() ([]byte, error) { 114 return l.AppendText(nil) 115 } 116 117 // UnmarshalText implements [encoding.TextUnmarshaler]. 118 // It accepts any string produced by [Level.MarshalText], 119 // ignoring case. 120 // It also accepts numeric offsets that would result in a different string on 121 // output. For example, "Error-8" would marshal as "INFO". 122 func (l *Level) UnmarshalText(data []byte) error { 123 return l.parse(string(data)) 124 } 125 126 func (l *Level) parse(s string) (err error) { 127 defer func() { 128 if err != nil { 129 err = fmt.Errorf("slog: level string %q: %w", s, err) 130 } 131 }() 132 133 name := s 134 offset := 0 135 if i := strings.IndexAny(s, "+-"); i >= 0 { 136 name = s[:i] 137 offset, err = strconv.Atoi(s[i:]) 138 if err != nil { 139 return err 140 } 141 } 142 switch strings.ToUpper(name) { 143 case "DEBUG": 144 *l = LevelDebug 145 case "INFO": 146 *l = LevelInfo 147 case "WARN": 148 *l = LevelWarn 149 case "ERROR": 150 *l = LevelError 151 default: 152 return errors.New("unknown name") 153 } 154 *l += Level(offset) 155 return nil 156 } 157 158 // Level returns the receiver. 159 // It implements [Leveler]. 160 func (l Level) Level() Level { return l } 161 162 // A LevelVar is a [Level] variable, to allow a [Handler] level to change 163 // dynamically. 164 // It implements [Leveler] as well as a Set method, 165 // and it is safe for use by multiple goroutines. 166 // The zero LevelVar corresponds to [LevelInfo]. 167 type LevelVar struct { 168 val atomic.Int64 169 } 170 171 // Level returns v's level. 172 func (v *LevelVar) Level() Level { 173 return Level(int(v.val.Load())) 174 } 175 176 // Set sets v's level to l. 177 func (v *LevelVar) Set(l Level) { 178 v.val.Store(int64(l)) 179 } 180 181 func (v *LevelVar) String() string { 182 return fmt.Sprintf("LevelVar(%s)", v.Level()) 183 } 184 185 // AppendText implements [encoding.TextAppender] 186 // by calling [Level.AppendText]. 187 func (v *LevelVar) AppendText(b []byte) ([]byte, error) { 188 return v.Level().AppendText(b) 189 } 190 191 // MarshalText implements [encoding.TextMarshaler] 192 // by calling [LevelVar.AppendText]. 193 func (v *LevelVar) MarshalText() ([]byte, error) { 194 return v.AppendText(nil) 195 } 196 197 // UnmarshalText implements [encoding.TextUnmarshaler] 198 // by calling [Level.UnmarshalText]. 199 func (v *LevelVar) UnmarshalText(data []byte) error { 200 var l Level 201 if err := l.UnmarshalText(data); err != nil { 202 return err 203 } 204 v.Set(l) 205 return nil 206 } 207 208 // A Leveler provides a [Level] value. 209 // 210 // As Level itself implements Leveler, clients typically supply 211 // a Level value wherever a Leveler is needed, such as in [HandlerOptions]. 212 // Clients who need to vary the level dynamically can provide a more complex 213 // Leveler implementation such as *[LevelVar]. 214 type Leveler interface { 215 Level() Level 216 } 217