Skip to content

Commit

Permalink
Update minsev to allow dynamic severities (#6116)
Browse files Browse the repository at this point in the history
Similar to [`slog`](https://pkg.go.dev/log/slog#hdr-Levels), allow the
`minsev.LogProcessor` to be configured with dynamic severity resolution.

---------

Co-authored-by: Robert Pająk <[email protected]>
  • Loading branch information
MrAlias and pellared committed Sep 19, 2024
1 parent e1758b2 commit 2a72871
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 18 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,23 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

### Added

- The `Severitier` and `SeverityVar` types are added to `go.opentelemetry.io/contrib/processors/minsev` allowing dynamic configuration of the severity used by the `LogProcessor`. (#6116)

### Changed

- The function signature of `NewLogProcessor` in `go.opentelemetry.io/contrib/processors/minsev` has changed to accept the added `Severitier` interface instead of a `log.Severity`. (#6116)

### Fixed

- Possible nil dereference panic in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace`. (#5965)

### Removed

- The `Minimum` field of the `LogProcessor` in `go.opentelemetry.io/contrib/processors/minsev` is removed.
Use `NewLogProcessor` to configure this setting. (#6116)

<!-- Released section -->
<!-- Don't change this section unless doing release -->

Expand Down
50 changes: 50 additions & 0 deletions processors/minsev/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package minsev // import "go.opentelemetry.io/contrib/processors/minsev"

import (
"context"
"fmt"
"os"
"strings"
"sync"

"go.opentelemetry.io/otel/log"
)

const key = "OTEL_LOG_LEVEL"

var getSeverity = sync.OnceValue(func() log.Severity {
conv := map[string]log.Severity{
"": log.SeverityInfo, // Default to SeverityInfo for unset.
"debug": log.SeverityDebug,
"info": log.SeverityInfo,
"warn": log.SeverityWarn,
"error": log.SeverityError,
}
// log.SeverityUndefined for unknown values.
return conv[strings.ToLower(os.Getenv(key))]
})

type EnvSeverity struct{}

func (EnvSeverity) Severity() log.Severity { return getSeverity() }

func ExampleSeveritier() {
// Mock an environment variable setup that would be done externally.
_ = os.Setenv(key, "error")

p := NewLogProcessor(&processor{}, EnvSeverity{})

ctx, params := context.Background(), log.EnabledParameters{}
params.SetSeverity(log.SeverityDebug)
fmt.Println(p.Enabled(ctx, params))

params.SetSeverity(log.SeverityError)
fmt.Println(p.Enabled(ctx, params))

// Output:
// false
// true
}
26 changes: 18 additions & 8 deletions processors/minsev/minsev.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,22 @@ import (
// NewLogProcessor returns a new [LogProcessor] that wraps the downstream
// [log.Processor].
//
// severity reports the minimum record severity that will be logged. The
// LogProcessor discards records with lower severities. If severity is nil,
// SeverityInfo is used as a default. The LogProcessor calls severity.Severity
// for each record processed or queried; to adjust the minimum level
// dynamically, use a [SeverityVar].
//
// If downstream is nil a default No-Op [log.Processor] is used. The returned
// processor will not be enabled for nor emit any records.
func NewLogProcessor(downstream log.Processor, minimum api.Severity) *LogProcessor {
func NewLogProcessor(downstream log.Processor, severity Severitier) *LogProcessor {
if downstream == nil {
downstream = defaultProcessor
}
p := &LogProcessor{Processor: downstream, Minimum: minimum}
if severity == nil {
severity = SeverityInfo
}
p := &LogProcessor{Processor: downstream, sev: severity}
if fp, ok := downstream.(filterProcessor); ok {
p.filter = fp
}
Expand All @@ -45,8 +54,8 @@ type filterProcessor interface {
type LogProcessor struct {
log.Processor

filter filterProcessor
Minimum api.Severity
filter filterProcessor
sev Severitier
}

// Compile time assertion that LogProcessor implements log.Processor.
Expand All @@ -56,7 +65,7 @@ var _ log.Processor = (*LogProcessor)(nil)
// of record is greater than or equal to p.Minimum. Otherwise, record is
// dropped.
func (p *LogProcessor) OnEmit(ctx context.Context, record *log.Record) error {
if record.Severity() >= p.Minimum {
if record.Severity() >= p.sev.Severity() {
return p.Processor.OnEmit(ctx, record)
}
return nil
Expand All @@ -66,15 +75,16 @@ func (p *LogProcessor) OnEmit(ctx context.Context, record *log.Record) error {
// severity of param is greater than or equal to p.Minimum. Otherwise false is
// returned.
func (p *LogProcessor) Enabled(ctx context.Context, param api.EnabledParameters) bool {
lvl, ok := param.Severity()
sev, ok := param.Severity()
if !ok {
return true
}

if p.filter != nil {
return lvl >= p.Minimum && p.filter.Enabled(ctx, param)
return sev >= p.sev.Severity() &&
p.filter.Enabled(ctx, param)
}
return lvl >= p.Minimum
return sev >= p.sev.Severity()
}

var defaultProcessor = noopProcessor{}
Expand Down
71 changes: 61 additions & 10 deletions processors/minsev/minsev_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ var severities = []api.Severity{
api.SeverityFatal, api.SeverityFatal1, api.SeverityFatal2, api.SeverityFatal3, api.SeverityFatal4,
}

type apiSev api.Severity

func (s apiSev) Severity() api.Severity { return api.Severity(s) }

type emitArgs struct {
Ctx context.Context
Record *log.Record
Expand Down Expand Up @@ -68,11 +72,33 @@ func (p *processor) Reset() {
p.ForceFlushCalls = p.ForceFlushCalls[:0]
}

func TestLogProcessorDynamicSeverity(t *testing.T) {
sev := new(SeverityVar)
wrapped := new(processor)
p := NewLogProcessor(wrapped, sev)

ctx := context.Background()
params := &api.EnabledParameters{}
params.SetSeverity(api.SeverityDebug)
assert.False(t, p.Enabled(ctx, *params), api.SeverityDebug.String())

params.SetSeverity(api.SeverityInfo)
assert.True(t, p.Enabled(ctx, *params), api.SeverityInfo.String())

sev.Set(SeverityError)

params.SetSeverity(api.SeverityInfo)
assert.False(t, p.Enabled(ctx, *params), api.SeverityInfo.String())

params.SetSeverity(api.SeverityError)
assert.True(t, p.Enabled(ctx, *params), api.SeverityError.String())
}

func TestLogProcessorOnEmit(t *testing.T) {
t.Run("Passthrough", func(t *testing.T) {
wrapped := &processor{ReturnErr: assert.AnError}

p := NewLogProcessor(wrapped, api.SeverityTrace1)
p := NewLogProcessor(wrapped, SeverityTrace1)
ctx := context.Background()
r := &log.Record{}
for _, sev := range severities {
Expand All @@ -90,12 +116,12 @@ func TestLogProcessorOnEmit(t *testing.T) {
t.Run("Dropped", func(t *testing.T) {
wrapped := &processor{ReturnErr: assert.AnError}

p := NewLogProcessor(wrapped, api.SeverityFatal4+1)
p := NewLogProcessor(wrapped, apiSev(api.SeverityFatal4+1))
ctx := context.Background()
r := &log.Record{}
for _, sev := range severities {
r.SetSeverity(sev)
assert.NoError(t, p.OnEmit(ctx, r), assert.AnError, sev.String())
assert.NoError(t, p.OnEmit(ctx, r), sev.String())

if !assert.Lenf(t, wrapped.OnEmitCalls, 0, "Record with severity %s passed-through", sev) {
wrapped.Reset()
Expand All @@ -108,7 +134,7 @@ func TestLogProcessorEnabled(t *testing.T) {
t.Run("Passthrough", func(t *testing.T) {
wrapped := &processor{}

p := NewLogProcessor(wrapped, api.SeverityTrace1)
p := NewLogProcessor(wrapped, SeverityTrace1)
ctx := context.Background()
param := api.EnabledParameters{}
for _, sev := range severities {
Expand All @@ -126,7 +152,7 @@ func TestLogProcessorEnabled(t *testing.T) {
t.Run("NotEnabled", func(t *testing.T) {
wrapped := &processor{}

p := NewLogProcessor(wrapped, api.SeverityFatal4+1)
p := NewLogProcessor(wrapped, apiSev(api.SeverityFatal4+1))
ctx := context.Background()
param := api.EnabledParameters{}
for _, sev := range severities {
Expand All @@ -138,12 +164,32 @@ func TestLogProcessorEnabled(t *testing.T) {
}
}
})

t.Run("NoFiltered", func(t *testing.T) {
wrapped := &processor{}

pruned := struct{ log.Processor }{wrapped} // Remove the Enabled method.
p := NewLogProcessor(pruned, SeverityInfo)
ctx := context.Background()
params := &api.EnabledParameters{}

params.SetSeverity(api.SeverityDebug)
assert.False(t, p.Enabled(ctx, *params))

params.SetSeverity(api.SeverityInfo)
assert.True(t, p.Enabled(ctx, *params))

params.SetSeverity(api.SeverityError)
assert.True(t, p.Enabled(ctx, *params))

assert.Len(t, wrapped.EnabledCalls, 0)
})
}

func TestLogProcessorForceFlushPassthrough(t *testing.T) {
wrapped := &processor{ReturnErr: assert.AnError}

p := NewLogProcessor(wrapped, api.SeverityTrace1)
p := NewLogProcessor(wrapped, SeverityTrace1)
ctx := context.Background()
assert.ErrorIs(t, p.ForceFlush(ctx), assert.AnError)
assert.Len(t, wrapped.ForceFlushCalls, 1, "ForceFlush not passed-through")
Expand All @@ -152,14 +198,19 @@ func TestLogProcessorForceFlushPassthrough(t *testing.T) {
func TestLogProcessorShutdownPassthrough(t *testing.T) {
wrapped := &processor{ReturnErr: assert.AnError}

p := NewLogProcessor(wrapped, api.SeverityTrace1)
p := NewLogProcessor(wrapped, SeverityTrace1)
ctx := context.Background()
assert.ErrorIs(t, p.Shutdown(ctx), assert.AnError)
assert.Len(t, wrapped.ShutdownCalls, 1, "Shutdown not passed-through")
}

func TestLogProcessorNilSeverity(t *testing.T) {
p := NewLogProcessor(nil, nil)
assert.Equal(t, SeverityInfo, p.sev.(Severity))
}

func TestLogProcessorNilDownstream(t *testing.T) {
p := NewLogProcessor(nil, api.SeverityTrace1)
p := NewLogProcessor(nil, SeverityTrace1)
ctx := context.Background()
r := new(log.Record)
r.SetSeverity(api.SeverityTrace1)
Expand Down Expand Up @@ -200,6 +251,6 @@ func BenchmarkLogProcessor(b *testing.B) {
}

b.Run("Base", run(defaultProcessor))
b.Run("Enabled", run(NewLogProcessor(nil, api.SeverityTrace)))
b.Run("Disabled", run(NewLogProcessor(nil, api.SeverityDebug)))
b.Run("Enabled", run(NewLogProcessor(nil, SeverityTrace)))
b.Run("Disabled", run(NewLogProcessor(nil, SeverityDebug)))
}
Loading

0 comments on commit 2a72871

Please sign in to comment.