44package minsev // import "go.opentelemetry.io/contrib/processors/minsev"
55
66import (
7+ "encoding"
8+ "encoding/json"
9+ "errors"
10+ "fmt"
11+ "strconv"
12+ "strings"
713 "sync/atomic"
814
915 "go.opentelemetry.io/otel/log"
@@ -15,6 +21,19 @@ import (
1521// as errors and critical events).
1622type Severity int
1723
24+ var (
25+ // Ensure Severity implements fmt.Stringer.
26+ _ fmt.Stringer = Severity (0 )
27+ // Ensure Severity implements json.Marshaler.
28+ _ json.Marshaler = Severity (0 )
29+ // Ensure Severity implements json.Unmarshaler.
30+ _ json.Unmarshaler = (* Severity )(nil )
31+ // Ensure Severity implements encoding.TextMarshaler.
32+ _ encoding.TextMarshaler = Severity (0 )
33+ // Ensure Severity implements encoding.TextUnmarshaler.
34+ _ encoding.TextUnmarshaler = (* Severity )(nil )
35+ )
36+
1837// Severity values defined by OpenTelemetry.
1938const (
2039 // A fine-grained debugging log record. Typically disabled in default
@@ -99,6 +118,171 @@ var translations = map[Severity]log.Severity{
99118 SeverityFatal4 : log .SeverityFatal4 ,
100119}
101120
121+ // String returns a name for the severity level. If the severity level has a
122+ // name, then that name in uppercase is returned. If the severity level is
123+ // outside named values, then an signed integer is appended to the uppercased
124+ // name.
125+ //
126+ // Examples:
127+ //
128+ // SeverityWarn1.String() => "WARN"
129+ // (SeverityInfo1+2).String() => "INFO2"
130+ // (SeverityFatal4+2).String() => "FATAL+6"
131+ // (SeverityTrace1-3).String() => "TRACE-3"
132+ func (s Severity ) String () string {
133+ str := func (base string , val Severity ) string {
134+ switch val {
135+ case 0 :
136+ return base
137+ case 1 , 2 , 3 :
138+ // No sign for known fine-grained severity values.
139+ return fmt .Sprintf ("%s%d" , base , val + 1 )
140+ }
141+
142+ if val > 0 {
143+ // Exclude zero from positive scale count.
144+ val ++
145+ }
146+ return fmt .Sprintf ("%s%+d" , base , val )
147+ }
148+
149+ switch {
150+ case s < SeverityDebug1 :
151+ return str ("TRACE" , s - SeverityTrace1 )
152+ case s < SeverityInfo1 :
153+ return str ("DEBUG" , s - SeverityDebug1 )
154+ case s < SeverityWarn1 :
155+ return str ("INFO" , s - SeverityInfo1 )
156+ case s < SeverityError1 :
157+ return str ("WARN" , s - SeverityWarn1 )
158+ case s < SeverityFatal1 :
159+ return str ("ERROR" , s - SeverityError1 )
160+ default :
161+ return str ("FATAL" , s - SeverityFatal1 )
162+ }
163+ }
164+
165+ // MarshalJSON implements [encoding/json.Marshaler] by quoting the output of
166+ // [Severity.String].
167+ func (s Severity ) MarshalJSON () ([]byte , error ) {
168+ // AppendQuote is sufficient for JSON-encoding all Severity strings. They
169+ // don't contain any runes that would produce invalid JSON when escaped.
170+ return strconv .AppendQuote (nil , s .String ()), nil
171+ }
172+
173+ // UnmarshalJSON implements [encoding/json.Unmarshaler] It accepts any string
174+ // produced by [Severity.MarshalJSON], ignoring case. It also accepts numeric
175+ // offsets that would result in a different string on output. For example,
176+ // "ERROR-8" will unmarshal as SeverityInfo.
177+ func (s * Severity ) UnmarshalJSON (data []byte ) error {
178+ str , err := strconv .Unquote (string (data ))
179+ if err != nil {
180+ return err
181+ }
182+ return s .parse (str )
183+ }
184+
185+ // AppendText implements [encoding.TextAppender] by calling [Severity.String].
186+ func (s Severity ) AppendText (b []byte ) ([]byte , error ) {
187+ return append (b , s .String ()... ), nil
188+ }
189+
190+ // MarshalText implements [encoding.TextMarshaler] by calling
191+ // [Severity.AppendText].
192+ func (s Severity ) MarshalText () ([]byte , error ) {
193+ return s .AppendText (nil )
194+ }
195+
196+ // UnmarshalText implements [encoding.TextUnmarshaler]. It accepts any string
197+ // produced by [Severity.MarshalText], ignoring case. It also accepts numeric
198+ // offsets that would result in a different string on output. For example,
199+ // "ERROR-8" will marshal as SeverityInfo.
200+ func (s * Severity ) UnmarshalText (data []byte ) error {
201+ return s .parse (string (data ))
202+ }
203+
204+ // parse parses str into s.
205+ //
206+ // It will return an error if str is not a valid severity string.
207+ //
208+ // The string is expected to be in the format of "NAME[N][+/-OFFSET]", where
209+ // NAME is one of the severity names ("TRACE", "DEBUG", "INFO", "WARN",
210+ // "ERROR", "FATAL"), OFFSET is an optional signed integer offset, and N is an
211+ // optional fine-grained severity level that modifies the base severity name.
212+ //
213+ // Name is parsed in a case-insensitive way. Meaning, "info", "Info",
214+ // "iNfO", etc. are all equivalent to "INFO".
215+ //
216+ // Fine-grained severity levels are expected to be in the range of 1 to 4,
217+ // where 1 is the base severity level, and 2, 3, and 4 are more fine-grained
218+ // levels. However, fine-grained levels greater than 4 are also accepted, and
219+ // they will be treated as an 1-based offset from the base severity level.
220+ //
221+ // For example, "ERROR3" will be parsed as "ERROR" with a fine-grained level of
222+ // 3, which corresponds to `SeverityError3`, "FATAL+2" will be parsed as
223+ // "FATAL" with an offset of +2, which corresponds to `SeverityFatal2`, and
224+ // "INFO2+1" is parsed as INFO with a fine-grained level of 2 and an offset of
225+ // +1, which corresponds to `SeverityInfo3`.
226+ //
227+ // Fine-grained severity levels are based on counting numbres excluding zero.
228+ // If a fine-grained level of 0 is provided it is treaded as equivalent to the
229+ // base severity level. For example, "INFO0" is equivalent to `SeverityInfo1`.
230+ func (s * Severity ) parse (str string ) (err error ) {
231+ defer func () {
232+ if err != nil {
233+ err = fmt .Errorf ("minsev: severity string %q: %w" , str , err )
234+ }
235+ }()
236+
237+ name := str
238+ offset := 0
239+
240+ // Parse +/- offset suffix, if present.
241+ if i := strings .IndexAny (str , "+-" ); i >= 0 {
242+ name = str [:i ]
243+ offset , err = strconv .Atoi (str [i :])
244+ if err != nil {
245+ return err
246+ }
247+ }
248+
249+ // Parse fine-grained severity level suffix, if present.
250+ // This supports formats like "ERROR3", "FATAL4", etc.
251+ i := len (name )
252+ for i > 0 && str [i - 1 ] >= '0' && str [i - 1 ] <= '9' {
253+ i --
254+ }
255+ if i < len (name ) {
256+ n , err := strconv .Atoi (name [i :])
257+ if err != nil {
258+ return err
259+ }
260+ name = name [:i ]
261+ if n != 0 {
262+ offset += n - 1 // Convert 1-based to 0-based.
263+ }
264+ }
265+
266+ switch strings .ToUpper (name ) {
267+ case "TRACE" :
268+ * s = SeverityTrace1
269+ case "DEBUG" :
270+ * s = SeverityDebug1
271+ case "INFO" :
272+ * s = SeverityInfo1
273+ case "WARN" :
274+ * s = SeverityWarn1
275+ case "ERROR" :
276+ * s = SeverityError1
277+ case "FATAL" :
278+ * s = SeverityFatal1
279+ default :
280+ return errors .New ("unknown name" )
281+ }
282+ * s += Severity (offset )
283+ return nil
284+ }
285+
102286// A SeverityVar is a [Severity] variable, to allow a [LogProcessor] severity
103287// to change dynamically. It implements [Severitier] as well as a Set method,
104288// and it is safe for use by multiple goroutines.
@@ -108,6 +292,15 @@ type SeverityVar struct {
108292 val atomic.Int64
109293}
110294
295+ var (
296+ // Ensure Severity implements fmt.Stringer.
297+ _ fmt.Stringer = (* SeverityVar )(nil )
298+ // Ensure Severity implements encoding.TextMarshaler.
299+ _ encoding.TextMarshaler = (* SeverityVar )(nil )
300+ // Ensure Severity implements encoding.TextUnmarshaler.
301+ _ encoding.TextUnmarshaler = (* SeverityVar )(nil )
302+ )
303+
111304// Severity returns v's severity.
112305func (v * SeverityVar ) Severity () log.Severity {
113306 return Severity (int (v .val .Load ())).Severity ()
@@ -118,6 +311,34 @@ func (v *SeverityVar) Set(l Severity) {
118311 v .val .Store (int64 (l ))
119312}
120313
314+ // String returns a string representation of the SeverityVar.
315+ func (v * SeverityVar ) String () string {
316+ return fmt .Sprintf ("SeverityVar(%s)" , Severity (int (v .val .Load ())).String ())
317+ }
318+
319+ // AppendText implements [encoding.TextAppender]
320+ // by calling [Severity.AppendText].
321+ func (v * SeverityVar ) AppendText (b []byte ) ([]byte , error ) {
322+ return Severity (int (v .val .Load ())).AppendText (b )
323+ }
324+
325+ // MarshalText implements [encoding.TextMarshaler]
326+ // by calling [SeverityVar.AppendText].
327+ func (v * SeverityVar ) MarshalText () ([]byte , error ) {
328+ return v .AppendText (nil )
329+ }
330+
331+ // UnmarshalText implements [encoding.TextUnmarshaler]
332+ // by calling [Severity.UnmarshalText].
333+ func (v * SeverityVar ) UnmarshalText (data []byte ) error {
334+ var s Severity
335+ if err := s .UnmarshalText (data ); err != nil {
336+ return err
337+ }
338+ v .Set (s )
339+ return nil
340+ }
341+
121342// A Severitier provides a [log.Severity] value.
122343type Severitier interface {
123344 Severity () log.Severity
0 commit comments