diff --git a/.chloggen/debug-exporter-normal-verbosity-traces.yaml b/.chloggen/debug-exporter-normal-verbosity-traces.yaml new file mode 100644 index 00000000000..00a105bac1e --- /dev/null +++ b/.chloggen/debug-exporter-normal-verbosity-traces.yaml @@ -0,0 +1,25 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# 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: In `normal` verbosity, display one line of text for each span + +# 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: + +# 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: [] diff --git a/exporter/debugexporter/README.md b/exporter/debugexporter/README.md index 9746c7a1658..1e75cc872ca 100644 --- a/exporter/debugexporter/README.md +++ b/exporter/debugexporter/README.md @@ -63,21 +63,22 @@ Here's an example output: ### Normal verbosity -With `verbosity: normal`, the exporter outputs about one line for each telemetry record, including its body and attributes. +With `verbosity: normal`, the exporter outputs about one line for each telemetry record. The "one line per telemetry record" is not a strict rule. For example, logs with multiline body will be output as multiple lines. > [!IMPORTANT] -> Currently the `normal` verbosity is only implemented for logs. -> Metrics and traces are going to be implemented in the future. -> The current behavior for metrics and traces is the same as in `basic` verbosity. +> Currently the `normal` verbosity is only implemented for logs and traces. +> Metrics are going to be implemented in the future. +> The current behavior for metrics is the same as in `basic` verbosity. Here's an example output: ```console -2024-05-27T12:46:22.423+0200 info LogsExporter {"kind": "exporter", "data_type": "logs", "name": "debug", "resource logs": 1, "log records": 1} -2024-05-27T12:46:22.423+0200 info the message app=server - {"kind": "exporter", "data_type": "logs", "name": "debug"} +2024-05-31T13:26:37.531+0200 info TracesExporter {"kind": "exporter", "data_type": "traces", "name": "debug", "resource spans": 1, "spans": 2} +2024-05-31T13:26:37.531+0200 info okey-dokey-0 082bc2f70f519e32a39fd26ae69b43c0 51201084f4d65159 +lets-go 082bc2f70f519e32a39fd26ae69b43c0 cd321682f3514378 + {"kind": "exporter", "data_type": "traces", "name": "debug"} ``` ### Detailed verbosity diff --git a/exporter/debugexporter/exporter.go b/exporter/debugexporter/exporter.go index ab5865e41c7..6f3a59298d7 100644 --- a/exporter/debugexporter/exporter.go +++ b/exporter/debugexporter/exporter.go @@ -30,17 +30,20 @@ type debugExporter struct { func newDebugExporter(logger *zap.Logger, verbosity configtelemetry.Level) *debugExporter { var logsMarshaler plog.Marshaler + var tracesMarshaler ptrace.Marshaler if verbosity == configtelemetry.LevelDetailed { logsMarshaler = otlptext.NewTextLogsMarshaler() + tracesMarshaler = otlptext.NewTextTracesMarshaler() } else { logsMarshaler = normal.NewNormalLogsMarshaler() + tracesMarshaler = normal.NewNormalTracesMarshaler() } return &debugExporter{ verbosity: verbosity, logger: logger, logsMarshaler: logsMarshaler, metricsMarshaler: otlptext.NewTextMetricsMarshaler(), - tracesMarshaler: otlptext.NewTextTracesMarshaler(), + tracesMarshaler: tracesMarshaler, } } @@ -48,7 +51,7 @@ 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.LevelDetailed { + if s.verbosity == configtelemetry.LevelBasic { return nil } diff --git a/exporter/debugexporter/internal/normal/common.go b/exporter/debugexporter/internal/normal/common.go new file mode 100644 index 00000000000..957583f9c8e --- /dev/null +++ b/exporter/debugexporter/internal/normal/common.go @@ -0,0 +1,20 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package normal // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/normal" + +import ( + "fmt" + + "go.opentelemetry.io/collector/pdata/pcommon" +) + +// writeAttributes returns a slice of strings in the form "attrKey=attrValue" +func writeAttributes(attributes pcommon.Map) (attributeStrings []string) { + attributes.Range(func(k string, v pcommon.Value) bool { + attribute := fmt.Sprintf("%s=%s", k, v.AsString()) + attributeStrings = append(attributeStrings, attribute) + return true + }) + return attributeStrings +} diff --git a/exporter/debugexporter/internal/normal/logs.go b/exporter/debugexporter/internal/normal/logs.go index 9a2ff4cd808..dbf2e5dda08 100644 --- a/exporter/debugexporter/internal/normal/logs.go +++ b/exporter/debugexporter/internal/normal/logs.go @@ -8,7 +8,6 @@ import ( "fmt" "strings" - "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/plog" ) @@ -40,13 +39,3 @@ func (normalLogsMarshaler) MarshalLogs(ld plog.Logs) ([]byte, error) { } return buffer.Bytes(), nil } - -// writeAttributes returns a slice of strings in the form "attrKey=attrValue" -func writeAttributes(attributes pcommon.Map) (attributeStrings []string) { - attributes.Range(func(k string, v pcommon.Value) bool { - attribute := fmt.Sprintf("%s=%s", k, v.AsString()) - attributeStrings = append(attributeStrings, attribute) - return true - }) - return attributeStrings -} diff --git a/exporter/debugexporter/internal/normal/traces.go b/exporter/debugexporter/internal/normal/traces.go new file mode 100644 index 00000000000..3aa32731ad6 --- /dev/null +++ b/exporter/debugexporter/internal/normal/traces.go @@ -0,0 +1,51 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package normal // import "go.opentelemetry.io/collector/exporter/debugexporter/internal/normal" + +import ( + "bytes" + "strings" + + "go.opentelemetry.io/collector/pdata/ptrace" +) + +type normalTracesMarshaler struct{} + +// Ensure normalTracesMarshaller implements interface ptrace.Marshaler +var _ ptrace.Marshaler = normalTracesMarshaler{} + +// NewNormalTracesMarshaler returns a ptrace.Marshaler for normal verbosity. It writes one line of text per log record +func NewNormalTracesMarshaler() ptrace.Marshaler { + return normalTracesMarshaler{} +} + +func (normalTracesMarshaler) MarshalTraces(md ptrace.Traces) ([]byte, error) { + var buffer bytes.Buffer + for i := 0; i < md.ResourceSpans().Len(); i++ { + resourceTraces := md.ResourceSpans().At(i) + for j := 0; j < resourceTraces.ScopeSpans().Len(); j++ { + scopeTraces := resourceTraces.ScopeSpans().At(j) + for k := 0; k < scopeTraces.Spans().Len(); k++ { + span := scopeTraces.Spans().At(k) + + buffer.WriteString(span.Name()) + + buffer.WriteString(" ") + buffer.WriteString(span.TraceID().String()) + + buffer.WriteString(" ") + buffer.WriteString(span.SpanID().String()) + + if span.Attributes().Len() > 0 { + spanAttributes := writeAttributes(span.Attributes()) + buffer.WriteString(" ") + buffer.WriteString(strings.Join(spanAttributes, " ")) + } + + buffer.WriteString("\n") + } + } + } + return buffer.Bytes(), nil +} diff --git a/exporter/debugexporter/internal/normal/traces_test.go b/exporter/debugexporter/internal/normal/traces_test.go new file mode 100644 index 00000000000..84f69d4a176 --- /dev/null +++ b/exporter/debugexporter/internal/normal/traces_test.go @@ -0,0 +1,48 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package normal + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/pdata/ptrace" +) + +func TestMarshalTraces(t *testing.T) { + tests := []struct { + name string + input ptrace.Traces + expected string + }{ + { + name: "empty traces", + input: ptrace.NewTraces(), + expected: "", + }, + { + name: "one span", + input: func() ptrace.Traces { + traces := ptrace.NewTraces() + span := traces.ResourceSpans().AppendEmpty().ScopeSpans().AppendEmpty().Spans().AppendEmpty() + span.SetName("span-name") + span.SetTraceID([16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}) + span.SetSpanID([8]byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}) + span.Attributes().PutStr("key1", "value1") + span.Attributes().PutStr("key2", "value2") + return traces + }(), + expected: `span-name 0102030405060708090a0b0c0d0e0f10 1112131415161718 key1=value1 key2=value2 +`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + output, err := NewNormalTracesMarshaler().MarshalTraces(tt.input) + assert.NoError(t, err) + assert.Equal(t, tt.expected, string(output)) + }) + } +}