Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update minsev to allow dynamic severities #6116

Merged
merged 15 commits into from
Sep 19, 2024
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
dmathieu marked this conversation as resolved.
Show resolved Hide resolved
}
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
Loading