Skip to content

Commit

Permalink
[exporter/debug] make normal verbosity output one line per telemetr…
Browse files Browse the repository at this point in the history
…y item
  • Loading branch information
andrzej-stencel committed Apr 9, 2024
1 parent fc28929 commit c726577
Show file tree
Hide file tree
Showing 10 changed files with 595 additions and 18 deletions.
33 changes: 33 additions & 0 deletions .chloggen/debug-exporter-normal-verbosity.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: breaking

# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
component: exporter/debug

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Change `normal` verbosity behavior

# One or more tracking issues or pull requests related to the change
issues: [7806]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext: |
The Debug exporter's behavior of `normal` verbosity used to be the same as for `basic` verbosity,
which is to output only one line of text for each telemetry batch.
This changes the behavior of `normal` verbosity to output one line of text
for each telemetry item - log record, metric data point, or span.
This is a good middle ground between `basic` (one line per telemetry batch)
and `detailed` (multiple lines per telemetry item).
This only applies to the Debug exporter;
the deprecated Logging exporter keeps old behavior.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: []
99 changes: 99 additions & 0 deletions exporter/debugexporter/exporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package debugexporter

import (
"context"

"go.opentelemetry.io/collector/exporter/debugexporter/internal/normal"

"go.opentelemetry.io/collector/config/configtelemetry"
"go.opentelemetry.io/collector/exporter/internal/otlptext"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
"go.uber.org/zap"
)

type debugExporter struct {
verbosity configtelemetry.Level
logger *zap.Logger
logsMarshaler plog.Marshaler
metricsMarshaler pmetric.Marshaler
tracesMarshaler ptrace.Marshaler
}

func newDebugExporter(logger *zap.Logger, verbosity configtelemetry.Level) *debugExporter {
var logsMarshaler plog.Marshaler
var metricsMarshaler pmetric.Marshaler
var tracesMarshaler ptrace.Marshaler
if verbosity == configtelemetry.LevelDetailed {
logsMarshaler = otlptext.NewTextLogsMarshaler()
metricsMarshaler = otlptext.NewTextMetricsMarshaler()
tracesMarshaler = otlptext.NewTextTracesMarshaler()
} else {
logsMarshaler = normal.NewNormalLogsMarshaler()
metricsMarshaler = normal.NewNormalMetricsMarshaler()
tracesMarshaler = normal.NewNormalTracesMarshaler()
}
return &debugExporter{
verbosity: verbosity,
logger: logger,
logsMarshaler: logsMarshaler,
metricsMarshaler: metricsMarshaler,
tracesMarshaler: tracesMarshaler,
}
}

func (s *debugExporter) pushTraces(_ context.Context, td ptrace.Traces) error {
s.logger.Info("TracesExporter",
zap.Int("resource spans", td.ResourceSpans().Len()),
zap.Int("spans", td.SpanCount()))

if s.verbosity == configtelemetry.LevelBasic {
return nil
}

buf, err := s.tracesMarshaler.MarshalTraces(td)
if err != nil {
return err
}
s.logger.Info(string(buf))
return nil
}

func (s *debugExporter) pushMetrics(_ context.Context, md pmetric.Metrics) error {
s.logger.Info("MetricsExporter",
zap.Int("resource metrics", md.ResourceMetrics().Len()),
zap.Int("metrics", md.MetricCount()),
zap.Int("data points", md.DataPointCount()))

if s.verbosity == configtelemetry.LevelBasic {
return nil
}

buf, err := s.metricsMarshaler.MarshalMetrics(md)
if err != nil {
return err
}
s.logger.Info(string(buf))
return nil
}

func (s *debugExporter) pushLogs(_ context.Context, ld plog.Logs) error {
s.logger.Info("LogsExporter",
zap.Int("resource logs", ld.ResourceLogs().Len()),
zap.Int("log records", ld.LogRecordCount()))

if s.verbosity == configtelemetry.LevelBasic {
return nil
}

buf, err := s.logsMarshaler.MarshalLogs(ld)
if err != nil {
return err
}
s.logger.Info(string(buf))
return nil
}
57 changes: 41 additions & 16 deletions exporter/debugexporter/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ package debugexporter // import "go.opentelemetry.io/collector/exporter/debugexp

import (
"context"
"time"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configtelemetry"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/debugexporter/internal/metadata"
"go.opentelemetry.io/collector/exporter/internal/common"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/exporter/internal/otlptext"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

// The value of "type" key in configuration.
Expand Down Expand Up @@ -42,27 +47,47 @@ func createDefaultConfig() component.Config {

func createTracesExporter(ctx context.Context, set exporter.CreateSettings, config component.Config) (exporter.Traces, error) {
cfg := config.(*Config)
return common.CreateTracesExporter(ctx, set, config, &common.Common{
Verbosity: cfg.Verbosity,
SamplingInitial: cfg.SamplingInitial,
SamplingThereafter: cfg.SamplingThereafter,
})
exporterLogger := createLogger(cfg, set.TelemetrySettings.Logger)
debugExporter := newDebugExporter(exporterLogger, cfg.Verbosity)
return exporterhelper.NewTracesExporter(ctx, set, config,
debugExporter.pushTraces,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}),
exporterhelper.WithShutdown(otlptext.LoggerSync(exporterLogger)),
)
}

func createMetricsExporter(ctx context.Context, set exporter.CreateSettings, config component.Config) (exporter.Metrics, error) {
cfg := config.(*Config)
return common.CreateMetricsExporter(ctx, set, config, &common.Common{
Verbosity: cfg.Verbosity,
SamplingInitial: cfg.SamplingInitial,
SamplingThereafter: cfg.SamplingThereafter,
})
exporterLogger := createLogger(cfg, set.TelemetrySettings.Logger)
debugExporter := newDebugExporter(exporterLogger, cfg.Verbosity)
return exporterhelper.NewMetricsExporter(ctx, set, config,
debugExporter.pushMetrics,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}),
exporterhelper.WithShutdown(otlptext.LoggerSync(exporterLogger)),
)
}

func createLogsExporter(ctx context.Context, set exporter.CreateSettings, config component.Config) (exporter.Logs, error) {
cfg := config.(*Config)
return common.CreateLogsExporter(ctx, set, config, &common.Common{
Verbosity: cfg.Verbosity,
SamplingInitial: cfg.SamplingInitial,
SamplingThereafter: cfg.SamplingThereafter,
})
exporterLogger := createLogger(cfg, set.TelemetrySettings.Logger)
debugExporter := newDebugExporter(exporterLogger, cfg.Verbosity)
return exporterhelper.NewLogsExporter(ctx, set, config,
debugExporter.pushLogs,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}),
exporterhelper.WithShutdown(otlptext.LoggerSync(exporterLogger)),
)
}

func createLogger(cfg *Config, logger *zap.Logger) *zap.Logger {
core := zapcore.NewSamplerWithOptions(
logger.Core(),
1*time.Second,
cfg.SamplingInitial,
cfg.SamplingThereafter,
)

return zap.New(core)
}
4 changes: 2 additions & 2 deletions exporter/debugexporter/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ require (
go.opentelemetry.io/collector/component v0.97.0
go.opentelemetry.io/collector/config/configtelemetry v0.97.0
go.opentelemetry.io/collector/confmap v0.97.0
go.opentelemetry.io/collector/consumer v0.97.0
go.opentelemetry.io/collector/exporter v0.97.0
go.opentelemetry.io/collector/pdata v1.4.0
go.opentelemetry.io/otel/metric v1.24.0
go.opentelemetry.io/otel/trace v1.24.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.0
)

