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

View as plain text