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

sdk/log/logtest: Add RecordFactory #5258

Merged
merged 28 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1bf980b
sdk/log/logtest: Add RecordBuilder
pellared Apr 19, 2024
0430e54
Merge branch 'main' into sdklogtest-2
pellared Apr 22, 2024
ecb21ad
Introduce assertAttributes
pellared Apr 22, 2024
2464512
Add TestRecordBuilderMultiple
pellared Apr 22, 2024
22e5f4b
Merge branch 'sdklogtest-2' of https://github.com/pellared/openteleme…
pellared Apr 22, 2024
0faec77
Add SetObservedTimestamp
pellared Apr 22, 2024
aa422b4
Add SetSeverity
pellared Apr 22, 2024
a20910e
Refactor TestRecordBuilderMultiple
pellared Apr 22, 2024
c68ebf0
Add SetSeverityText and SetBody
pellared Apr 22, 2024
0f44304
Add SetTraceID SetSpanID SetTraceFlags
pellared Apr 22, 2024
5c16e95
Add setters comments
pellared Apr 22, 2024
f3b40d9
Document RecordBuilder type and RecordBuilder.Record method
pellared Apr 22, 2024
be8e20f
Add a notice to not use RecordBuilder in production code
pellared Apr 22, 2024
89858e8
Update changelog
pellared Apr 22, 2024
2cab9ad
Fix changelog
pellared Apr 22, 2024
7676b8b
Add package comment
pellared Apr 22, 2024
4df52f0
Add ExampleRecordBuilder
pellared Apr 22, 2024
44794f5
Add errcheck
pellared Apr 22, 2024
2f48c40
Merge branch 'main' into sdklogtest-2
pellared Apr 23, 2024
0b9cb31
Merge branch 'main' into sdklogtest-2
pellared Apr 23, 2024
ea8b319
sdk/log/logtest: Add RecordFactory
pellared Apr 23, 2024
2bb62ff
Rename Record to NewRecord
pellared Apr 23, 2024
6e2ff4e
Rename files
pellared Apr 23, 2024
45902d9
Merge branch 'main' into sdklogtest-3
pellared Apr 23, 2024
59f0956
Update CHANGELOG.md
pellared Apr 24, 2024
4d01324
Merge branch 'main' into sdklogtest-3
pellared Apr 24, 2024
803c1c4
Merge branch 'main' into sdklogtest-3
pellared Apr 24, 2024
fff83e7
Update changelog
pellared Apr 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

### Added

