level.go raw

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