228 lines
6.5 KiB
Go
228 lines
6.5 KiB
Go
package console_exporter
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"slices"
|
|
|
|
"git.ma-al.com/maal-libraries/observer/pkg/attr"
|
|
"git.ma-al.com/maal-libraries/observer/pkg/level"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/sdk/trace"
|
|
)
|
|
|
|
type TraceFormatter interface {
|
|
FormatSpanStart(span trace.ReadOnlySpan, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error)
|
|
FormatSpanEnd(span trace.ReadOnlySpan, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error)
|
|
FormatEvent(event trace.Event, span trace.ReadOnlySpan, selectedAttr []attribute.KeyValue, lvl level.SeverityLevel) (string, error)
|
|
}
|
|
|
|
// Configuration for the exporter.
|
|
//
|
|
// Most of options are passed to the formatter.
|
|
type ProcessorOptions struct {
|
|
// Try to parse filters from an environment variable with a name provided by this field.
|
|
// Result will only by applied to unset options. Currently it will only attempt to parse
|
|
// severity level from the variable and use that as a filter.
|
|
FilterFromEnvVar *string
|
|
// Filter the output based on the [level.SeverityLevel].
|
|
FilterOnLevel level.SeverityLevel
|
|
// Fields that should be removed from the output.
|
|
FilterOutFields []attribute.Key
|
|
// Print only trace events instead of whole traces.
|
|
EmitEventsOnly bool
|
|
// Add trace id as an attribute.
|
|
AddTraceId bool
|
|
// Print output only when an error is found
|
|
SkipNonErrors bool
|
|
// Used only when `EmitEventsOnly` is set to true.
|
|
TraceFormatter *TraceFormatter
|
|
SkipEmittingOnSpanStart bool
|
|
SkipEmittingOnSpanEnd bool
|
|
SkipAttributesOnSpanEnd bool
|
|
SkippAttributesOnSpanStart bool
|
|
}
|
|
|
|
type Processor struct {
|
|
lvl level.SeverityLevel
|
|
removedFields []attribute.Key
|
|
addTraceId bool
|
|
onlyErrs bool
|
|
onlyEvents bool
|
|
traceFormatter TraceFormatter
|
|
skipSpanStart bool
|
|
skipSpanEnd bool
|
|
skipAttrsOnSpanEnd bool
|
|
skipAttrsOnSpanStart bool
|
|
}
|
|
|
|
// NOTE: The configuration might change in future releases
|
|
func DefaultConsoleExportProcessor() trace.SpanProcessor {
|
|
fmt := NewPrettyMultilineFormatter()
|
|
return NewProcessor(ProcessorOptions{
|
|
FilterFromEnvVar: nil,
|
|
FilterOutFields: []attribute.Key{},
|
|
EmitEventsOnly: false,
|
|
SkipEmittingOnSpanStart: false,
|
|
SkippAttributesOnSpanStart: true,
|
|
AddTraceId: false,
|
|
TraceFormatter: &fmt,
|
|
})
|
|
}
|
|
|
|
func NewProcessor(opts ProcessorOptions) trace.SpanProcessor {
|
|
var formatter TraceFormatter
|
|
var lvl level.SeverityLevel
|
|
|
|
if opts.TraceFormatter != nil {
|
|
formatter = *opts.TraceFormatter
|
|
} else {
|
|
fmt := NewPrettyMultilineFormatter()
|
|
formatter = fmt
|
|
}
|
|
if opts.FilterFromEnvVar != nil {
|
|
envFilter := os.Getenv(*opts.FilterFromEnvVar)
|
|
parsedLvl := level.FromString(envFilter)
|
|
if parsedLvl != level.SeverityLevel(0) {
|
|
lvl = parsedLvl
|
|
}
|
|
}
|
|
if opts.FilterOnLevel != level.SeverityLevel(0) {
|
|
lvl = opts.FilterOnLevel
|
|
} else if lvl == level.SeverityLevel(0) {
|
|
lvl = level.TRACE + 1
|
|
}
|
|
|
|
return &Processor{
|
|
traceFormatter: formatter,
|
|
removedFields: opts.FilterOutFields,
|
|
addTraceId: opts.AddTraceId,
|
|
onlyEvents: opts.EmitEventsOnly,
|
|
onlyErrs: opts.SkipNonErrors,
|
|
skipSpanStart: opts.SkipEmittingOnSpanStart,
|
|
skipSpanEnd: opts.SkipEmittingOnSpanEnd,
|
|
skipAttrsOnSpanEnd: opts.SkipAttributesOnSpanEnd,
|
|
skipAttrsOnSpanStart: opts.SkippAttributesOnSpanStart,
|
|
lvl: lvl,
|
|
}
|
|
}
|
|
|
|
// Implements [trace.SpanProcessor]
|
|
func (e *Processor) OnStart(ctx context.Context, span trace.ReadWriteSpan) {
|
|
if !e.skipSpanStart && !e.onlyEvents {
|
|
attrs := span.Attributes()
|
|
filteredAttrs := make([]attribute.KeyValue, 0)
|
|
severityLvl := level.TRACE
|
|
|
|
if !e.skipAttrsOnSpanStart {
|
|
for i := range attrs {
|
|
if !slices.Contains(e.removedFields, attrs[i].Key) {
|
|
filteredAttrs = append(filteredAttrs, attrs[i])
|
|
}
|
|
if attrs[i].Key == attr.SeverityLevelKey {
|
|
severityLvl = level.FromString(attrs[i].Value.AsString())
|
|
}
|
|
|
|
}
|
|
}
|
|
if e.addTraceId {
|
|
filteredAttrs = append(filteredAttrs, attribute.String("trace_id", span.SpanContext().TraceID().String()))
|
|
}
|
|
if severityLvl <= e.lvl {
|
|
line, err := e.traceFormatter.FormatSpanStart(span, filteredAttrs, severityLvl)
|
|
if err != nil {
|
|
fmt.Println("FAILED TO FORMAT SPAN START")
|
|
} else {
|
|
fmt.Printf("%s", line)
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Implements [trace.SpanProcessor]
|
|
func (e *Processor) OnEnd(span trace.ReadOnlySpan) {
|
|
eventsString := ""
|
|
spanEndString := ""
|
|
|
|
for _, event := range span.Events() {
|
|
attrs := event.Attributes
|
|
filteredAttrs := make([]attribute.KeyValue, 0)
|
|
severityLvl := level.TRACE
|
|
for i := range attrs {
|
|
if !slices.Contains(e.removedFields, attrs[i].Key) {
|
|
filteredAttrs = append(filteredAttrs, attrs[i])
|
|
}
|
|
if attrs[i].Key == attr.SeverityLevelKey {
|
|
severityLvl = level.FromString(attrs[i].Value.AsString())
|
|
}
|
|
}
|
|
if e.addTraceId {
|
|
filteredAttrs = append(filteredAttrs, attribute.String("trace_id", span.SpanContext().TraceID().String()))
|
|
}
|
|
if severityLvl <= e.lvl {
|
|
eventString, err := e.traceFormatter.FormatEvent(event, span, filteredAttrs, severityLvl)
|
|
if err != nil {
|
|
fmt.Println("FAILED TO FORMAT TRACE EVENT")
|
|
} else {
|
|
eventsString += eventString
|
|
}
|
|
}
|
|
}
|
|
|
|
if !e.skipSpanEnd && !e.onlyEvents {
|
|
attrs := span.Attributes()
|
|
filteredAttrs := make([]attribute.KeyValue, len(attrs))
|
|
severityLvl := level.TRACE
|
|
|
|
if !e.skipAttrsOnSpanEnd {
|
|
for i := range attrs {
|
|
if !slices.Contains(e.removedFields, attrs[i].Key) {
|
|
filteredAttrs = append(filteredAttrs, attrs[i])
|
|
}
|
|
if attrs[i].Key == attr.SeverityLevelKey {
|
|
severityLvl = level.FromString(attrs[i].Value.AsString())
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
if e.addTraceId {
|
|
filteredAttrs = append(filteredAttrs, attribute.String("trace_id", span.SpanContext().TraceID().String()))
|
|
}
|
|
if severityLvl <= e.lvl {
|
|
spanString, err := e.traceFormatter.FormatSpanEnd(span, filteredAttrs, severityLvl)
|
|
if err != nil {
|
|
fmt.Println("FAILED TO FORMAT SPAN END")
|
|
} else {
|
|
spanEndString += spanString
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if e.skipSpanStart {
|
|
fmt.Printf("%s", spanEndString+eventsString)
|
|
} else {
|
|
fmt.Printf("%s", eventsString+spanEndString)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Implements [trace.SpanProcessor]
|
|
func (e *Processor) ForceFlush(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
// Implements [trace.SpanExporter]
|
|
func (e *Processor) Shutdown(ctx context.Context) error {
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
default:
|
|
}
|
|
return nil
|
|
}
|