- Add `RecordFactory` in `go.opentelemetry.io/otel/sdk/log/logtest` to facilitate testing the exporter and processor implementations. (#5258)

## [1.26.0/0.48.0/0.2.0-alpha] 2024-04-24

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

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

// Package logtest is a testing helper package.
package logtest_test

import (
"context"
"fmt"
"io"
"os"

logapi "go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/log/logtest"
)

func ExampleRecordFactory() {
exp := exporter{os.Stdout}
rf := logtest.RecordFactory{
InstrumentationScope: instrumentation.Scope{Name: "myapp"},
}

rf.Body = logapi.StringValue("foo")
r1 := rf.NewRecord()

rf.Body = logapi.StringValue("bar")
r2 := rf.NewRecord()

_ = exp.Export(context.Background(), []log.Record{r1, r2})

// Output:
// scope=myapp msg=foo
// scope=myapp msg=bar
}

// Compile time check exporter implements log.Exporter.
var _ log.Exporter = exporter{}

type exporter struct{ io.Writer }

func (e exporter) Export(ctx context.Context, records []log.Record) error {
for i, r := range records {
if i != 0 {
if _, err := e.Write([]byte("\n")); err != nil {
return err
}
}
if _, err := fmt.Fprintf(e, "scope=%s msg=%s", r.InstrumentationScope().Name, r.Body().String()); err != nil {
return err
}
}
return nil
}

func (e exporter) Shutdown(context.Context) error {
return nil
}

// appropriate error should be returned in these situations.
func (e exporter) ForceFlush(context.Context) error {
return nil
}
102 changes: 102 additions & 0 deletions sdk/log/logtest/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// Package logtest is a testing helper package.
package logtest // import "go.opentelemetry.io/otel/sdk/log/logtest"

import (
"context"
"slices"
"time"

"go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/sdk/instrumentation"
sdklog "go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/trace"
)

// RecordFactory is used to facilitate unit testing implementations of
// [go.opentelemetry.io/otel/sdk/log.Exporter]
// and [go.opentelemetry.io/otel/sdk/log.Processor].
//
// Do not use RecordFactory to create records in production code.
type RecordFactory struct {
Timestamp time.Time
ObservedTimestamp time.Time
Severity log.Severity
SeverityText string
Body log.Value
Attributes []log.KeyValue
TraceID trace.TraceID
SpanID trace.SpanID
TraceFlags trace.TraceFlags

Resource *resource.Resource
InstrumentationScope instrumentation.Scope

DroppedAttributes int
}

// NewRecord returns a log record.
func (b RecordFactory) NewRecord() sdklog.Record {
var record sdklog.Record
p := processor(func(r sdklog.Record) {
r.SetTimestamp(b.Timestamp)
r.SetObservedTimestamp(b.ObservedTimestamp)
r.SetSeverity(b.Severity)
r.SetSeverityText(b.SeverityText)
r.SetBody(b.Body)
r.SetAttributes(slices.Clone(b.Attributes)...)

// Generate dropped attributes.
for i := 0; i < b.DroppedAttributes; i++ {
r.AddAttributes(log.KeyValue{})
}

r.SetTraceID(b.TraceID)
r.SetSpanID(b.SpanID)
r.SetTraceFlags(b.TraceFlags)

record = r
})

attributeCountLimit := -1
if b.DroppedAttributes > 0 {
// Make sure that we can generate dropped attributes.
attributeCountLimit = len(b.Attributes)
}

provider := sdklog.NewLoggerProvider(
sdklog.WithResource(b.Resource),
sdklog.WithAttributeCountLimit(attributeCountLimit),
sdklog.WithAttributeValueLengthLimit(-1),
sdklog.WithProcessor(p),
)

l := provider.Logger(b.InstrumentationScope.Name,
log.WithInstrumentationVersion(b.InstrumentationScope.Version),
log.WithSchemaURL(b.InstrumentationScope.SchemaURL),
)
l.Emit(context.Background(), log.Record{}) // This executes the processor function.
return record
}

type processor func(r sdklog.Record)

func (p processor) OnEmit(ctx context.Context, r sdklog.Record) error {
p(r)
return nil
}

func (processor) Enabled(context.Context, sdklog.Record) bool {
return true
}

func (processor) Shutdown(ctx context.Context) error {
return nil
}

func (processor) ForceFlush(context.Context) error {
return nil
}
124 changes: 124 additions & 0 deletions sdk/log/logtest/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package logtest

import (
"slices"
"testing"
"time"

"github.com/stretchr/testify/assert"

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/sdk/instrumentation"
sdklog "go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/trace"
)

func TestRecordFactory(t *testing.T) {
now := time.Now()
observed := now.Add(time.Second)
severity := log.SeverityDebug
severityText := "DBG"
body := log.StringValue("Message")
attrs := []log.KeyValue{
log.Int("int", 1),
log.String("str", "foo"),
log.Float64("flt", 3.14),
}
traceID := trace.TraceID([16]byte{1})
spanID := trace.SpanID([8]byte{2})
traceFlags := trace.FlagsSampled
dropped := 3
scope := instrumentation.Scope{
Name: t.Name(),
}
r := resource.NewSchemaless(attribute.Bool("works", true))

got := RecordFactory{
Timestamp: now,
ObservedTimestamp: observed,
Severity: severity,
SeverityText: severityText,
Body: body,
Attributes: attrs,
TraceID: traceID,
SpanID: spanID,
TraceFlags: traceFlags,
DroppedAttributes: dropped,
InstrumentationScope: scope,
Resource: r,
}.NewRecord()

assert.Equal(t, now, got.Timestamp())
assert.Equal(t, observed, got.ObservedTimestamp())
assert.Equal(t, severity, got.Severity())
assert.Equal(t, severityText, got.SeverityText())
assertBody(t, body, got)
assertAttributes(t, attrs, got)
assert.Equal(t, dropped, got.DroppedAttributes())
assert.Equal(t, traceID, got.TraceID())
assert.Equal(t, spanID, got.SpanID())
assert.Equal(t, traceFlags, got.TraceFlags())
assert.Equal(t, scope, got.InstrumentationScope())
assert.Equal(t, *r, got.Resource())
}

func TestRecordFactoryMultiple(t *testing.T) {
now := time.Now()
attrs := []log.KeyValue{
log.Int("int", 1),
log.String("str", "foo"),
log.Float64("flt", 3.14),
}
scope := instrumentation.Scope{
Name: t.Name(),
}

f := RecordFactory{
Timestamp: now,
Attributes: attrs,
DroppedAttributes: 1,
InstrumentationScope: scope,
}

record1 := f.NewRecord()

f.Attributes = append(f.Attributes, log.Bool("added", true))
f.DroppedAttributes = 2
record2 := f.NewRecord()

assert.Equal(t, now, record2.Timestamp())
assertAttributes(t, append(attrs, log.Bool("added", true)), record2)
assert.Equal(t, 2, record2.DroppedAttributes())
assert.Equal(t, scope, record2.InstrumentationScope())

// Previously returned record is unharmed by the builder changes.
assert.Equal(t, now, record1.Timestamp())
assertAttributes(t, attrs, record1)
assert.Equal(t, 1, record1.DroppedAttributes())
assert.Equal(t, scope, record1.InstrumentationScope())
}

func assertBody(t *testing.T, want log.Value, r sdklog.Record) {
t.Helper()
got := r.Body()
if !got.Equal(want) {
t.Errorf("Body value is not equal:\nwant: %v\ngot: %v", want, got)
}
}

func assertAttributes(t *testing.T, want []log.KeyValue, r sdklog.Record) {
t.Helper()
var got []log.KeyValue
r.WalkAttributes(func(kv log.KeyValue) bool {
got = append(got, kv)
return true
})
if !slices.EqualFunc(want, got, log.KeyValue.Equal) {
t.Errorf("Attributes are not equal:\nwant: %v\ngot: %v", want, got)
}
}
Loading