From c6dbdd919887016fe4ed116de7693e0a23e46ac0 Mon Sep 17 00:00:00 2001 From: Anastas Dancha Date: Tue, 14 Jul 2026 14:34:34 -0400 Subject: [PATCH] feat(contrib/lovoo/goka): add goka integration with APM and DSM support Add a new contrib package instrumenting github.com/lovoo/goka with both Datadog APM distributed tracing and Data Streams Monitoring in a single integration. goka builds its consumer group on top of IBM/sarama but owns the sarama.ConsumerGroupHandler internally, so the contrib/IBM/sarama integration cannot attach. This package instruments through goka's public extension points instead, requiring no changes to goka: - NewTracer + WrapContext (goka.WithContextWrapper) sets the inbound DSM checkpoint and returns a context whose Emit/Loopback propagate the active consume span and a DSM outbound checkpoint into the emitted headers. - WrapCallback runs each input message inside a kafka.consume span, finishing it around the callback (panics recovered and error-tagged) and recording the DSM commit offset on success, honoring ctx.DeferCommit. - No kafka.produce span is created: goka.Context.Emit is asynchronous with no completion handle, so trace continuity is preserved by propagating the consume span through the outbound headers instead. - EmitHeaders covers the standalone goka.Emitter path. - WithService, WithDataStreams, and WithLoopSuffix options; a single header carrier satisfies both the tracer and datastreams TextMap interfaces. Register PackageLovooGoka in instrumentation/packages.go and contribIntegrations in ddtrace/tracer/option.go. Includes unit tests (carrier round-trip, DSM no-op when disabled, span parent/child linkage, error tagging, emit/loopback injection, DeferCommit, context delegation) and an INTEGRATION-gated end-to-end test. Co-Authored-By: Claude Opus 4.8 --- contrib/lovoo/goka/carrier.go | 49 +++ contrib/lovoo/goka/example_test.go | 44 ++ contrib/lovoo/goka/go.mod | 103 +++++ contrib/lovoo/goka/go.sum | 398 +++++++++++++++++ contrib/lovoo/goka/goka.go | 406 +++++++++++++++++ contrib/lovoo/goka/goka_integration_test.go | 117 +++++ contrib/lovoo/goka/goka_test.go | 454 ++++++++++++++++++++ contrib/lovoo/goka/option.go | 64 +++ ddtrace/tracer/option.go | 1 + go.work | 7 +- go.work.sum | 12 +- instrumentation/packages.go | 19 + 12 files changed, 1665 insertions(+), 9 deletions(-) create mode 100644 contrib/lovoo/goka/carrier.go create mode 100644 contrib/lovoo/goka/example_test.go create mode 100644 contrib/lovoo/goka/go.mod create mode 100644 contrib/lovoo/goka/go.sum create mode 100644 contrib/lovoo/goka/goka.go create mode 100644 contrib/lovoo/goka/goka_integration_test.go create mode 100644 contrib/lovoo/goka/goka_test.go create mode 100644 contrib/lovoo/goka/option.go diff --git a/contrib/lovoo/goka/carrier.go b/contrib/lovoo/goka/carrier.go new file mode 100644 index 00000000000..7210ff62afd --- /dev/null +++ b/contrib/lovoo/goka/carrier.go @@ -0,0 +1,49 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package goka + +import ( + "github.com/lovoo/goka" + + "github.com/DataDog/dd-trace-go/v2/datastreams" + "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer" +) + +// gokaHeadersCarrier adapts goka.Headers (map[string][]byte) to the text-map +// interfaces used by both APM trace propagation (tracer.Extract/Inject) and Data +// Streams Monitoring (datastreams.ExtractFromBase64Carrier/InjectToBase64Carrier). +// A single carrier serves both because the four interfaces share the same method +// shapes. The carrier shares the underlying map, so Set mutates the goka.Headers +// it wraps. +type gokaHeadersCarrier goka.Headers + +var ( + _ tracer.TextMapReader = gokaHeadersCarrier(nil) + _ tracer.TextMapWriter = gokaHeadersCarrier(nil) + _ datastreams.TextMapReader = gokaHeadersCarrier(nil) + _ datastreams.TextMapWriter = gokaHeadersCarrier(nil) +) + +// ForeachKey implements tracer.TextMapReader and datastreams.TextMapReader. +func (c gokaHeadersCarrier) ForeachKey(handler func(key, val string) error) error { + for k, v := range c { + if err := handler(k, string(v)); err != nil { + return err + } + } + return nil +} + +// Set implements tracer.TextMapWriter and datastreams.TextMapWriter. +func (c gokaHeadersCarrier) Set(key, val string) { + c[key] = []byte(val) +} + +// ExtractSpanContext extracts the span context propagated in a goka message's +// headers, allowing callers to create manual child spans. +func ExtractSpanContext(headers goka.Headers) (*tracer.SpanContext, error) { + return tracer.Extract(gokaHeadersCarrier(headers)) +} diff --git a/contrib/lovoo/goka/example_test.go b/contrib/lovoo/goka/example_test.go new file mode 100644 index 00000000000..20577061bbc --- /dev/null +++ b/contrib/lovoo/goka/example_test.go @@ -0,0 +1,44 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package goka_test + +import ( + "log" + + gokatrace "github.com/DataDog/dd-trace-go/contrib/lovoo/goka/v2" + + "github.com/lovoo/goka" + "github.com/lovoo/goka/codec" +) + +func Example() { + tr := gokatrace.NewTracer( + gokatrace.WithService("orders-processor"), + gokatrace.WithDataStreams(), + ) + + // handle processes an input message. Because the callback is wrapped, it runs + // inside a "kafka.consume" span, and ctx.Emit continues the trace downstream. + handle := func(ctx goka.Context, msg any) { + ctx.Emit("orders-enriched", ctx.Key(), msg) + } + + g := goka.DefineGroup( + "orders", + goka.Input("orders", new(codec.String), tr.WrapCallback(handle)), + goka.Output("orders-enriched", new(codec.String)), + ) + + p, err := goka.NewProcessor( + []string{"localhost:9092"}, + g, + goka.WithContextWrapper(tr.WrapContext), + ) + if err != nil { + log.Fatal(err) + } + _ = p +} diff --git a/contrib/lovoo/goka/go.mod b/contrib/lovoo/goka/go.mod new file mode 100644 index 00000000000..ba65cf322ab --- /dev/null +++ b/contrib/lovoo/goka/go.mod @@ -0,0 +1,103 @@ +module github.com/DataDog/dd-trace-go/contrib/lovoo/goka/v2 + +go 1.25.0 + +require ( + github.com/DataDog/dd-trace-go/v2 v2.11.0-dev + github.com/lovoo/goka v1.1.8 + github.com/stretchr/testify v1.11.1 +) + +require ( + github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.79.0 // indirect + github.com/DataDog/datadog-agent/pkg/obfuscate v0.79.0 // indirect + github.com/DataDog/datadog-agent/pkg/opentelemetry-mapping-go/otlp/attributes v0.79.0 // indirect + github.com/DataDog/datadog-agent/pkg/proto v0.79.0 // indirect + github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.79.0 // indirect + github.com/DataDog/datadog-agent/pkg/trace v0.79.0 // indirect + github.com/DataDog/datadog-agent/pkg/trace/log v0.79.0 // indirect + github.com/DataDog/datadog-agent/pkg/trace/stats v0.79.0 // indirect + github.com/DataDog/datadog-agent/pkg/trace/traceutil v0.79.0 // indirect + github.com/DataDog/datadog-go/v5 v5.8.3 // indirect + github.com/DataDog/go-libddwaf/v5 v5.0.0 // indirect + github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20260217080614-b0f4edc38a6d // indirect + github.com/DataDog/go-sqllexer v0.2.1 // indirect + github.com/DataDog/go-tuf v1.1.1-0.5.2 // indirect + github.com/DataDog/sketches-go v1.4.8 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Shopify/sarama v1.37.2 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/eapache/go-resiliency v1.3.0 // indirect + github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect + github.com/eapache/queue v1.1.0 // indirect + github.com/ebitengine/purego v0.10.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-stack/stack v1.8.1 // indirect + github.com/golang/mock v1.7.0-rc.1 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/go-version v1.9.0 // indirect + github.com/jcmturner/aescts/v2 v2.0.0 // indirect + github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect + github.com/jcmturner/gofork v1.7.6 // indirect + github.com/jcmturner/gokrb5/v8 v8.4.3 // indirect + github.com/jcmturner/rpc/v2 v2.0.3 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.18.6 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/linkdata/deadlock v0.5.5 // indirect + github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect + github.com/minio/simdjson-go v0.4.5 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/outcaste-io/ristretto v0.2.3 // indirect + github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 // indirect + github.com/philhofer/fwd v1.2.0 // indirect + github.com/pierrec/lz4/v4 v4.1.17 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/puzpuzpuz/xsync/v4 v4.5.0 // indirect + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect + github.com/shirou/gopsutil/v4 v4.26.3 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect + github.com/tinylib/msgp v1.6.3 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect + github.com/trailofbits/go-mutexasserts v0.0.0-20250514102930-c1f3d2e37561 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/collector/component v1.56.0 // indirect + go.opentelemetry.io/collector/featuregate v1.56.0 // indirect + go.opentelemetry.io/collector/pdata v1.56.0 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.150.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.1 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.52.0 // indirect + golang.org/x/exp v0.0.0-20260209203927-2842357ff358 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.55.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/time v0.15.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/DataDog/dd-trace-go/v2 => ../../.. diff --git a/contrib/lovoo/goka/go.sum b/contrib/lovoo/goka/go.sum new file mode 100644 index 00000000000..6f72de8b420 --- /dev/null +++ b/contrib/lovoo/goka/go.sum @@ -0,0 +1,398 @@ +github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.79.0 h1:VZlYzVCzCsY3TsEdDCFHklzL2k2C3OeQvgDhU2gllg0= +github.com/DataDog/datadog-agent/comp/core/tagger/origindetection v0.79.0/go.mod h1:dz3fkTBxANLLLigy9KQ5jhNZ28nA4dI9AxuLoGrmR0o= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.79.0 h1:PeYLm/gZQM7iKXOG4NgbKo6ZfpG73/+f8TSzmCUtA6g= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.79.0/go.mod h1:1Us875zf/kICMHUcxHE5HNo1rCQlvLBmo1tfZEuzJVw= +github.com/DataDog/datadog-agent/pkg/opentelemetry-mapping-go/otlp/attributes v0.79.0 h1:J4rWJpsGnGAXUtcdr9Vq5cMN2ccrf+VhUJnHuRuc1zg= +github.com/DataDog/datadog-agent/pkg/opentelemetry-mapping-go/otlp/attributes v0.79.0/go.mod h1:GqMFabP1nLfle3seDNJYcj/wfCSKbAcUGFkkEasd7JE= +github.com/DataDog/datadog-agent/pkg/proto v0.79.0 h1:8QbV9DFIWQn16eUHJ6WGxcdnk+0zKcVD3Oug/1Pz8q4= +github.com/DataDog/datadog-agent/pkg/proto v0.79.0/go.mod h1:cHqU71Z8zkEt79uOK8dz534jbpDlXQyHgoLQRuaHpMw= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.79.0 h1:7oCAk0/aLP+0xJqw+xSDb0WN7g9Yramx6uOltgICplo= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.79.0/go.mod h1:kFG+DDnQHvaQ1EUlhKmVRny7pYXmWhpQVWCM48uWP8I= +github.com/DataDog/datadog-agent/pkg/trace v0.79.0 h1:gvqATgSGFbIWTm+v7TapIHvR8rDOJvE7eZUgxx6Bvhk= +github.com/DataDog/datadog-agent/pkg/trace v0.79.0/go.mod h1:3oEA+txSBo1/jH7wIi5GhPRuMki7PSl5r/YM8cubtxs= +github.com/DataDog/datadog-agent/pkg/trace/log v0.79.0 h1:MQACMCllIqhkq1Z45WU4ymKQBjVG2zPWpQ1gerf8CkM= +github.com/DataDog/datadog-agent/pkg/trace/log v0.79.0/go.mod h1:7JxAuxV2tRfr9Si8C9W+dF5qFU0x0K1hi07nrn9xIAU= +github.com/DataDog/datadog-agent/pkg/trace/stats v0.79.0 h1:MUJOjHKp0xt08CWoiZwwLhEyW2+9KspcjOZMLclmGT8= +github.com/DataDog/datadog-agent/pkg/trace/stats v0.79.0/go.mod h1:/pNMg3r6LPDz1PBNK60/3FqhKPh6J19eNO2newT6bwU= +github.com/DataDog/datadog-agent/pkg/trace/traceutil v0.79.0 h1:NM/Fw/ku/Hjj26l8Y2Jtojd7BEI+C7sb5jikAPBX6lE= +github.com/DataDog/datadog-agent/pkg/trace/traceutil v0.79.0/go.mod h1:+LtS5cUALIDB7lT3G3w/pfpHSgxPQ/7z1Ub2UT/BW5s= +github.com/DataDog/datadog-go/v5 v5.8.3 h1:s58CUJ9s8lezjhTNJO/SxkPBv2qZjS3ktpRSqGF5n0s= +github.com/DataDog/datadog-go/v5 v5.8.3/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= +github.com/DataDog/go-libddwaf/v5 v5.0.0 h1:uPQfIVPH9T8jLSplAdfETAHwL3iRLabP6JxRTH8NL7Y= +github.com/DataDog/go-libddwaf/v5 v5.0.0/go.mod h1:/UzdsIsO8qk8fmbqNnk6Na7MsaNvU2R7SlBRHEgSIOQ= +github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20260217080614-b0f4edc38a6d h1:cH9Bm0tJ8FEQbA4FRi0iRm7Zr/5Lata/Or31c+Dth0E= +github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20260217080614-b0f4edc38a6d/go.mod h1:yDuvU+Ak1TKwgd4K8DNcpJmUrrK8ONLkBMGNAppmBRk= +github.com/DataDog/go-sqllexer v0.2.1 h1:al1RMRTxPoAa1P/RTu7D5yVXC6wCLwTRYLVAVH5MLjQ= +github.com/DataDog/go-sqllexer v0.2.1/go.mod h1:3xTFXBU69vUikYpESggScvC0RKYA7ZIdVrIkLwUOWdE= +github.com/DataDog/go-tuf v1.1.1-0.5.2 h1:YWvghV4ZvrQsPcUw8IOUMSDpqc3W5ruOIC+KJxPknv0= +github.com/DataDog/go-tuf v1.1.1-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= +github.com/DataDog/sketches-go v1.4.8 h1:pFk9BNn+Rzv8IMIoPUttoOpOr3bJOqU3P6EP5wK+Lv8= +github.com/DataDog/sketches-go v1.4.8/go.mod h1:a/wjRUqzqtGS8qRHRPDCs4EAQfmvPDZGDlMIF5mxXOE= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Shopify/sarama v1.37.2 h1:LoBbU0yJPte0cE5TZCGdlzZRmMgMtZU/XgnUKZg9Cv4= +github.com/Shopify/sarama v1.37.2/go.mod h1:Nxye/E+YPru//Bpaorfhc3JsSGYwCaDDj+R4bK52U5o= +github.com/Shopify/toxiproxy/v2 v2.5.0 h1:i4LPT+qrSlKNtQf5QliVjdP08GyAH8+BUIc9gT0eahc= +github.com/Shopify/toxiproxy/v2 v2.5.0/go.mod h1:yhM2epWtAmel9CB8r2+L+PCmhH6yH2pITaPAo7jxJl0= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1c8PVE1PubbNx3mjUr09OqWGCs= +github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eapache/go-resiliency v1.3.0 h1:RRL0nge+cWGlxXbUzJ7yMcq6w2XBEr19dCN6HECGaT0= +github.com/eapache/go-resiliency v1.3.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/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-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +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/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.3 h1:iTonLeSJOn7MVUtyMT+arAn5AKAPrkilzhGw8wE/Tq8= +github.com/jcmturner/gokrb5/v8 v8.4.3/go.mod h1:dqRwJGXznQrzw6cWmyo6kH+E7jksEQG/CyVWsJEsJO0= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/linkdata/deadlock v0.5.5 h1:d6O+rzEqasSfamGDA8u7bjtaq7hOX8Ha4Zn36Wxrkvo= +github.com/linkdata/deadlock v0.5.5/go.mod h1:tXb28stzAD3trzEEK0UJWC+rZKuobCoPktPYzebb1u0= +github.com/lovoo/goka v1.1.8 h1:NG/GkMXmfTxN9C7aap446M1GzVhKsvsoPA4/HvwYb1M= +github.com/lovoo/goka v1.1.8/go.mod h1:DryGtfaTY4pXEmtoFFGcLkOZr2vxWHeqrJhTPK5ul1M= +github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM= +github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/minio/simdjson-go v0.4.5 h1:r4IQwjRGmWCQ2VeMc7fGiilu1z5du0gJ/I/FsKwgo5A= +github.com/minio/simdjson-go v0.4.5/go.mod h1:eoNz0DcLQRyEDeaPr4Ru6JpjlZPzbA0IodxVJk8lO8E= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= +github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= +github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 h1:rh2lKw/P/EqHa724vYH2+VVQ1YnW4u6EOXl0PMAovZE= +github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= +github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= +github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc= +github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/puzpuzpuz/xsync/v4 v4.5.0 h1:vOSWu6b57/emh+L/Cw0BeQfvxa/cogFywXHeGUxQxAg= +github.com/puzpuzpuz/xsync/v4 v4.5.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 h1:4+LEVOB87y175cLJC/mbsgKmoDOjrBldtXvioEy96WY= +github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3/go.mod h1:vl5+MqJ1nBINuSsUI2mGgH79UweUT/B5Fy8857PqyyI= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/secure-systems-lab/go-securesystemslib v0.10.0 h1:l+H5ErcW0PAehBNrBxoGv1jjNpGYdZ9RcheFkB2WI14= +github.com/secure-systems-lab/go-securesystemslib v0.10.0/go.mod h1:MRKONWmRoFzPNQ9USRF9i1mc7MvAVvF1LlW8X5VWDvk= +github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc= +github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= +github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= +github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s= +github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= +github.com/trailofbits/go-mutexasserts v0.0.0-20250514102930-c1f3d2e37561 h1:qqa3P9AtNn6RMe90l/lxd3eJWnIRxjI4eb5Rx8xqCLA= +github.com/trailofbits/go-mutexasserts v0.0.0-20250514102930-c1f3d2e37561/go.mod h1:GA3+Mq3kt3tYAfM0WZCu7ofy+GW9PuGysHfhr+6JX7s= +github.com/vmihailenco/msgpack/v4 v4.3.13 h1:A2wsiTbvp63ilDaWmsk2wjx6xZdxQOvpiNlKBGKKXKI= +github.com/vmihailenco/msgpack/v4 v4.3.13/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= +github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/collector/component v1.56.0 h1:fOCs36Dxg95w2RQCVI2i5IsHc5IbZ99vmbipK9FM7pQ= +go.opentelemetry.io/collector/component v1.56.0/go.mod h1:MkAjcSc2T0BiYf/uARZdTlfnxBB9BwmvY6v08D+qeY4= +go.opentelemetry.io/collector/component/componenttest v0.150.0 h1:pT7avT/Pfn8tAOOlmFWgtOaGvXY0nxSwrivnhOl/LH0= +go.opentelemetry.io/collector/component/componenttest v0.150.0/go.mod h1:D+7mfbcZ/TfneQRZNtVwH+/YKQdalc1joa9NhH1BGPk= +go.opentelemetry.io/collector/featuregate v1.56.0 h1:NjcbOZkdCSXddAJmFLdO+pv1gmAgrU6sC5PBga2KlKI= +go.opentelemetry.io/collector/featuregate v1.56.0/go.mod h1:4ga1QBMPEejXXmpyJS8lmaRpknJ3Lb9Bvk6e420bUFU= +go.opentelemetry.io/collector/internal/testutil v0.150.0 h1:J4PLQGPfbLVaL5eI1aMc0m0TMixV9wzBhNhoHU00J0I= +go.opentelemetry.io/collector/internal/testutil v0.150.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE= +go.opentelemetry.io/collector/pdata v1.56.0 h1:W+QAfN2Iz8SNss1T5JNzRWFnw+7oP1vXBQH9ZuOJkXY= +go.opentelemetry.io/collector/pdata v1.56.0/go.mod h1:usR9utboXufbD1rp1oJy+3smQXXpZ+CsI3WN7QsiOs0= +go.opentelemetry.io/collector/pdata/pprofile v0.150.0 h1:Ae+FxmYXDdcqeLqIAdNSO3YGxco7RS2mIMTdjvavfso= +go.opentelemetry.io/collector/pdata/pprofile v0.150.0/go.mod h1:tEBeGysY/LpIh39NLoQQl3qmUBOF9wyH5p/fmn7smzM= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM= +go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk= +go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ= +go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U= +go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0= +go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/exp v0.0.0-20260209203927-2842357ff358 h1:kpfSV7uLwKJbFSEgNhWzGSL47NDSF/5pYYQw1V0ub6c= +golang.org/x/exp v0.0.0-20260209203927-2842357ff358/go.mod h1:R3t0oliuryB5eenPWl3rrQxwnNM3WTwnsRZZiXLAAW8= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +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/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +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= diff --git a/contrib/lovoo/goka/goka.go b/contrib/lovoo/goka/goka.go new file mode 100644 index 00000000000..d96c120dd55 --- /dev/null +++ b/contrib/lovoo/goka/goka.go @@ -0,0 +1,406 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +// Package goka provides Datadog APM tracing and Data Streams Monitoring (DSM) +// for the goka Kafka-streams library (github.com/lovoo/goka). +// +// goka does not expose a seam that the IBM/sarama integration can wrap (it uses +// its own sarama consumer-group handler internally), so this package instruments +// through goka's public extension points instead: WithContextWrapper on the +// consume side and per-emit headers on the produce side. +// +// Wire it into a processor by registering the context wrapper and wrapping each +// input callback: +// +// tr := goka.NewTracer(goka.WithService("orders"), goka.WithDataStreams()) +// p, err := goka.NewProcessor(brokers, goka.DefineGroup(group, +// goka.Input(topic, codec, tr.WrapCallback(handle)), +// ), goka.WithContextWrapper(tr.WrapContext)) +package goka + +import ( + "context" + "fmt" + "time" + + "github.com/lovoo/goka" + + "github.com/DataDog/dd-trace-go/v2/datastreams" + "github.com/DataDog/dd-trace-go/v2/datastreams/options" + "github.com/DataDog/dd-trace-go/v2/ddtrace/ext" + "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer" + "github.com/DataDog/dd-trace-go/v2/instrumentation" +) + +const componentName = "lovoo/goka" + +var instr *instrumentation.Instrumentation + +func init() { + instr = instrumentation.Load(instrumentation.PackageLovooGoka) +} + +// Tracer instruments a goka processor. It is safe for concurrent use: the goka +// context wrapper runs concurrently across partitions, and each invocation +// builds its own per-message state. +type Tracer struct { + cfg config +} + +// NewTracer returns a Tracer configured with the given options. +func NewTracer(opts ...Option) *Tracer { + cfg := config{} + defaults(&cfg) + for _, o := range opts { + o.apply(&cfg) + } + return &Tracer{cfg: cfg} +} + +// WrapContext is a goka.ContextWrapper (func(goka.Context) goka.Context) intended +// for goka.WithContextWrapper. It sets the inbound DSM checkpoint for the message +// and returns a context whose Emit/Loopback inject APM trace context and DSM +// pathway into outbound headers. +// +// The APM consume span is started by WrapCallback, not here, because +// WithContextWrapper has no hook for finishing a span once the callback returns. +// +// Note: goka also invokes the context wrapper for VisitValues visit callbacks, +// whose Topic() is the visit name rather than a Kafka topic. With DSM enabled, +// such visits produce an inbound checkpoint keyed on the visit name; ignore those +// nodes in the Data Streams Monitoring graph. +func (tr *Tracer) WrapContext(ctx goka.Context) goka.Context { + tc := &tracedContext{gctx: ctx, tr: tr} + if tr.cfg.dataStreamsEnabled { + tc.dsmCtx = tr.setConsumeCheckpoint(ctx) + } + return tc +} + +// WrapCallback wraps a goka.ProcessCallback so that each input message is +// processed inside a "kafka.consume" span. The span is finished when the callback +// returns; any panic that unwinds the callback (Context.Fail, an internal goka +// failure, or a bug in the handler) is recovered so the span is tagged with the +// error and then re-raised so goka still shuts the processor down. +// +// Wrap every input callback whose messages you want traced, and register +// WrapContext via goka.WithContextWrapper so emitted messages continue the trace. +// +// DSM commit-offset tracking is also performed here on successful processing (or, +// if the handler calls ctx.DeferCommit, when that deferred commit succeeds), so +// an Input whose callback is not wrapped reports no committed offset to Data +// Streams Monitoring. Wrap every input callback for which you want DSM lag. +// +// Because goka.Context.Emit is asynchronous, the span is finished when the +// callback returns and does not reflect the outcome of emits that resolve later; +// an emit that fails asynchronously (and shuts the partition down) is not tagged +// on the span. +func (tr *Tracer) WrapCallback(cb goka.ProcessCallback) goka.ProcessCallback { + return func(ctx goka.Context, msg any) { + tc, ok := ctx.(*tracedContext) + if !ok { + // WrapContext was not registered; process without a span rather than panic. + cb(ctx, msg) + return + } + finish := tc.startConsumeSpan() + defer finish() + cb(tc, msg) + } +} + +// EmitHeaders returns headers carrying APM trace context and a DSM outbound +// checkpoint for a message produced to topic. Use it with a standalone +// goka.Emitter (which has no processor context): +// +// headers := tr.EmitHeaders(ctx, topic) +// emitter.EmitSyncWithHeaders(key, value, headers) +// +// The span is taken from ctx if one is active there. When neither tracing nor DSM +// is enabled the returned headers are empty. +func (tr *Tracer) EmitHeaders(ctx context.Context, topic string) goka.Headers { + if ctx == nil { + ctx = context.Background() + } + var span *tracer.Span + if s, ok := tracer.SpanFromContext(ctx); ok { + span = s + } + return tr.outboundHeaders(span, ctx, topic, 0) +} + +func (tr *Tracer) setConsumeCheckpoint(gctx goka.Context) context.Context { + topic := string(gctx.Topic()) + edges := []string{"direction:in", "topic:" + topic, "type:kafka"} + group := string(gctx.Group()) + if group != "" { + edges = append(edges, "group:"+group) + } + // goka's Context exposes neither the raw message value nor its encoded size + // at this hook (Value() returns the group-table state, not the input body), + // so the payload size counts only the key and header bytes we can observe. + params := options.CheckpointParams{ + PayloadSize: int64(len(gctx.Key())) + headersSize(gctx.Headers()), + } + ctx, ok := tracer.SetDataStreamsCheckpointWithParams( + datastreams.ExtractFromBase64Carrier(gctx.Context(), gokaHeadersCarrier(gctx.Headers())), + params, + edges..., + ) + if !ok { + return nil + } + return ctx +} + +// trackCommit records the consumed offset for DSM lag tracking. It runs only +// after the callback returns without failing, mirroring goka, which commits the +// offset after successful processing; a message that fails is reprocessed and +// must not be counted as committed. +func (tc *tracedContext) trackCommit() { + if !tc.tr.cfg.dataStreamsEnabled { + return + } + group := string(tc.gctx.Group()) + if group == "" { + return + } + tracer.TrackKafkaCommitOffset(group, string(tc.gctx.Topic()), tc.gctx.Partition(), tc.gctx.Offset()) +} + +// injectProduceCheckpoint sets a DSM outbound checkpoint on base and injects the +// resulting pathway into headers. base must be non-nil and should carry the +// inbound pathway so the produce checkpoint chains onto it; callers own that +// fallback (produce uses the consume context, EmitHeaders the supplied context). +func (tr *Tracer) injectProduceCheckpoint(base context.Context, topic string, headers goka.Headers, payloadSize int64) { + if !tr.cfg.dataStreamsEnabled { + return + } + ctx, ok := tracer.SetDataStreamsCheckpointWithParams( + base, + options.CheckpointParams{PayloadSize: payloadSize}, + "direction:out", "topic:"+topic, "type:kafka", + ) + if !ok { + return + } + datastreams.InjectToBase64Carrier(ctx, gokaHeadersCarrier(headers)) +} + +// headersSize returns the total byte size of the key and value pairs in h. +func headersSize(h goka.Headers) int64 { + var n int64 + for k, v := range h { + n += int64(len(k) + len(v)) + } + return n +} + +// valueSize returns the byte size of an emitted value when it is a raw []byte or +// string. goka encodes other values with a codec we cannot see here, so their +// size is reported as 0. +func valueSize(v any) int64 { + switch t := v.(type) { + case []byte: + return int64(len(t)) + case string: + return int64(len(t)) + default: + return 0 + } +} + +// tracedContext wraps a goka.Context, overriding Emit/Loopback/Context/Fail to +// carry Datadog trace and DSM propagation. All other methods delegate to gctx. +type tracedContext struct { + gctx goka.Context + tr *Tracer + + dsmCtx context.Context // inbound DSM pathway; base for outbound checkpoints + span *tracer.Span // APM consume span, set by startConsumeSpan + tracedCtx context.Context // span context returned by Context() + spanErr error // recorded by Fail for the deferred span finish + commitDeferred bool // set by DeferCommit; offset tracking moves to its callback +} + +func (tc *tracedContext) startConsumeSpan() func() { + cfg := tc.tr.cfg + gctx := tc.gctx + topic := string(gctx.Topic()) + opts := []tracer.StartSpanOption{ + instrumentation.ServiceNameWithSource(cfg.consumerServiceName, cfg.serviceSource), + tracer.ResourceName("Consume Topic " + topic), + tracer.SpanType(ext.SpanTypeMessageConsumer), + tracer.Tag(ext.MessagingKafkaPartition, gctx.Partition()), + tracer.Tag("offset", gctx.Offset()), + tracer.Tag(ext.Component, componentName), + tracer.Tag(ext.SpanKind, ext.SpanKindConsumer), + tracer.Tag(ext.MessagingSystem, ext.MessagingSystemKafka), + tracer.Tag(ext.MessagingDestinationName, topic), + tracer.Measured(), + } + if group := gctx.Group(); group != "" { + opts = append(opts, tracer.Tag("kafka.group", string(group))) + } + carrier := gokaHeadersCarrier(gctx.Headers()) + if parent, err := tracer.Extract(carrier); err == nil && parent != nil { + if parent.SpanLinks() != nil { + opts = append(opts, tracer.WithSpanLinks(parent.SpanLinks())) + } + opts = append(opts, tracer.ChildOf(parent)) + } + + span, spanCtx := tracer.StartSpanFromContext(gctx.Context(), cfg.consumerSpanName, opts...) + tc.span = span + tc.tracedCtx = spanCtx + + return func() { + // A failure unwinds the callback by panicking: goka.Context.Fail (via our + // override, which sets spanErr) or an internal goka failure / plain panic + // that never reaches spanErr. Recover so the span records the error either + // way, then re-panic so goka still shuts the processor down. + if r := recover(); r != nil { + err := tc.spanErr + if err == nil { + if e, ok := r.(error); ok { + err = e + } else { + err = fmt.Errorf("goka: message processing panicked: %v", r) + } + } + span.Finish(tracer.WithError(err)) + panic(r) + } + if tc.spanErr != nil { + span.Finish(tracer.WithError(tc.spanErr)) + return + } + // When the handler deferred the commit, offset tracking is handled by the + // DeferCommit callback instead, so it isn't double-counted here. + if !tc.commitDeferred { + tc.trackCommit() + } + span.Finish() + } +} + +// outboundHeaders builds the headers to attach to a message emitted to topic: +// span (the active consume span) is injected for APM trace propagation so the +// downstream consumer continues the trace, and a DSM outbound checkpoint is +// chained onto base. It is the single header builder shared by processor emits +// and the standalone EmitHeaders so the two cannot drift apart. +// +// No "kafka.produce" span is created: goka.Context.Emit is asynchronous and +// returns no handle, so a produce span could never be finished on the emit's +// actual completion or tagged with an async delivery error. Trace continuity is +// preserved by propagating the consume span through the headers instead. +func (tr *Tracer) outboundHeaders(span *tracer.Span, base context.Context, topic string, payloadSize int64) goka.Headers { + headers := goka.Headers{} + if span != nil { + tracer.Inject(span.Context(), gokaHeadersCarrier(headers)) + } + tr.injectProduceCheckpoint(base, topic, headers, payloadSize) + return headers +} + +// produce builds the outbound headers for an emit to topic (propagating the +// consume span and a DSM outbound checkpoint) and prepends them as a +// ContextOption before delegating. It centralises header injection and DSM +// checkpointing so Emit and Loopback share one path. Caller-supplied +// ContextOptions win on header-key collisions (goka merges per-emit headers over +// the ones we prepend). +func (tc *tracedContext) produce(topic, key string, value any, opts []goka.ContextOption, emit func(opts []goka.ContextOption)) { + base := tc.dsmCtx + if base == nil { + base = tc.gctx.Context() + } + size := int64(len(key)) + valueSize(value) + if headers := tc.tr.outboundHeaders(tc.span, base, topic, size); len(headers) > 0 { + opts = append([]goka.ContextOption{goka.WithCtxEmitHeaders(headers)}, opts...) + } + emit(opts) +} + +// Emit injects trace/DSM headers, then delegates to the wrapped context. +func (tc *tracedContext) Emit(topic goka.Stream, key string, value any, opts ...goka.ContextOption) { + tc.produce(string(topic), key, value, opts, func(opts []goka.ContextOption) { + tc.gctx.Emit(topic, key, value, opts...) + }) +} + +// Loopback injects trace/DSM headers for the group's loop stream, then delegates. +// +// The loop topic is derived from cfg.loopSuffix (default "-loop"); if the +// application changed goka's suffix via goka.SetLoopSuffix, pass the matching +// WithLoopSuffix or the DSM loop edge and span tags will name the wrong topic. +func (tc *tracedContext) Loopback(key string, value any, opts ...goka.ContextOption) { + tc.produce(tc.loopTopic(), key, value, opts, func(opts []goka.ContextOption) { + tc.gctx.Loopback(key, value, opts...) + }) +} + +// loopTopic returns the name of the group's loop stream, used to tag the +// Loopback DSM checkpoint. It mirrors goka's own "" naming. +func (tc *tracedContext) loopTopic() string { + return string(tc.gctx.Group()) + tc.tr.cfg.loopSuffix +} + +// Fail records the error for the deferred span finish, then delegates (which +// panics to unwind the callback). +func (tc *tracedContext) Fail(err error) { + tc.spanErr = err + tc.gctx.Fail(err) +} + +// Context returns the span context so downstream work becomes a child of the +// consume span, falling back to the wrapped context's when no span is active. +func (tc *tracedContext) Context() context.Context { + if tc.tracedCtx != nil { + return tc.tracedCtx + } + return tc.gctx.Context() +} + +// DeferCommit wraps goka's DeferCommit so DSM commit-offset tracking follows the +// real (deferred) commit instead of the callback return: the offset is recorded +// only when the returned function is called with a nil error. The message +// coordinates are captured now, as the context must not be used once the callback +// has returned. +func (tc *tracedContext) DeferCommit() func(error) { + commit := tc.gctx.DeferCommit() + tc.commitDeferred = true + + track := tc.tr.cfg.dataStreamsEnabled + group := string(tc.gctx.Group()) + topic := string(tc.gctx.Topic()) + partition := tc.gctx.Partition() + offset := tc.gctx.Offset() + + return func(err error) { + if err == nil && track && group != "" { + tracer.TrackKafkaCommitOffset(group, topic, partition, offset) + } + commit(err) + } +} + +// The remaining methods delegate unchanged to the wrapped goka.Context. + +func (tc *tracedContext) Topic() goka.Stream { return tc.gctx.Topic() } +func (tc *tracedContext) Key() string { return tc.gctx.Key() } +func (tc *tracedContext) Partition() int32 { return tc.gctx.Partition() } +func (tc *tracedContext) Offset() int64 { return tc.gctx.Offset() } +func (tc *tracedContext) Group() goka.Group { return tc.gctx.Group() } +func (tc *tracedContext) Value() any { return tc.gctx.Value() } +func (tc *tracedContext) Headers() goka.Headers { return tc.gctx.Headers() } +func (tc *tracedContext) Timestamp() time.Time { return tc.gctx.Timestamp() } +func (tc *tracedContext) Join(topic goka.Table) any { return tc.gctx.Join(topic) } +func (tc *tracedContext) Lookup(topic goka.Table, key string) any { return tc.gctx.Lookup(topic, key) } +func (tc *tracedContext) SetValue(value any, opts ...goka.ContextOption) { + tc.gctx.SetValue(value, opts...) +} +func (tc *tracedContext) Delete(opts ...goka.ContextOption) { tc.gctx.Delete(opts...) } + +var _ goka.Context = (*tracedContext)(nil) diff --git a/contrib/lovoo/goka/goka_integration_test.go b/contrib/lovoo/goka/goka_integration_test.go new file mode 100644 index 00000000000..74a2c52f6d9 --- /dev/null +++ b/contrib/lovoo/goka/goka_integration_test.go @@ -0,0 +1,117 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package goka + +import ( + "context" + "os" + "testing" + "time" + + "github.com/lovoo/goka" + "github.com/lovoo/goka/codec" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/DataDog/dd-trace-go/v2/ddtrace/ext" + "github.com/DataDog/dd-trace-go/v2/ddtrace/mocktracer" + "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer" +) + +// kafkaBrokers is the broker list used by the integration test. Override with the +// KAFKA_BROKERS environment variable (comma-separated) if needed. +var kafkaBrokers = []string{"localhost:9092"} + +// TestProcessorIntegration exercises the full path against a real Kafka broker: +// an upstream producer injects trace + DSM context via EmitHeaders, and a traced +// goka processor consumes it, continuing the same trace. It is skipped unless the +// INTEGRATION environment variable is set. +func TestProcessorIntegration(t *testing.T) { + if _, ok := os.LookupEnv("INTEGRATION"); !ok { + t.Skip("INTEGRATION environment variable not set") + } + t.Setenv("DD_DATA_STREAMS_ENABLED", "true") + + mt := mocktracer.Start() + defer mt.Stop() + + const ( + inputTopic = goka.Stream("dd-goka-integration-in") + group = goka.Group("dd-goka-integration-grp") + ) + + tr := NewTracer(WithService("goka-integration"), WithDataStreams()) + + // Ensure the input topic exists. + tm, err := goka.NewTopicManager(kafkaBrokers, goka.DefaultConfig(), goka.NewTopicManagerConfig()) + require.NoError(t, err) + defer tm.Close() + require.NoError(t, tm.EnsureStreamExists(string(inputTopic), 1)) + + // The callback records the trace ID it observes so we can assert the consume + // span continued the upstream trace. + processed := make(chan struct{}) + var gotTraceID string + cb := tr.WrapCallback(func(ctx goka.Context, _ any) { + if span, ok := tracer.SpanFromContext(ctx.Context()); ok { + gotTraceID = span.Context().TraceID() + } + close(processed) + }) + + g := goka.DefineGroup(group, goka.Input(inputTopic, new(codec.String), cb)) + p, err := goka.NewProcessor(kafkaBrokers, g, goka.WithContextWrapper(tr.WrapContext)) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan error, 1) + go func() { done <- p.Run(ctx) }() + + // Wait until the processor has joined the group and is consuming, so it sees + // the message we are about to emit (goka defaults to OffsetNewest). + require.NoError(t, p.WaitForReady()) + + // Emit a message carrying an upstream parent span and a DSM outbound checkpoint. + parent := tracer.StartSpan("upstream") + headers := tr.EmitHeaders(tracer.ContextWithSpan(context.Background(), parent), string(inputTopic)) + require.NotEmpty(t, headers) + + em, err := goka.NewEmitter(kafkaBrokers, inputTopic, new(codec.String)) + require.NoError(t, err) + require.NoError(t, em.EmitSyncWithHeaders("key", "value", headers)) + require.NoError(t, em.Finish()) + parent.Finish() + + select { + case <-processed: + case <-time.After(30 * time.Second): + cancel() + <-done + t.Fatal("timed out waiting for the message to be processed") + } + + cancel() + require.NoError(t, <-done) + + assert.Equal(t, parent.Context().TraceID(), gotTraceID, + "consume span should continue the upstream trace") + + var consume *mocktracer.Span + for _, s := range mt.FinishedSpans() { + if s.OperationName() == "kafka.consume" { + consume = s + break + } + } + require.NotNil(t, consume, "expected a kafka.consume span") + assert.Equal(t, parent.Context().TraceID(), consume.Context().TraceID()) + assert.Equal(t, parent.Context().SpanID(), consume.ParentID()) + assert.Equal(t, "goka-integration", consume.Tag(ext.ServiceName)) + assert.Equal(t, "Consume Topic "+string(inputTopic), consume.Tag(ext.ResourceName)) + assert.Equal(t, componentName, consume.Tag(ext.Component)) + assert.Equal(t, "consumer", consume.Tag(ext.SpanKind)) + assert.Equal(t, string(inputTopic), consume.Tag(ext.MessagingDestinationName)) +} diff --git a/contrib/lovoo/goka/goka_test.go b/contrib/lovoo/goka/goka_test.go new file mode 100644 index 00000000000..157cd42d30b --- /dev/null +++ b/contrib/lovoo/goka/goka_test.go @@ -0,0 +1,454 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package goka + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/lovoo/goka" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/DataDog/dd-trace-go/v2/datastreams" + "github.com/DataDog/dd-trace-go/v2/ddtrace/ext" + "github.com/DataDog/dd-trace-go/v2/ddtrace/mocktracer" + "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer" +) + +// mockContext is a minimal goka.Context implementation for unit testing. It +// exposes settable inbound message metadata and captures Emit/Loopback calls. +type mockContext struct { + topic goka.Stream + key string + partition int32 + offset int64 + group goka.Group + headers goka.Headers + baseCtx context.Context + ts time.Time + value any + joinVal any + lookupVal any + + emits []emitCall + loopbacks []emitCall + setValue any + setValueCalled bool + deleteCalled bool + deferCommitted bool + committedCalled bool + committedErr error +} + +type emitCall struct { + topic string + key string + value any + opts []goka.ContextOption +} + +var _ goka.Context = (*mockContext)(nil) + +func (m *mockContext) Topic() goka.Stream { return m.topic } +func (m *mockContext) Key() string { return m.key } +func (m *mockContext) Partition() int32 { return m.partition } +func (m *mockContext) Offset() int64 { return m.offset } +func (m *mockContext) Group() goka.Group { return m.group } +func (m *mockContext) Value() any { return m.value } +func (m *mockContext) Headers() goka.Headers { + if m.headers == nil { + return goka.Headers{} + } + return m.headers +} +func (m *mockContext) SetValue(v any, _ ...goka.ContextOption) { + m.setValue = v + m.setValueCalled = true +} +func (m *mockContext) Delete(...goka.ContextOption) { m.deleteCalled = true } +func (m *mockContext) Timestamp() time.Time { return m.ts } +func (m *mockContext) Join(goka.Table) any { return m.joinVal } +func (m *mockContext) Lookup(goka.Table, string) any { return m.lookupVal } +func (m *mockContext) DeferCommit() func(error) { + m.deferCommitted = true + return func(err error) { + m.committedCalled = true + m.committedErr = err + } +} +func (m *mockContext) Context() context.Context { + if m.baseCtx != nil { + return m.baseCtx + } + return context.Background() +} +func (m *mockContext) Emit(topic goka.Stream, key string, value any, opts ...goka.ContextOption) { + m.emits = append(m.emits, emitCall{string(topic), key, value, opts}) +} +func (m *mockContext) Loopback(key string, value any, opts ...goka.ContextOption) { + m.loopbacks = append(m.loopbacks, emitCall{"", key, value, opts}) +} + +// Fail mimics goka: it panics to unwind the callback. +func (m *mockContext) Fail(err error) { panic(err) } + +func TestCarrierRoundTrip(t *testing.T) { + headers := goka.Headers{} + carrier := gokaHeadersCarrier(headers) + carrier.Set("k1", "v1") + carrier.Set("k2", "v2") + + assert.Equal(t, []byte("v1"), headers["k1"]) + assert.Equal(t, []byte("v2"), headers["k2"]) + + got := map[string]string{} + require.NoError(t, carrier.ForeachKey(func(k, v string) error { + got[k] = v + return nil + })) + assert.Equal(t, map[string]string{"k1": "v1", "k2": "v2"}, got) +} + +func TestWrapContext_NoCheckpointWhenDSMDisabled(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + tr := NewTracer() // DSM off by default + ctx := tr.WrapContext(&mockContext{topic: "in", group: "g"}) + tc := ctx.(*tracedContext) + assert.Nil(t, tc.dsmCtx, "no inbound DSM pathway should be set when DSM is disabled") + + // Outbound headers should carry no DSM pathway either. + headers := tc.tr.outboundHeaders(nil, tc.gctx.Context(), "out", 0) + _, ok := datastreams.PathwayFromContext(datastreams.ExtractFromBase64Carrier(context.Background(), gokaHeadersCarrier(headers))) + assert.False(t, ok, "no DSM pathway expected when DSM is disabled") +} + +func TestConsumeSpan_ParentChildLinkage(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + // Simulate an upstream producer by injecting a parent span into the headers. + parent := tracer.StartSpan("upstream") + inHeaders := goka.Headers{} + require.NoError(t, tracer.Inject(parent.Context(), gokaHeadersCarrier(inHeaders))) + + tr := NewTracer(WithService("svc")) + gctx := &mockContext{topic: "orders", partition: 3, offset: 42, group: "g", headers: inHeaders} + + called := false + cb := tr.WrapCallback(func(goka.Context, any) { called = true }) + cb(tr.WrapContext(gctx), "msg") + + assert.True(t, called) + parent.Finish() + + spans := mt.FinishedSpans() + require.Len(t, spans, 2) + var consume *mocktracer.Span + for _, s := range spans { + if s.OperationName() == "kafka.consume" { + consume = s + } + } + require.NotNil(t, consume, "expected a kafka.consume span") + + assert.Equal(t, parent.Context().TraceID(), consume.Context().TraceID()) + assert.Equal(t, parent.Context().SpanID(), consume.ParentID()) + assert.Equal(t, "svc", consume.Tag(ext.ServiceName)) + assert.Equal(t, "Consume Topic orders", consume.Tag(ext.ResourceName)) + assert.Equal(t, componentName, consume.Tag(ext.Component)) + assert.Equal(t, "consumer", consume.Tag(ext.SpanKind)) + assert.Equal(t, "kafka", consume.Tag(ext.MessagingSystem)) + assert.Equal(t, "orders", consume.Tag(ext.MessagingDestinationName)) + assert.EqualValues(t, 3, consume.Tag(ext.MessagingKafkaPartition)) + assert.EqualValues(t, 42, consume.Tag("offset")) +} + +func TestConsumeSpan_ErrorTagging(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + tr := NewTracer() + gctx := &mockContext{topic: "orders", group: "g"} + + cb := tr.WrapCallback(func(ctx goka.Context, _ any) { + ctx.Fail(assert.AnError) // records the error, then panics like goka + }) + + // goka expects the Fail panic to unwind; recover it here as goka would. + require.Panics(t, func() { cb(tr.WrapContext(gctx), "msg") }) + + spans := mt.FinishedSpans() + require.Len(t, spans, 1) + assert.Equal(t, assert.AnError.Error(), spans[0].Tag(ext.ErrorMsg)) +} + +func TestConsumeSpan_PanicWithoutFail(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + tr := NewTracer() + gctx := &mockContext{topic: "orders", group: "g"} + + // A panic that never goes through ctx.Fail (e.g. an internal goka failure or + // a bug in the handler) must still tag the consume span with the error. + boom := errors.New("boom") + cb := tr.WrapCallback(func(goka.Context, any) { panic(boom) }) + + require.PanicsWithValue(t, boom, func() { cb(tr.WrapContext(gctx), "msg") }) + + spans := mt.FinishedSpans() + require.Len(t, spans, 1) + assert.Equal(t, boom.Error(), spans[0].Tag(ext.ErrorMsg)) +} + +func TestConsumeSpan_PanicNonError(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + tr := NewTracer() + gctx := &mockContext{topic: "orders", group: "g"} + + cb := tr.WrapCallback(func(goka.Context, any) { panic("kaboom") }) + + require.PanicsWithValue(t, "kaboom", func() { cb(tr.WrapContext(gctx), "msg") }) + + spans := mt.FinishedSpans() + require.Len(t, spans, 1) + assert.Contains(t, spans[0].Tag(ext.ErrorMsg), "kaboom") +} + +func TestEmit_NoProduceSpanPropagatesConsume(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + tr := NewTracer(WithService("svc")) + gctx := &mockContext{topic: "orders", group: "g"} + + cb := tr.WrapCallback(func(ctx goka.Context, _ any) { + ctx.Emit("orders-enriched", "k", "v") + }) + cb(tr.WrapContext(gctx), "msg") + + // goka.Context.Emit is async with no completion handle, so no kafka.produce + // span is created; only the consume span is emitted. + spans := mt.FinishedSpans() + require.Len(t, spans, 1) + assert.Equal(t, "kafka.consume", spans[0].OperationName()) + + // The emit still carries an injected headers option propagating the trace. + require.Len(t, gctx.emits, 1) + require.NotEmpty(t, gctx.emits[0].opts, "an emit-headers option should be prepended") +} + +func TestDeferCommit_DefersOffsetTracking(t *testing.T) { + t.Setenv("DD_DATA_STREAMS_ENABLED", "true") + mt := mocktracer.Start() + defer mt.Stop() + + tr := NewTracer(WithDataStreams()) + gctx := &mockContext{topic: "orders", group: "g"} + + var commit func(error) + cb := tr.WrapCallback(func(ctx goka.Context, _ any) { + commit = ctx.DeferCommit() + }) + cb(tr.WrapContext(gctx), "msg") + + // The callback deferred the commit, so the underlying commit is not called on + // callback return — it runs only when the returned function is invoked. + assert.True(t, gctx.deferCommitted) + assert.False(t, gctx.committedCalled) + + require.NotNil(t, commit) + commit(nil) + assert.True(t, gctx.committedCalled) + assert.NoError(t, gctx.committedErr) +} + +func TestLoopTopic_UsesConfiguredSuffix(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + def := NewTracer().WrapContext(&mockContext{group: "g"}).(*tracedContext) + assert.Equal(t, "g-loop", def.loopTopic()) + + custom := NewTracer(WithLoopSuffix("-loop1")).WrapContext(&mockContext{group: "g"}).(*tracedContext) + assert.Equal(t, "g-loop1", custom.loopTopic()) +} + +func TestEmit_InjectsTraceAndDSM(t *testing.T) { + t.Setenv("DD_DATA_STREAMS_ENABLED", "true") + mt := mocktracer.Start() + defer mt.Stop() + + tr := NewTracer(WithDataStreams()) + gctx := &mockContext{topic: "orders", group: "g"} + + cb := tr.WrapCallback(func(ctx goka.Context, _ any) { + ctx.Emit("orders-enriched", "k", "v") + }) + cb(tr.WrapContext(gctx), "msg") + + require.Len(t, gctx.emits, 1) + call := gctx.emits[0] + assert.Equal(t, "orders-enriched", call.topic) + assert.Equal(t, "k", call.key) + assert.Equal(t, "v", call.value) + require.NotEmpty(t, call.opts, "an emit-headers option should be prepended") +} + +func TestOutboundHeaders_TraceAndDSMContent(t *testing.T) { + t.Setenv("DD_DATA_STREAMS_ENABLED", "true") + mt := mocktracer.Start() + defer mt.Stop() + + tr := NewTracer(WithDataStreams()) + gctx := &mockContext{topic: "orders", group: "g"} + tc := tr.WrapContext(gctx).(*tracedContext) + finish := tc.startConsumeSpan() + + headers := tc.tr.outboundHeaders(tc.span, tc.dsmCtx, "orders-enriched", 0) + + // APM: the emitted headers carry a span context that is a child of the consume trace. + extracted, err := ExtractSpanContext(headers) + require.NoError(t, err) + require.NotNil(t, extracted) + assert.Equal(t, tc.span.Context().TraceID(), extracted.TraceID()) + + // DSM: the emitted headers carry an outbound pathway. + _, ok := datastreams.PathwayFromContext(datastreams.ExtractFromBase64Carrier(context.Background(), gokaHeadersCarrier(headers))) + assert.True(t, ok, "expected a DSM pathway in outbound headers") + + finish() +} + +func TestWrapCallback_PassthroughWhenContextNotWrapped(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + tr := NewTracer() + called := false + cb := tr.WrapCallback(func(goka.Context, any) { called = true }) + + // A raw (unwrapped) context must not start a span, just run the callback. + cb(&mockContext{topic: "orders"}, "msg") + assert.True(t, called) + assert.Empty(t, mt.FinishedSpans()) +} + +func TestEmitHeaders_Standalone(t *testing.T) { + t.Setenv("DD_DATA_STREAMS_ENABLED", "true") + mt := mocktracer.Start() + defer mt.Stop() + + tr := NewTracer(WithDataStreams()) + + span := tracer.StartSpan("producer") + headers := tr.EmitHeaders(tracer.ContextWithSpan(context.Background(), span), "orders") + span.Finish() + + // APM span context propagated. + extracted, err := ExtractSpanContext(headers) + require.NoError(t, err) + require.NotNil(t, extracted) + assert.Equal(t, span.Context().TraceID(), extracted.TraceID()) + + // DSM outbound pathway present. + _, ok := datastreams.PathwayFromContext(datastreams.ExtractFromBase64Carrier(context.Background(), gokaHeadersCarrier(headers))) + assert.True(t, ok, "expected a DSM pathway in standalone emit headers") +} + +func TestEmitHeaders_EmptyWhenDisabled(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + tr := NewTracer() // no DSM, no active span + headers := tr.EmitHeaders(context.Background(), "orders") + assert.Empty(t, headers) +} + +func TestLoopback_InjectsTraceHeaders(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + tr := NewTracer(WithService("svc")) + gctx := &mockContext{topic: "orders", group: "g"} + + cb := tr.WrapCallback(func(ctx goka.Context, _ any) { + ctx.Loopback("k", "v") + }) + cb(tr.WrapContext(gctx), "msg") + + require.Len(t, gctx.loopbacks, 1) + call := gctx.loopbacks[0] + assert.Equal(t, "k", call.key) + assert.Equal(t, "v", call.value) + require.NotEmpty(t, call.opts, "trace-headers option should be prepended on loopback") +} + +func TestContext_FallsBackWhenNoSpan(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + base := context.WithValue(context.Background(), struct{ k string }{"k"}, "v") + tr := NewTracer() + tc := tr.WrapContext(&mockContext{topic: "orders", baseCtx: base}).(*tracedContext) + + // No span started yet: Context() returns the wrapped context. + assert.Equal(t, "v", tc.Context().Value(struct{ k string }{"k"})) + + // After a span starts, Context() returns the traced (span) context. + finish := tc.startConsumeSpan() + defer finish() + span, ok := tracer.SpanFromContext(tc.Context()) + require.True(t, ok) + assert.Equal(t, tc.span.Context().TraceID(), span.Context().TraceID()) +} + +func TestTracedContext_DelegatesToWrapped(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + ts := time.Unix(1700000000, 0) + gctx := &mockContext{ + topic: "orders", + key: "the-key", + partition: 7, + offset: 99, + group: "the-group", + value: "the-value", + joinVal: "joined", + lookupVal: "looked-up", + ts: ts, + } + tc := NewTracer().WrapContext(gctx) + + assert.Equal(t, goka.Stream("orders"), tc.Topic()) + assert.Equal(t, "the-key", tc.Key()) + assert.EqualValues(t, 7, tc.Partition()) + assert.EqualValues(t, 99, tc.Offset()) + assert.Equal(t, goka.Group("the-group"), tc.Group()) + assert.Equal(t, "the-value", tc.Value()) + assert.Equal(t, ts, tc.Timestamp()) + assert.Equal(t, "joined", tc.Join("t")) + assert.Equal(t, "looked-up", tc.Lookup("t", "k")) + assert.NotNil(t, tc.Headers()) + + tc.SetValue("new-value") + assert.True(t, gctx.setValueCalled) + assert.Equal(t, "new-value", gctx.setValue) + + tc.Delete() + assert.True(t, gctx.deleteCalled) + + require.NotNil(t, tc.DeferCommit()) + assert.True(t, gctx.deferCommitted) +} diff --git a/contrib/lovoo/goka/option.go b/contrib/lovoo/goka/option.go new file mode 100644 index 00000000000..b29ba748bfe --- /dev/null +++ b/contrib/lovoo/goka/option.go @@ -0,0 +1,64 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package goka + +import "github.com/DataDog/dd-trace-go/v2/instrumentation" + +// defaultLoopSuffix mirrors goka's own default loop-topic suffix. goka exposes +// no accessor for the current suffix, so we track it here and let callers who +// change it via goka.SetLoopSuffix mirror the change with WithLoopSuffix. +const defaultLoopSuffix = "-loop" + +type config struct { + consumerServiceName string + serviceSource string + consumerSpanName string + dataStreamsEnabled bool + loopSuffix string +} + +func defaults(cfg *config) { + cfg.consumerServiceName = instr.ServiceName(instrumentation.ComponentConsumer, nil) + cfg.serviceSource = string(instrumentation.PackageLovooGoka) + cfg.consumerSpanName = instr.OperationName(instrumentation.ComponentConsumer, nil) + cfg.dataStreamsEnabled = instr.DataStreamsEnabled() + cfg.loopSuffix = defaultLoopSuffix +} + +// Option configures the goka integration. +type Option interface { + apply(*config) +} + +// OptionFn represents options applicable to NewTracer. +type OptionFn func(*config) + +func (fn OptionFn) apply(cfg *config) { fn(cfg) } + +// WithService sets the service name for the kafka.consume spans. +func WithService(serviceName string) Option { + return OptionFn(func(cfg *config) { + cfg.consumerServiceName = serviceName + cfg.serviceSource = instrumentation.ServiceSourceWithServiceOption + }) +} + +// WithDataStreams enables the Data Streams Monitoring product features: +// https://www.datadoghq.com/product/data-streams-monitoring/ +func WithDataStreams() Option { + return OptionFn(func(cfg *config) { + cfg.dataStreamsEnabled = true + }) +} + +// WithLoopSuffix sets the loop-topic suffix used to tag Loopback spans and DSM +// checkpoints. It must match the suffix configured in goka via goka.SetLoopSuffix +// (default "-loop"); set it only if the application changed goka's suffix. +func WithLoopSuffix(suffix string) Option { + return OptionFn(func(cfg *config) { + cfg.loopSuffix = suffix + }) +} diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 5e6a79e1273..a01ac3c7781 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -114,6 +114,7 @@ var contribIntegrations = map[string]struct { "github.com/tidwall/buntdb": {"BuntDB", false}, "github.com/twitchtv/twirp": {"Twirp", false}, "github.com/twmb/franz-go": {"franz-go", false}, + "github.com/lovoo/goka": {"goka", false}, "github.com/uptrace/bun": {"Bun", false}, "github.com/urfave/negroni": {"Negroni", false}, "github.com/valyala/fasthttp": {"FastHTTP", false}, diff --git a/go.work b/go.work index 97a6d2485f3..f30c57a843c 100644 --- a/go.work +++ b/go.work @@ -4,13 +4,13 @@ use ( . ./.github/workflows/apps ./contrib/99designs/gqlgen - ./contrib/aerospike/aerospike-client-go.v7 ./contrib/IBM/sarama ./contrib/Shopify/sarama - ./contrib/azure/apim-callout + ./contrib/aerospike/aerospike-client-go.v7 ./contrib/aws/aws-sdk-go ./contrib/aws/aws-sdk-go-v2 ./contrib/aws/datadog-lambda-go + ./contrib/azure/apim-callout ./contrib/bradfitz/gomemcache ./contrib/cloud.google.com/go/pubsub.v1 ./contrib/cloud.google.com/go/pubsub.v2 @@ -52,6 +52,7 @@ use ( ./contrib/labstack/echo.v4 ./contrib/labstack/echo.v5 ./contrib/log/slog + ./contrib/lovoo/goka ./contrib/mark3labs/mcp-go ./contrib/miekg/dns ./contrib/modelcontextprotocol/go-sdk @@ -64,8 +65,8 @@ use ( ./contrib/sirupsen/logrus ./contrib/syndtr/goleveldb ./contrib/tidwall/buntdb - ./contrib/twmb/franz-go ./contrib/twitchtv/twirp + ./contrib/twmb/franz-go ./contrib/uptrace/bun ./contrib/urfave/negroni ./contrib/valkey-io/valkey-go diff --git a/go.work.sum b/go.work.sum index 20f199d5e82..1d93085c292 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1082,6 +1082,7 @@ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= github.com/eapache/go-resiliency v1.4.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= +github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/emicklei/go-restful v2.16.0+incompatible h1:rgqiKNjTnFQA6kkhFe16D8epTksy9HQ1MyrbDXSdYhM= @@ -1338,6 +1339,7 @@ github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3ro github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= +github.com/klauspost/compress v1.15.14/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= @@ -1590,7 +1592,6 @@ github.com/yashtewari/glob-intersection v0.1.0 h1:6gJvMYQlTDOL3dMsPF6J0+26vwX9MB github.com/yashtewari/glob-intersection v0.1.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= @@ -2155,7 +2156,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= @@ -2221,7 +2221,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= @@ -2249,7 +2248,7 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= @@ -2299,6 +2298,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= @@ -2332,7 +2332,6 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2386,6 +2385,7 @@ golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= @@ -2455,7 +2455,6 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= @@ -2735,6 +2734,7 @@ gopkg.in/jinzhu/gorm.v1 v1.9.2/go.mod h1:56JJPUzbikvTVnoyP1nppSkbJ2L8sunqTBDY2fD gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= gopkg.in/olivere/elastic.v3 v3.0.75 h1:u3B8p1VlHF3yNLVOlhIWFT3F1ICcHfM5V6FFJe6pPSo= gopkg.in/olivere/elastic.v3 v3.0.75/go.mod h1:yDEuSnrM51Pc8dM5ov7U8aI/ToR3PG0llA8aRv2qmw0= +gopkg.in/redis.v5 v5.2.9/go.mod h1:6gtv0/+A4iM08kdRfocWYB3bLX2tebpNtfKlFT6H4mY= gopkg.in/retry.v1 v1.0.3 h1:a9CArYczAVv6Qs6VGoLMio99GEs7kY9UzSF9+LD+iGs= gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= diff --git a/instrumentation/packages.go b/instrumentation/packages.go index 8288bacd0ab..47ce91ab08d 100644 --- a/instrumentation/packages.go +++ b/instrumentation/packages.go @@ -60,6 +60,7 @@ const ( PackageShopifySarama Package = "Shopify/sarama" PackageSegmentioKafkaGo Package = "segmentio/kafka-go" PackageTwmbFranzGo Package = "twmb/franz-go" + PackageLovooGoka Package = "lovoo/goka" PackageRedisGoRedisV9 Package = "redis/go-redis.v9" PackageOlivereElasticV5 Package = "olivere/elastic.v5" PackageMiekgDNS Package = "miekg/dns" @@ -684,6 +685,24 @@ var packages = map[Package]PackageInfo{ }, }, }, + PackageLovooGoka: { + TracedPackage: "github.com/lovoo/goka", + EnvVarPrefix: "KAFKA", + naming: map[Component]componentNames{ + ComponentConsumer: { + useDDServiceV0: true, + buildServiceNameV0: staticName("kafka"), + buildOpNameV0: staticName("kafka.consume"), + buildOpNameV1: staticName("kafka.process"), + }, + ComponentProducer: { + useDDServiceV0: false, + buildServiceNameV0: staticName("kafka"), + buildOpNameV0: staticName("kafka.produce"), + buildOpNameV1: staticName("kafka.send"), + }, + }, + }, PackageRedisGoRedisV9: { TracedPackage: "github.com/redis/go-redis/v9", EnvVarPrefix: "REDIS",