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  

View as plain text