Skip to content

Commit

Permalink
move logtest into a recorder within the api
Browse files Browse the repository at this point in the history
  • Loading branch information
dmathieu committed Apr 3, 2024
1 parent 675010f commit 6067a31
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 129 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add `otel.scope.name` and `otel.scope.version` tags to spans exported by `go.opentelemetry.io/otel/exporters/zipkin`. (#5108)
- Add support for `AddLink` to `go.opentelemetry.io/otel/bridge/opencensus`. (#5116)
- Add `String` method to `Value` and `KeyValue` in `go.opentelemetry.io/otel/log`. (#5117)
- `InMemory` log exporter implementation to help with testing integrations. (#5134)
- `InMemory` log recorder implementation to help with testing integrations. (#5134)

### Changed

Expand Down
3 changes: 3 additions & 0 deletions log/logtest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Log Test

[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/log/logtest)](https://pkg.go.dev/go.opentelemetry.io/otel/log/logtest)
41 changes: 41 additions & 0 deletions log/logtest/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package logtest // import "go.opentelemetry.io/otel/log/logtest"

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

type config struct {
minSeverity log.Severity
}

func newConfig(options []Option) config {
var c config
for _, opt := range options {
c = opt.apply(c)
}

return c
}

// Option configures a [Hook].
type Option interface {
apply(config) config
}

type optFunc func(config) config

func (f optFunc) apply(c config) config { return f(c) }

// WithMinSeverity returns an [Option] that configures the minimum severity the
// recorder will return true for when Enabled is called.
//
// By default, the recorder will be enabled for all levels.
func WithMinSeverity(l log.Severity) Option {
return optFunc(func(c config) config {
c.minSeverity = l
return c
})
}
108 changes: 108 additions & 0 deletions log/logtest/recorder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// Package logtest is a testing helper package. User can retrieve an in-memory
// logger to verify the behavior of their integrations.
package logtest // import "go.opentelemetry.io/otel/log/logtest"

import (
"context"
"sync"

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

// embeddedLogger is a type alias so the embedded.Logger type doesn't conflict
// with the Logger method of the recorder when it is embedded.
type embeddedLogger = embedded.Logger // nolint:unused // Used below.

type enablerKey uint

var enableKey enablerKey

// NewInMemoryRecorder returns a new InMemoryRecorder.
func NewInMemoryRecorder(options ...Option) *InMemoryRecorder {
cfg := newConfig(options)
return &InMemoryRecorder{
minSeverity: cfg.minSeverity,
}
}

// Scope represents the instrumentation scope.
type Scope struct {
// Name is the name of the instrumentation scope.
Name string
// Version is the version of the instrumentation scope.
Version string
// SchemaURL of the telemetry emitted by the scope.
SchemaURL string
}

// InMemoryRecorder is a recorder that stores all received log records
// in-memory.
type InMemoryRecorder struct {
embedded.LoggerProvider
embeddedLogger // nolint:unused // Used to embed embedded.Logger.

mu sync.Mutex

records []log.Record

// Scope is the Logger scope recorder received when Logger was called.
Scope Scope

// minSeverity is the minimum severity the recorder will return true for
// when Enabled is called (unless enableKey is set).
minSeverity log.Severity
}

// Logger retrieves acopy of InMemoryRecorder with the provided scope
// information.
func (i *InMemoryRecorder) Logger(name string, opts ...log.LoggerOption) log.Logger {
cfg := log.NewLoggerConfig(opts...)

i.Scope = Scope{
Name: name,
Version: cfg.InstrumentationVersion(),
SchemaURL: cfg.SchemaURL(),
}

return i
}

// Enabled indicates whether a specific record should be stored, according to
// its severity, or context values.
func (i *InMemoryRecorder) Enabled(ctx context.Context, record log.Record) bool {
return ctx.Value(enableKey) != nil || record.Severity() >= i.minSeverity
}

// Emit stores the log record.
func (i *InMemoryRecorder) Emit(_ context.Context, record log.Record) {
i.mu.Lock()
defer i.mu.Unlock()

i.records = append(i.records, record)
}

// GetRecords returns the current in-memory recorder log records.
func (i *InMemoryRecorder) GetRecords() []log.Record {
i.mu.Lock()
defer i.mu.Unlock()
ret := make([]log.Record, len(i.records))
copy(ret, i.records)
return ret
}

// Reset the current in-memory recorder log records.
func (i *InMemoryRecorder) Reset() {
i.mu.Lock()
defer i.mu.Unlock()
i.records = []log.Record{}
}

// ContextWithEnabledRecorder forces enabling the recorder, no matter the log
// severity level.
func ContextWithEnabledRecorder(ctx context.Context) context.Context {
return context.WithValue(ctx, enableKey, true)
}
113 changes: 113 additions & 0 deletions log/logtest/recorder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package logtest

import (
"context"
"testing"

"github.com/stretchr/testify/assert"

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

func TestInMemoryRecorderLogger(t *testing.T) {
for _, tt := range []struct {
name string
options []Option

loggerName string
loggerOptions []log.LoggerOption

expectedLogger log.Logger
}{
{
name: "provides a default logger",

expectedLogger: &InMemoryRecorder{},
},
{
name: "provides a logger with a configured scope",

loggerName: "test",
loggerOptions: []log.LoggerOption{
log.WithInstrumentationVersion("logtest v42"),
log.WithSchemaURL("https://example.com"),
},

expectedLogger: &InMemoryRecorder{
Scope: Scope{
Name: "test",
Version: "logtest v42",
SchemaURL: "https://example.com",
},
},
},
} {
t.Run(tt.name, func(t *testing.T) {
l := NewInMemoryRecorder(tt.options...).Logger(tt.loggerName, tt.loggerOptions...)
assert.Equal(t, tt.expectedLogger, l)
})
}
}

func TestInMemoryRecorderEnabled(t *testing.T) {
for _, tt := range []struct {
name string
options []Option
ctx context.Context
buildRecord func() log.Record

isEnabled bool
}{
{
name: "the default option enables unset levels",
ctx: context.Background(),
buildRecord: func() log.Record {
return log.Record{}
},

isEnabled: true,
},
{
name: "with a minimum severity set disables",
options: []Option{
WithMinSeverity(log.SeverityWarn1),
},
ctx: context.Background(),
buildRecord: func() log.Record {
return log.Record{}
},

isEnabled: false,
},
{
name: "with a context that forces an enabled recorder",
options: []Option{
WithMinSeverity(log.SeverityWarn1),
},
ctx: ContextWithEnabledRecorder(context.Background()),
buildRecord: func() log.Record {
return log.Record{}
},

isEnabled: true,
},
} {
t.Run(tt.name, func(t *testing.T) {
e := NewInMemoryRecorder(tt.options...).Enabled(tt.ctx, tt.buildRecord())
assert.Equal(t, tt.isEnabled, e)
})
}
}

func TestInMemoryRecorderEmitAndReset(t *testing.T) {
r := NewInMemoryRecorder()
assert.Len(t, r.GetRecords(), 0)
r.Emit(context.Background(), log.Record{})
assert.Len(t, r.GetRecords(), 1)

r.Reset()
assert.Len(t, r.GetRecords(), 0)
}
3 changes: 0 additions & 3 deletions sdk/log/logtest/README.md

This file was deleted.

62 changes: 0 additions & 62 deletions sdk/log/logtest/exporter.go

This file was deleted.

0 comments on commit 6067a31

Please sign in to comment.