require (
Expand Down Expand Up @@ -40,15 +42,13 @@ require (
github.com/prometheus/procfs v0.12.0 // indirect
go.opentelemetry.io/collector v0.97.0 // indirect
go.opentelemetry.io/collector/config/configretry v0.97.0 // indirect
go.opentelemetry.io/collector/consumer v0.97.0 // indirect
go.opentelemetry.io/collector/extension v0.97.0 // indirect
go.opentelemetry.io/collector/receiver v0.97.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.46.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.24.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
Expand Down
41 changes: 41 additions & 0 deletions exporter/debugexporter/internal/normal/logs.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 normal

import (
"bytes"
"fmt"
"strings"

"go.opentelemetry.io/collector/pdata/plog"
)

type normalLogsMarshaler struct{}

// Ensure normalLogsMarshaller implements interface plog.Marshaler
var _ plog.Marshaler = normalLogsMarshaler{}

// NewNormalLogsMarshaler returns a plog.Marshaler for normal verbosity. It writes one line of text per log record
func NewNormalLogsMarshaler() plog.Marshaler {
return normalLogsMarshaler{}
}

func (normalLogsMarshaler) MarshalLogs(ld plog.Logs) ([]byte, error) {
var buffer bytes.Buffer
for i := 0; i < ld.ResourceLogs().Len(); i++ {
resourceLog := ld.ResourceLogs().At(i)
for j := 0; j < resourceLog.ScopeLogs().Len(); j++ {
scopeLog := resourceLog.ScopeLogs().At(j)
for k := 0; k < scopeLog.LogRecords().Len(); k++ {
logRecord := scopeLog.LogRecords().At(k)
logAttributes := writeAttributes(logRecord.Attributes())

logString := fmt.Sprintf("%s %s", logRecord.Body().AsString(), strings.Join(logAttributes, " "))
buffer.WriteString(logString)
buffer.WriteString("\n")
}
}
}
return buffer.Bytes(), nil
}
100 changes: 100 additions & 0 deletions exporter/debugexporter/internal/normal/logs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package normal

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
)

func TestMarshalLogs(t *testing.T) {
tests := []struct {
name string
input plog.Logs
expected string
}{
{
name: "empty logs",
input: plog.NewLogs(),
expected: "",
},
{
name: "one log record",
input: func() plog.Logs {
logs := plog.NewLogs()
logRecord := logs.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(time.Date(2024, 1, 23, 17, 54, 41, 153, time.UTC)))
logRecord.SetSeverityNumber(plog.SeverityNumberInfo)
logRecord.SetSeverityText("INFO")
logRecord.Body().SetStr("Single line log message")
logRecord.Attributes().PutStr("key1", "value1")
logRecord.Attributes().PutStr("key2", "value2")
return logs
}(),
expected: `Single line log message key1=value1 key2=value2
`,
},
{
name: "two log records",
input: func() plog.Logs {
logs := plog.NewLogs()
logRecords := logs.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords()

logRecord := logRecords.AppendEmpty()
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(time.Date(2024, 1, 23, 17, 54, 41, 153, time.UTC)))
logRecord.SetSeverityNumber(plog.SeverityNumberInfo)
logRecord.SetSeverityText("INFO")
logRecord.Body().SetStr("Single line log message")
logRecord.Attributes().PutStr("key1", "value1")
logRecord.Attributes().PutStr("key2", "value2")

logRecord = logRecords.AppendEmpty()
logRecord.Body().SetStr("Multi-line\nlog message")
logRecord.Attributes().PutStr("mykey2", "myvalue2")
logRecord.Attributes().PutStr("mykey1", "myvalue1")
return logs
}(),
expected: `Single line log message key1=value1 key2=value2
Multi-line
log message mykey2=myvalue2 mykey1=myvalue1
`,
},
{
name: "log with maps in body and attributes",
input: func() plog.Logs {
logs := plog.NewLogs()
logRecord := logs.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty()
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(time.Date(2020, 2, 11, 20, 26, 13, 789, time.UTC)))
logRecord.SetSeverityNumber(plog.SeverityNumberInfo)
logRecord.SetSeverityText("INFO")
body := logRecord.Body().SetEmptyMap()
body.PutStr("app", "CurrencyConverter")
bodyEvent := body.PutEmptyMap("event")
bodyEvent.PutStr("operation", "convert")
bodyEvent.PutStr("result", "success")
conversionAttr := logRecord.Attributes().PutEmptyMap("conversion")
conversionSourceAttr := conversionAttr.PutEmptyMap("source")
conversionSourceAttr.PutStr("currency", "USD")
conversionSourceAttr.PutDouble("amount", 34.22)
conversionDestinationAttr := conversionAttr.PutEmptyMap("destination")
conversionDestinationAttr.PutStr("currency", "EUR")
logRecord.Attributes().PutStr("service", "payments")
return logs
}(),
expected: `{"app":"CurrencyConverter","event":{"operation":"convert","result":"success"}} conversion={"destination":{"currency":"EUR"},"source":{"amount":34.22,"currency":"USD"}} service=payments
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output, err := NewNormalLogsMarshaler().MarshalLogs(tt.input)
assert.NoError(t, err)
assert.Equal(t, tt.expected, string(output))
})
}
}
Loading

0 comments on commit c726577

Please sign in to comment.