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  		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].
    81  func (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".
    93  func (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  // AppendText implements [encoding.TextAppender]
   102  // by calling [Level.String].
   103  func (l Level) AppendText(b []byte) ([]byte, error) {
   104  	return append(b, l.String()...), nil
   105  }
   106  
   107  // MarshalText implements [encoding.TextMarshaler]
   108  // by calling [Level.AppendText].
   109  func (l Level) MarshalText() ([]byte, error) {
   110  	return l.AppendText(nil)
   111  }
   112  
   113  // UnmarshalText implements [encoding.TextUnmarshaler].
   114  // It accepts any string produced by [Level.MarshalText],
   115  // ignoring case.
   116  // It also accepts numeric offsets that would result in a different string on
   117  // output. For example, "Error-8" would marshal as "INFO".
   118  func (l *Level) UnmarshalText(data []byte) error {
   119  	return l.parse(string(data))
   120  }
   121  
   122  func (l *Level) parse(s string) (err error) {
   123  	defer func() {
   124  		if err != nil {
   125  			err = fmt.Errorf("slog: level string %q: %w", s, err)
   126  		}
   127  	}()
   128  
   129  	name := s
   130  	offset := 0
   131  	if i := strings.IndexAny(s, "+-"); i >= 0 {
   132  		name = s[:i]
   133  		offset, err = strconv.Atoi(s[i:])
   134  		if err != nil {
   135  			return err
   136  		}
   137  	}
   138  	switch strings.ToUpper(name) {
   139  	case "DEBUG":
   140  		*l = LevelDebug
   141  	case "INFO":
   142  		*l = LevelInfo
   143  	case "WARN":
   144  		*l = LevelWarn
   145  	case "ERROR":
   146  		*l = LevelError
   147  	default:
   148  		return errors.New("unknown name")
   149  	}
   150  	*l += Level(offset)
   151  	return nil
   152  }
   153  
   154  // Level returns the receiver.
   155  // It implements [Leveler].
   156  func (l Level) Level() Level { return l }
   157  
   158  // A LevelVar is a [Level] variable, to allow a [Handler] level to change
   159  // dynamically.
   160  // It implements [Leveler] as well as a Set method,
   161  // and it is safe for use by multiple goroutines.
   162  // The zero LevelVar corresponds to [LevelInfo].
   163  type LevelVar struct {
   164  	val atomic.Int64
   165  }
   166  
   167  // Level returns v's level.
   168  func (v *LevelVar) Level() Level {
   169  	return Level(int(v.val.Load()))
   170  }
   171  
   172  // Set sets v's level to l.
   173  func (v *LevelVar) Set(l Level) {
   174  	v.val.Store(int64(l))
   175  }
   176  
   177  func (v *LevelVar) String() string {
   178  	return fmt.Sprintf("LevelVar(%s)", v.Level())
   179  }
   180  
   181  // AppendText implements [encoding.TextAppender]
   182  // by calling [Level.AppendText].
   183  func (v *LevelVar) AppendText(b []byte) ([]byte, error) {
   184  	return v.Level().AppendText(b)
   185  }
   186  
   187  // MarshalText implements [encoding.TextMarshaler]
   188  // by calling [LevelVar.AppendText].
   189  func (v *LevelVar) MarshalText() ([]byte, error) {
   190  	return v.AppendText(nil)
   191  }
   192  
   193  // UnmarshalText implements [encoding.TextUnmarshaler]
   194  // by calling [Level.UnmarshalText].
   195  func (v *LevelVar) UnmarshalText(data []byte) error {
   196  	var l Level
   197  	if err := l.UnmarshalText(data); err != nil {
   198  		return err
   199  	}
   200  	v.Set(l)
   201  	return nil
   202  }
   203  
   204  // A Leveler provides a [Level] value.
   205  //
   206  // As Level itself implements Leveler, clients typically supply
   207  // a Level value wherever a Leveler is needed, such as in [HandlerOptions].
   208  // Clients who need to vary the level dynamically can provide a more complex
   209  // Leveler implementation such as *[LevelVar].
   210  type Leveler interface {
   211  	Level() Level
   212  }
   213  

View as plain text