Skip to content

Commit

Permalink
Merge branch 'main' into remove_context_check
Browse files Browse the repository at this point in the history
  • Loading branch information
pellared committed Apr 25, 2024
2 parents 638c349 + 27e0344 commit 53b3bba
Show file tree
Hide file tree
Showing 11 changed files with 459 additions and 21 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

### Added

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

### Changed

- `go.opentelemetry.io/otel/exporters/stdout/stdoutlog` won't print timestamps when `WithoutTimestamps` option is set. (#5241)

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

### Added
Expand Down
36 changes: 21 additions & 15 deletions exporters/stdout/stdoutlog/exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestExporter(t *testing.T) {

return exporter
}(),
want: getJSON(now),
want: getJSON(&now),
},
}

Expand Down Expand Up @@ -113,7 +113,7 @@ func TestExporterExport(t *testing.T) {
options: []Option{},
ctx: context.Background(),
records: records,
wantResult: getJSONs(now),
wantResult: getJSONs(&now),
},
{
name: "NoRecords",
Expand All @@ -127,21 +127,21 @@ func TestExporterExport(t *testing.T) {
options: []Option{WithPrettyPrint()},
ctx: context.Background(),
records: records,
wantResult: getPrettyJSONs(now),
wantResult: getPrettyJSONs(&now),
},
{
name: "WithoutTimestamps",
options: []Option{WithoutTimestamps()},
ctx: context.Background(),
records: records,
wantResult: getJSONs(time.Time{}),
wantResult: getJSONs(nil),
},
{
name: "WithoutTimestamps and WithPrettyPrint",
options: []Option{WithoutTimestamps(), WithPrettyPrint()},
ctx: context.Background(),
records: records,
wantResult: getPrettyJSONs(time.Time{}),
wantResult: getPrettyJSONs(nil),
},
{
name: "WithCanceledContext",
Expand Down Expand Up @@ -171,22 +171,28 @@ func TestExporterExport(t *testing.T) {
}
}

func getJSON(now time.Time) string {
serializedNow, _ := json.Marshal(now)
func getJSON(now *time.Time) string {
var timestamps string
if now != nil {
serializedNow, _ := json.Marshal(now)
timestamps = "\"Timestamp\":" + string(serializedNow) + ",\"ObservedTimestamp\":" + string(serializedNow) + ","
}

return "{\"Timestamp\":" + string(serializedNow) + ",\"ObservedTimestamp\":" + string(serializedNow) + ",\"Severity\":9,\"SeverityText\":\"INFO\",\"Body\":{},\"Attributes\":[{\"Key\":\"key\",\"Value\":{}},{\"Key\":\"key2\",\"Value\":{}},{\"Key\":\"key3\",\"Value\":{}},{\"Key\":\"key4\",\"Value\":{}},{\"Key\":\"key5\",\"Value\":{}},{\"Key\":\"bool\",\"Value\":{}}],\"TraceID\":\"0102030405060708090a0b0c0d0e0f10\",\"SpanID\":\"0102030405060708\",\"TraceFlags\":\"01\",\"Resource\":null,\"Scope\":{\"Name\":\"\",\"Version\":\"\",\"SchemaURL\":\"\"},\"AttributeValueLengthLimit\":0,\"AttributeCountLimit\":0}\n"
return "{" + timestamps + "\"Severity\":9,\"SeverityText\":\"INFO\",\"Body\":{},\"Attributes\":[{\"Key\":\"key\",\"Value\":{}},{\"Key\":\"key2\",\"Value\":{}},{\"Key\":\"key3\",\"Value\":{}},{\"Key\":\"key4\",\"Value\":{}},{\"Key\":\"key5\",\"Value\":{}},{\"Key\":\"bool\",\"Value\":{}}],\"TraceID\":\"0102030405060708090a0b0c0d0e0f10\",\"SpanID\":\"0102030405060708\",\"TraceFlags\":\"01\",\"Resource\":null,\"Scope\":{\"Name\":\"\",\"Version\":\"\",\"SchemaURL\":\"\"},\"AttributeValueLengthLimit\":0,\"AttributeCountLimit\":0}\n"
}

func getJSONs(now time.Time) string {
func getJSONs(now *time.Time) string {
return getJSON(now) + getJSON(now)
}

func getPrettyJSON(now time.Time) string {
serializedNow, _ := json.Marshal(now)
func getPrettyJSON(now *time.Time) string {
var timestamps string
if now != nil {
serializedNow, _ := json.Marshal(now)
timestamps = "\n\t\"Timestamp\": " + string(serializedNow) + ",\n\t\"ObservedTimestamp\": " + string(serializedNow) + ","
}

return `{
"Timestamp": ` + string(serializedNow) + `,
"ObservedTimestamp": ` + string(serializedNow) + `,
return `{` + timestamps + `
"Severity": 9,
"SeverityText": "INFO",
"Body": {},
Expand Down Expand Up @@ -231,7 +237,7 @@ func getPrettyJSON(now time.Time) string {
`
}

func getPrettyJSONs(now time.Time) string {
func getPrettyJSONs(now *time.Time) string {
return getPrettyJSON(now) + getPrettyJSON(now)
}

Expand Down
11 changes: 7 additions & 4 deletions exporters/stdout/stdoutlog/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (

// recordJSON is a JSON-serializable representation of a Record.
type recordJSON struct {
Timestamp time.Time
ObservedTimestamp time.Time
Timestamp *time.Time `json:",omitempty"`
ObservedTimestamp *time.Time `json:",omitempty"`
Severity log.Severity
SeverityText string
Body log.Value
Expand Down Expand Up @@ -53,8 +53,11 @@ func (e *Exporter) newRecordJSON(r sdklog.Record) recordJSON {
})

if e.timestamps {
newRecord.Timestamp = r.Timestamp()
newRecord.ObservedTimestamp = r.ObservedTimestamp()
timestamp := r.Timestamp()
newRecord.Timestamp = &timestamp

observedTimestamp := r.ObservedTimestamp()
newRecord.ObservedTimestamp = &observedTimestamp
}

return newRecord
Expand Down
5 changes: 5 additions & 0 deletions log/logtest/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// Package logtest is a testing helper package.
package logtest // import "go.opentelemetry.io/otel/log/logtest"
36 changes: 36 additions & 0 deletions log/logtest/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

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

import (
"time"

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

// RecordFactory is used to facilitate unit testing bridge implementations that
// make use of a [go.opentelemetry.io/otel/log.Record]
//
// 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
}

// NewRecord returns a log record.
func (b RecordFactory) NewRecord() log.Record {
var record log.Record
record.SetTimestamp(b.Timestamp)
record.SetObservedTimestamp(b.ObservedTimestamp)
record.SetSeverity(b.Severity)
record.SetSeverityText(b.SeverityText)
record.SetBody(b.Body)
record.AddAttributes(b.Attributes...)

return record
}
88 changes: 88 additions & 0 deletions log/logtest/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// 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/log"
)

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),
}

got := RecordFactory{
Timestamp: now,
ObservedTimestamp: observed,
Severity: severity,
SeverityText: severityText,
Body: body,
Attributes: attrs,
}.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)
}

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

f := RecordFactory{
Timestamp: now,
Attributes: attrs,
}

record1 := f.NewRecord()
f.Attributes = append(f.Attributes, log.Bool("added", true))

record2 := f.NewRecord()
assert.Equal(t, now, record2.Timestamp())
assertAttributes(t, append(attrs, log.Bool("added", true)), record2)

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

func assertBody(t *testing.T, want log.Value, r log.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 log.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)
}
}
2 changes: 0 additions & 2 deletions log/logtest/recorder.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

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

import (
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
}
Loading

0 comments on commit 53b3bba

Please sign in to comment.