feat(tracing): capturing messages
This commit is contained in:
parent
6324d62c84
commit
89ed751e04
2 changed files with 166 additions and 145 deletions
166
span.go
Normal file
166
span.go
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
package unitel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
spanContextKey = contextKey{"span"}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Span struct {
|
||||||
|
otel trace.Span
|
||||||
|
sentry *sentry.Span
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Span) Context() context.Context {
|
||||||
|
return context.WithValue(s.sentry.Context(), spanContextKey, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SpanFromContext(ctx context.Context) *Span {
|
||||||
|
if span, ok := ctx.Value(spanContextKey).(*Span); ok {
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Span) AddAttributes(attributes ...attribute.KeyValue) *Span {
|
||||||
|
for _, attr := range attributes {
|
||||||
|
s.sentry.SetData(string(attr.Key), attr.Value.AsInterface())
|
||||||
|
}
|
||||||
|
|
||||||
|
s.otel.SetAttributes(attributes...)
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Span) AddAttribute(key string, value any) *Span {
|
||||||
|
var attr attribute.KeyValue
|
||||||
|
switch v := value.(type) {
|
||||||
|
case string:
|
||||||
|
attr = attribute.String(key, v)
|
||||||
|
case fmt.Stringer:
|
||||||
|
attr = attribute.String(key, v.String())
|
||||||
|
default:
|
||||||
|
attr = attribute.String(key, fmt.Sprintf("%v", v))
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.AddAttributes(attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpanStatus struct {
|
||||||
|
Otel codes.Code
|
||||||
|
Sentry sentry.SpanStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Span) SetStatus(status SpanStatus, description string) *Span {
|
||||||
|
s.otel.SetStatus(status.Otel, description)
|
||||||
|
s.sentry.Status = status.Sentry
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type SimpleStatus uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
Unset SimpleStatus = 0
|
||||||
|
Error SimpleStatus = 1
|
||||||
|
Ok SimpleStatus = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Span) SetSimpleStatus(status SimpleStatus, description string) *Span {
|
||||||
|
var otelStatus codes.Code
|
||||||
|
var sentryStatus sentry.SpanStatus
|
||||||
|
|
||||||
|
switch status {
|
||||||
|
case Error:
|
||||||
|
otelStatus = codes.Error
|
||||||
|
sentryStatus = sentry.SpanStatusInternalError
|
||||||
|
case Ok:
|
||||||
|
otelStatus = codes.Ok
|
||||||
|
sentryStatus = sentry.SpanStatusOK
|
||||||
|
default:
|
||||||
|
otelStatus = codes.Unset
|
||||||
|
sentryStatus = sentry.SpanStatusUndefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.SetStatus(SpanStatus{Otel: otelStatus, Sentry: sentryStatus}, description)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Span) CaptureError(err error) *Span {
|
||||||
|
if hub := sentry.GetHubFromContext(s.Context()); hub != nil {
|
||||||
|
hub.CaptureException(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.otel.RecordError(err, trace.WithStackTrace(true))
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Span) CaptureMessage(message string) *Span {
|
||||||
|
if hub := sentry.GetHubFromContext(s.Context()); hub != nil {
|
||||||
|
hub.CaptureMessage(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.otel.AddEvent(message)
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Span) SetName(name string) *Span {
|
||||||
|
s.otel.SetName(name)
|
||||||
|
s.sentry.Name = name
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Span) SetUser(id uint64, username, ip, permissions string) *Span {
|
||||||
|
uid := fmt.Sprintf("%d", id)
|
||||||
|
|
||||||
|
if hub := sentry.GetHubFromContext(s.Context()); hub != nil {
|
||||||
|
hub.Scope().SetUser(sentry.User{
|
||||||
|
ID: uid,
|
||||||
|
Username: username,
|
||||||
|
IPAddress: ip,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
s.otel.SetAttributes(
|
||||||
|
semconv.EnduserID(uid),
|
||||||
|
semconv.EnduserScope(permissions),
|
||||||
|
attribute.String(libBase+"/username", username),
|
||||||
|
semconv.ClientAddress(ip),
|
||||||
|
)
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Span) End() {
|
||||||
|
s.otel.End()
|
||||||
|
s.sentry.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Span) Recover(ctx context.Context, err error, flushTimeout *time.Duration) {
|
||||||
|
s.CaptureError(err)
|
||||||
|
|
||||||
|
hub := sentry.GetHubFromContext(s.Context())
|
||||||
|
if hub == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// context.WithValue(context.Background(), sentry.RequestContextKey, c),
|
||||||
|
eventID := hub.RecoverWithContext(ctx, err)
|
||||||
|
|
||||||
|
if flushTimeout != nil && eventID != nil {
|
||||||
|
hub.Flush(*flushTimeout)
|
||||||
|
}
|
||||||
|
}
|
145
tracing.go
145
tracing.go
|
@ -2,16 +2,11 @@ package unitel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
|
||||||
"go.opentelemetry.io/otel/codes"
|
|
||||||
"go.opentelemetry.io/otel/propagation"
|
"go.opentelemetry.io/otel/propagation"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,14 +16,8 @@ type contextKey struct {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tracerContextKey = contextKey{"tracer"}
|
tracerContextKey = contextKey{"tracer"}
|
||||||
spanContextKey = contextKey{"span"}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Span struct {
|
|
||||||
otel trace.Span
|
|
||||||
sentry *sentry.Span
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Telemetry) StartSpan(ctx context.Context, operation, name string, cfgs ...ConfigureSpanStartFunc) *Span {
|
func (t *Telemetry) StartSpan(ctx context.Context, operation, name string, cfgs ...ConfigureSpanStartFunc) *Span {
|
||||||
otelStartOpts := make([]trace.SpanStartOption, 0)
|
otelStartOpts := make([]trace.SpanStartOption, 0)
|
||||||
sentryStartOpts := []sentry.SpanOption{sentry.WithTransactionName(name), sentry.WithDescription(name)}
|
sentryStartOpts := []sentry.SpanOption{sentry.WithTransactionName(name), sentry.WithDescription(name)}
|
||||||
|
@ -59,116 +48,6 @@ func (t *Telemetry) StartSpan(ctx context.Context, operation, name string, cfgs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Span) Context() context.Context {
|
|
||||||
return context.WithValue(s.sentry.Context(), spanContextKey, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Span) AddAttributes(attributes ...attribute.KeyValue) *Span {
|
|
||||||
for _, attr := range attributes {
|
|
||||||
s.sentry.SetData(string(attr.Key), attr.Value.AsInterface())
|
|
||||||
}
|
|
||||||
|
|
||||||
s.otel.SetAttributes(attributes...)
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Span) AddAttribute(key string, value any) *Span {
|
|
||||||
var attr attribute.KeyValue
|
|
||||||
switch v := value.(type) {
|
|
||||||
case string:
|
|
||||||
attr = attribute.String(key, v)
|
|
||||||
case fmt.Stringer:
|
|
||||||
attr = attribute.String(key, v.String())
|
|
||||||
default:
|
|
||||||
attr = attribute.String(key, fmt.Sprintf("%v", v))
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.AddAttributes(attr)
|
|
||||||
}
|
|
||||||
|
|
||||||
type SpanStatus struct {
|
|
||||||
Otel codes.Code
|
|
||||||
Sentry sentry.SpanStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Span) SetStatus(status SpanStatus, description string) *Span {
|
|
||||||
s.otel.SetStatus(status.Otel, description)
|
|
||||||
s.sentry.Status = status.Sentry
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
type SimpleStatus uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
Unset SimpleStatus = 0
|
|
||||||
Error SimpleStatus = 1
|
|
||||||
Ok SimpleStatus = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Span) SetSimpleStatus(status SimpleStatus, description string) *Span {
|
|
||||||
var otelStatus codes.Code
|
|
||||||
var sentryStatus sentry.SpanStatus
|
|
||||||
|
|
||||||
switch status {
|
|
||||||
case Error:
|
|
||||||
otelStatus = codes.Error
|
|
||||||
sentryStatus = sentry.SpanStatusInternalError
|
|
||||||
case Ok:
|
|
||||||
otelStatus = codes.Ok
|
|
||||||
sentryStatus = sentry.SpanStatusOK
|
|
||||||
default:
|
|
||||||
otelStatus = codes.Unset
|
|
||||||
sentryStatus = sentry.SpanStatusUndefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.SetStatus(SpanStatus{Otel: otelStatus, Sentry: sentryStatus}, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Span) CaptureError(err error) *Span {
|
|
||||||
if hub := sentry.GetHubFromContext(s.Context()); hub != nil {
|
|
||||||
hub.CaptureException(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.otel.RecordError(err, trace.WithStackTrace(true))
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Span) SetName(name string) *Span {
|
|
||||||
s.otel.SetName(name)
|
|
||||||
s.sentry.Name = name
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Span) SetUser(id uint64, username, ip, permissions string) *Span {
|
|
||||||
uid := fmt.Sprintf("%d", id)
|
|
||||||
|
|
||||||
if hub := sentry.GetHubFromContext(s.Context()); hub != nil {
|
|
||||||
hub.Scope().SetUser(sentry.User{
|
|
||||||
ID: uid,
|
|
||||||
Username: username,
|
|
||||||
IPAddress: ip,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
s.otel.SetAttributes(
|
|
||||||
semconv.EnduserID(uid),
|
|
||||||
semconv.EnduserScope(permissions),
|
|
||||||
attribute.String(libBase+"/username", username),
|
|
||||||
semconv.ClientAddress(ip),
|
|
||||||
)
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Span) End() {
|
|
||||||
s.otel.End()
|
|
||||||
s.sentry.Finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigureSpanStartFunc = func(context.Context) (context.Context, []trace.SpanStartOption, []sentry.SpanOption)
|
type ConfigureSpanStartFunc = func(context.Context) (context.Context, []trace.SpanStartOption, []sentry.SpanOption)
|
||||||
|
|
||||||
func WithOtelOptions(opts ...trace.SpanStartOption) ConfigureSpanStartFunc {
|
func WithOtelOptions(opts ...trace.SpanStartOption) ConfigureSpanStartFunc {
|
||||||
|
@ -202,27 +81,3 @@ func WithOtelTracer(tracer trace.Tracer) ConfigureSpanStartFunc {
|
||||||
return context.WithValue(ctx, tracerContextKey, tracer), []trace.SpanStartOption{}, []sentry.SpanOption{}
|
return context.WithValue(ctx, tracerContextKey, tracer), []trace.SpanStartOption{}, []sentry.SpanOption{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SpanFromContext(ctx context.Context) *Span {
|
|
||||||
if span, ok := ctx.Value(spanContextKey).(*Span); ok {
|
|
||||||
return span
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Span) Recover(ctx context.Context, err error, flushTimeout *time.Duration) {
|
|
||||||
s.CaptureError(err)
|
|
||||||
|
|
||||||
hub := sentry.GetHubFromContext(s.Context())
|
|
||||||
if hub == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// context.WithValue(context.Background(), sentry.RequestContextKey, c),
|
|
||||||
eventID := hub.RecoverWithContext(ctx, err)
|
|
||||||
|
|
||||||
if flushTimeout != nil && eventID != nil {
|
|
||||||
hub.Flush(*flushTimeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue