chore: init
This commit is contained in:
commit
61763d05af
10 changed files with 1234 additions and 0 deletions
280
fiber_middleware.go
Normal file
280
fiber_middleware.go
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
package unitel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/utils"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/valyala/fasthttp/fasthttpadaptor"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
const fiberMwClientID = libBase + "#FiberMiddleware"
|
||||||
|
|
||||||
|
type FiberMiddlewareConfig struct {
|
||||||
|
Repanic bool
|
||||||
|
WaitForDelivery bool
|
||||||
|
Timeout time.Duration
|
||||||
|
TraceRequestHeaders []string
|
||||||
|
TraceResponseHeaders []string
|
||||||
|
IgnoredRoutes []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var fiberMiddlewareConfigDefault = FiberMiddlewareConfig{
|
||||||
|
Repanic: false,
|
||||||
|
WaitForDelivery: false,
|
||||||
|
Timeout: time.Second * 2,
|
||||||
|
TraceRequestHeaders: []string{},
|
||||||
|
TraceResponseHeaders: []string{},
|
||||||
|
IgnoredRoutes: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFiberMiddlewareTracer(tp trace.TracerProvider) trace.Tracer {
|
||||||
|
return tp.Tracer(fiberMwClientID, trace.WithInstrumentationVersion(libVersion))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFiberMiddlewareMeter(mp metric.MeterProvider) metric.Meter {
|
||||||
|
return mp.Meter(fiberMwClientID, metric.WithInstrumentationVersion(libVersion))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Telemetry) FiberMiddleware(config ...FiberMiddlewareConfig) fiber.Handler {
|
||||||
|
cfg := fiberMiddlewareConfigDefault
|
||||||
|
if len(config) > 0 {
|
||||||
|
cfg = config[0]
|
||||||
|
}
|
||||||
|
if cfg.Timeout == 0 {
|
||||||
|
cfg.Timeout = time.Second * 2
|
||||||
|
}
|
||||||
|
if cfg.TraceRequestHeaders == nil {
|
||||||
|
cfg.TraceRequestHeaders = []string{}
|
||||||
|
}
|
||||||
|
if cfg.TraceResponseHeaders == nil {
|
||||||
|
cfg.TraceResponseHeaders = []string{}
|
||||||
|
}
|
||||||
|
if cfg.IgnoredRoutes == nil {
|
||||||
|
cfg.IgnoredRoutes = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
meter := newFiberMiddlewareMeter(t.meterProvider)
|
||||||
|
tracer := newFiberMiddlewareTracer(t.tracerProvider)
|
||||||
|
|
||||||
|
mDuration, err := meter.Float64Histogram(
|
||||||
|
"http.server.duration",
|
||||||
|
metric.WithDescription("HTTP request response times"),
|
||||||
|
metric.WithUnit("ms"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("failed to create request duration histogram")
|
||||||
|
}
|
||||||
|
|
||||||
|
mActiveRequests, err := meter.Int64UpDownCounter(
|
||||||
|
"http.server.active_requests",
|
||||||
|
metric.WithDescription("Number of in-flight HTTP requests"),
|
||||||
|
metric.WithUnit("1"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("failed to create active requests counter")
|
||||||
|
}
|
||||||
|
|
||||||
|
mRequestSize, err := meter.Int64Histogram(
|
||||||
|
"http.server.request.size",
|
||||||
|
metric.WithUnit("By"),
|
||||||
|
metric.WithDescription("HTTP request sizes"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("failed to create request size histogram")
|
||||||
|
}
|
||||||
|
|
||||||
|
mResponseSize, err := meter.Int64Histogram(
|
||||||
|
"http.server.response.size",
|
||||||
|
metric.WithUnit("By"),
|
||||||
|
metric.WithDescription("HTTP response sizes"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("failed to create response size histogram")
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
// Skip ignored routes (/ping for example)
|
||||||
|
if slices.Contains(cfg.IgnoredRoutes, c.Path()) {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
requestMetricsAttrs := httpServerTraceAttributesFromRequest(c)
|
||||||
|
mActiveRequests.Add(c.Context(), 1, metric.WithAttributes(requestMetricsAttrs...))
|
||||||
|
|
||||||
|
responseMetricAttrs := make([]attribute.KeyValue, len(requestMetricsAttrs))
|
||||||
|
copy(responseMetricAttrs, requestMetricsAttrs)
|
||||||
|
|
||||||
|
var stdRequest http.Request
|
||||||
|
if err := fasthttpadaptor.ConvertRequest(c.Context(), &stdRequest, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := t.propagator.Extract(c.UserContext(), propagation.HeaderCarrier(stdRequest.Header))
|
||||||
|
|
||||||
|
hub := sentry.CurrentHub().Clone()
|
||||||
|
if client := hub.Client(); client != nil {
|
||||||
|
client.SetSDKIdentifier(fiberMwClientID)
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := hub.Scope()
|
||||||
|
scope.SetRequest(&stdRequest)
|
||||||
|
scope.SetRequestBody(utils.CopyBytes(c.Body()))
|
||||||
|
ctx = sentry.SetHubOnContext(ctx, hub)
|
||||||
|
|
||||||
|
description := fmt.Sprintf("%s %s", c.Method(), c.Path())
|
||||||
|
span := t.StartSpan(
|
||||||
|
ctx,
|
||||||
|
"http.server",
|
||||||
|
description,
|
||||||
|
WithOtelOptions(trace.WithSpanKind(trace.SpanKindServer)),
|
||||||
|
WithOtelTracer(tracer),
|
||||||
|
t.ContinueFromRequest(&stdRequest),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
// TODO: Report panics properly
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
timeout := (*time.Duration)(nil)
|
||||||
|
if cfg.WaitForDelivery {
|
||||||
|
timeout = &cfg.Timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
span.Recover(ctx, fmt.Errorf("%v", err), timeout)
|
||||||
|
|
||||||
|
if cfg.Repanic {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
h := propagation.HeaderCarrier{}
|
||||||
|
t.propagator.Inject(ctx, h)
|
||||||
|
|
||||||
|
for _, k := range h.Keys() {
|
||||||
|
c.Set(k, h.Get(k))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
span.AddAttributes(httpServerTraceAttributesFromRequest(c)...)
|
||||||
|
for _, k := range cfg.TraceRequestHeaders {
|
||||||
|
if h := c.Get(k); h != "" {
|
||||||
|
span.AddAttributes(attribute.String(fmt.Sprintf("http.request.header.%s", k), h))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx = span.Context()
|
||||||
|
c.SetUserContext(ctx)
|
||||||
|
|
||||||
|
var err error = nil
|
||||||
|
if err = c.Next(); err != nil {
|
||||||
|
shouldReport := false
|
||||||
|
|
||||||
|
switch err := err.(type) {
|
||||||
|
case *fiber.Error:
|
||||||
|
shouldReport = err.Code >= http.StatusInternalServerError
|
||||||
|
default:
|
||||||
|
shouldReport = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldReport {
|
||||||
|
span.CaptureError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.App().Config().ErrorHandler(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
responseAttrs := []attribute.KeyValue{
|
||||||
|
semconv.HTTPResponseStatusCode(c.Response().StatusCode()),
|
||||||
|
semconv.HTTPRouteKey.String(c.Route().Path),
|
||||||
|
}
|
||||||
|
|
||||||
|
requestSize := int64(len(c.Request().Body()))
|
||||||
|
responseSize := int64(len(c.Response().Body()))
|
||||||
|
|
||||||
|
responseMetricAttrs = append(responseMetricAttrs, responseAttrs...)
|
||||||
|
|
||||||
|
mActiveRequests.Add(c.Context(), -1, metric.WithAttributes(requestMetricsAttrs...))
|
||||||
|
mDuration.Record(ctx, float64(time.Since(start).Milliseconds()), metric.WithAttributes(responseMetricAttrs...))
|
||||||
|
mRequestSize.Record(ctx, requestSize, metric.WithAttributes(responseMetricAttrs...))
|
||||||
|
mResponseSize.Record(ctx, responseSize, metric.WithAttributes(responseMetricAttrs...))
|
||||||
|
|
||||||
|
span.
|
||||||
|
AddAttributes(responseAttrs...).
|
||||||
|
AddAttributes(attribute.Int64("http.request.headers.content-length", requestSize)).
|
||||||
|
SetName(c.Route().Path).
|
||||||
|
SetStatus(httpStatusToSpanStatus(c.Response().StatusCode(), true), "")
|
||||||
|
|
||||||
|
for _, k := range cfg.TraceResponseHeaders {
|
||||||
|
if h := c.GetRespHeader(k); h != "" {
|
||||||
|
span.AddAttributes(attribute.String(fmt.Sprintf("http.response.header.%s", k), h))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpStatusToSpanStatus(code int, isServer bool) SpanStatus {
|
||||||
|
sentryStatus := sentry.HTTPtoSpanStatus(code)
|
||||||
|
|
||||||
|
if code < http.StatusBadRequest {
|
||||||
|
return SpanStatus{codes.Ok, sentryStatus}
|
||||||
|
}
|
||||||
|
|
||||||
|
if code < http.StatusInternalServerError {
|
||||||
|
// For HTTP status codes in the 4xx range span status MUST be left unset
|
||||||
|
// in case of SpanKind.SERVER and MUST be set to Error in case of SpanKind.CLIENT.
|
||||||
|
if isServer {
|
||||||
|
return SpanStatus{codes.Unset, sentryStatus}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SpanStatus{codes.Error, sentryStatus}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SpanStatus{codes.Error, sentryStatus}
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpFlavorAttribute(c *fiber.Ctx) attribute.KeyValue {
|
||||||
|
if c.Request().Header.IsHTTP11() {
|
||||||
|
return semconv.NetworkProtocolName("HTTP/1.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
return semconv.NetworkProtocolName("HTTP/1.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpServerTraceAttributesFromRequest(c *fiber.Ctx) []attribute.KeyValue {
|
||||||
|
attrs := []attribute.KeyValue{
|
||||||
|
httpFlavorAttribute(c),
|
||||||
|
semconv.HTTPRequestMethodKey.String(utils.CopyString(c.Method())),
|
||||||
|
attribute.Int("http.response.header.content-length", c.Request().Header.ContentLength()),
|
||||||
|
semconv.URLScheme(utils.CopyString(c.Protocol())),
|
||||||
|
semconv.URLPath(utils.CopyString(string(c.Request().RequestURI()))),
|
||||||
|
semconv.URLFull(utils.CopyString(c.OriginalURL())),
|
||||||
|
semconv.ServerAddress(utils.CopyString(c.Hostname())),
|
||||||
|
semconv.UserAgentOriginalKey.String(utils.CopyString(string(c.Request().Header.UserAgent()))),
|
||||||
|
semconv.NetworkTransportTCP,
|
||||||
|
}
|
||||||
|
|
||||||
|
clientIP := c.IP()
|
||||||
|
if len(clientIP) > 0 {
|
||||||
|
attrs = append(attrs, semconv.ClientAddressKey.String(utils.CopyString(clientIP)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
}
|
51
go.mod
Normal file
51
go.mod
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
module git.devminer.xyz/devminer/unitel
|
||||||
|
|
||||||
|
go 1.22.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/getsentry/sentry-go v0.28.1
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.5
|
||||||
|
github.com/jackc/pgx/v5 v5.6.0
|
||||||
|
github.com/qustavo/sqlhooks/v2 v2.1.0
|
||||||
|
github.com/rs/zerolog v1.33.0
|
||||||
|
github.com/valyala/fasthttp v1.55.0
|
||||||
|
go.opentelemetry.io/otel v1.28.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0
|
||||||
|
go.opentelemetry.io/otel/metric v1.28.0
|
||||||
|
go.opentelemetry.io/otel/sdk v1.28.0
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.28.0
|
||||||
|
go.opentelemetry.io/otel/trace v1.28.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.9 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||||
|
golang.org/x/crypto v0.24.0 // indirect
|
||||||
|
golang.org/x/net v0.26.0 // indirect
|
||||||
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
|
golang.org/x/sys v0.21.0 // indirect
|
||||||
|
golang.org/x/text v0.16.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||||
|
google.golang.org/grpc v1.64.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
|
)
|
128
go.sum
Normal file
128
go.sum
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k=
|
||||||
|
github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg=
|
||||||
|
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||||
|
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||||
|
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
|
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||||
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
|
||||||
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
|
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
|
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/qustavo/sqlhooks/v2 v2.1.0 h1:54yBemHnGHp/7xgT+pxwmIlMSDNYKx5JW5dfRAiCZi0=
|
||||||
|
github.com/qustavo/sqlhooks/v2 v2.1.0/go.mod h1:aMREyKo7fOKTwiLuWPsaHRXEmtqG4yREztO0idF83AU=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
|
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||||
|
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8=
|
||||||
|
github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM=
|
||||||
|
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||||
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
|
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||||
|
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0 h1:BJee2iLkfRfl9lc7aFmBwkWxY/RI1RDdXepSF6y8TPE=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0/go.mod h1:DIzlHs3DRscCIBU3Y9YSzPfScwnYnzfnCd4g8zA7bZc=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 h1:EVSnY9JbEEW92bEkIYOVMw4q1WJxIAGoFTrtYOzWuRQ=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0/go.mod h1:Ea1N1QQryNXpCD0I1fdLibBAIpQuBkznMmkdKrapk1Y=
|
||||||
|
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
||||||
|
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg=
|
||||||
|
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||||
|
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||||
|
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||||
|
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||||
|
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||||
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||||
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
|
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||||
|
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||||
|
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||||
|
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||||
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
40
helper.go
Normal file
40
helper.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package unitel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetHubFromFiberContext(ctx *fiber.Ctx) *sentry.Hub {
|
||||||
|
return sentry.GetHubFromContext(ctx.UserContext())
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustGetHubFromContext(ctx context.Context) *sentry.Hub {
|
||||||
|
hub := sentry.GetHubFromContext(ctx)
|
||||||
|
if hub == nil {
|
||||||
|
panic("sentry hub not found in context")
|
||||||
|
}
|
||||||
|
|
||||||
|
return hub
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddBreadcrumbToFiberCtx(c *fiber.Ctx, breadcrumb *sentry.Breadcrumb, hint *sentry.BreadcrumbHint) {
|
||||||
|
if hub := GetHubFromFiberContext(c); hub != nil {
|
||||||
|
hub.AddBreadcrumb(breadcrumb, hint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddBreadcrumbToContext(c context.Context, breadcrumb *sentry.Breadcrumb) {
|
||||||
|
if hub := sentry.GetHubFromContext(c); hub != nil {
|
||||||
|
hub.AddBreadcrumb(breadcrumb, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Telemetry) Trace(ctx context.Context, op string, options []ConfigureSpanStartFunc, fn func(context.Context)) {
|
||||||
|
tx := o.StartSpan(ctx, op, op, options...)
|
||||||
|
defer tx.End()
|
||||||
|
|
||||||
|
fn(tx.Context())
|
||||||
|
}
|
98
pgx_tracing.go
Normal file
98
pgx_tracing.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package unitel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
pgx "github.com/jackc/pgx/v5/stdlib"
|
||||||
|
"github.com/qustavo/sqlhooks/v2"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
pgxClientID = libBase + "#TracedPGx"
|
||||||
|
TracedPGxDriverName = "pgx-traced"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tracedPgxHooks struct {
|
||||||
|
t *Telemetry
|
||||||
|
printQueries bool
|
||||||
|
tracer trace.Tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *tracedPgxHooks) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
|
||||||
|
cleanedQuery := strings.ReplaceAll(query, "\n", " ")
|
||||||
|
cleanedQuery = strings.ReplaceAll(cleanedQuery, "\t", " ")
|
||||||
|
cleanedQuery = strings.ReplaceAll(cleanedQuery, " ", " ")
|
||||||
|
cleanedQuery = strings.TrimSpace(cleanedQuery)
|
||||||
|
|
||||||
|
s := h.t.StartSpan(ctx, "db.sql.query", query)
|
||||||
|
s.AddAttributes(
|
||||||
|
attribute.String("db.system", "postgres"),
|
||||||
|
attribute.String("db.statement", cleanedQuery),
|
||||||
|
attribute.StringSlice("db.params", formatArgs(args)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if h.printQueries {
|
||||||
|
l := log.Trace()
|
||||||
|
|
||||||
|
for i, arg := range args {
|
||||||
|
l = l.Interface(fmt.Sprintf("arg%d", i), arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Msg(cleanedQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Context(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *tracedPgxHooks) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
|
||||||
|
if s := SpanFromContext(ctx); s != nil {
|
||||||
|
s.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Telemetry) RegisterTracedPGx(printQueries bool) {
|
||||||
|
tracer := t.tracerProvider.Tracer(pgxClientID, trace.WithInstrumentationVersion(libVersion))
|
||||||
|
|
||||||
|
sql.Register(TracedPGxDriverName, sqlhooks.Wrap(&pgx.Driver{}, &tracedPgxHooks{
|
||||||
|
printQueries: printQueries,
|
||||||
|
t: t,
|
||||||
|
tracer: tracer,
|
||||||
|
}))
|
||||||
|
|
||||||
|
log.Debug().Msgf("Registered %s driver", TracedPGxDriverName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatArgs(args []interface{}) []string {
|
||||||
|
formattedArgs := make([]string, len(args))
|
||||||
|
|
||||||
|
for i, arg := range args {
|
||||||
|
switch v := arg.(type) {
|
||||||
|
case int:
|
||||||
|
formattedArgs[i] = fmt.Sprint(v)
|
||||||
|
case int64:
|
||||||
|
formattedArgs[i] = fmt.Sprint(v)
|
||||||
|
case float64:
|
||||||
|
formattedArgs[i] = fmt.Sprint(v)
|
||||||
|
case string:
|
||||||
|
formattedArgs[i] = v
|
||||||
|
case []byte:
|
||||||
|
formattedArgs[i] = string(v)
|
||||||
|
case bool:
|
||||||
|
formattedArgs[i] = fmt.Sprint(v)
|
||||||
|
case fmt.Stringer:
|
||||||
|
formattedArgs[i] = v.String()
|
||||||
|
default:
|
||||||
|
formattedArgs[i] = fmt.Sprintf("%+v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedArgs
|
||||||
|
}
|
85
request_logger.go
Normal file
85
request_logger.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package unitel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
const requestBufferSize = 1000
|
||||||
|
|
||||||
|
func RequestLogger(l zerolog.Logger) fiber.Handler {
|
||||||
|
type request struct {
|
||||||
|
Method string
|
||||||
|
Path string
|
||||||
|
Status int
|
||||||
|
Duration time.Duration
|
||||||
|
OtelSpanID *trace.SpanID
|
||||||
|
SentrySpanID *sentry.SpanID
|
||||||
|
}
|
||||||
|
|
||||||
|
reqs := make(chan request, requestBufferSize)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
r := <-reqs
|
||||||
|
|
||||||
|
l2 := l.Trace().
|
||||||
|
Str("method", r.Method).
|
||||||
|
Str("path", r.Path).
|
||||||
|
Int("status", r.Status).
|
||||||
|
Dur("duration", r.Duration)
|
||||||
|
|
||||||
|
if r.OtelSpanID != nil {
|
||||||
|
l2 = l2.Str("otel_span_id", r.OtelSpanID.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.SentrySpanID != nil {
|
||||||
|
l2 = l2.Str("sentry_span_id", r.SentrySpanID.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
l2.Msg("request")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
var otelSpanId *trace.SpanID = nil
|
||||||
|
if spanId := trace.SpanContextFromContext(c.UserContext()).SpanID(); spanId.IsValid() {
|
||||||
|
otelSpanId = &spanId
|
||||||
|
}
|
||||||
|
|
||||||
|
var sentrySpanId *sentry.SpanID = nil
|
||||||
|
if span := sentry.SpanFromContext(c.UserContext()); span != nil {
|
||||||
|
sentrySpanId = &span.SpanID
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
err := c.Next()
|
||||||
|
duration := time.Since(start)
|
||||||
|
|
||||||
|
status := c.Response().StatusCode()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if e, ok := err.(*fiber.Error); ok {
|
||||||
|
status = e.Code
|
||||||
|
} else {
|
||||||
|
status = fiber.StatusInternalServerError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushing the request to the logger, so we don't block the requests
|
||||||
|
reqs <- request{
|
||||||
|
Method: c.Method(),
|
||||||
|
Path: c.Path(),
|
||||||
|
Status: status,
|
||||||
|
Duration: duration,
|
||||||
|
OtelSpanID: otelSpanId,
|
||||||
|
SentrySpanID: sentrySpanId,
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
240
telemetry.go
Normal file
240
telemetry.go
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
package unitel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||||
|
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
|
||||||
|
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||||
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
"go.opentelemetry.io/otel/trace/noop"
|
||||||
|
)
|
||||||
|
|
||||||
|
type otlpContextKey struct{}
|
||||||
|
|
||||||
|
type Opts struct {
|
||||||
|
Environment string
|
||||||
|
ServiceName string
|
||||||
|
OTLPEndpoint string
|
||||||
|
SentryDSN string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseOpts(serviceName string) Opts {
|
||||||
|
return Opts{
|
||||||
|
Environment: os.Getenv("ENVIRONMENT"),
|
||||||
|
ServiceName: serviceName,
|
||||||
|
OTLPEndpoint: os.Getenv("OTLP_ENDPOINT"),
|
||||||
|
SentryDSN: os.Getenv("SENTRY_DSN"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Telemetry struct {
|
||||||
|
opts Opts
|
||||||
|
propagator propagation.TextMapPropagator
|
||||||
|
tracerProvider trace.TracerProvider
|
||||||
|
meterProvider metric.MeterProvider
|
||||||
|
Tracer trace.Tracer
|
||||||
|
Meter metric.Meter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Telemetry) Shutdown(ctx context.Context) {
|
||||||
|
if tp, ok := o.tracerProvider.(*sdktrace.TracerProvider); ok {
|
||||||
|
if err := tp.Shutdown(ctx); err != nil {
|
||||||
|
log.Error().Err(err).Msg("failed to shutdown TracerProvider")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mp, ok := o.meterProvider.(*sdkmetric.MeterProvider); ok {
|
||||||
|
if err := mp.Shutdown(ctx); err != nil {
|
||||||
|
log.Error().Err(err).Msg("failed to stop MeterProvider")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.opts.SentryDSN != "" {
|
||||||
|
if ok := sentry.Flush(5 * time.Second); !ok {
|
||||||
|
log.Error().Msg("failed to flush Sentry")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetOnContext(o *Telemetry, ctx context.Context) context.Context {
|
||||||
|
return context.WithValue(ctx, otlpContextKey{}, o)
|
||||||
|
}
|
||||||
|
func FromContext(ctx context.Context) *Telemetry {
|
||||||
|
return ctx.Value(otlpContextKey{}).(*Telemetry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSpanExporter(ctx context.Context, opts Opts) (sdktrace.SpanExporter, error) {
|
||||||
|
return otlptracegrpc.New(
|
||||||
|
ctx,
|
||||||
|
otlptracegrpc.WithInsecure(),
|
||||||
|
otlptracegrpc.WithEndpoint(opts.OTLPEndpoint),
|
||||||
|
otlptracegrpc.WithReconnectionPeriod(time.Second*5),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMetricExporter(ctx context.Context, opts Opts) (*otlpmetricgrpc.Exporter, error) {
|
||||||
|
return otlpmetricgrpc.New(
|
||||||
|
ctx,
|
||||||
|
otlpmetricgrpc.WithInsecure(),
|
||||||
|
otlpmetricgrpc.WithEndpoint(opts.OTLPEndpoint),
|
||||||
|
otlpmetricgrpc.WithReconnectionPeriod(time.Second*5),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTextMapPropagator() propagation.TextMapPropagator {
|
||||||
|
return propagation.NewCompositeTextMapPropagator(
|
||||||
|
propagation.TraceContext{},
|
||||||
|
propagation.Baggage{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSampleRate(opts Opts) float64 {
|
||||||
|
switch opts.Environment {
|
||||||
|
case "development":
|
||||||
|
return 1.0
|
||||||
|
case "staging":
|
||||||
|
return 0.5
|
||||||
|
case "production":
|
||||||
|
return 0.2
|
||||||
|
default:
|
||||||
|
return 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initializeSentry(opts Opts) error {
|
||||||
|
if opts.SentryDSN == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return sentry.Init(sentry.ClientOptions{
|
||||||
|
Dsn: opts.SentryDSN,
|
||||||
|
Release: libVersion,
|
||||||
|
AttachStacktrace: true,
|
||||||
|
SampleRate: getSampleRate(opts),
|
||||||
|
EnableTracing: true,
|
||||||
|
TracesSampleRate: 1.0,
|
||||||
|
ProfilesSampleRate: 1.0,
|
||||||
|
Debug: false,
|
||||||
|
Environment: opts.Environment,
|
||||||
|
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
|
||||||
|
// Only send stuff to Sentry when it includes exceptions
|
||||||
|
if len(event.Exception) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return event
|
||||||
|
},
|
||||||
|
// BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
|
||||||
|
// // Only send stuff to Sentry when it includes exceptions
|
||||||
|
// if len(event.Exception) == 0 {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return event
|
||||||
|
// },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Initialize(opts Opts) (*Telemetry, error) {
|
||||||
|
propagator := newTextMapPropagator()
|
||||||
|
otel.SetTextMapPropagator(propagator)
|
||||||
|
|
||||||
|
r, err := resource.Merge(
|
||||||
|
resource.Default(),
|
||||||
|
resource.NewWithAttributes(
|
||||||
|
semconv.SchemaURL,
|
||||||
|
semconv.ServiceName(opts.ServiceName),
|
||||||
|
semconv.ServiceVersion(libVersion),
|
||||||
|
semconv.DeploymentEnvironment(opts.Environment),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var tracerProvider trace.TracerProvider
|
||||||
|
if opts.OTLPEndpoint == "" {
|
||||||
|
tracerProvider = noop.NewTracerProvider()
|
||||||
|
} else {
|
||||||
|
var exp sdktrace.SpanExporter
|
||||||
|
var err error
|
||||||
|
if opts.OTLPEndpoint == "console" {
|
||||||
|
exp, err = stdouttrace.New(stdouttrace.WithPrettyPrint())
|
||||||
|
} else {
|
||||||
|
exp, err = newSpanExporter(context.Background(), opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tracerProvider = sdktrace.NewTracerProvider(
|
||||||
|
sdktrace.WithBatcher(exp),
|
||||||
|
sdktrace.WithResource(r),
|
||||||
|
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(getSampleRate(opts)))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var metricProvider metric.MeterProvider
|
||||||
|
if opts.OTLPEndpoint == "" {
|
||||||
|
// noop meter provider
|
||||||
|
metricProvider = sdkmetric.NewMeterProvider(sdkmetric.WithResource(r))
|
||||||
|
} else {
|
||||||
|
var exp sdkmetric.Exporter
|
||||||
|
if opts.OTLPEndpoint == "console" {
|
||||||
|
exp, err = stdoutmetric.New(stdoutmetric.WithPrettyPrint())
|
||||||
|
} else {
|
||||||
|
exp, err = newMetricExporter(context.Background(), opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
metricProvider = sdkmetric.NewMeterProvider(
|
||||||
|
sdkmetric.WithResource(r),
|
||||||
|
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exp, sdkmetric.WithInterval(15*time.Second))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
otel.SetTracerProvider(tracerProvider)
|
||||||
|
otel.SetMeterProvider(metricProvider)
|
||||||
|
|
||||||
|
if err := initializeSentry(opts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Telemetry{
|
||||||
|
opts: opts,
|
||||||
|
propagator: propagator,
|
||||||
|
tracerProvider: tracerProvider,
|
||||||
|
meterProvider: metricProvider,
|
||||||
|
Tracer: tracerProvider.Tracer(opts.ServiceName),
|
||||||
|
Meter: metricProvider.Meter(opts.ServiceName),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Telemetry) HandleError(ctx context.Context, err error) {
|
||||||
|
if span := SpanFromContext(ctx); span != nil {
|
||||||
|
span.CaptureError(err)
|
||||||
|
} else {
|
||||||
|
if hub := sentry.CurrentHub().Clone(); hub != nil {
|
||||||
|
hub.CaptureException(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
otel.Handle(err)
|
||||||
|
}
|
||||||
|
}
|
78
traced_transport.go
Normal file
78
traced_transport.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package unitel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
const httpClientTransportClientID = libBase + "#HTTPTansport"
|
||||||
|
|
||||||
|
type HTTPTransport struct {
|
||||||
|
telemetry *Telemetry
|
||||||
|
forwardTrace bool
|
||||||
|
tracedRequestHeaders []string
|
||||||
|
tracedResponseHeaders []string
|
||||||
|
Transport http.RoundTripper
|
||||||
|
|
||||||
|
tracer trace.Tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Telemetry) NewTracedTransport(transport http.RoundTripper, forwardTrace bool, tracedRequestHeaders []string, tracedResponseHeaders []string) *HTTPTransport {
|
||||||
|
return &HTTPTransport{
|
||||||
|
telemetry: t,
|
||||||
|
forwardTrace: forwardTrace,
|
||||||
|
tracedRequestHeaders: tracedRequestHeaders,
|
||||||
|
tracedResponseHeaders: tracedResponseHeaders,
|
||||||
|
Transport: transport,
|
||||||
|
|
||||||
|
tracer: t.tracerProvider.Tracer(httpClientTransportClientID, trace.WithInstrumentationVersion(libVersion)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
span := t.telemetry.
|
||||||
|
StartSpan(req.Context(), "http.client", fmt.Sprintf("%s %s", req.Method, req.URL), WithOtelOptions(trace.WithSpanKind(trace.SpanKindClient))).
|
||||||
|
AddAttributes(
|
||||||
|
semconv.HTTPRequestMethodKey.String(req.Method),
|
||||||
|
semconv.URLFull(req.URL.String()),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
ctx := span.Context()
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
|
if t.forwardTrace {
|
||||||
|
t.telemetry.InjectIntoHeaders(ctx, req.Header)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, header := range t.tracedRequestHeaders {
|
||||||
|
if v := req.Header.Get(header); v != "" {
|
||||||
|
span.AddAttributes(attribute.String(fmt.Sprintf("http.request.header.%s", header), v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := t.Transport.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
if hub := sentry.GetHubFromContext(ctx); hub != nil {
|
||||||
|
hub.CaptureException(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
span.
|
||||||
|
AddAttributes(semconv.HTTPResponseStatusCode(resp.StatusCode)).
|
||||||
|
SetStatus(httpStatusToSpanStatus(resp.StatusCode, false), "")
|
||||||
|
|
||||||
|
for _, header := range t.tracedResponseHeaders {
|
||||||
|
if v := resp.Header.Get(header); v != "" {
|
||||||
|
span.AddAttributes(attribute.String(fmt.Sprintf("http.response.header.%s", header), v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
}
|
228
tracing.go
Normal file
228
tracing.go
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
package unitel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
type contextKey struct {
|
||||||
|
Key string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
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 {
|
||||||
|
otelStartOpts := make([]trace.SpanStartOption, 0)
|
||||||
|
sentryStartOpts := []sentry.SpanOption{sentry.WithTransactionName(name), sentry.WithDescription(name)}
|
||||||
|
|
||||||
|
for _, opt := range cfgs {
|
||||||
|
var otel []trace.SpanStartOption
|
||||||
|
var sentry []sentry.SpanOption
|
||||||
|
|
||||||
|
ctx, otel, sentry = opt(ctx)
|
||||||
|
|
||||||
|
otelStartOpts = append(otelStartOpts, otel...)
|
||||||
|
sentryStartOpts = append(sentryStartOpts, sentry...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var otelTracer trace.Tracer
|
||||||
|
if tracer, ok := ctx.Value(tracerContextKey).(trace.Tracer); ok && tracer != nil {
|
||||||
|
otelTracer = tracer
|
||||||
|
} else {
|
||||||
|
otelTracer = t.Tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, otelSpan := otelTracer.Start(ctx, name, otelStartOpts...)
|
||||||
|
sentrySpan := sentry.StartSpan(ctx, operation, sentryStartOpts...)
|
||||||
|
|
||||||
|
return &Span{
|
||||||
|
otel: otelSpan,
|
||||||
|
sentry: sentrySpan,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
func WithOtelOptions(opts ...trace.SpanStartOption) ConfigureSpanStartFunc {
|
||||||
|
return func(ctx context.Context) (context.Context, []trace.SpanStartOption, []sentry.SpanOption) {
|
||||||
|
return ctx, opts, []sentry.SpanOption{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Telemetry) InjectIntoHeaders(ctx context.Context, headers http.Header) {
|
||||||
|
t.propagator.Inject(ctx, propagation.HeaderCarrier(headers))
|
||||||
|
|
||||||
|
if sentrySpan := sentry.SpanFromContext(ctx); sentrySpan == nil {
|
||||||
|
// this should never happen...
|
||||||
|
log.Trace().Msgf("failed to inject Sentry span ID, Sentry span could not be extracted from the context.Context")
|
||||||
|
} else {
|
||||||
|
headers.Set("sentry-trace", sentrySpan.ToSentryTrace())
|
||||||
|
headers.Set("sentry-baggage", sentrySpan.ToBaggage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Telemetry) ContinueFromRequest(r *http.Request) ConfigureSpanStartFunc {
|
||||||
|
return func(ctx context.Context) (context.Context, []trace.SpanStartOption, []sentry.SpanOption) {
|
||||||
|
ctx = t.propagator.Extract(ctx, propagation.HeaderCarrier(r.Header))
|
||||||
|
|
||||||
|
return ctx, []trace.SpanStartOption{}, []sentry.SpanOption{sentry.ContinueFromRequest(r), sentry.WithTransactionSource(sentry.SourceURL)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithOtelTracer(tracer trace.Tracer) ConfigureSpanStartFunc {
|
||||||
|
return func(ctx context.Context) (context.Context, []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)
|
||||||
|
}
|
||||||
|
}
|
6
version.go
Normal file
6
version.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package unitel
|
||||||
|
|
||||||
|
const (
|
||||||
|
libBase = "git.devminer.xyz/devminer/unitel"
|
||||||
|
libVersion = "0.0.1"
|
||||||
|
)
|
Loading…
Reference in a new issue