diff --git a/example/main.go b/example/main.go index cad1895..e8a6bff 100644 --- a/example/main.go +++ b/example/main.go @@ -8,19 +8,17 @@ import ( "os/signal" "time" - "git.ma-al.com/gora_filip/observer/pkg/tracer" "git.ma-al.com/gora_filip/pkg/attr/layer_attr" + "git.ma-al.com/gora_filip/pkg/event" "git.ma-al.com/gora_filip/pkg/exporters" tracing "git.ma-al.com/gora_filip/pkg/fiber_tracing" "git.ma-al.com/gora_filip/pkg/level" "github.com/gofiber/fiber/v2" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" ) func main() { - - main := fiber.New(fiber.Config{ - StreamRequestBody: true, - }) + main := fiber.New() exps := make([]exporters.TraceExporter, 0) exps = append(exps, exporters.DevConsoleExporter()) @@ -28,7 +26,7 @@ func main() { if err == nil { exps = append(exps, gelfExp) } - jaegerExp, err := exporters.OtlpHTTPExporter() + jaegerExp, err := exporters.OtlpHTTPExporter(otlptracehttp.WithEndpointURL("http://localhost:4318/v1/traces")) if err == nil { exps = append(exps, jaegerExp) } @@ -41,26 +39,7 @@ func main() { })) defer tracing.ShutdownTracer() - main.Get("/", func(c *fiber.Ctx) error { - ctx, span := tracing.FStart(c, layer_attr.Handler{ - Level: level.DEBUG, - }.AsOpts()) - defer span.End() - - span.AddEvent( - "smthing is happening", - layer_attr.Handler{ - Level: level.INFO, - }.AsOpts(), - ) - - err := Serv(ctx) - if err != nil { - return tracer.RecordError(span, err) - } - - return c.SendString("xd") - }) + main.Get("/", Handler) // handle interrupts (shutdown) c := make(chan os.Signal, 1) @@ -77,8 +56,29 @@ func main() { } +func Handler(c *fiber.Ctx) error { + ctx, span := tracing.FStart(c, layer_attr.Handler{ + Level: level.DEBUG, + }.AsOpts()) + defer span.End() + event.NewInSpan(event.Event{ + Level: level.WARN, + ShortMessage: "a warning event", + }, span) + + err := Serv(ctx) + if err != nil { + return event.NewErrInSpan(event.Error{Err: err, Level: level.ERR}, span) + } + + return c.SendStatus(fiber.StatusOK) +} + func Serv(ctx context.Context) *fiber.Error { - ctx, span := tracer.Service(ctx, "service", "service span") + ctx, span := tracing.Start(ctx, "service span", layer_attr.Service{ + Level: level.INFO, + Name: "some service", + }.AsOpts()) defer span.End() for range []int{1, 2, 3} { @@ -94,7 +94,10 @@ func Serv(ctx context.Context) *fiber.Error { } func Repo(ctx context.Context) error { - ctx, span := tracer.Repository(ctx, "repo", "repo span") + ctx, span := tracing.Start(ctx, "repo span", layer_attr.Repo{ + Level: level.DEBUG, + Name: "some repo", + }.AsOpts()) defer span.End() for range []int{1, 2, 3} { diff --git a/pkg/attr/attr.go b/pkg/attr/attr.go index 7960463..3c244d3 100644 --- a/pkg/attr/attr.go +++ b/pkg/attr/attr.go @@ -51,6 +51,7 @@ const ( SessionCurrencyIdKey = attribute.Key("session.currency_id") ProcessThreadsAvailableKey = attribute.Key("process.threads_available") ServiceLayerKey = attribute.Key("service.layer") + ServiceLayerNameKey = attribute.Key("service.layer_name") ) type ServiceArchitectureLayer string @@ -244,3 +245,10 @@ func ServiceLayer(layer ServiceArchitectureLayer) attribute.KeyValue { Value: attribute.StringValue(string(layer)), } } + +func ServiceLayerName(name string) attribute.KeyValue { + return attribute.KeyValue{ + Key: ServiceLayerNameKey, + Value: attribute.StringValue(name), + } +} diff --git a/pkg/attr/layer_attr/layer_attr.go b/pkg/attr/layer_attr/layer_attr.go index 527c3e1..5c90be4 100644 --- a/pkg/attr/layer_attr/layer_attr.go +++ b/pkg/attr/layer_attr/layer_attr.go @@ -11,6 +11,7 @@ type Handler struct { // Extra attributes to be attached. Can be also added with [Handler.CustomAttrs] method. Attributes []attribute.KeyValue Level level.SeverityLevel + Name string extraSkipInStack int } @@ -18,6 +19,9 @@ 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)) + if len(h.Name) > 0 { + attrs = append(attrs, attr.ServiceLayerName(h.Name)) + } attrs = append(attrs, h.Attributes...) return attrs } @@ -31,6 +35,12 @@ func (h *Handler) SkipMoreInCallStack(skip int) { h.extraSkipInStack += skip } +// Works the same as [Handler.IntoTraceAttributes] +func (h Handler) AsAttrs() []attribute.KeyValue { + h.extraSkipInStack += 1 + return h.IntoTraceAttributes() +} + func (h Handler) AsOpts() trace.SpanStartEventOption { h.extraSkipInStack += 1 return trace.WithAttributes(h.IntoTraceAttributes()...) @@ -40,17 +50,27 @@ type Service struct { // Extra attributes to be attached. Can be also added with [Service.CustomAttrs] method. Attributes []attribute.KeyValue Level level.SeverityLevel + Name string extraSkipInStack int } func (s Service) IntoTraceAttributes() []attribute.KeyValue { - attrs := make([]attribute.KeyValue, 5+len(s.Attributes)) + attrs := make([]attribute.KeyValue, 6+len(s.Attributes)) attrs = append(attrs, attr.SourceCodeLocation(1+s.extraSkipInStack)...) attrs = append(attrs, attr.ServiceLayer(attr.LayerService), attr.SeverityLevel(s.Level)) + if len(s.Name) > 0 { + attrs = append(attrs, attr.ServiceLayerName(s.Name)) + } attrs = append(attrs, s.Attributes...) return attrs } +// Works the same as [Service.IntoTraceAttributes] +func (s Service) AsAttrs() []attribute.KeyValue { + s.extraSkipInStack += 1 + return s.IntoTraceAttributes() +} + func (s Service) CustomAttrs(attrs ...interface{}) Service { s.Attributes = append(s.Attributes, attr.CollectAttributes(attrs...)...) return s @@ -69,17 +89,27 @@ type Repo struct { // Extra attributes to be attached. Can be also added with [Repo.CustomAttrs] method Attributes []attribute.KeyValue Level level.SeverityLevel + Name string extraSkipInStack int } func (r Repo) IntoTraceAttributes() []attribute.KeyValue { - attrs := make([]attribute.KeyValue, 5+len(r.Attributes)) + attrs := make([]attribute.KeyValue, 6+len(r.Attributes)) attrs = append(attrs, attr.SourceCodeLocation(1+r.extraSkipInStack)...) attrs = append(attrs, attr.ServiceLayer(attr.LayerRepository), attr.SeverityLevel(r.Level)) + if len(r.Name) > 0 { + attrs = append(attrs, attr.ServiceLayerName(r.Name)) + } attrs = append(attrs, r.Attributes...) return attrs } +// Works the same as [Repo.IntoTraceAttributes] +func (r Repo) AsAttrs() []attribute.KeyValue { + r.extraSkipInStack += 1 + return r.IntoTraceAttributes() +} + func (r Repo) CustomAttrs(attrs ...interface{}) Repo { r.Attributes = append(r.Attributes, attr.CollectAttributes(attrs...)...) return r diff --git a/pkg/event/event.go b/pkg/event/event.go new file mode 100644 index 0000000..aa6a4fa --- /dev/null +++ b/pkg/event/event.go @@ -0,0 +1,101 @@ +package event + +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" +) + +// An event that maps well to a log message +type Event struct { + FullMessage string + ShortMessage string + Attributes []attribute.KeyValue + // Defaults to INFO when converted into attributes and unset + Level level.SeverityLevel + extraSkipInStack int +} + +type IntoEvent interface { + IntoEvent() Event +} + +func (e Event) IntoEvent() Event { + return e +} + +func (e Event) IntoTraceAttributes() []attribute.KeyValue { + attrs := make([]attribute.KeyValue, 0) + attrs = append(attrs, attr.SourceCodeLocation(1+e.extraSkipInStack)...) + attrs = append(attrs, e.Attributes...) + if len(e.FullMessage) > 0 { + attrs = append(attrs, attr.LogMessageLong(e.FullMessage)) + } + if len(e.ShortMessage) > 0 { + attrs = append(attrs, attr.LogMessageShort(e.ShortMessage)) + } + if e.Level == 0 { + e.Level = level.INFO + } + attrs = append(attrs, attr.SeverityLevel(e.Level)) + return attrs +} + +func (e Event) AsOpts() trace.EventOption { + e.extraSkipInStack += 1 + return trace.WithAttributes(e.IntoTraceAttributes()...) +} + +func NewInSpan[E IntoEvent](ev E, span trace.Span) { + event := ev.IntoEvent() + event.extraSkipInStack += 1 + span.AddEvent(event.ShortMessage, trace.WithAttributes(event.IntoTraceAttributes()...)) +} + +type Error struct { + Err error + ExtendedMessage string + // Defaults to ALERT when converted into attributes and unset + Level level.SeverityLevel + Attributes []attribute.KeyValue + extraSkipInStack int +} + +type IntoErrorEvent interface { + IntoErrorEvent() Error +} + +func (e Error) Error() string { + return e.Err.Error() +} + +func (e Error) IntoErrorEvent() Error { + return e +} + +func (e Error) IntoTraceAttributes() []attribute.KeyValue { + attrs := make([]attribute.KeyValue, 0) + attrs = append(attrs, e.Attributes...) + if len(e.ExtendedMessage) > 0 { + attrs = append(attrs, attr.LogMessageLong(e.ExtendedMessage)) + } + if e.Level == 0 { + e.Level = level.ALERT + } + attrs = append(attrs, attr.SeverityLevel(e.Level), attr.LogMessageShort(e.Err.Error())) + attrs = append(attrs, attr.SourceCodeLocation(1+e.extraSkipInStack)...) + return attrs +} + +func (e Error) AsOpts() trace.EventOption { + e.extraSkipInStack += 1 + return trace.WithAttributes(e.IntoTraceAttributes()...) +} + +func NewErrInSpan[E IntoErrorEvent](err E, span trace.Span) E { + er := err.IntoErrorEvent() + er.extraSkipInStack += 1 + span.RecordError(er.Err, er.AsOpts()) + return err +} diff --git a/pkg/exporters/console_exporter/console_exporter.go b/pkg/exporters/console_exporter/console_exporter.go index 927b04d..e18a550 100644 --- a/pkg/exporters/console_exporter/console_exporter.go +++ b/pkg/exporters/console_exporter/console_exporter.go @@ -14,7 +14,7 @@ import ( 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, selectedAttr []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. @@ -57,7 +57,7 @@ type Processor struct { // NOTE: The configuration might change in future releases func DefaultConsoleExportProcessor() trace.SpanProcessor { - fmt := NewMultilineFormatter() + fmt := NewPrettyMultilineFormatter() return NewProcessor(ProcessorOptions{ FilterFromEnvVar: nil, FilterOutFields: []attribute.Key{}, @@ -85,15 +85,16 @@ func NewProcessor(opts ProcessorOptions) trace.SpanProcessor { } 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, - lvl: lvl, + 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, } } @@ -151,7 +152,7 @@ func (e *Processor) OnEnd(span trace.ReadOnlySpan) { filteredAttrs = append(filteredAttrs, attribute.String("trace_id", span.SpanContext().TraceID().String())) } if severityLvl <= e.lvl { - eventString, err := e.traceFormatter.FormatEvent(event, filteredAttrs, severityLvl) + eventString, err := e.traceFormatter.FormatEvent(event, span, filteredAttrs, severityLvl) if err != nil { fmt.Println("FAILED TO FORMAT TRACE EVENT") } else { diff --git a/pkg/exporters/console_exporter/multiline_formatter.go b/pkg/exporters/console_exporter/multiline_formatter.go index 989ab8a..486275a 100644 --- a/pkg/exporters/console_exporter/multiline_formatter.go +++ b/pkg/exporters/console_exporter/multiline_formatter.go @@ -2,6 +2,7 @@ package console_exporter import ( "fmt" + "slices" "git.ma-al.com/gora_filip/pkg/console_fmt" "git.ma-al.com/gora_filip/pkg/level" @@ -9,16 +10,24 @@ import ( "go.opentelemetry.io/otel/sdk/trace" ) -func NewMultilineFormatter() TraceFormatter { - return &MultilineFormatter{} +func NewPrettyMultilineFormatter() TraceFormatter { + return &PrettyMultilineFormatter{} } // 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{} +type PrettyMultilineFormatter struct{} -func (f *MultilineFormatter) FormatSpanStart(span trace.ReadOnlySpan, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error) { +func (f *PrettyMultilineFormatter) FormatSpanStart(span trace.ReadOnlySpan, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error) { attrs := "" + slices.SortFunc(selectedAttrs, func(a, b attribute.KeyValue) int { + if a.Key > b.Key { + return 1 + } else { + return -1 + } + }) + for _, kv := range selectedAttrs { if len(kv.Key) > 0 { attrs += fmt.Sprintf("\t%s = %s\n", string(kv.Key), kv.Value.AsString()) @@ -33,8 +42,16 @@ func (f *MultilineFormatter) FormatSpanStart(span trace.ReadOnlySpan, selectedAt return formattedSpanString, nil } -func (f *MultilineFormatter) FormatSpanEnd(span trace.ReadOnlySpan, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error) { +func (f *PrettyMultilineFormatter) FormatSpanEnd(span trace.ReadOnlySpan, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error) { attrs := "" + slices.SortFunc(selectedAttrs, func(a, b attribute.KeyValue) int { + if a.Key > b.Key { + return 1 + } else { + return -1 + } + }) + for _, kv := range selectedAttrs { if len(kv.Key) > 0 { attrs += fmt.Sprintf("\t%s = %s\n", string(kv.Key), kv.Value.AsString()) @@ -50,8 +67,16 @@ func (f *MultilineFormatter) FormatSpanEnd(span trace.ReadOnlySpan, selectedAttr return formattedSpanString, nil } -func (f *MultilineFormatter) FormatEvent(event trace.Event, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error) { +func (f *PrettyMultilineFormatter) FormatEvent(event trace.Event, span trace.ReadOnlySpan, selectedAttrs []attribute.KeyValue, lvl level.SeverityLevel) (string, error) { attrs := "" + slices.SortFunc(selectedAttrs, func(a, b attribute.KeyValue) int { + if a.Key > b.Key { + return 1 + } else { + return -1 + } + }) + for _, kv := range selectedAttrs { if len(kv.Key) > 0 { attrs += fmt.Sprintf("\t%s = %s\n", string(kv.Key), kv.Value.AsString()) diff --git a/pkg/fiber_tracing/fiber_tracing.go b/pkg/fiber_tracing/fiber_tracing.go index 96784fc..123bb40 100644 --- a/pkg/fiber_tracing/fiber_tracing.go +++ b/pkg/fiber_tracing/fiber_tracing.go @@ -45,7 +45,8 @@ func Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) // 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...) + ctx := fiberOpentelemetry.FromCtx(c) + return fiberOpentelemetry.Tracer.Start(ctx, c.Method()+" "+c.Path(), opts...) } func NewMiddleware(config Config) func(*fiber.Ctx) error {