plenty of changes to make the package more ergonomic
Including: bug fixes, api changes, new packages, and more!
This commit is contained in:
parent
e835318689
commit
3c51f5575b
@ -9,7 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.ma-al.com/gora_filip/observer/pkg/tracer"
|
"git.ma-al.com/gora_filip/observer/pkg/tracer"
|
||||||
"git.ma-al.com/gora_filip/pkg/attr"
|
"git.ma-al.com/gora_filip/pkg/attr/layer_attr"
|
||||||
"git.ma-al.com/gora_filip/pkg/exporters"
|
"git.ma-al.com/gora_filip/pkg/exporters"
|
||||||
"git.ma-al.com/gora_filip/pkg/fiber_tracing"
|
"git.ma-al.com/gora_filip/pkg/fiber_tracing"
|
||||||
"git.ma-al.com/gora_filip/pkg/level"
|
"git.ma-al.com/gora_filip/pkg/level"
|
||||||
@ -25,12 +25,13 @@ func main() {
|
|||||||
StreamRequestBody: true,
|
StreamRequestBody: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
exps := make([]exporters.ExporterWithConfig, 2)
|
exps := make([]exporters.TraceExporter, 0)
|
||||||
exps = append(exps, exporters.DevConsoleExporter())
|
exps = append(exps, exporters.DevConsoleExporter())
|
||||||
gelfExp, err := exporters.GelfExporter()
|
gelfExp, err := exporters.GelfExporter()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
exps = append(exps, gelfExp)
|
exps = append(exps, gelfExp)
|
||||||
}
|
}
|
||||||
|
|
||||||
main.Use(fiber_tracing.NewMiddleware(fiber_tracing.Config{
|
main.Use(fiber_tracing.NewMiddleware(fiber_tracing.Config{
|
||||||
AppName: "example",
|
AppName: "example",
|
||||||
Version: "0.0.0",
|
Version: "0.0.0",
|
||||||
@ -40,12 +41,16 @@ func main() {
|
|||||||
defer fiber_tracing.ShutdownTracer()
|
defer fiber_tracing.ShutdownTracer()
|
||||||
|
|
||||||
main.Get("/", func(c *fiber.Ctx) error {
|
main.Get("/", func(c *fiber.Ctx) error {
|
||||||
ctx, span := tracer.Handler(c)
|
ctx, span := fiber_tracing.FStart(c, layer_attr.Handler{
|
||||||
|
Level: level.DEBUG,
|
||||||
|
}.AsOpts())
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
span.AddEvent(
|
span.AddEvent(
|
||||||
"smthing is happening",
|
"smthing is happening",
|
||||||
attr.WithAttributes(attr.SeverityLevel(level.INFO), attr.SourceCodeLocation(0)),
|
layer_attr.Handler{
|
||||||
|
Level: level.INFO,
|
||||||
|
}.AsOpts(),
|
||||||
)
|
)
|
||||||
|
|
||||||
err := Serv(ctx)
|
err := Serv(ctx)
|
||||||
|
94
pkg/attr/layer_attr/layer_attr.go
Normal file
94
pkg/attr/layer_attr/layer_attr.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package layer_attr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/gora_filip/pkg/attr"
|
||||||
|
"git.ma-al.com/gora_filip/pkg/level"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
// Extra attributes to be attached. Can be also added with [Handler.CustomAttrs] method.
|
||||||
|
Attributes []attribute.KeyValue
|
||||||
|
Level level.SeverityLevel
|
||||||
|
extraSkipInStack int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) IntoTraceAttributes() []attribute.KeyValue {
|
||||||
|
attrs := make([]attribute.KeyValue, 5+len(h.Attributes))
|
||||||
|
attrs = append(attrs, attr.SourceCodeLocation(1+h.extraSkipInStack)...) // 3 attrs added here
|
||||||
|
attrs = append(attrs, attr.ServiceLayer(attr.LayerHandler), attr.SeverityLevel(h.Level))
|
||||||
|
attrs = append(attrs, h.Attributes...)
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) CustomAttrs(attrs ...interface{}) Handler {
|
||||||
|
h.Attributes = append(h.Attributes, attr.CollectAttributes(attrs...)...)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) SkipMoreInCallStack(skip int) {
|
||||||
|
h.extraSkipInStack += skip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) AsOpts() trace.SpanStartEventOption {
|
||||||
|
h.extraSkipInStack += 1
|
||||||
|
return trace.WithAttributes(h.IntoTraceAttributes()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
// Extra attributes to be attached. Can be also added with [Service.CustomAttrs] method.
|
||||||
|
Attributes []attribute.KeyValue
|
||||||
|
Level level.SeverityLevel
|
||||||
|
extraSkipInStack int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) IntoTraceAttributes() []attribute.KeyValue {
|
||||||
|
attrs := make([]attribute.KeyValue, 5+len(s.Attributes))
|
||||||
|
attrs = append(attrs, attr.SourceCodeLocation(1+s.extraSkipInStack)...)
|
||||||
|
attrs = append(attrs, attr.ServiceLayer(attr.LayerService), attr.SeverityLevel(s.Level))
|
||||||
|
attrs = append(attrs, s.Attributes...)
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) CustomAttrs(attrs ...interface{}) Service {
|
||||||
|
s.Attributes = append(s.Attributes, attr.CollectAttributes(attrs...)...)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) SkipMoreInCallStack(skip int) {
|
||||||
|
s.extraSkipInStack += skip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) AsOpts() trace.SpanStartEventOption {
|
||||||
|
s.extraSkipInStack += 1
|
||||||
|
return trace.WithAttributes(s.IntoTraceAttributes()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repo struct {
|
||||||
|
// Extra attributes to be attached. Can be also added with [Repo.CustomAttrs] method
|
||||||
|
Attributes []attribute.KeyValue
|
||||||
|
Level level.SeverityLevel
|
||||||
|
extraSkipInStack int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Repo) IntoTraceAttributes() []attribute.KeyValue {
|
||||||
|
attrs := make([]attribute.KeyValue, 5+len(r.Attributes))
|
||||||
|
attrs = append(attrs, attr.SourceCodeLocation(1+r.extraSkipInStack)...)
|
||||||
|
attrs = append(attrs, attr.ServiceLayer(attr.LayerRepository), attr.SeverityLevel(r.Level))
|
||||||
|
attrs = append(attrs, r.Attributes...)
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Repo) CustomAttrs(attrs ...interface{}) Repo {
|
||||||
|
r.Attributes = append(r.Attributes, attr.CollectAttributes(attrs...)...)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
func (r *Repo) SkipMoreInCallStack(skip int) {
|
||||||
|
r.extraSkipInStack += skip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Repo) AsOpts() trace.SpanStartEventOption {
|
||||||
|
r.extraSkipInStack += 1
|
||||||
|
return trace.WithAttributes(r.IntoTraceAttributes()...)
|
||||||
|
}
|
@ -19,6 +19,10 @@ const (
|
|||||||
ColorBold = "\033[1m"
|
ColorBold = "\033[1m"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func Bold(txt string) string {
|
||||||
|
return ColorBold + txt + ColorReset
|
||||||
|
}
|
||||||
|
|
||||||
func Red(txt string) string {
|
func Red(txt string) string {
|
||||||
return ColorRed + txt + ColorReset
|
return ColorRed + txt + ColorReset
|
||||||
}
|
}
|
||||||
|
@ -3,119 +3,210 @@ package console_exporter
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"slices"
|
||||||
|
|
||||||
|
"git.ma-al.com/gora_filip/pkg/attr"
|
||||||
"git.ma-al.com/gora_filip/pkg/level"
|
"git.ma-al.com/gora_filip/pkg/level"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/sdk/trace"
|
"go.opentelemetry.io/otel/sdk/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TraceFormatter interface {
|
type TraceFormatter interface {
|
||||||
FormatSpans(spans []trace.ReadOnlySpan, removeFields []attribute.Key, verbosityLevel level.SeverityLevel, addTraceId bool, onlyErrors bool) (string, error)
|
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, selectedAttr []attribute.KeyValue, lvl level.SeverityLevel) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configuration for the exporter.
|
// Configuration for the exporter.
|
||||||
//
|
//
|
||||||
// Most of options are passed to the formatter.
|
// Most of options are passed to the formatter.
|
||||||
type ExporterOptions struct {
|
type ProcessorOptions struct {
|
||||||
// Try to parse filters from an environment variable with a name provided by this field.
|
// Try to parse filters from an environment variable with a name provided by this field.
|
||||||
// Result will only by applied to unset options. NOT IMPLEMENTED!
|
// Result will only by applied to unset options. NOT IMPLEMENTED!
|
||||||
FilterFromEnvVar *string
|
FilterFromEnvVar *string
|
||||||
// Filter the output based on the [level.SeverityLevel].
|
// Filter the output based on the [level.SeverityLevel].
|
||||||
FilterOnLevel *level.SeverityLevel
|
FilterOnLevel level.SeverityLevel
|
||||||
// Fields that should be removed from the output.
|
// Fields that should be removed from the output.
|
||||||
FilterOutFields []attribute.Key
|
FilterOutFields []attribute.Key
|
||||||
// Print only trace events instead of whole traces.
|
// Print only trace events instead of whole traces.
|
||||||
EmitEventsOnly bool
|
EmitEventsOnly bool
|
||||||
// Add trace id to output
|
// Add trace id as an attribute.
|
||||||
EmitTraceId bool
|
AddTraceId bool
|
||||||
// Print output only when an error is found
|
// Print output only when an error is found
|
||||||
EmitOnlyOnError bool
|
SkipNonErrors bool
|
||||||
// Used only when `EmitEventsOnly` is set to true.
|
// Used only when `EmitEventsOnly` is set to true.
|
||||||
TraceFormatter *TraceFormatter
|
TraceFormatter *TraceFormatter
|
||||||
|
SkipEmittingOnSpanStart bool
|
||||||
|
SkipEmittingOnSpanEnd bool
|
||||||
|
SkipAttributesOnSpanEnd bool
|
||||||
|
SkippAttributesOnSpanStart bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Exporter struct {
|
type Processor struct {
|
||||||
lvl level.SeverityLevel
|
lvl level.SeverityLevel
|
||||||
removedFields []attribute.Key
|
removedFields []attribute.Key
|
||||||
addTraceId bool
|
addTraceId bool
|
||||||
onlyErrs bool
|
onlyErrs bool
|
||||||
|
onlyEvents bool
|
||||||
traceFormatter TraceFormatter
|
traceFormatter TraceFormatter
|
||||||
printerMu sync.Mutex
|
skipSpanStart bool
|
||||||
stoppedMu sync.RWMutex
|
skipSpanEnd bool
|
||||||
stopped bool
|
skipAttrsOnSpanEnd bool
|
||||||
|
skipAttrsOnSpanStart bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: The configuration might change in future releases
|
// NOTE: The configuration might change in future releases
|
||||||
func DefaultConsoleExporter() trace.SpanExporter {
|
func DefaultConsoleExportProcessor() trace.SpanProcessor {
|
||||||
lvl := level.DEBUG
|
fmt := NewMultilineFormatter()
|
||||||
fmt := NewEventsOnlyFormatter()
|
return NewProcessor(ProcessorOptions{
|
||||||
return NewExporter(ExporterOptions{
|
|
||||||
FilterFromEnvVar: nil,
|
FilterFromEnvVar: nil,
|
||||||
FilterOnLevel: &lvl,
|
|
||||||
FilterOutFields: []attribute.Key{},
|
FilterOutFields: []attribute.Key{},
|
||||||
EmitEventsOnly: false,
|
EmitEventsOnly: false,
|
||||||
EmitTraceId: true,
|
SkipEmittingOnSpanStart: false,
|
||||||
EmitOnlyOnError: false,
|
SkippAttributesOnSpanStart: true,
|
||||||
|
AddTraceId: false,
|
||||||
TraceFormatter: &fmt,
|
TraceFormatter: &fmt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExporter(opts ExporterOptions) trace.SpanExporter {
|
func NewProcessor(opts ProcessorOptions) trace.SpanProcessor {
|
||||||
var formatter TraceFormatter
|
var formatter TraceFormatter
|
||||||
var lvl level.SeverityLevel
|
var lvl level.SeverityLevel
|
||||||
|
|
||||||
if opts.TraceFormatter != nil {
|
if opts.TraceFormatter != nil {
|
||||||
formatter = *opts.TraceFormatter
|
formatter = *opts.TraceFormatter
|
||||||
} else {
|
} else {
|
||||||
formatter = TraceFormatter(&EventsOnlyFormatter{})
|
formatter = TraceFormatter(nil)
|
||||||
}
|
}
|
||||||
if opts.FilterOnLevel != nil {
|
if opts.FilterOnLevel != 0 {
|
||||||
lvl = *opts.FilterOnLevel
|
lvl = opts.FilterOnLevel
|
||||||
} else {
|
} else {
|
||||||
lvl = level.TRACE
|
lvl = level.TRACE + 10
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Exporter{
|
return &Processor{
|
||||||
traceFormatter: formatter,
|
traceFormatter: formatter,
|
||||||
removedFields: opts.FilterOutFields,
|
removedFields: opts.FilterOutFields,
|
||||||
addTraceId: opts.EmitTraceId,
|
addTraceId: opts.AddTraceId,
|
||||||
onlyErrs: opts.EmitOnlyOnError,
|
onlyEvents: opts.EmitEventsOnly,
|
||||||
|
onlyErrs: opts.SkipNonErrors,
|
||||||
|
skipSpanStart: opts.SkipEmittingOnSpanStart,
|
||||||
|
skipSpanEnd: opts.SkipEmittingOnSpanEnd,
|
||||||
|
skipAttrsOnSpanEnd: opts.SkipAttributesOnSpanEnd,
|
||||||
lvl: lvl,
|
lvl: lvl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements [trace.SpanExporter]
|
// Implements [trace.SpanProcessor]
|
||||||
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) error {
|
func (e *Processor) OnStart(ctx context.Context, span trace.ReadWriteSpan) {
|
||||||
e.stoppedMu.RLock()
|
if !e.skipSpanStart && !e.onlyEvents {
|
||||||
stopped := e.stopped
|
attrs := span.Attributes()
|
||||||
e.stoppedMu.RUnlock()
|
filteredAttrs := make([]attribute.KeyValue, 0)
|
||||||
if stopped {
|
severityLvl := level.TRACE
|
||||||
return nil
|
|
||||||
|
if !e.skipAttrsOnSpanStart {
|
||||||
|
for i := range attrs {
|
||||||
|
if !slices.Contains(e.removedFields, attrs[i].Key) {
|
||||||
|
filteredAttrs = append(filteredAttrs, attrs[i])
|
||||||
}
|
}
|
||||||
if len(spans) == 0 {
|
if attrs[i].Key == attr.SeverityLevelKey {
|
||||||
return nil
|
severityLvl = level.FromString(attrs[i].Value.AsString())
|
||||||
}
|
}
|
||||||
|
|
||||||
e.printerMu.Lock()
|
}
|
||||||
defer e.printerMu.Unlock()
|
}
|
||||||
printLine, err := e.traceFormatter.FormatSpans(spans, e.removedFields, e.lvl, e.addTraceId, e.onlyErrs)
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("FAILED TO FORMAT A TRACE WITH ERR: %#v\n", err)
|
fmt.Println("FAILED TO FORMAT SPAN START")
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s", line)
|
||||||
}
|
}
|
||||||
if len(printLine) > 0 {
|
}
|
||||||
fmt.Println(printLine)
|
}
|
||||||
|
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, 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements [trace.SpanExporter]
|
// Implements [trace.SpanExporter]
|
||||||
func (e *Exporter) Shutdown(ctx context.Context) error {
|
func (e *Processor) Shutdown(ctx context.Context) error {
|
||||||
e.stoppedMu.Lock()
|
|
||||||
e.stopped = true
|
|
||||||
e.stoppedMu.Unlock()
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
package console_exporter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"git.ma-al.com/gora_filip/pkg/attr"
|
|
||||||
"git.ma-al.com/gora_filip/pkg/code_location"
|
|
||||||
"git.ma-al.com/gora_filip/pkg/console_fmt"
|
|
||||||
"git.ma-al.com/gora_filip/pkg/level"
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
|
||||||
"go.opentelemetry.io/otel/sdk/trace"
|
|
||||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
|
||||||
"go.opentelemetry.io/otel/semconv/v1.25.0"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewEventsOnlyFormatter() TraceFormatter {
|
|
||||||
return &EventsOnlyFormatter{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A formatter that will print only events using a multiline format with colors.
|
|
||||||
// It uses attributes from the [attr] and [semconv] packages.
|
|
||||||
type EventsOnlyFormatter struct{}
|
|
||||||
|
|
||||||
func (f *EventsOnlyFormatter) FormatSpans(spans []trace.ReadOnlySpan, removeFields []attribute.Key, verbosityLevel level.SeverityLevel, addTraceId bool, onlyOnError bool) (string, error) {
|
|
||||||
stubs := tracetest.SpanStubsFromReadOnlySpans(spans)
|
|
||||||
|
|
||||||
var formattedSpanString string
|
|
||||||
|
|
||||||
for i := range stubs {
|
|
||||||
stub := &stubs[i]
|
|
||||||
for j := range stub.Events {
|
|
||||||
var attributes map[attribute.Key]string = make(map[attribute.Key]string, 0)
|
|
||||||
var msg string
|
|
||||||
var lvl level.SeverityLevel
|
|
||||||
var isErr bool
|
|
||||||
var location code_location.CodeLocation
|
|
||||||
|
|
||||||
for _, attrKV := range stub.Attributes {
|
|
||||||
if _, exists := attributes[attrKV.Key]; !exists {
|
|
||||||
attributes[attrKV.Key] = attrKV.Value.AsString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, attrKV := range stub.Events[j].Attributes {
|
|
||||||
switch attrKV.Key {
|
|
||||||
case attr.LogMessageLongKey:
|
|
||||||
msg = attrKV.Value.AsString()
|
|
||||||
case attr.LogMessageShortKey:
|
|
||||||
if len(msg) == 0 {
|
|
||||||
msg = attrKV.Value.AsString()
|
|
||||||
}
|
|
||||||
case attr.SeverityLevelKey:
|
|
||||||
lvl = level.FromString(attrKV.Value.AsString())
|
|
||||||
case semconv.CodeFilepathKey:
|
|
||||||
location.FilePath = attrKV.Value.AsString()
|
|
||||||
case semconv.CodeLineNumberKey:
|
|
||||||
location.LineNumber = int(attrKV.Value.AsInt64())
|
|
||||||
case semconv.CodeColumnKey:
|
|
||||||
location.ColumnNumber = int(attrKV.Value.AsInt64())
|
|
||||||
case semconv.ExceptionMessageKey:
|
|
||||||
attributes[attrKV.Key] = attrKV.Value.AsString()
|
|
||||||
isErr = true
|
|
||||||
default:
|
|
||||||
if !slices.Contains(removeFields, attrKV.Key) && len(attrKV.Key) > 0 {
|
|
||||||
attributes[attrKV.Key] = attrKV.Value.AsString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(msg) == 0 {
|
|
||||||
msg = stub.Name
|
|
||||||
}
|
|
||||||
if addTraceId {
|
|
||||||
attributes[attribute.Key("trace_id")] = stub.SpanContext.TraceID().String()
|
|
||||||
}
|
|
||||||
if len(location.FilePath) > 0 {
|
|
||||||
attributes["code.location"] = fmt.Sprintf("%s:%d:%d", location.FilePath, location.LineNumber, location.ColumnNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(!isErr && onlyOnError) && lvl <= verbosityLevel {
|
|
||||||
attrs := ""
|
|
||||||
for k, v := range attributes {
|
|
||||||
attrs += fmt.Sprintf("\t%s%s%s = %s\n", console_fmt.ColorBold, k, console_fmt.ColorReset, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
formattedSpanString += fmt.Sprintf(
|
|
||||||
"%s %s\n%s",
|
|
||||||
fmt.Sprintf("%s[%s]", console_fmt.SeverityLevelToColor(lvl), lvl.String()),
|
|
||||||
fmt.Sprintf("%s%s", msg, console_fmt.ColorReset),
|
|
||||||
attrs,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return formattedSpanString, nil
|
|
||||||
}
|
|
68
pkg/exporters/console_exporter/multiline_formatter.go
Normal file
68
pkg/exporters/console_exporter/multiline_formatter.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package console_exporter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.ma-al.com/gora_filip/pkg/console_fmt"
|
||||||
|
"git.ma-al.com/gora_filip/pkg/level"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewMultilineFormatter() TraceFormatter {
|
||||||
|
return &MultilineFormatter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A formatter that will print only events using a multiline format with colors.
|
||||||
|
// It uses attributes from the [attr] and [semconv] packages.
|
||||||
|
type MultilineFormatter struct{}
|
||||||
|
|
||||||
|
func (f *MultilineFormatter) FormatSpanStart(span trace.ReadOnlySpan, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error) {
|
||||||
|
attrs := ""
|
||||||
|
for _, kv := range selectedAttrs {
|
||||||
|
if len(kv.Key) > 0 {
|
||||||
|
attrs += fmt.Sprintf("\t%s = %s\n", string(kv.Key), kv.Value.AsString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedSpanString := fmt.Sprintf(
|
||||||
|
"%s\n%s",
|
||||||
|
console_fmt.Bold(console_fmt.SeverityLevelToColor(lvl)+fmt.Sprintf("[%s][SpanStart] ", lvl.String())+span.Name()),
|
||||||
|
attrs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return formattedSpanString, nil
|
||||||
|
}
|
||||||
|
func (f *MultilineFormatter) FormatSpanEnd(span trace.ReadOnlySpan, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error) {
|
||||||
|
attrs := ""
|
||||||
|
for _, kv := range selectedAttrs {
|
||||||
|
if len(kv.Key) > 0 {
|
||||||
|
attrs += fmt.Sprintf("\t%s = %s\n", string(kv.Key), kv.Value.AsString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedSpanString := fmt.Sprintf(
|
||||||
|
"%s\n%s",
|
||||||
|
console_fmt.Bold(console_fmt.SeverityLevelToColor(lvl)+fmt.Sprintf("[%s][SpanEnd] ", lvl.String())+span.Name()),
|
||||||
|
attrs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return formattedSpanString, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
func (f *MultilineFormatter) FormatEvent(event trace.Event, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error) {
|
||||||
|
attrs := ""
|
||||||
|
for _, kv := range selectedAttrs {
|
||||||
|
if len(kv.Key) > 0 {
|
||||||
|
attrs += fmt.Sprintf("\t%s = %s\n", string(kv.Key), kv.Value.AsString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedSpanString := fmt.Sprintf(
|
||||||
|
"%s\n%s",
|
||||||
|
console_fmt.Bold(console_fmt.SeverityLevelToColor(lvl)+fmt.Sprintf("[%s][Event] ", lvl.String())+event.Name),
|
||||||
|
attrs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return formattedSpanString, nil
|
||||||
|
}
|
@ -2,20 +2,40 @@ package exporters
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.ma-al.com/gora_filip/pkg/exporters/console_exporter"
|
"git.ma-al.com/gora_filip/pkg/exporters/console_exporter"
|
||||||
gelf_exporter "git.ma-al.com/gora_filip/pkg/exporters/gelf_exporter"
|
gelf_exporter "git.ma-al.com/gora_filip/pkg/exporters/gelf_exporter"
|
||||||
otlphttp_exporter "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
otlphttp_exporter "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewWithConfig(exporter sdktrace.SpanExporter) ExporterWithConfig {
|
// Private type preventing implementation of TraceProcessor by external packages.
|
||||||
|
type traceProviderOpt sdktrace.TracerProviderOption
|
||||||
|
|
||||||
|
type TraceExporter interface {
|
||||||
|
IntoTraceProviderOption() traceProviderOpt
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFromSpanExporter(exporter sdktrace.SpanExporter) ExporterWithConfig {
|
||||||
return ExporterWithConfig{
|
return ExporterWithConfig{
|
||||||
exporter: exporter,
|
exporter: exporter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sneaky wrapper that makes it so that the TraceExporter can be created from SpanProcessor.
|
||||||
|
type proc struct {
|
||||||
|
sdktrace.SpanProcessor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p proc) IntoTraceProviderOption() traceProviderOpt {
|
||||||
|
return sdktrace.WithSpanProcessor(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFromSpanProcessor(processor sdktrace.SpanProcessor) TraceExporter {
|
||||||
|
return TraceExporter(proc{
|
||||||
|
SpanProcessor: processor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Combined exporter with batch processor config
|
// Combined exporter with batch processor config
|
||||||
type ExporterWithConfig struct {
|
type ExporterWithConfig struct {
|
||||||
exporter sdktrace.SpanExporter
|
exporter sdktrace.SpanExporter
|
||||||
@ -27,32 +47,30 @@ func (ecfg ExporterWithConfig) Add(opt sdktrace.BatchSpanProcessorOption) Export
|
|||||||
return ecfg
|
return ecfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ecfg ExporterWithConfig) IntoTraceProviderOption() sdktrace.TracerProviderOption {
|
func (ecfg ExporterWithConfig) IntoTraceProviderOption() traceProviderOpt {
|
||||||
return sdktrace.WithBatcher(ecfg.exporter, ecfg.config...)
|
return sdktrace.WithBatcher(ecfg.exporter, ecfg.config...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// An exporter printing to console with very small delay
|
// An exporter printing to console with very small delay
|
||||||
func DevConsoleExporter(opts ...console_exporter.ExporterOptions) ExporterWithConfig {
|
func DevConsoleExporter(opts ...console_exporter.ProcessorOptions) TraceExporter {
|
||||||
batchTimeout := (time.Millisecond * 250)
|
var exporter TraceExporter
|
||||||
exportTimeout := (time.Millisecond * 250)
|
|
||||||
var exporter ExporterWithConfig
|
|
||||||
if len(opts) > 0 {
|
if len(opts) > 0 {
|
||||||
exporter = NewWithConfig(console_exporter.NewExporter(opts[0]))
|
exporter = NewFromSpanProcessor(console_exporter.NewProcessor(opts[0]))
|
||||||
} else {
|
} else {
|
||||||
exporter = NewWithConfig(console_exporter.DefaultConsoleExporter())
|
exporter = NewFromSpanProcessor(console_exporter.DefaultConsoleExportProcessor())
|
||||||
}
|
}
|
||||||
return exporter.Add(sdktrace.WithBatchTimeout(batchTimeout)).Add(sdktrace.WithExportTimeout(exportTimeout))
|
return TraceExporter(exporter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default exporter to Graylog.
|
// Default exporter to Graylog.
|
||||||
func GelfExporter(opts ...gelf_exporter.Option) (ExporterWithConfig, error) {
|
func GelfExporter(opts ...gelf_exporter.Option) (ExporterWithConfig, error) {
|
||||||
gelfExp, err := gelf_exporter.New(opts...)
|
gelfExp, err := gelf_exporter.New(opts...)
|
||||||
return NewWithConfig(gelfExp), err
|
return NewFromSpanExporter(gelfExp), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exporter for traces over HTTP. Can be used with Jaeger.
|
// Exporter for traces over HTTP. Can be used with Jaeger.
|
||||||
// See documentation of [otlhttp_exporter] for details.
|
// See documentation of [otlhttp_exporter] for details.
|
||||||
func OtlpHTTPExporter(opts ...otlphttp_exporter.Option) (ExporterWithConfig, error) {
|
func OtlpHTTPExporter(opts ...otlphttp_exporter.Option) (ExporterWithConfig, error) {
|
||||||
otlpExp, err := otlphttp_exporter.New(context.Background(), opts...)
|
otlpExp, err := otlphttp_exporter.New(context.Background(), opts...)
|
||||||
return NewWithConfig(otlpExp), err
|
return NewFromSpanExporter(otlpExp), err
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan)
|
|||||||
ExtraFields: attributes,
|
ExtraFields: attributes,
|
||||||
}
|
}
|
||||||
for _, attrKV := range event.Attributes {
|
for _, attrKV := range event.Attributes {
|
||||||
if attrKV.Key == "long_message" {
|
if attrKV.Key == attr.LogMessageLongKey {
|
||||||
gelf.LongMessage = attrKV.Value.AsString()
|
gelf.LongMessage = attrKV.Value.AsString()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ type Config struct {
|
|||||||
Version string
|
Version string
|
||||||
// Name of an organization providing the service
|
// Name of an organization providing the service
|
||||||
ServiceProvider string
|
ServiceProvider string
|
||||||
Exporters []exporters.ExporterWithConfig
|
Exporters []exporters.TraceExporter
|
||||||
}
|
}
|
||||||
|
|
||||||
func newResource(config Config) *resource.Resource {
|
func newResource(config Config) *resource.Resource {
|
||||||
@ -38,6 +38,16 @@ func newResource(config Config) *resource.Resource {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: You can use [trace.WithAttributes] as a parameter to opts argument
|
||||||
|
func Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
|
||||||
|
return fiberOpentelemetry.Tracer.Start(ctx, spanName, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: You can use [trace.WithAttributes] as a parameter to opts argument
|
||||||
|
func FStart(c *fiber.Ctx, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
|
||||||
|
return fiberOpentelemetry.Tracer.Start(c.UserContext(), c.Method()+" "+c.Path(), opts...)
|
||||||
|
}
|
||||||
|
|
||||||
func NewMiddleware(config Config) func(*fiber.Ctx) error {
|
func NewMiddleware(config Config) func(*fiber.Ctx) error {
|
||||||
var tracerProviders []trc.TracerProviderOption
|
var tracerProviders []trc.TracerProviderOption
|
||||||
|
|
||||||
|
@ -8,10 +8,13 @@ import (
|
|||||||
type SeverityLevel uint8
|
type SeverityLevel uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// A magical zero value.
|
||||||
|
// WARN: DO NOT USE IN LOGS OR BASICALLY EVER
|
||||||
|
unset SeverityLevel = iota
|
||||||
// This event requires an immediate action. If you suspect that occurence of an event may signal that the
|
// This event requires an immediate action. If you suspect that occurence of an event may signal that the
|
||||||
// data will get lost, corrupted, or that the application will change its behaviour following the event in
|
// data will get lost, corrupted, or that the application will change its behaviour following the event in
|
||||||
// an undesired way, select the ALERT level.
|
// an undesired way, select the ALERT level.
|
||||||
ALERT SeverityLevel = iota
|
ALERT
|
||||||
// A critical error has occured. Critical errors are such which can be tough to fix or made the users
|
// A critical error has occured. Critical errors are such which can be tough to fix or made the users
|
||||||
// experience significantly worse but unlike errors that trigger ALERT they can be fixed at any moment.
|
// experience significantly worse but unlike errors that trigger ALERT they can be fixed at any moment.
|
||||||
CRIT
|
CRIT
|
||||||
@ -50,6 +53,8 @@ func (l SeverityLevel) String() string {
|
|||||||
return "INFO"
|
return "INFO"
|
||||||
case DEBUG:
|
case DEBUG:
|
||||||
return "DEBUG"
|
return "DEBUG"
|
||||||
|
case TRACE:
|
||||||
|
return "TRACE"
|
||||||
default:
|
default:
|
||||||
return "CRIT"
|
return "CRIT"
|
||||||
}
|
}
|
||||||
@ -69,6 +74,8 @@ func FromString(level string) SeverityLevel {
|
|||||||
return INFO
|
return INFO
|
||||||
case "DEBUG":
|
case "DEBUG":
|
||||||
return DEBUG
|
return DEBUG
|
||||||
|
case "TRACE":
|
||||||
|
return TRACE
|
||||||
default:
|
default:
|
||||||
return CRIT
|
return CRIT
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user