golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/slog/level.go (about)

     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  }