From 6dc6fa9deaf11a3765ea922eae22f77fa92b6197 Mon Sep 17 00:00:00 2001 From: Eliott Bouhana Date: Fri, 7 Feb 2025 17:25:19 +0100 Subject: [PATCH 1/9] fix issue where reducing the value of DD_TELEMETRY_HEARTBEAT_INTERVAL would not reduce the value of the flushing interval Signed-off-by: Eliott Bouhana --- internal/newtelemetry/client_config.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/newtelemetry/client_config.go b/internal/newtelemetry/client_config.go index d0688e4434..06d91d56f7 100644 --- a/internal/newtelemetry/client_config.go +++ b/internal/newtelemetry/client_config.go @@ -163,6 +163,12 @@ func defaultConfig(config ClientConfig) ClientConfig { } } + if config.HeartbeatInterval == 0 { + config.HeartbeatInterval = globalinternal.DurationEnv("DD_TELEMETRY_HEARTBEAT_INTERVAL", defaultHeartbeatInterval) + } else { + config.HeartbeatInterval = defaultAuthorizedHearbeatRange.Clamp(config.HeartbeatInterval) + } + if config.FlushInterval.Min == 0 { config.FlushInterval.Min = defaultFlushIntervalRange.Min } else { From 664a192166ab6a8d9f001a107202127cf896a182 Mon Sep 17 00:00:00 2001 From: Eliott Bouhana Date: Thu, 30 Jan 2025 20:49:01 +0100 Subject: [PATCH 2/9] delete old telemetry package Signed-off-by: Eliott Bouhana --- internal/telemetry/client.go | 618 ------------------ internal/telemetry/client_test.go | 484 -------------- internal/telemetry/message.go | 305 --------- internal/telemetry/option.go | 150 ----- internal/telemetry/telemetry.go | 126 ---- internal/telemetry/telemetry_test.go | 169 ----- .../telemetry/telemetrytest/telemetrytest.go | 106 --- internal/telemetry/utils.go | 101 --- 8 files changed, 2059 deletions(-) delete mode 100644 internal/telemetry/client.go delete mode 100644 internal/telemetry/client_test.go delete mode 100644 internal/telemetry/message.go delete mode 100644 internal/telemetry/option.go delete mode 100644 internal/telemetry/telemetry.go delete mode 100644 internal/telemetry/telemetry_test.go delete mode 100644 internal/telemetry/telemetrytest/telemetrytest.go delete mode 100644 internal/telemetry/utils.go diff --git a/internal/telemetry/client.go b/internal/telemetry/client.go deleted file mode 100644 index 4e55b77a82..0000000000 --- a/internal/telemetry/client.go +++ /dev/null @@ -1,618 +0,0 @@ -// 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 2022 Datadog, Inc. - -// Package telemetry implements a client for sending telemetry information to -// Datadog regarding usage of an APM library such as tracing or profiling. -package telemetry - -import ( - "bytes" - "encoding/json" - "fmt" - "net" - "net/http" - "os" - "runtime" - "runtime/debug" - "strings" - "sync" - "time" - - "gopkg.in/DataDog/dd-trace-go.v1/internal" - "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" - logger "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/osinfo" - "gopkg.in/DataDog/dd-trace-go.v1/internal/version" -) - -// Client buffers and sends telemetry messages to Datadog (possibly through an -// agent). -type Client interface { - RegisterAppConfig(name string, val interface{}, origin Origin) - ProductChange(namespace Namespace, enabled bool, configuration []Configuration) - ConfigChange(configuration []Configuration) - Record(namespace Namespace, metric MetricKind, name string, value float64, tags []string, common bool) - Count(namespace Namespace, name string, value float64, tags []string, common bool) - ApplyOps(opts ...Option) - Stop() -} - -var ( - // GlobalClient acts as a global telemetry client that the - // tracer, profiler, and appsec products will use - GlobalClient Client - globalClient sync.Mutex - - // integrations tracks the integrations enabled - contribPackages []Integration - contrib sync.Mutex - - // copied from dd-trace-go/profiler - defaultHTTPClient = &http.Client{ - // We copy the transport to avoid using the default one, as it might be - // augmented with tracing and we don't want these calls to be recorded. - // See https://golang.org/pkg/net/http/#DefaultTransport . - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - DualStack: true, - }).DialContext, - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - }, - Timeout: 5 * time.Second, - } - - // protects agentlessURL, which may be changed for testing purposes - agentlessEndpointLock sync.RWMutex - // agentlessURL is the endpoint used to send telemetry in an agentless environment. It is - // also the default URL in case connecting to the agent URL fails. - agentlessURL = "https://instrumentation-telemetry-intake.datadoghq.com/api/v2/apmtelemetry" - - defaultHeartbeatInterval = 60.0 // seconds - - // LogPrefix specifies the prefix for all telemetry logging - LogPrefix = "Instrumentation telemetry: " - - hostname string -) - -func init() { - var err error - hostname, err = os.Hostname() - if err != nil { - hostname = "unknown" - } - GlobalClient = new(client) -} - -// client implements Client interface. Client.Start should be called before any other methods. -// -// Client is safe to use from multiple goroutines concurrently. The client will -// send all telemetry requests in the background, in order to avoid blocking the -// caller since telemetry should not disrupt an application. Metrics are -// aggregated by the Client. -type client struct { - // URL for the Datadog agent or Datadog telemetry endpoint - URL string - // APIKey should be supplied if the endpoint is not a Datadog agent, - // i.e. you are sending telemetry directly to Datadog - APIKey string - // The interval for sending a heartbeat signal to the backend. - // Configurable with DD_TELEMETRY_HEARTBEAT_INTERVAL. Default 60s. - heartbeatInterval time.Duration - - // e.g. "tracers", "profilers", "appsec" - Namespace Namespace - - // App-specific information - Service string - Env string - Version string - - // Client will be used for telemetry uploads. This http.Client, if - // provided, should be the same as would be used for any other - // interaction with the Datadog agent, e.g. if the agent is accessed - // over UDS, or if the user provides their own http.Client to the - // profiler/tracer to access the agent over a proxy. - // - // If Client is nil, an http.Client with the same Transport settings as - // http.DefaultTransport and a 5 second timeout will be used. - Client *http.Client - - // mu guards all of the following fields - mu sync.Mutex - - // debug enables the debug flag for all requests, see - // https://dtdg.co/3bv2MMv. - // DD_INSTRUMENTATION_TELEMETRY_DEBUG configures this field. - debug bool - // started is true in between when Start() returns and the next call to - // Stop() - started bool - // seqID is a sequence number used to order telemetry messages by - // the back end. - seqID int64 - // heartbeatT is used to schedule heartbeat messages - heartbeatT *time.Timer - // requests hold all messages which don't need to be immediately sent - requests []*Request - // metrics holds un-sent metrics that will be aggregated the next time - // metrics are sent - metrics map[Namespace]map[string]*metric - newMetrics bool - - // syncFlushOnStop forces a sync flush to ensure all metrics are sent before stopping the client - syncFlushOnStop bool - - // Globally registered application configuration sent in the app-started request, along with the locally-defined - // configuration of the event. - globalAppConfig []Configuration -} - -func log(msg string, args ...interface{}) { - // Debug level so users aren't spammed with telemetry info. - logger.Debug(LogPrefix+msg, args...) -} - -// RegisterAppConfig allows to register a globally-defined application configuration. -// This configuration will be sent when the telemetry client is started and over related configuration updates. -func (c *client) RegisterAppConfig(name string, value interface{}, origin Origin) { - c.globalAppConfig = append(c.globalAppConfig, Configuration{ - Name: name, - Value: value, - Origin: origin, - }) -} - -// start registers that the app has begun running with the app-started event. -// Must be called with c.mu locked. -// start also configures the telemetry client based on the following telemetry -// environment variables: DD_INSTRUMENTATION_TELEMETRY_ENABLED, -// DD_TELEMETRY_HEARTBEAT_INTERVAL, DD_INSTRUMENTATION_TELEMETRY_DEBUG, -// and DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED. -// TODO: implement passing in error information about tracer start -func (c *client) start(configuration []Configuration, namespace Namespace, flush bool) { - if Disabled() { - return - } - if c.started { - log("attempted to start telemetry client when client has already started - ignoring attempt") - return - } - // Don't start the telemetry client if there is some error configuring the client with fallback - // options, e.g. an API key was not found but agentless telemetry is expected. - if err := c.fallbackOps(); err != nil { - log(err.Error()) - return - } - - c.started = true - c.metrics = make(map[Namespace]map[string]*metric) - c.debug = internal.BoolEnv("DD_INSTRUMENTATION_TELEMETRY_DEBUG", false) - - productInfo := Products{ - AppSec: ProductDetails{ - Version: version.Tag, - // if appsec is the one starting the telemetry client, - // then AppSec is enabled - Enabled: namespace == NamespaceAppSec, - }, - Profiler: ProductDetails{ - Version: version.Tag, - // if the profiler is the one starting the telemetry client, - // then profiling is enabled. - Enabled: namespace == NamespaceProfilers, - }, - } - - var cfg []Configuration - cfg = append(cfg, c.globalAppConfig...) - cfg = append(cfg, configuration...) - - // State whether the app has its Go dependencies available or not - deps, ok := debug.ReadBuildInfo() - if !ok { - deps = nil // because not guaranteed to be nil by the public doc when !ok - } - cfg = append(cfg, BoolConfig("dependencies_available", ok)) - collectDependenciesEnabled := collectDependencies() - cfg = append(cfg, BoolConfig("DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED", collectDependenciesEnabled)) // TODO: report all the possible telemetry config option automatically - if !collectDependenciesEnabled { - deps = nil // to simplify the condition below to `deps != nil` - } - - payload := &AppStarted{ - Configuration: cfg, - Products: productInfo, - } - appStarted := c.newRequest(RequestTypeAppStarted) - appStarted.Body.Payload = payload - c.scheduleSubmit(appStarted) - - if deps != nil { - var depPayload Dependencies - for _, dep := range deps.Deps { - depPayload.Dependencies = append(depPayload.Dependencies, - Dependency{ - Name: dep.Path, - Version: strings.TrimPrefix(dep.Version, "v"), - }, - ) - } - // Send the telemetry request if and only if the dependencies are actually present in the binary. - // For instance, bazel doesn't include them out of the box (cf. https://github.com/bazelbuild/rules_go/issues/3090), - // which would result in an empty list of dependencies. - dep := c.newRequest(RequestTypeDependenciesLoaded) - dep.Body.Payload = depPayload - c.scheduleSubmit(dep) - } - - if len(contribPackages) > 0 { - req := c.newRequest(RequestTypeAppIntegrationsChange) - req.Body.Payload = IntegrationsChange{Integrations: contribPackages} - c.scheduleSubmit(req) - } - - if flush { - c.flush(false) - } - c.heartbeatInterval = heartbeatInterval() - c.heartbeatT = time.AfterFunc(c.heartbeatInterval, c.backgroundHeartbeat) -} - -func heartbeatInterval() time.Duration { - heartbeat := internal.FloatEnv("DD_TELEMETRY_HEARTBEAT_INTERVAL", defaultHeartbeatInterval) - if heartbeat <= 0 || heartbeat > 3600 { - log("DD_TELEMETRY_HEARTBEAT_INTERVAL=%d not in [1,3600] range, setting to default of %f", heartbeat, defaultHeartbeatInterval) - heartbeat = defaultHeartbeatInterval - } - return time.Duration(heartbeat * float64(time.Second)) -} - -// Stop notifies the telemetry endpoint that the app is closing. All outstanding -// messages will also be sent. No further messages will be sent until the client -// is started again -func (c *client) Stop() { - c.mu.Lock() - defer c.mu.Unlock() - if !c.started { - return - } - c.started = false - c.heartbeatT.Stop() - // close request types have no body - r := c.newRequest(RequestTypeAppClosing) - c.scheduleSubmit(r) - c.flush(c.syncFlushOnStop) -} - -// Disabled returns whether instrumentation telemetry is disabled -// according to the DD_INSTRUMENTATION_TELEMETRY_ENABLED env var -func Disabled() bool { - return !internal.BoolEnv("DD_INSTRUMENTATION_TELEMETRY_ENABLED", true) -} - -// collectDependencies returns whether dependencies telemetry information is sent -func collectDependencies() bool { - return internal.BoolEnv("DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED", true) -} - -// MetricKind specifies the type of metric being reported. -// Metric types mirror Datadog metric types - for a more detailed -// description of metric types, see: -// https://docs.datadoghq.com/metrics/types/?tab=count#metric-types -type MetricKind string - -var ( - // MetricKindGauge represents a gauge type metric - MetricKindGauge MetricKind = "gauge" - // MetricKindCount represents a count type metric - MetricKindCount MetricKind = "count" - // MetricKindDist represents a distribution type metric - MetricKindDist MetricKind = "distribution" -) - -type metric struct { - name string - kind MetricKind - value float64 - // Unix timestamp - ts float64 - tags []string - common bool -} - -// TODO: Can there be identically named/tagged metrics with a "common" and "not -// common" variant? - -func newMetric(name string, kind MetricKind, tags []string, common bool) *metric { - return &metric{ - name: name, - kind: kind, - tags: append([]string{}, tags...), - common: common, - } -} - -func metricKey(name string, tags []string, kind MetricKind) string { - return name + string(kind) + strings.Join(tags, "-") -} - -// Record sets the value for a gauge or distribution metric type -// with the given name and tags. If the metric is not language-specific, common should be set to true -func (c *client) Record(namespace Namespace, kind MetricKind, name string, value float64, tags []string, common bool) { - c.mu.Lock() - defer c.mu.Unlock() - if !c.started { - return - } - if _, ok := c.metrics[namespace]; !ok { - c.metrics[namespace] = map[string]*metric{} - } - key := metricKey(name, tags, kind) - m, ok := c.metrics[namespace][key] - if !ok { - m = newMetric(name, kind, tags, common) - c.metrics[namespace][key] = m - } - m.value = value - m.ts = float64(time.Now().Unix()) - c.newMetrics = true -} - -// Count adds the value to a count with the given name and tags. If the metric -// is not language-specific, common should be set to true -func (c *client) Count(namespace Namespace, name string, value float64, tags []string, common bool) { - c.mu.Lock() - defer c.mu.Unlock() - if !c.started { - return - } - if _, ok := c.metrics[namespace]; !ok { - c.metrics[namespace] = map[string]*metric{} - } - key := metricKey(name, tags, MetricKindCount) - m, ok := c.metrics[namespace][key] - if !ok { - m = newMetric(name, MetricKindCount, tags, common) - c.metrics[namespace][key] = m - } - m.value += value - m.ts = float64(time.Now().Unix()) - c.newMetrics = true -} - -// flush sends any outstanding telemetry messages and aggregated metrics to be -// sent to the backend. Requests are sent in the background. Must be called -// with c.mu locked -func (c *client) flush(sync bool) { - // initialize submissions slice of capacity len(c.requests) + 2 - // to hold all the new events, plus two potential metric events - submissions := make([]*Request, 0, len(c.requests)+2) - - // copy over requests so we can do the actual submission without holding - // the lock. Zero out the old stuff so we don't leak references - for i, r := range c.requests { - submissions = append(submissions, r) - c.requests[i] = nil - } - c.requests = c.requests[:0] - - if c.newMetrics { - c.newMetrics = false - for namespace := range c.metrics { - // metrics can either be request type generate-metrics or distributions - dPayload := &DistributionMetrics{ - Namespace: namespace, - } - gPayload := &Metrics{ - Namespace: namespace, - } - for _, m := range c.metrics[namespace] { - if m.kind == MetricKindDist { - dPayload.Series = append(dPayload.Series, DistributionSeries{ - Metric: m.name, - Tags: m.tags, - Common: m.common, - Points: []float64{m.value}, - }) - } else { - gPayload.Series = append(gPayload.Series, Series{ - Metric: m.name, - Type: string(m.kind), - Tags: m.tags, - Common: m.common, - Points: [][2]float64{{m.ts, m.value}}, - }) - } - } - if len(dPayload.Series) > 0 { - distributions := c.newRequest(RequestTypeDistributions) - distributions.Body.Payload = dPayload - submissions = append(submissions, distributions) - } - if len(gPayload.Series) > 0 { - generateMetrics := c.newRequest(RequestTypeGenerateMetrics) - generateMetrics.Body.Payload = gPayload - submissions = append(submissions, generateMetrics) - } - } - } - - submit := func() { - for _, r := range submissions { - err := r.submit() - if err != nil { - log("submission error: %s", err.Error()) - } - } - } - - if sync { - submit() - } else { - go submit() - } -} - -// newRequests populates a request with the common fields shared by all requests -// sent through this Client -func (c *client) newRequest(t RequestType) *Request { - c.seqID++ - body := &Body{ - APIVersion: "v2", - RequestType: t, - TracerTime: time.Now().Unix(), - RuntimeID: globalconfig.RuntimeID(), - SeqID: c.seqID, - Debug: c.debug, - Application: Application{ - ServiceName: c.Service, - Env: c.Env, - ServiceVersion: c.Version, - TracerVersion: version.Tag, - LanguageName: "go", - LanguageVersion: runtime.Version(), - }, - Host: Host{ - Hostname: hostname, - OS: osinfo.OSName(), - OSVersion: osinfo.OSVersion(), - Architecture: osinfo.Architecture(), - KernelName: osinfo.KernelName(), - KernelRelease: osinfo.KernelRelease(), - KernelVersion: osinfo.KernelVersion(), - }, - } - - header := &http.Header{ - "Content-Type": {"application/json"}, - "DD-Telemetry-API-Version": {"v2"}, - "DD-Telemetry-Request-Type": {string(t)}, - "DD-Client-Library-Language": {"go"}, - "DD-Client-Library-Version": {version.Tag}, - "DD-Agent-Env": {c.Env}, - "DD-Agent-Hostname": {hostname}, - } - if cid := internal.ContainerID(); cid != "" { - header.Set("Datadog-Container-ID", cid) - } - if eid := internal.EntityID(); eid != "" { - header.Set("Datadog-Entity-ID", eid) - } - if c.URL == getAgentlessURL() { - header.Set("DD-API-KEY", c.APIKey) - } - client := c.Client - if client == nil { - client = defaultHTTPClient - } - return &Request{Body: body, - Header: header, - HTTPClient: client, - URL: c.URL, - } -} - -// submit sends a telemetry request -func (r *Request) submit() error { - retry, err := r.trySubmit() - if retry { - // retry telemetry submissions in instances where the telemetry client has trouble - // connecting with the agent - log("telemetry submission failed, retrying with agentless: %s", err) - r.URL = getAgentlessURL() - r.Header.Set("DD-API-KEY", defaultAPIKey()) - if _, err := r.trySubmit(); err == nil { - return nil - } - log("retrying with agentless telemetry failed: %s", err) - } - return err -} - -// agentlessRetry determines if we should retry a failed a request with -// by submitting to the agentless endpoint -func agentlessRetry(req *Request, resp *http.Response, err error) bool { - if req.URL == getAgentlessURL() { - // no need to retry with agentless endpoint if it already failed - return false - } - if err != nil { - // we didn't get a response which might signal a connectivity problem with - // agent - retry with agentless - return true - } - // TODO: add more status codes we do not want to retry on - doNotRetry := []int{http.StatusBadRequest, http.StatusTooManyRequests, http.StatusUnauthorized, http.StatusForbidden} - for status := range doNotRetry { - if resp.StatusCode == status { - return false - } - } - return true -} - -// trySubmit submits a telemetry request to the specified URL -// in the Request struct. If submission fails, return whether or not -// this submission should be re-tried with the agentless endpoint -// as well as the error that occurred -func (r *Request) trySubmit() (retry bool, err error) { - b, err := json.Marshal(r.Body) - if err != nil { - return false, err - } - - req, err := http.NewRequest(http.MethodPost, r.URL, bytes.NewReader(b)) - if err != nil { - return false, err - } - req.Header = *r.Header - - req.ContentLength = int64(len(b)) - - client := r.HTTPClient - if client == nil { - client = defaultHTTPClient - } - resp, err := client.Do(req) - if err != nil { - return agentlessRetry(r, resp, err), err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusOK { - return agentlessRetry(r, resp, err), errBadStatus(resp.StatusCode) - } - return false, nil -} - -type errBadStatus int - -func (e errBadStatus) Error() string { return fmt.Sprintf("bad HTTP response status %d", e) } - -// scheduleSubmit queues a request to be sent to the backend. Should be called -// with c.mu locked -func (c *client) scheduleSubmit(r *Request) { - c.requests = append(c.requests, r) -} - -// backgroundHeartbeat is invoked at every heartbeat interval, -// sending the app-heartbeat event and flushing any outstanding -// telemetry messages -func (c *client) backgroundHeartbeat() { - c.mu.Lock() - defer c.mu.Unlock() - if !c.started { - return - } - c.scheduleSubmit(c.newRequest(RequestTypeAppHeartbeat)) - c.flush(false) - c.heartbeatT.Reset(c.heartbeatInterval) -} diff --git a/internal/telemetry/client_test.go b/internal/telemetry/client_test.go deleted file mode 100644 index 45cd4172a3..0000000000 --- a/internal/telemetry/client_test.go +++ /dev/null @@ -1,484 +0,0 @@ -// 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 2022 Datadog, Inc. - -package telemetry - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "reflect" - "runtime" - "sort" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "gopkg.in/DataDog/dd-trace-go.v1/internal" - logging "gopkg.in/DataDog/dd-trace-go.v1/internal/log" -) - -func TestClient(t *testing.T) { - t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "1") - heartbeat := make(chan struct{}) - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - h := r.Header.Get("DD-Telemetry-Request-Type") - if len(h) == 0 { - t.Fatal("didn't get telemetry request type header") - } - if RequestType(h) == RequestTypeAppHeartbeat { - select { - case heartbeat <- struct{}{}: - default: - } - } - })) - defer server.Close() - - client := &client{ - URL: server.URL, - } - client.mu.Lock() - client.start(nil, NamespaceTracers, true) - client.start(nil, NamespaceTracers, true) // test idempotence - client.mu.Unlock() - defer client.Stop() - - timeout := time.After(30 * time.Second) - select { - case <-timeout: - t.Fatal("Heartbeat took more than 30 seconds. Should have been ~1 second") - case <-heartbeat: - } - -} - -func TestMetrics(t *testing.T) { - t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "1") - var ( - mu sync.Mutex - got []Series - ) - closed := make(chan struct{}, 1) - - // we will try to set three metrics that the server must receive - expectedMetrics := 3 - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - rType := RequestType(r.Header.Get("DD-Telemetry-Request-Type")) - if rType != RequestTypeGenerateMetrics { - return - } - req := Body{ - Payload: new(Metrics), - } - dec := json.NewDecoder(r.Body) - err := dec.Decode(&req) - if err != nil { - t.Fatal(err) - } - v, ok := req.Payload.(*Metrics) - if !ok { - t.Fatal("payload set metrics but didn't get metrics") - } - for _, s := range v.Series { - for i, p := range s.Points { - // zero out timestamps - s.Points[i] = [2]float64{0, p[1]} - } - } - mu.Lock() - defer mu.Unlock() - got = append(got, v.Series...) - if len(got) == expectedMetrics { - select { - case closed <- struct{}{}: - default: - } - return - } - })) - defer server.Close() - - go func() { - client := &client{ - URL: server.URL, - } - client.start(nil, NamespaceTracers, true) - - // Records should have the most recent value - client.Record(NamespaceTracers, MetricKindGauge, "foobar", 1, nil, false) - client.Record(NamespaceTracers, MetricKindGauge, "foobar", 2, nil, false) - // Counts should be aggregated - client.Count(NamespaceTracers, "baz", 3, nil, true) - client.Count(NamespaceTracers, "baz", 1, nil, true) - // Tags should be passed through - client.Count(NamespaceTracers, "bonk", 4, []string{"org:1"}, false) - - client.mu.Lock() - client.flush(false) - client.mu.Unlock() - }() - - <-closed - - want := []Series{ - {Metric: "baz", Type: "count", Interval: 0, Points: [][2]float64{{0, 4}}, Tags: []string{}, Common: true}, - {Metric: "bonk", Type: "count", Interval: 0, Points: [][2]float64{{0, 4}}, Tags: []string{"org:1"}}, - {Metric: "foobar", Type: "gauge", Interval: 0, Points: [][2]float64{{0, 2}}, Tags: []string{}}, - } - sort.Slice(got, func(i, j int) bool { - return got[i].Metric < got[j].Metric - }) - if !reflect.DeepEqual(want, got) { - t.Fatalf("want %+v, got %+v", want, got) - } -} - -func TestDistributionMetrics(t *testing.T) { - t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "1") - var ( - mu sync.Mutex - got []DistributionSeries - ) - closed := make(chan struct{}, 1) - - // we will try to set one metric that the server must receive - expectedMetrics := 1 - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - rType := RequestType(r.Header.Get("DD-Telemetry-Request-Type")) - if rType != RequestTypeDistributions { - return - } - req := Body{ - Payload: new(DistributionMetrics), - } - dec := json.NewDecoder(r.Body) - err := dec.Decode(&req) - if err != nil { - t.Fatal(err) - } - v, ok := req.Payload.(*DistributionMetrics) - if !ok { - t.Fatal("payload set metrics but didn't get metrics") - } - mu.Lock() - defer mu.Unlock() - got = append(got, v.Series...) - if len(got) == expectedMetrics { - select { - case closed <- struct{}{}: - default: - } - return - } - })) - defer server.Close() - - go func() { - client := &client{ - URL: server.URL, - } - client.start(nil, NamespaceTracers, true) - // Records should have the most recent value - client.Record(NamespaceTracers, MetricKindDist, "soobar", 1, nil, false) - client.Record(NamespaceTracers, MetricKindDist, "soobar", 3, nil, false) - client.mu.Lock() - client.flush(false) - client.mu.Unlock() - }() - - <-closed - - want := []DistributionSeries{ - // Distributions do not record metric types since it is its own event - {Metric: "soobar", Points: []float64{3}, Tags: []string{}}, - } - if !reflect.DeepEqual(want, got) { - t.Fatalf("want %+v, got %+v", want, got) - } -} - -func TestDisabledClient(t *testing.T) { - t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "1") - t.Setenv("DD_INSTRUMENTATION_TELEMETRY_ENABLED", "0") - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - t.Fatal("shouldn't have got any requests") - })) - defer server.Close() - - client := &client{ - URL: server.URL, - } - client.start(nil, NamespaceTracers, true) - client.Record(NamespaceTracers, MetricKindGauge, "foobar", 1, nil, false) - client.Count(NamespaceTracers, "bonk", 4, []string{"org:1"}, false) - client.Stop() -} - -func TestNonStartedClient(t *testing.T) { - t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "1") - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - t.Fatal("shouldn't have got any requests") - })) - defer server.Close() - - client := &client{ - URL: server.URL, - } - client.Record(NamespaceTracers, MetricKindGauge, "foobar", 1, nil, false) - client.Count(NamespaceTracers, "bonk", 4, []string{"org:1"}, false) - client.Stop() -} - -func TestConcurrentClient(t *testing.T) { - t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "1") - var ( - mu sync.Mutex - got []Series - ) - closed := make(chan struct{}, 1) - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - t.Log("foo") - req := Body{ - Payload: new(Metrics), - } - dec := json.NewDecoder(r.Body) - err := dec.Decode(&req) - if err != nil { - t.Fatal(err) - } - if req.RequestType != RequestTypeGenerateMetrics { - return - } - v, ok := req.Payload.(*Metrics) - if !ok { - t.Fatal("payload set metrics but didn't get metrics") - } - for _, s := range v.Series { - for i, p := range s.Points { - // zero out timestamps - s.Points[i] = [2]float64{0, p[1]} - } - } - mu.Lock() - defer mu.Unlock() - got = append(got, v.Series...) - select { - case closed <- struct{}{}: - default: - return - } - })) - defer server.Close() - - go func() { - client := &client{ - URL: server.URL, - } - client.start(nil, NamespaceTracers, true) - defer client.Stop() - - var wg sync.WaitGroup - for i := 0; i < 8; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for j := 0; j < 10; j++ { - client.Count(NamespaceTracers, "foobar", 1, []string{"tag"}, false) - } - }() - } - wg.Wait() - }() - - <-closed - - want := []Series{ - {Metric: "foobar", Type: "count", Points: [][2]float64{{0, 80}}, Tags: []string{"tag"}}, - } - sort.Slice(got, func(i, j int) bool { - return got[i].Metric < got[j].Metric - }) - if !reflect.DeepEqual(want, got) { - t.Fatalf("want %+v, got %+v", want, got) - } -} - -// fakeAgentless is a helper function for TestAgentlessRetry. It replaces the agentless -// endpoint in the telemetry package with a custom server URL and returns -// 1. a function that waits for a telemetry request to that server -// 2. a cleanup function that closes the server and resets the agentless endpoint to -// its original value -func fakeAgentless(ctx context.Context, t *testing.T) (wait func(), cleanup func()) { - received := make(chan struct{}, 1) - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("DD-Telemetry-Request-Type") == string(RequestTypeAppStarted) { - select { - case received <- struct{}{}: - default: - } - } - })) - prevEndpoint := SetAgentlessEndpoint(server.URL) - return func() { - select { - case <-ctx.Done(): - t.Fatalf("fake agentless endpoint timed out waiting for telemetry") - case <-received: - return - } - }, func() { - server.Close() - SetAgentlessEndpoint(prevEndpoint) - } -} - -// TestAgentlessRetry tests the behavior of the telemetry client in the case where -// the client cannot connect to the agent. The client should re-try the request -// with the agentless endpoint. -func TestAgentlessRetry(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - waitAgentlessEndpoint, cleanup := fakeAgentless(ctx, t) - defer cleanup() - - brokenServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - })) - brokenServer.Close() - - client := &client{ - URL: brokenServer.URL, - } - client.start(nil, NamespaceTracers, true) - waitAgentlessEndpoint() -} - -func TestCollectDependencies(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - received := make(chan *Dependencies) - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("DD-Telemetry-Request-Type") == string(RequestTypeDependenciesLoaded) { - var body Body - body.Payload = new(Dependencies) - err := json.NewDecoder(r.Body).Decode(&body) - if err != nil { - t.Errorf("bad body: %s", err) - } - select { - case received <- body.Payload.(*Dependencies): - default: - } - } - })) - defer server.Close() - client := &client{ - URL: server.URL, - } - client.start(nil, NamespaceTracers, true) - select { - case <-received: - case <-ctx.Done(): - t.Fatalf("Timed out waiting for dependency payload") - } -} - -func Test_heartbeatInterval(t *testing.T) { - defaultInterval := time.Second * time.Duration(defaultHeartbeatInterval) - tests := []struct { - name string - setup func(t *testing.T) - want time.Duration - }{ - { - name: "default", - setup: func(t *testing.T) {}, - want: defaultInterval, - }, - { - name: "float", - setup: func(t *testing.T) { t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "0.2") }, - want: time.Millisecond * 200, - }, - { - name: "integer", - setup: func(t *testing.T) { t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "2") }, - want: time.Second * 2, - }, - { - name: "negative", - setup: func(t *testing.T) { t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "-1") }, - want: defaultInterval, - }, - { - name: "zero", - setup: func(t *testing.T) { t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "0") }, - want: defaultInterval, - }, - { - name: "long", - setup: func(t *testing.T) { t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "4000") }, - want: defaultInterval, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.setup(t) - assert.Equal(t, tt.want, heartbeatInterval()) - }) - } -} - -func TestNoEmptyHeaders(t *testing.T) { - if runtime.GOOS != "linux" { - t.Skip("skipping test on non-linux OS") - } - if internal.EntityID() == "" || internal.ContainerID() == "" { - t.Skip("skipping test when entity ID and container ID are not available") - } - c := &client{} - req := c.newRequest(RequestTypeAppStarted) - assertNotEmpty := func(header string) { - headers := *req.Header - vals := headers[header] - assert.Greater(t, len(vals), 0, "header %s should not be empty", header) - for _, v := range vals { - assert.NotEmpty(t, v, "%s header should not be empty", header) - } - } - assertNotEmpty("Datadog-Container-ID") - assertNotEmpty("Datadog-Entity-ID") -} - -func TestTelementryClientLogging(t *testing.T) { - var ( - rl = new(logging.RecordLogger) - oldLevel = logging.GetLevel() - ) - logging.SetLevel(logging.LevelDebug) - undo := logging.UseLogger(rl) - defer func() { - logging.SetLevel(oldLevel) - undo() - }() - - // We simulate a client that has already started - c := &client{ - started: true, - } - c.start(nil, NamespaceTracers, true) - - assert := assert.New(t) - assert.Contains(rl.Logs()[0], LogPrefix+"attempted to start telemetry client when client has already started - ignoring attempt") -} diff --git a/internal/telemetry/message.go b/internal/telemetry/message.go deleted file mode 100644 index 7da6a9fa24..0000000000 --- a/internal/telemetry/message.go +++ /dev/null @@ -1,305 +0,0 @@ -// 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 2022 Datadog, Inc. - -package telemetry - -import ( - "bytes" - "fmt" - "net/http" -) - -// Request captures all necessary information for a telemetry event submission -type Request struct { - Body *Body - Header *http.Header - HTTPClient *http.Client - URL string -} - -// Body is the common high-level structure encapsulating a telemetry request body -type Body struct { - APIVersion string `json:"api_version"` - RequestType RequestType `json:"request_type"` - TracerTime int64 `json:"tracer_time"` - RuntimeID string `json:"runtime_id"` - SeqID int64 `json:"seq_id"` - Debug bool `json:"debug"` - Payload interface{} `json:"payload"` - Application Application `json:"application"` - Host Host `json:"host"` -} - -// RequestType determines how the Payload of a request should be handled -type RequestType string - -const ( - // RequestTypeAppStarted is the first message sent by the telemetry - // client, containing the configuration loaded at startup - RequestTypeAppStarted RequestType = "app-started" - // RequestTypeAppHeartbeat is sent periodically by the client to indicate - // that the app is still running - RequestTypeAppHeartbeat RequestType = "app-heartbeat" - // RequestTypeGenerateMetrics contains count, gauge, or rate metrics accumulated by the - // client, and is sent periodically along with the heartbeat - RequestTypeGenerateMetrics RequestType = "generate-metrics" - // RequestTypeDistributions is to send distribution type metrics accumulated by the - // client, and is sent periodically along with the heartbeat - RequestTypeDistributions RequestType = "distributions" - // RequestTypeAppClosing is sent when the telemetry client is stopped - RequestTypeAppClosing RequestType = "app-closing" - // RequestTypeDependenciesLoaded is sent if DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED - // is enabled. Sent when Start is called for the telemetry client. - RequestTypeDependenciesLoaded RequestType = "app-dependencies-loaded" - // RequestTypeAppClientConfigurationChange is sent if there are changes - // to the client library configuration - RequestTypeAppClientConfigurationChange RequestType = "app-client-configuration-change" - // RequestTypeAppProductChange is sent when products are enabled/disabled - RequestTypeAppProductChange RequestType = "app-product-change" - // RequestTypeAppIntegrationsChange is sent when the telemetry client starts - // with info on which integrations are used. - RequestTypeAppIntegrationsChange RequestType = "app-integrations-change" -) - -// Namespace describes an APM product to distinguish telemetry coming from -// different products used by the same application -type Namespace string - -const ( - // NamespaceGeneral is for general use - NamespaceGeneral Namespace = "general" - // NamespaceTracers is for distributed tracing - NamespaceTracers Namespace = "tracers" - // NamespaceProfilers is for continuous profiling - NamespaceProfilers Namespace = "profilers" - // NamespaceAppSec is for application security management - NamespaceAppSec Namespace = "appsec" - // NamespaceCiVisibility is for CI Visibility - NamespaceCiVisibility Namespace = "civisibility" -) - -// Application is identifying information about the app itself -type Application struct { - ServiceName string `json:"service_name"` - Env string `json:"env"` - ServiceVersion string `json:"service_version"` - TracerVersion string `json:"tracer_version"` - LanguageName string `json:"language_name"` - LanguageVersion string `json:"language_version"` - RuntimeName string `json:"runtime_name"` - RuntimeVersion string `json:"runtime_version"` - RuntimePatches string `json:"runtime_patches,omitempty"` -} - -// Host is identifying information about the host on which the app -// is running -type Host struct { - Hostname string `json:"hostname"` - OS string `json:"os"` - OSVersion string `json:"os_version,omitempty"` - Architecture string `json:"architecture"` - KernelName string `json:"kernel_name"` - KernelRelease string `json:"kernel_release"` - KernelVersion string `json:"kernel_version"` -} - -// AppStarted corresponds to the "app-started" request type -type AppStarted struct { - Configuration []Configuration `json:"configuration,omitempty"` - Products Products `json:"products,omitempty"` - AdditionalPayload []AdditionalPayload `json:"additional_payload,omitempty"` - Error Error `json:"error,omitempty"` - RemoteConfig *RemoteConfig `json:"remote_config,omitempty"` -} - -// IntegrationsChange corresponds to the app-integrations-change requesty type -type IntegrationsChange struct { - Integrations []Integration `json:"integrations"` -} - -// Integration is an integration that is configured to be traced automatically. -type Integration struct { - Name string `json:"name"` - Enabled bool `json:"enabled"` - Version string `json:"version,omitempty"` - AutoEnabled bool `json:"auto_enabled,omitempty"` - Compatible bool `json:"compatible,omitempty"` - Error string `json:"error,omitempty"` -} - -// ConfigurationChange corresponds to the `AppClientConfigurationChange` event -// that contains information about configuration changes since the app-started event -type ConfigurationChange struct { - Configuration []Configuration `json:"configuration"` - RemoteConfig *RemoteConfig `json:"remote_config,omitempty"` -} - -type Origin int - -const ( - OriginDefault Origin = iota - OriginCode - OriginDDConfig - OriginEnvVar - OriginRemoteConfig -) - -func (o Origin) String() string { - switch o { - case OriginDefault: - return "default" - case OriginCode: - return "code" - case OriginDDConfig: - return "dd_config" - case OriginEnvVar: - return "env_var" - case OriginRemoteConfig: - return "remote_config" - default: - return fmt.Sprintf("unknown origin %d", o) - } -} - -func (o Origin) MarshalJSON() ([]byte, error) { - var b bytes.Buffer - b.WriteString(`"`) - b.WriteString(o.String()) - b.WriteString(`"`) - return b.Bytes(), nil -} - -// Configuration is a library-specific configuration value -// that should be initialized through StringConfig, IntConfig, FloatConfig, or BoolConfig -type Configuration struct { - Name string `json:"name"` - Value interface{} `json:"value"` - // origin is the source of the config. It is one of {default, env_var, code, dd_config, remote_config}. - Origin Origin `json:"origin"` - Error Error `json:"error"` - IsOverriden bool `json:"is_overridden"` -} - -// TODO: be able to pass in origin, error, isOverriden info to config -// constructors - -// StringConfig returns a Configuration struct with a string value -func StringConfig(key string, val string) Configuration { - return Configuration{Name: key, Value: val} -} - -// IntConfig returns a Configuration struct with a int value -func IntConfig(key string, val int) Configuration { - return Configuration{Name: key, Value: val} -} - -// FloatConfig returns a Configuration struct with a float value -func FloatConfig(key string, val float64) Configuration { - return Configuration{Name: key, Value: val} -} - -// BoolConfig returns a Configuration struct with a bool value -func BoolConfig(key string, val bool) Configuration { - return Configuration{Name: key, Value: val} -} - -// ProductsPayload is the top-level key for the app-product-change payload. -type ProductsPayload struct { - Products Products `json:"products"` -} - -// Products specifies information about available products. -type Products struct { - AppSec ProductDetails `json:"appsec,omitempty"` - Profiler ProductDetails `json:"profiler,omitempty"` -} - -// ProductDetails specifies details about a product. -type ProductDetails struct { - Enabled bool `json:"enabled"` - Version string `json:"version,omitempty"` - Error Error `json:"error,omitempty"` -} - -// Dependencies stores a list of dependencies -type Dependencies struct { - Dependencies []Dependency `json:"dependencies"` -} - -// Dependency is a Go module on which the application depends. This information -// can be accesed at run-time through the runtime/debug.ReadBuildInfo API. -type Dependency struct { - Name string `json:"name"` - Version string `json:"version"` -} - -// RemoteConfig contains information about remote-config -type RemoteConfig struct { - UserEnabled string `json:"user_enabled"` // whether the library has made a request to fetch remote-config - ConfigsRecieved bool `json:"configs_received"` // whether the library receives a valid config response - RcID string `json:"rc_id,omitempty"` - RcRevision string `json:"rc_revision,omitempty"` - RcVersion string `json:"rc_version,omitempty"` - Error Error `json:"error,omitempty"` -} - -// Error stores error information about various tracer events -type Error struct { - Code int `json:"code"` - Message string `json:"message"` -} - -// AdditionalPayload can be used to add extra information to the app-started -// event -type AdditionalPayload struct { - Name string `json:"name"` - Value interface{} `json:"value"` -} - -// Metrics corresponds to the "generate-metrics" request type -type Metrics struct { - Namespace Namespace `json:"namespace"` - Series []Series `json:"series"` -} - -// DistributionMetrics corresponds to the "distributions" request type -type DistributionMetrics struct { - Namespace Namespace `json:"namespace"` - Series []DistributionSeries `json:"series"` -} - -// Series is a sequence of observations for a single named metric. -// The `Points` field will store a timestamp and value. -type Series struct { - Metric string `json:"metric"` - Points [][2]float64 `json:"points"` - // Interval is required for gauge and rate metrics - Interval int `json:"interval,omitempty"` - Type string `json:"type,omitempty"` - Tags []string `json:"tags"` - // Common distinguishes metrics which are cross-language vs. - // language-specific. - // - // NOTE: If this field isn't present in the request, the API assumes - // the metric is common. So we can't "omitempty" even though the - // field is technically optional. - Common bool `json:"common"` - Namespace string `json:"namespace"` -} - -// DistributionSeries is a sequence of observations for a distribution metric. -// Unlike `Series`, DistributionSeries does not store timestamps in `Points` -type DistributionSeries struct { - Metric string `json:"metric"` - Points []float64 `json:"points"` - Tags []string `json:"tags"` - // Common distinguishes metrics which are cross-language vs. - // language-specific. - // - // NOTE: If this field isn't present in the request, the API assumes - // the metric is common. So we can't "omitempty" even though the - // field is technically optional. - Common bool `json:"common"` -} diff --git a/internal/telemetry/option.go b/internal/telemetry/option.go deleted file mode 100644 index 8320d1a5fb..0000000000 --- a/internal/telemetry/option.go +++ /dev/null @@ -1,150 +0,0 @@ -// 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 2023 Datadog, Inc. - -package telemetry - -import ( - "errors" - "net/http" - "net/url" - "os" - "path/filepath" - - "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" -) - -// An Option is used to configure the telemetry client's settings -type Option func(*client) - -// ApplyOps sets various fields of the client. -// To be called before starting any product. -func (c *client) ApplyOps(opts ...Option) { - c.mu.Lock() - defer c.mu.Unlock() - for _, opt := range opts { - opt(c) - } -} - -// WithNamespace sets name as the telemetry client's namespace (tracer, profiler, appsec) -func WithNamespace(name Namespace) Option { - return func(client *client) { - client.Namespace = name - } -} - -// WithEnv sets the app specific environment for the telemetry client -func WithEnv(env string) Option { - return func(client *client) { - client.Env = env - } -} - -// WithService sets the app specific service for the telemetry client -func WithService(service string) Option { - return func(client *client) { - client.Service = service - } -} - -// WithVersion sets the app specific version for the telemetry client -func WithVersion(version string) Option { - return func(client *client) { - client.Version = version - } -} - -// WithHTTPClient specifies the http client for the telemetry client -func WithHTTPClient(httpClient *http.Client) Option { - return func(client *client) { - client.Client = httpClient - } -} - -// SyncFlushOnStop forces a sync flush on client stop -func SyncFlushOnStop() Option { - return func(client *client) { - client.syncFlushOnStop = true - } -} - -func defaultAPIKey() string { - return os.Getenv("DD_API_KEY") -} - -// WithAPIKey sets the DD API KEY for the telemetry client -func WithAPIKey(v string) Option { - return func(client *client) { - client.APIKey = v - } -} - -// WithURL sets the URL for where telemetry information is flushed to. -// For the URL, uploading through agent goes through -// -// ${AGENT_URL}/telemetry/proxy/api/v2/apmtelemetry -// -// for agentless: -// -// https://instrumentation-telemetry-intake.datadoghq.com/api/v2/apmtelemetry -// -// with an API key -func WithURL(agentless bool, agentURL string) Option { - return func(client *client) { - if agentless { - client.URL = getAgentlessURL() - } else { - u, err := url.Parse(agentURL) - if err == nil { - u.Path = "/telemetry/proxy/api/v2/apmtelemetry" - client.URL = u.String() - } else { - log("Agent URL %s is invalid, switching to agentless telemetry endpoint", agentURL) - client.URL = getAgentlessURL() - } - } - } -} - -func getAgentlessURL() string { - agentlessEndpointLock.RLock() - defer agentlessEndpointLock.RUnlock() - return agentlessURL -} - -// configEnvFallback returns the value of environment variable with the -// given key if def == "" -func configEnvFallback(key, def string) string { - if def != "" { - return def - } - return os.Getenv(key) -} - -// fallbackOps populates missing fields of the client with environment variables -// or default values. -func (c *client) fallbackOps() error { - if c.Client == nil { - WithHTTPClient(defaultHTTPClient)(c) - } - if len(c.APIKey) == 0 && c.URL == getAgentlessURL() { - WithAPIKey(defaultAPIKey())(c) - if c.APIKey == "" { - return errors.New("agentless is turned on, but valid DD API key was not found") - } - } - c.Service = configEnvFallback("DD_SERVICE", c.Service) - if len(c.Service) == 0 { - if name := globalconfig.ServiceName(); len(name) != 0 { - c.Service = name - } else { - c.Service = filepath.Base(os.Args[0]) - - } - } - c.Env = configEnvFallback("DD_ENV", c.Env) - c.Version = configEnvFallback("DD_VERSION", c.Version) - return nil -} diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go deleted file mode 100644 index 994dee89a8..0000000000 --- a/internal/telemetry/telemetry.go +++ /dev/null @@ -1,126 +0,0 @@ -// 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 2023 Datadog, Inc. - -// Package telemetry implements a client for sending telemetry information to -// Datadog regarding usage of an APM library such as tracing or profiling. -package telemetry - -import ( - "time" -) - -// ProductChange signals that the product has changed with some configuration -// information. It will start the telemetry client if it is not already started, -// unless enabled is false (in which case the call does nothing). ProductChange -// assumes that the telemetry client has been configured already by the caller -// using the ApplyOps method. -// If the client is already started, it will send any necessary -// app-product-change events to indicate whether the product is enabled, as well -// as an app-client-configuration-change event in case any new configuration -// information is available. -func (c *client) ProductChange(namespace Namespace, enabled bool, configuration []Configuration) { - c.mu.Lock() - defer c.mu.Unlock() - if !c.started { - if !enabled { - // Namespace is not enabled & telemetry isn't started, won't start it now. - return - } - c.start(configuration, namespace, true) - return - } - - var cfg []Configuration - cfg = append(cfg, c.globalAppConfig...) - cfg = append(cfg, configuration...) - c.configChange(cfg) - - switch namespace { - case NamespaceTracers, NamespaceProfilers, NamespaceAppSec, NamespaceCiVisibility: - c.productChange(namespace, enabled) - default: - log("unknown product namespace %q provided to ProductChange", namespace) - } -} - -// ConfigChange is a thread-safe method to enqueue an app-client-configuration-change event. -func (c *client) ConfigChange(configuration []Configuration) { - c.mu.Lock() - defer c.mu.Unlock() - c.configChange(configuration) -} - -// configChange enqueues an app-client-configuration-change event to be flushed. -// Must be called with c.mu locked. -func (c *client) configChange(configuration []Configuration) { - if !c.started { - log("attempted to send config change event, but telemetry client has not started") - return - } - if len(configuration) > 0 { - configChange := new(ConfigurationChange) - configChange.Configuration = configuration - configReq := c.newRequest(RequestTypeAppClientConfigurationChange) - configReq.Body.Payload = configChange - c.scheduleSubmit(configReq) - } -} - -// productChange enqueues an app-product-change event that signals a product has been `enabled`. -// Must be called with c.mu locked. An app-product-change event with enabled=true indicates -// that a certain product has been used for this application. -func (c *client) productChange(namespace Namespace, enabled bool) { - if !c.started { - log("attempted to send product change event, but telemetry client has not started") - return - } - products := new(ProductsPayload) - switch namespace { - case NamespaceAppSec: - products.Products.AppSec = ProductDetails{Enabled: enabled} - case NamespaceProfilers: - products.Products.Profiler = ProductDetails{Enabled: enabled} - case NamespaceTracers, NamespaceCiVisibility: - // Nothing to do - default: - log("unknown product namespace: %q. The app-product-change telemetry event will not send", namespace) - return - } - productReq := c.newRequest(RequestTypeAppProductChange) - productReq.Body.Payload = products - c.scheduleSubmit(productReq) -} - -// Integrations returns which integrations are tracked by telemetry. -func Integrations() []Integration { - contrib.Lock() - defer contrib.Unlock() - return contribPackages -} - -// LoadIntegration notifies telemetry that an integration is being used. -func LoadIntegration(name string) { - if Disabled() { - return - } - contrib.Lock() - defer contrib.Unlock() - contribPackages = append(contribPackages, Integration{Name: name, Enabled: true}) -} - -// Time is used to track a distribution metric that measures the time (ms) -// of some portion of code. It returns a function that should be called when -// the desired code finishes executing. -// For example, by adding: -// defer Time(namespace, "init_time", nil, true)() -// at the beginning of the tracer Start function, the tracer start time is measured -// and stored as a metric to be flushed by the global telemetry client. -func Time(namespace Namespace, name string, tags []string, common bool) (finish func()) { - start := time.Now() - return func() { - elapsed := time.Since(start) - GlobalClient.Record(namespace, MetricKindDist, name, float64(elapsed.Milliseconds()), tags, common) - } -} diff --git a/internal/telemetry/telemetry_test.go b/internal/telemetry/telemetry_test.go deleted file mode 100644 index 87d40ff02a..0000000000 --- a/internal/telemetry/telemetry_test.go +++ /dev/null @@ -1,169 +0,0 @@ -// 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 2023 Datadog, Inc. - -// Package telemetry implements a client for sending telemetry information to -// Datadog regarding usage of an APM library such as tracing or profiling. - -package telemetry - -import ( - "context" - "net/http" - "net/http/httptest" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestProductEnabled(t *testing.T) { - client := new(client) - client.start(nil, NamespaceTracers, true) - client.productChange(NamespaceProfilers, true) - // should just contain app-product-change - require.Len(t, client.requests, 1) - body := client.requests[0].Body - - assert.Equal(t, RequestTypeAppProductChange, body.RequestType) - var productsPayload = body.Payload.(*ProductsPayload) - assert.True(t, productsPayload.Products.Profiler.Enabled) -} - -func TestConfigChange(t *testing.T) { - client := new(client) - client.start(nil, NamespaceTracers, true) - client.configChange([]Configuration{BoolConfig("delta_profiles", true)}) - require.Len(t, client.requests, 1) - - body := client.requests[0].Body - assert.Equal(t, RequestTypeAppClientConfigurationChange, body.RequestType) - var configPayload = client.requests[0].Body.Payload.(*ConfigurationChange) - require.Len(t, configPayload.Configuration, 1) - - Check(t, configPayload.Configuration, "delta_profiles", true) -} - -// mockServer initializes a server that expects a strict amount of telemetry events. It saves these -// events in a slice until the expected number of events is reached. -// the `genTelemetry` argument accepts a function that should generate the expected telemetry events via calls to the global client -// the `expectedHits` argument specifies the number of telemetry events the server should expect. -func mockServer(ctx context.Context, t *testing.T, expectedHits int, genTelemetry func(), exclude ...RequestType) (waitForEvents func() []RequestType, cleanup func()) { - messages := make([]RequestType, expectedHits) - hits := 0 - done := make(chan struct{}) - mu := sync.Mutex{} - excludeEvent := make(map[RequestType]struct{}) - for _, event := range exclude { - excludeEvent[event] = struct{}{} - } - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/telemetry/proxy/api/v2/apmtelemetry" { - return - } - rType := RequestType(r.Header.Get("DD-Telemetry-Request-Type")) - if _, ok := excludeEvent[rType]; ok { - return - } - mu.Lock() - defer mu.Unlock() - if hits == expectedHits { - t.Fatalf("too many telemetry messages (expected %d)", expectedHits) - } - messages[hits] = rType - if hits++; hits == expectedHits { - done <- struct{}{} - } - })) - GlobalClient.ApplyOps(WithURL(false, server.URL)) - - return func() []RequestType { - genTelemetry() - select { - case <-ctx.Done(): - t.Fatal("TestProductChange timed out") - case <-done: - } - return messages - }, func() { - server.Close() - GlobalClient.Stop() - } -} - -func TestProductChange(t *testing.T) { - // this test is meant to ensure that a given sequence of ProductStart/ProductStop calls - // emits the expected telemetry events. - t.Setenv("DD_TELEMETRY_HEARTBEAT_INTERVAL", "1") - t.Setenv("DD_TRACE_STARTUP_LOGS", "0") - tests := []struct { - name string - wantedMessages []RequestType - genTelemetry func() - }{ - { - name: "tracer start, profiler start", - wantedMessages: []RequestType{RequestTypeAppStarted, RequestTypeDependenciesLoaded, RequestTypeAppClientConfigurationChange, RequestTypeAppProductChange}, - genTelemetry: func() { - GlobalClient.ProductChange(NamespaceTracers, true, nil) - GlobalClient.ProductChange(NamespaceProfilers, true, []Configuration{{Name: "key", Value: "value"}}) - }, - }, - /* This case is flaky (see #2688) - { - name: "profiler start, tracer start", - wantedMessages: []RequestType{RequestTypeAppStarted, RequestTypeDependenciesLoaded, RequestTypeAppClientConfigurationChange}, - genTelemetry: func() { - GlobalClient.ProductChange(NamespaceProfilers, true, nil) - GlobalClient.ProductChange(NamespaceTracers, true, []Configuration{{Name: "key", Value: "value"}}) - }, - }, - */ - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - telemetryClient := new(client) - defer MockGlobalClient(telemetryClient)() - excludedEvents := []RequestType{RequestTypeAppHeartbeat, RequestTypeGenerateMetrics, RequestTypeAppClosing} - waitForEvents, cleanup := mockServer(ctx, t, len(test.wantedMessages), test.genTelemetry, excludedEvents...) - defer cleanup() - messages := waitForEvents() - for i := range messages { - assert.Equal(t, test.wantedMessages[i], messages[i]) - } - }) - } -} - -// Test that globally registered app config is sent in telemetry requests including the configuration state. -func TestRegisterAppConfig(t *testing.T) { - client := new(client) - client.RegisterAppConfig("key1", "val1", OriginDefault) - - // Test that globally registered app config is sent in app-started payloads - client.start([]Configuration{{Name: "key2", Value: "val2", Origin: OriginDDConfig}}, NamespaceTracers, false) - - req := client.requests[0].Body - require.Equal(t, RequestTypeAppStarted, req.RequestType) - appStarted := req.Payload.(*AppStarted) - cfg := appStarted.Configuration - require.Contains(t, cfg, Configuration{Name: "key1", Value: "val1", Origin: OriginDefault}) - require.Contains(t, cfg, Configuration{Name: "key2", Value: "val2", Origin: OriginDDConfig}) - - // Test that globally registered app config is sent in app-client-configuration-change payloads - client.ProductChange(NamespaceTracers, true, []Configuration{{Name: "key3", Value: "val3", Origin: OriginCode}}) - - req = client.requests[2].Body - require.Equal(t, RequestTypeAppClientConfigurationChange, req.RequestType) - appConfigChange := req.Payload.(*ConfigurationChange) - cfg = appConfigChange.Configuration - require.Len(t, cfg, 2) - require.Contains(t, cfg, Configuration{Name: "key1", Value: "val1", Origin: OriginDefault}) - require.Contains(t, cfg, Configuration{Name: "key3", Value: "val3", Origin: OriginCode}) -} diff --git a/internal/telemetry/telemetrytest/telemetrytest.go b/internal/telemetry/telemetrytest/telemetrytest.go deleted file mode 100644 index 0348d39454..0000000000 --- a/internal/telemetry/telemetrytest/telemetrytest.go +++ /dev/null @@ -1,106 +0,0 @@ -// 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 2022 Datadog, Inc. - -// Package telemetrytest provides a mock implementation of the telemetry client for testing purposes -package telemetrytest - -import ( - "sync" - - "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" - - "github.com/stretchr/testify/mock" -) - -// MockClient implements Client and is used for testing purposes outside the telemetry package, -// e.g. the tracer and profiler. -type MockClient struct { - mock.Mock - mu sync.Mutex - Started bool - Configuration []telemetry.Configuration - Integrations []string - ProfilerEnabled bool - AsmEnabled bool - Metrics map[telemetry.Namespace]map[string]float64 -} - -func (c *MockClient) RegisterAppConfig(name string, val interface{}, origin telemetry.Origin) { - _ = c.Called(name, val, origin) -} - -// ProductChange starts and adds configuration data to the mock client. -func (c *MockClient) ProductChange(namespace telemetry.Namespace, enabled bool, configuration []telemetry.Configuration) { - c.mu.Lock() - defer c.mu.Unlock() - c.Started = true - c.Configuration = append(c.Configuration, configuration...) - if len(c.Metrics) == 0 { - c.Metrics = make(map[telemetry.Namespace]map[string]float64) - } - c.productChange(namespace, enabled) -} - -// ProductStop signals a product has stopped and disables that product in the mock client. -// ProductStop is NOOP for the tracer namespace, since the tracer is not considered a product. -func (c *MockClient) ProductStop(namespace telemetry.Namespace) { - c.mu.Lock() - defer c.mu.Unlock() - if namespace == telemetry.NamespaceTracers { - return - } - c.productChange(namespace, false) -} - -// ProductChange signals that a certain product is enabled or disabled for the mock client. -func (c *MockClient) productChange(namespace telemetry.Namespace, enabled bool) { - switch namespace { - case telemetry.NamespaceAppSec: - c.AsmEnabled = enabled - case telemetry.NamespaceProfilers: - c.ProfilerEnabled = enabled - case telemetry.NamespaceTracers: - return - default: - panic("invalid product namespace") - } -} - -// Record stores the value for the given metric. It is currently mocked for `Gauge` and `Distribution` metric types. -func (c *MockClient) Record(ns telemetry.Namespace, _ telemetry.MetricKind, name string, val float64, tags []string, common bool) { - c.On("Gauge", ns, name, val, tags, common).Return() - c.On("Record", ns, name, val, tags, common).Return() - _ = c.Called(ns, name, val, tags, common) - - c.mu.Lock() - defer c.mu.Unlock() - // record the val for tests that assert based on the value - if _, ok := c.Metrics[ns]; !ok { - c.Metrics[ns] = map[string]float64{} - } - c.Metrics[ns][name] = val -} - -// Count counts the value for the given metric -func (c *MockClient) Count(ns telemetry.Namespace, name string, val float64, tags []string, common bool) { - c.On("Count", ns, name, val, tags, common).Return() - _ = c.Called(ns, name, val, tags, common) -} - -// Stop is NOOP for the mock client. -func (c *MockClient) Stop() { -} - -// ApplyOps is used to record the number of ApplyOps method calls. -func (c *MockClient) ApplyOps(args ...telemetry.Option) { - c.On("ApplyOps", args).Return() - _ = c.Called(args) -} - -// ConfigChange is a mock for the ConfigChange method. -func (c *MockClient) ConfigChange(args []telemetry.Configuration) { - c.On("ConfigChange", args).Return() - _ = c.Called(args) -} diff --git a/internal/telemetry/utils.go b/internal/telemetry/utils.go deleted file mode 100644 index d2e454ecc1..0000000000 --- a/internal/telemetry/utils.go +++ /dev/null @@ -1,101 +0,0 @@ -// 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 2023 Datadog, Inc. - -// Package telemetry implements a client for sending telemetry information to -// Datadog regarding usage of an APM library such as tracing or profiling. -package telemetry - -import ( - "fmt" - "math" - "sort" - "strings" -) - -// MockGlobalClient replaces the global telemetry client with a custom -// implementation of TelemetryClient. It returns a function that can be deferred -// to reset the global telemetry client to its previous value. -func MockGlobalClient(client Client) func() { - globalClient.Lock() - defer globalClient.Unlock() - oldClient := GlobalClient - GlobalClient = client - return func() { - globalClient.Lock() - defer globalClient.Unlock() - GlobalClient = oldClient - } -} - -// Errorfer is an interface that allows to call the `Errorf` method. -// This is the same interface as `testing.T` because the goal of this -// interface is to remove the need to import `testing` in this package -// that is actually imported by users. -type Errorfer interface { - Errorf(format string, args ...any) -} - -// Check is a testing utility to assert that a target key in config contains the expected value -func Check(t Errorfer, configuration []Configuration, key string, expected interface{}) { - for _, kv := range configuration { - if kv.Name == key { - if kv.Value != expected { - t.Errorf("configuration %s: wanted '%v' type:%T, got '%v' type:%T", key, expected, expected, kv.Value, kv.Value) - } - return - } - } - t.Errorf("missing configuration %s", key) -} - -// SetAgentlessEndpoint is used for testing purposes to replace the real agentless -// endpoint with a custom one -func SetAgentlessEndpoint(endpoint string) string { - agentlessEndpointLock.Lock() - defer agentlessEndpointLock.Unlock() - prev := agentlessURL - agentlessURL = endpoint - return prev -} - -// Sanitize ensures the configuration values are valid and compatible. -// It removes NaN and Inf values and converts string slices and maps into comma-separated strings. -func Sanitize(c Configuration) Configuration { - switch val := c.Value.(type) { - case float64: - if math.IsNaN(val) || math.IsInf(val, 0) { - // Those values cause marshalling errors. - // https://github.com/golang/go/issues/59627 - c.Value = nil - } - case []string: - // The telemetry API only supports primitive types. - c.Value = strings.Join(val, ",") - case map[string]interface{}: - // The telemetry API only supports primitive types. - // Sort the keys to ensure the order is deterministic. - // This is technically not required but makes testing easier + it's not in a hot path. - keys := make([]string, 0, len(val)) - for k := range val { - keys = append(keys, k) - } - sort.Strings(keys) - var sb strings.Builder - for _, k := range keys { - if sb.Len() > 0 { - sb.WriteString(",") - } - sb.WriteString(k) - sb.WriteString(":") - sb.WriteString(fmt.Sprint(val[k])) - } - c.Value = sb.String() - default: - var sb strings.Builder - sb.WriteString(fmt.Sprint(val)) - c.Value = sb.String() - } - return c -} From 2fe414d9d7f3d82a4a3da195c6db0aeb79fc21af Mon Sep 17 00:00:00 2001 From: Eliott Bouhana Date: Thu, 30 Jan 2025 20:52:26 +0100 Subject: [PATCH 3/9] rename newtelemetry to telemetry Signed-off-by: Eliott Bouhana --- internal/newtelemetry/telemetrytest/mock.go | 93 ------------------- internal/{newtelemetry => telemetry}/api.go | 4 +- .../{newtelemetry => telemetry}/client.go | 8 +- .../client_config.go | 10 +- .../client_test.go | 10 +- .../configuration.go | 4 +- .../dependencies.go | 4 +- .../distributions.go | 8 +- .../globalclient.go | 6 +- .../integration.go | 4 +- .../internal/knownmetrics/common_metrics.json | 0 .../knownmetrics/generator/generator.go | 4 +- .../internal/knownmetrics/golang_metrics.json | 0 .../internal/knownmetrics/known_metrics.go | 4 +- .../internal/mapper/app_closing.go | 2 +- .../internal/mapper/app_started.go | 2 +- .../internal/mapper/default.go | 2 +- .../internal/mapper/mapper.go | 2 +- .../internal/range.go | 0 .../internal/recorder.go | 0 .../internal/ringbuffer.go | 0 .../internal/ringbuffer_test.go | 0 .../internal/syncmap.go | 0 .../internal/syncpool.go | 0 .../internal/ticker.go | 0 .../internal/tracerconfig.go | 0 .../internal/transport/app_closing.go | 0 .../transport/app_configuration_change.go | 0 .../transport/app_dependencies_loaded.go | 0 .../transport/app_extended_heartbeat.go | 0 .../internal/transport/app_heartbeat.go | 0 .../transport/app_integration_change.go | 0 .../internal/transport/app_product_change.go | 0 .../internal/transport/app_started.go | 0 .../internal/transport/body.go | 0 .../internal/transport/conf_key_value.go | 0 .../internal/transport/distributions.go | 0 .../internal/transport/error.go | 0 .../internal/transport/generate-metrics.go | 0 .../internal/transport/logs.go | 0 .../internal/transport/message_batch.go | 0 .../internal/transport/namespace.go | 0 .../internal/transport/origin.go | 0 .../internal/transport/payload.go | 0 .../internal/transport/requesttype.go | 0 .../internal/writer.go | 2 +- .../internal/writer_test.go | 2 +- .../{newtelemetry => telemetry}/log/log.go | 18 ++-- .../{newtelemetry => telemetry}/logger.go | 6 +- .../metrichandle.go | 4 +- .../{newtelemetry => telemetry}/metrics.go | 8 +- .../{newtelemetry => telemetry}/product.go | 4 +- .../telemetrytest/globalclient_test.go | 74 +++++++-------- internal/telemetry/telemetrytest/mock.go | 93 +++++++++++++++++++ .../telemetrytest/record.go | 50 +++++----- 55 files changed, 211 insertions(+), 217 deletions(-) delete mode 100644 internal/newtelemetry/telemetrytest/mock.go rename internal/{newtelemetry => telemetry}/api.go (98%) rename internal/{newtelemetry => telemetry}/client.go (97%) rename internal/{newtelemetry => telemetry}/client_config.go (96%) rename internal/{newtelemetry => telemetry}/client_test.go (99%) rename internal/{newtelemetry => telemetry}/configuration.go (97%) rename internal/{newtelemetry => telemetry}/dependencies.go (95%) rename internal/{newtelemetry => telemetry}/distributions.go (90%) rename internal/{newtelemetry => telemetry}/globalclient.go (98%) rename internal/{newtelemetry => telemetry}/integration.go (90%) rename internal/{newtelemetry => telemetry}/internal/knownmetrics/common_metrics.json (100%) rename internal/{newtelemetry => telemetry}/internal/knownmetrics/generator/generator.go (96%) rename internal/{newtelemetry => telemetry}/internal/knownmetrics/golang_metrics.json (100%) rename internal/{newtelemetry => telemetry}/internal/knownmetrics/known_metrics.go (92%) rename internal/{newtelemetry => telemetry}/internal/mapper/app_closing.go (90%) rename internal/{newtelemetry => telemetry}/internal/mapper/app_started.go (95%) rename internal/{newtelemetry => telemetry}/internal/mapper/default.go (97%) rename internal/{newtelemetry => telemetry}/internal/mapper/mapper.go (89%) rename internal/{newtelemetry => telemetry}/internal/range.go (100%) rename internal/{newtelemetry => telemetry}/internal/recorder.go (100%) rename internal/{newtelemetry => telemetry}/internal/ringbuffer.go (100%) rename internal/{newtelemetry => telemetry}/internal/ringbuffer_test.go (100%) rename internal/{newtelemetry => telemetry}/internal/syncmap.go (100%) rename internal/{newtelemetry => telemetry}/internal/syncpool.go (100%) rename internal/{newtelemetry => telemetry}/internal/ticker.go (100%) rename internal/{newtelemetry => telemetry}/internal/tracerconfig.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/app_closing.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/app_configuration_change.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/app_dependencies_loaded.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/app_extended_heartbeat.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/app_heartbeat.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/app_integration_change.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/app_product_change.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/app_started.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/body.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/conf_key_value.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/distributions.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/error.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/generate-metrics.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/logs.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/message_batch.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/namespace.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/origin.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/payload.go (100%) rename internal/{newtelemetry => telemetry}/internal/transport/requesttype.go (100%) rename internal/{newtelemetry => telemetry}/internal/writer.go (99%) rename internal/{newtelemetry => telemetry}/internal/writer_test.go (98%) rename internal/{newtelemetry => telemetry}/log/log.go (65%) rename internal/{newtelemetry => telemetry}/logger.go (93%) rename internal/{newtelemetry => telemetry}/metrichandle.go (95%) rename internal/{newtelemetry => telemetry}/metrics.go (96%) rename internal/{newtelemetry => telemetry}/product.go (90%) rename internal/{newtelemetry => telemetry}/telemetrytest/globalclient_test.go (51%) create mode 100644 internal/telemetry/telemetrytest/mock.go rename internal/{newtelemetry => telemetry}/telemetrytest/record.go (64%) diff --git a/internal/newtelemetry/telemetrytest/mock.go b/internal/newtelemetry/telemetrytest/mock.go deleted file mode 100644 index fdf9855084..0000000000 --- a/internal/newtelemetry/telemetrytest/mock.go +++ /dev/null @@ -1,93 +0,0 @@ -// 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 2025 Datadog, Inc. - -// Package telemetrytest provides a mock implementation of the telemetry client for testing purposes -package telemetrytest - -import ( - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry" - - "github.com/stretchr/testify/mock" -) - -// MockClient implements Client and is used for testing purposes outside the telemetry package, -// e.g. the tracer and profiler. -type MockClient struct { - mock.Mock -} - -func (m *MockClient) Close() error { - return nil -} - -type MockMetricHandle struct { - mock.Mock -} - -func (m *MockMetricHandle) Submit(value float64) { - m.Called(value) -} - -func (m *MockMetricHandle) Get() float64 { - return m.Called().Get(0).(float64) -} - -func (m *MockClient) Count(namespace newtelemetry.Namespace, name string, tags []string) newtelemetry.MetricHandle { - return m.Called(namespace, name, tags).Get(0).(newtelemetry.MetricHandle) -} - -func (m *MockClient) Rate(namespace newtelemetry.Namespace, name string, tags []string) newtelemetry.MetricHandle { - return m.Called(namespace, name, tags).Get(0).(newtelemetry.MetricHandle) -} - -func (m *MockClient) Gauge(namespace newtelemetry.Namespace, name string, tags []string) newtelemetry.MetricHandle { - return m.Called(namespace, name, tags).Get(0).(newtelemetry.MetricHandle) -} - -func (m *MockClient) Distribution(namespace newtelemetry.Namespace, name string, tags []string) newtelemetry.MetricHandle { - return m.Called(namespace, name, tags).Get(0).(newtelemetry.MetricHandle) -} - -func (m *MockClient) Log(level newtelemetry.LogLevel, text string, options ...newtelemetry.LogOption) { - m.Called(level, text, options) -} - -func (m *MockClient) ProductStarted(product newtelemetry.Namespace) { - m.Called(product) -} - -func (m *MockClient) ProductStopped(product newtelemetry.Namespace) { - m.Called(product) -} - -func (m *MockClient) ProductStartError(product newtelemetry.Namespace, err error) { - m.Called(product, err) -} - -func (m *MockClient) RegisterAppConfig(key string, value any, origin newtelemetry.Origin) { - m.Called(key, value, origin) -} - -func (m *MockClient) RegisterAppConfigs(kvs ...newtelemetry.Configuration) { - m.Called(kvs) -} - -func (m *MockClient) MarkIntegrationAsLoaded(integration newtelemetry.Integration) { - m.Called(integration) -} - -func (m *MockClient) Flush() { - m.Called() -} - -func (m *MockClient) AppStart() { - m.Called() -} - -func (m *MockClient) AppStop() { - m.Called() -} - -var _ newtelemetry.Client = (*MockClient)(nil) diff --git a/internal/newtelemetry/api.go b/internal/telemetry/api.go similarity index 98% rename from internal/newtelemetry/api.go rename to internal/telemetry/api.go index 41cba3e6c7..275d3f23ec 100644 --- a/internal/newtelemetry/api.go +++ b/internal/telemetry/api.go @@ -3,12 +3,12 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2024 Datadog, Inc. -package newtelemetry +package telemetry import ( "io" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) // Namespace describes a product to distinguish telemetry coming from diff --git a/internal/newtelemetry/client.go b/internal/telemetry/client.go similarity index 97% rename from internal/newtelemetry/client.go rename to internal/telemetry/client.go index a37d25629d..6779e4edc9 100644 --- a/internal/newtelemetry/client.go +++ b/internal/telemetry/client.go @@ -3,7 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2024 Datadog, Inc. -package newtelemetry +package telemetry import ( "errors" @@ -12,9 +12,9 @@ import ( "sync" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/mapper" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/mapper" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) // NewClient creates a new telemetry client with the given service, environment, and version and config. diff --git a/internal/newtelemetry/client_config.go b/internal/telemetry/client_config.go similarity index 96% rename from internal/newtelemetry/client_config.go rename to internal/telemetry/client_config.go index 06d91d56f7..b6ff546c15 100644 --- a/internal/newtelemetry/client_config.go +++ b/internal/telemetry/client_config.go @@ -3,7 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2024 Datadog, Inc. -package newtelemetry +package telemetry import ( "fmt" @@ -15,7 +15,7 @@ import ( globalinternal "gopkg.in/DataDog/dd-trace-go.v1/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal" ) type ClientConfig struct { @@ -163,12 +163,6 @@ func defaultConfig(config ClientConfig) ClientConfig { } } - if config.HeartbeatInterval == 0 { - config.HeartbeatInterval = globalinternal.DurationEnv("DD_TELEMETRY_HEARTBEAT_INTERVAL", defaultHeartbeatInterval) - } else { - config.HeartbeatInterval = defaultAuthorizedHearbeatRange.Clamp(config.HeartbeatInterval) - } - if config.FlushInterval.Min == 0 { config.FlushInterval.Min = defaultFlushIntervalRange.Min } else { diff --git a/internal/newtelemetry/client_test.go b/internal/telemetry/client_test.go similarity index 99% rename from internal/newtelemetry/client_test.go rename to internal/telemetry/client_test.go index 66e2a508d5..2baaa42252 100644 --- a/internal/newtelemetry/client_test.go +++ b/internal/telemetry/client_test.go @@ -3,7 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2025 Datadog, Inc. -package newtelemetry +package telemetry import ( "bytes" @@ -25,9 +25,9 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" "gopkg.in/DataDog/dd-trace-go.v1/internal/osinfo" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" "gopkg.in/DataDog/dd-trace-go.v1/internal/version" ) @@ -661,7 +661,7 @@ func TestClientFlush(t *testing.T) { require.Len(t, logs.Logs, 1) assert.Equal(t, transport.LogLevelError, logs.Logs[0].Level) assert.Equal(t, "test", logs.Logs[0].Message) - assert.Contains(t, logs.Logs[0].StackTrace, "internal/newtelemetry/client_test.go") + assert.Contains(t, logs.Logs[0].StackTrace, "internal/telemetry/client_test.go") }, }, { @@ -676,7 +676,7 @@ func TestClientFlush(t *testing.T) { require.Len(t, logs.Logs, 1) assert.Equal(t, transport.LogLevelError, logs.Logs[0].Level) assert.Equal(t, "test", logs.Logs[0].Message) - assert.Contains(t, logs.Logs[0].StackTrace, "internal/newtelemetry/client_test.go") + assert.Contains(t, logs.Logs[0].StackTrace, "internal/telemetry/client_test.go") tags := strings.Split(logs.Logs[0].Tags, ",") assert.Contains(t, tags, "key:value") assert.Contains(t, tags, "key2:value2") diff --git a/internal/newtelemetry/configuration.go b/internal/telemetry/configuration.go similarity index 97% rename from internal/newtelemetry/configuration.go rename to internal/telemetry/configuration.go index 3cdbe76477..4a14c4f443 100644 --- a/internal/newtelemetry/configuration.go +++ b/internal/telemetry/configuration.go @@ -3,7 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2025 Datadog, Inc. -package newtelemetry +package telemetry import ( "encoding/json" @@ -14,7 +14,7 @@ import ( "strings" "sync" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) type configuration struct { diff --git a/internal/newtelemetry/dependencies.go b/internal/telemetry/dependencies.go similarity index 95% rename from internal/newtelemetry/dependencies.go rename to internal/telemetry/dependencies.go index 10e2bee3d3..b881aa3ac3 100644 --- a/internal/newtelemetry/dependencies.go +++ b/internal/telemetry/dependencies.go @@ -3,7 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2025 Datadog, Inc. -package newtelemetry +package telemetry import ( "runtime/debug" @@ -11,7 +11,7 @@ import ( "sync" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) type dependencies struct { diff --git a/internal/newtelemetry/distributions.go b/internal/telemetry/distributions.go similarity index 90% rename from internal/newtelemetry/distributions.go rename to internal/telemetry/distributions.go index a556936731..6405c67ae7 100644 --- a/internal/newtelemetry/distributions.go +++ b/internal/telemetry/distributions.go @@ -3,15 +3,15 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2025 Datadog, Inc. -package newtelemetry +package telemetry import ( "sync" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/knownmetrics" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/knownmetrics" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) type distributions struct { diff --git a/internal/newtelemetry/globalclient.go b/internal/telemetry/globalclient.go similarity index 98% rename from internal/newtelemetry/globalclient.go rename to internal/telemetry/globalclient.go index efd156bb0c..bf99d62d8d 100644 --- a/internal/newtelemetry/globalclient.go +++ b/internal/telemetry/globalclient.go @@ -3,7 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2024 Datadog, Inc. -package newtelemetry +package telemetry import ( "sync" @@ -11,8 +11,8 @@ import ( globalinternal "gopkg.in/DataDog/dd-trace-go.v1/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) var ( diff --git a/internal/newtelemetry/integration.go b/internal/telemetry/integration.go similarity index 90% rename from internal/newtelemetry/integration.go rename to internal/telemetry/integration.go index d77ef116cd..ff45af0494 100644 --- a/internal/newtelemetry/integration.go +++ b/internal/telemetry/integration.go @@ -3,12 +3,12 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2025 Datadog, Inc. -package newtelemetry +package telemetry import ( "sync" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) type integrations struct { diff --git a/internal/newtelemetry/internal/knownmetrics/common_metrics.json b/internal/telemetry/internal/knownmetrics/common_metrics.json similarity index 100% rename from internal/newtelemetry/internal/knownmetrics/common_metrics.json rename to internal/telemetry/internal/knownmetrics/common_metrics.json diff --git a/internal/newtelemetry/internal/knownmetrics/generator/generator.go b/internal/telemetry/internal/knownmetrics/generator/generator.go similarity index 96% rename from internal/newtelemetry/internal/knownmetrics/generator/generator.go rename to internal/telemetry/internal/knownmetrics/generator/generator.go index 1cdf13ff38..ddce6fc147 100644 --- a/internal/newtelemetry/internal/knownmetrics/generator/generator.go +++ b/internal/telemetry/internal/knownmetrics/generator/generator.go @@ -19,8 +19,8 @@ import ( "slices" "strings" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/knownmetrics" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/knownmetrics" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) // This represents the base64-encoded URL of api.github.com to download the configuration file. diff --git a/internal/newtelemetry/internal/knownmetrics/golang_metrics.json b/internal/telemetry/internal/knownmetrics/golang_metrics.json similarity index 100% rename from internal/newtelemetry/internal/knownmetrics/golang_metrics.json rename to internal/telemetry/internal/knownmetrics/golang_metrics.json diff --git a/internal/newtelemetry/internal/knownmetrics/known_metrics.go b/internal/telemetry/internal/knownmetrics/known_metrics.go similarity index 92% rename from internal/newtelemetry/internal/knownmetrics/known_metrics.go rename to internal/telemetry/internal/knownmetrics/known_metrics.go index 4fb1c6b079..fd6a90edcf 100644 --- a/internal/newtelemetry/internal/knownmetrics/known_metrics.go +++ b/internal/telemetry/internal/knownmetrics/known_metrics.go @@ -3,7 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2025 Datadog, Inc. -//go:generate go run gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/knownmetrics/generator +//go:generate go run gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/knownmetrics/generator package knownmetrics @@ -13,7 +13,7 @@ import ( "slices" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) //go:embed common_metrics.json diff --git a/internal/newtelemetry/internal/mapper/app_closing.go b/internal/telemetry/internal/mapper/app_closing.go similarity index 90% rename from internal/newtelemetry/internal/mapper/app_closing.go rename to internal/telemetry/internal/mapper/app_closing.go index aeba9c7adc..9a570d3ee2 100644 --- a/internal/newtelemetry/internal/mapper/app_closing.go +++ b/internal/telemetry/internal/mapper/app_closing.go @@ -6,7 +6,7 @@ package mapper import ( - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) // NewAppClosingMapper returns a new Mapper that appends an AppClosing payload to the given payloads and calls the underlying Mapper with it. diff --git a/internal/newtelemetry/internal/mapper/app_started.go b/internal/telemetry/internal/mapper/app_started.go similarity index 95% rename from internal/newtelemetry/internal/mapper/app_started.go rename to internal/telemetry/internal/mapper/app_started.go index f281672a17..64ce18b46e 100644 --- a/internal/newtelemetry/internal/mapper/app_started.go +++ b/internal/telemetry/internal/mapper/app_started.go @@ -7,7 +7,7 @@ package mapper import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) type appStartedReducer struct { diff --git a/internal/newtelemetry/internal/mapper/default.go b/internal/telemetry/internal/mapper/default.go similarity index 97% rename from internal/newtelemetry/internal/mapper/default.go rename to internal/telemetry/internal/mapper/default.go index 12f75a5fb8..6576821a96 100644 --- a/internal/newtelemetry/internal/mapper/default.go +++ b/internal/telemetry/internal/mapper/default.go @@ -10,7 +10,7 @@ import ( "golang.org/x/time/rate" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) // NewDefaultMapper returns a Mapper that transforms payloads into a MessageBatch and adds a heartbeat message. diff --git a/internal/newtelemetry/internal/mapper/mapper.go b/internal/telemetry/internal/mapper/mapper.go similarity index 89% rename from internal/newtelemetry/internal/mapper/mapper.go rename to internal/telemetry/internal/mapper/mapper.go index 84a376fee6..eaee0b95a6 100644 --- a/internal/newtelemetry/internal/mapper/mapper.go +++ b/internal/telemetry/internal/mapper/mapper.go @@ -6,7 +6,7 @@ package mapper import ( - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) // Mapper is an interface for transforming payloads to comply with different types of lifecycle events in the application. diff --git a/internal/newtelemetry/internal/range.go b/internal/telemetry/internal/range.go similarity index 100% rename from internal/newtelemetry/internal/range.go rename to internal/telemetry/internal/range.go diff --git a/internal/newtelemetry/internal/recorder.go b/internal/telemetry/internal/recorder.go similarity index 100% rename from internal/newtelemetry/internal/recorder.go rename to internal/telemetry/internal/recorder.go diff --git a/internal/newtelemetry/internal/ringbuffer.go b/internal/telemetry/internal/ringbuffer.go similarity index 100% rename from internal/newtelemetry/internal/ringbuffer.go rename to internal/telemetry/internal/ringbuffer.go diff --git a/internal/newtelemetry/internal/ringbuffer_test.go b/internal/telemetry/internal/ringbuffer_test.go similarity index 100% rename from internal/newtelemetry/internal/ringbuffer_test.go rename to internal/telemetry/internal/ringbuffer_test.go diff --git a/internal/newtelemetry/internal/syncmap.go b/internal/telemetry/internal/syncmap.go similarity index 100% rename from internal/newtelemetry/internal/syncmap.go rename to internal/telemetry/internal/syncmap.go diff --git a/internal/newtelemetry/internal/syncpool.go b/internal/telemetry/internal/syncpool.go similarity index 100% rename from internal/newtelemetry/internal/syncpool.go rename to internal/telemetry/internal/syncpool.go diff --git a/internal/newtelemetry/internal/ticker.go b/internal/telemetry/internal/ticker.go similarity index 100% rename from internal/newtelemetry/internal/ticker.go rename to internal/telemetry/internal/ticker.go diff --git a/internal/newtelemetry/internal/tracerconfig.go b/internal/telemetry/internal/tracerconfig.go similarity index 100% rename from internal/newtelemetry/internal/tracerconfig.go rename to internal/telemetry/internal/tracerconfig.go diff --git a/internal/newtelemetry/internal/transport/app_closing.go b/internal/telemetry/internal/transport/app_closing.go similarity index 100% rename from internal/newtelemetry/internal/transport/app_closing.go rename to internal/telemetry/internal/transport/app_closing.go diff --git a/internal/newtelemetry/internal/transport/app_configuration_change.go b/internal/telemetry/internal/transport/app_configuration_change.go similarity index 100% rename from internal/newtelemetry/internal/transport/app_configuration_change.go rename to internal/telemetry/internal/transport/app_configuration_change.go diff --git a/internal/newtelemetry/internal/transport/app_dependencies_loaded.go b/internal/telemetry/internal/transport/app_dependencies_loaded.go similarity index 100% rename from internal/newtelemetry/internal/transport/app_dependencies_loaded.go rename to internal/telemetry/internal/transport/app_dependencies_loaded.go diff --git a/internal/newtelemetry/internal/transport/app_extended_heartbeat.go b/internal/telemetry/internal/transport/app_extended_heartbeat.go similarity index 100% rename from internal/newtelemetry/internal/transport/app_extended_heartbeat.go rename to internal/telemetry/internal/transport/app_extended_heartbeat.go diff --git a/internal/newtelemetry/internal/transport/app_heartbeat.go b/internal/telemetry/internal/transport/app_heartbeat.go similarity index 100% rename from internal/newtelemetry/internal/transport/app_heartbeat.go rename to internal/telemetry/internal/transport/app_heartbeat.go diff --git a/internal/newtelemetry/internal/transport/app_integration_change.go b/internal/telemetry/internal/transport/app_integration_change.go similarity index 100% rename from internal/newtelemetry/internal/transport/app_integration_change.go rename to internal/telemetry/internal/transport/app_integration_change.go diff --git a/internal/newtelemetry/internal/transport/app_product_change.go b/internal/telemetry/internal/transport/app_product_change.go similarity index 100% rename from internal/newtelemetry/internal/transport/app_product_change.go rename to internal/telemetry/internal/transport/app_product_change.go diff --git a/internal/newtelemetry/internal/transport/app_started.go b/internal/telemetry/internal/transport/app_started.go similarity index 100% rename from internal/newtelemetry/internal/transport/app_started.go rename to internal/telemetry/internal/transport/app_started.go diff --git a/internal/newtelemetry/internal/transport/body.go b/internal/telemetry/internal/transport/body.go similarity index 100% rename from internal/newtelemetry/internal/transport/body.go rename to internal/telemetry/internal/transport/body.go diff --git a/internal/newtelemetry/internal/transport/conf_key_value.go b/internal/telemetry/internal/transport/conf_key_value.go similarity index 100% rename from internal/newtelemetry/internal/transport/conf_key_value.go rename to internal/telemetry/internal/transport/conf_key_value.go diff --git a/internal/newtelemetry/internal/transport/distributions.go b/internal/telemetry/internal/transport/distributions.go similarity index 100% rename from internal/newtelemetry/internal/transport/distributions.go rename to internal/telemetry/internal/transport/distributions.go diff --git a/internal/newtelemetry/internal/transport/error.go b/internal/telemetry/internal/transport/error.go similarity index 100% rename from internal/newtelemetry/internal/transport/error.go rename to internal/telemetry/internal/transport/error.go diff --git a/internal/newtelemetry/internal/transport/generate-metrics.go b/internal/telemetry/internal/transport/generate-metrics.go similarity index 100% rename from internal/newtelemetry/internal/transport/generate-metrics.go rename to internal/telemetry/internal/transport/generate-metrics.go diff --git a/internal/newtelemetry/internal/transport/logs.go b/internal/telemetry/internal/transport/logs.go similarity index 100% rename from internal/newtelemetry/internal/transport/logs.go rename to internal/telemetry/internal/transport/logs.go diff --git a/internal/newtelemetry/internal/transport/message_batch.go b/internal/telemetry/internal/transport/message_batch.go similarity index 100% rename from internal/newtelemetry/internal/transport/message_batch.go rename to internal/telemetry/internal/transport/message_batch.go diff --git a/internal/newtelemetry/internal/transport/namespace.go b/internal/telemetry/internal/transport/namespace.go similarity index 100% rename from internal/newtelemetry/internal/transport/namespace.go rename to internal/telemetry/internal/transport/namespace.go diff --git a/internal/newtelemetry/internal/transport/origin.go b/internal/telemetry/internal/transport/origin.go similarity index 100% rename from internal/newtelemetry/internal/transport/origin.go rename to internal/telemetry/internal/transport/origin.go diff --git a/internal/newtelemetry/internal/transport/payload.go b/internal/telemetry/internal/transport/payload.go similarity index 100% rename from internal/newtelemetry/internal/transport/payload.go rename to internal/telemetry/internal/transport/payload.go diff --git a/internal/newtelemetry/internal/transport/requesttype.go b/internal/telemetry/internal/transport/requesttype.go similarity index 100% rename from internal/newtelemetry/internal/transport/requesttype.go rename to internal/telemetry/internal/transport/requesttype.go diff --git a/internal/newtelemetry/internal/writer.go b/internal/telemetry/internal/writer.go similarity index 99% rename from internal/newtelemetry/internal/writer.go rename to internal/telemetry/internal/writer.go index b26c476748..4ed5a91262 100644 --- a/internal/newtelemetry/internal/writer.go +++ b/internal/telemetry/internal/writer.go @@ -22,8 +22,8 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/hostname" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" "gopkg.in/DataDog/dd-trace-go.v1/internal/osinfo" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" "gopkg.in/DataDog/dd-trace-go.v1/internal/version" ) diff --git a/internal/newtelemetry/internal/writer_test.go b/internal/telemetry/internal/writer_test.go similarity index 98% rename from internal/newtelemetry/internal/writer_test.go rename to internal/telemetry/internal/writer_test.go index bacf6accf7..33f772f029 100644 --- a/internal/newtelemetry/internal/writer_test.go +++ b/internal/telemetry/internal/writer_test.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) func TestNewWriter_ValidConfig(t *testing.T) { diff --git a/internal/newtelemetry/log/log.go b/internal/telemetry/log/log.go similarity index 65% rename from internal/newtelemetry/log/log.go rename to internal/telemetry/log/log.go index eabe43e9e1..1421f439d3 100644 --- a/internal/newtelemetry/log/log.go +++ b/internal/telemetry/log/log.go @@ -8,18 +8,18 @@ package log import ( "fmt" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" ) -func divideArgs(args []any) ([]newtelemetry.LogOption, []any) { +func divideArgs(args []any) ([]telemetry.LogOption, []any) { if len(args) == 0 { return nil, nil } - var options []newtelemetry.LogOption + var options []telemetry.LogOption var fmtArgs []any for _, arg := range args { - if opt, ok := arg.(newtelemetry.LogOption); ok { + if opt, ok := arg.(telemetry.LogOption); ok { options = append(options, opt) } else { fmtArgs = append(fmtArgs, arg) @@ -30,20 +30,20 @@ func divideArgs(args []any) ([]newtelemetry.LogOption, []any) { // Debug sends a telemetry payload with a debug log message to the backend. func Debug(format string, args ...any) { - log(newtelemetry.LogDebug, format, args) + log(telemetry.LogDebug, format, args) } // Warn sends a telemetry payload with a warning log message to the backend. func Warn(format string, args ...any) { - log(newtelemetry.LogWarn, format, args) + log(telemetry.LogWarn, format, args) } // Error sends a telemetry payload with an error log message to the backend. func Error(format string, args ...any) { - log(newtelemetry.LogError, format, args) + log(telemetry.LogError, format, args) } -func log(lvl newtelemetry.LogLevel, format string, args []any) { +func log(lvl telemetry.LogLevel, format string, args []any) { opts, fmtArgs := divideArgs(args) - newtelemetry.Log(lvl, fmt.Sprintf(format, fmtArgs...), opts...) + telemetry.Log(lvl, fmt.Sprintf(format, fmtArgs...), opts...) } diff --git a/internal/newtelemetry/logger.go b/internal/telemetry/logger.go similarity index 93% rename from internal/newtelemetry/logger.go rename to internal/telemetry/logger.go index b9a424d7d4..a316d3d452 100644 --- a/internal/newtelemetry/logger.go +++ b/internal/telemetry/logger.go @@ -3,7 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2025 Datadog, Inc. -package newtelemetry +package telemetry import ( "runtime" @@ -11,8 +11,8 @@ import ( "sync/atomic" "time" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) // WithTags returns a LogOption that sets the tags for the telemetry log message. Tags are key-value pairs that are then diff --git a/internal/newtelemetry/metrichandle.go b/internal/telemetry/metrichandle.go similarity index 95% rename from internal/newtelemetry/metrichandle.go rename to internal/telemetry/metrichandle.go index a191c953f2..fe91398d03 100644 --- a/internal/newtelemetry/metrichandle.go +++ b/internal/telemetry/metrichandle.go @@ -3,7 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2025 Datadog, Inc. -package newtelemetry +package telemetry import ( "math" @@ -11,7 +11,7 @@ import ( "sync/atomic" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal" ) // noopMetricHandle is a no-op implementation of a metric handle. diff --git a/internal/newtelemetry/metrics.go b/internal/telemetry/metrics.go similarity index 96% rename from internal/newtelemetry/metrics.go rename to internal/telemetry/metrics.go index 0ab4c28cc0..02ec013e54 100644 --- a/internal/newtelemetry/metrics.go +++ b/internal/telemetry/metrics.go @@ -3,7 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2025 Datadog, Inc. -package newtelemetry +package telemetry import ( "fmt" @@ -14,9 +14,9 @@ import ( "time" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/knownmetrics" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/knownmetrics" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) // metricKey is used as a key in the metrics store hash map. diff --git a/internal/newtelemetry/product.go b/internal/telemetry/product.go similarity index 90% rename from internal/newtelemetry/product.go rename to internal/telemetry/product.go index 84c53e8242..6f98876c6d 100644 --- a/internal/newtelemetry/product.go +++ b/internal/telemetry/product.go @@ -3,12 +3,12 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2025 Datadog, Inc. -package newtelemetry +package telemetry import ( "sync" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) type products struct { diff --git a/internal/newtelemetry/telemetrytest/globalclient_test.go b/internal/telemetry/telemetrytest/globalclient_test.go similarity index 51% rename from internal/newtelemetry/telemetrytest/globalclient_test.go rename to internal/telemetry/telemetrytest/globalclient_test.go index 1f6b86983b..779449bbf0 100644 --- a/internal/newtelemetry/telemetrytest/globalclient_test.go +++ b/internal/telemetry/telemetrytest/globalclient_test.go @@ -12,76 +12,76 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) func TestGlobalClient(t *testing.T) { t.Run("config", func(t *testing.T) { recorder := new(RecordClient) - defer newtelemetry.MockClient(recorder)() + defer telemetry.MockClient(recorder)() - newtelemetry.RegisterAppConfig("key", "value", newtelemetry.OriginCode) + telemetry.RegisterAppConfig("key", "value", telemetry.OriginCode) assert.Len(t, recorder.Configuration, 1) assert.Equal(t, "key", recorder.Configuration[0].Name) assert.Equal(t, "value", recorder.Configuration[0].Value) - assert.Equal(t, newtelemetry.OriginCode, recorder.Configuration[0].Origin) + assert.Equal(t, telemetry.OriginCode, recorder.Configuration[0].Origin) }) t.Run("configs", func(t *testing.T) { recorder := new(RecordClient) - defer newtelemetry.MockClient(recorder)() + defer telemetry.MockClient(recorder)() - newtelemetry.RegisterAppConfigs(newtelemetry.Configuration{Name: "key", Value: "value", Origin: newtelemetry.OriginCode}, newtelemetry.Configuration{Name: "key2", Value: "value2", Origin: newtelemetry.OriginRemoteConfig}) + telemetry.RegisterAppConfigs(telemetry.Configuration{Name: "key", Value: "value", Origin: telemetry.OriginCode}, telemetry.Configuration{Name: "key2", Value: "value2", Origin: telemetry.OriginRemoteConfig}) assert.Len(t, recorder.Configuration, 2) assert.Equal(t, "key", recorder.Configuration[0].Name) assert.Equal(t, "value", recorder.Configuration[0].Value) - assert.Equal(t, newtelemetry.OriginCode, recorder.Configuration[0].Origin) + assert.Equal(t, telemetry.OriginCode, recorder.Configuration[0].Origin) assert.Equal(t, "key2", recorder.Configuration[1].Name) assert.Equal(t, "value2", recorder.Configuration[1].Value) - assert.Equal(t, newtelemetry.OriginRemoteConfig, recorder.Configuration[1].Origin) + assert.Equal(t, telemetry.OriginRemoteConfig, recorder.Configuration[1].Origin) }) t.Run("app-stop", func(t *testing.T) { recorder := new(RecordClient) - defer newtelemetry.MockClient(recorder)() + defer telemetry.MockClient(recorder)() - newtelemetry.StopApp() + telemetry.StopApp() assert.True(t, recorder.Stopped) }) t.Run("product-start", func(t *testing.T) { recorder := new(RecordClient) - defer newtelemetry.MockClient(recorder)() + defer telemetry.MockClient(recorder)() - newtelemetry.ProductStarted(newtelemetry.NamespaceAppSec) + telemetry.ProductStarted(telemetry.NamespaceAppSec) assert.Len(t, recorder.Products, 1) - assert.True(t, recorder.Products[newtelemetry.NamespaceAppSec]) + assert.True(t, recorder.Products[telemetry.NamespaceAppSec]) }) t.Run("product-stopped", func(t *testing.T) { recorder := new(RecordClient) - defer newtelemetry.MockClient(recorder)() + defer telemetry.MockClient(recorder)() - newtelemetry.ProductStopped(newtelemetry.NamespaceAppSec) + telemetry.ProductStopped(telemetry.NamespaceAppSec) assert.Len(t, recorder.Products, 1) - assert.False(t, recorder.Products[newtelemetry.NamespaceAppSec]) + assert.False(t, recorder.Products[telemetry.NamespaceAppSec]) }) t.Run("integration-loaded", func(t *testing.T) { recorder := new(RecordClient) - defer newtelemetry.MockClient(recorder)() + defer telemetry.MockClient(recorder)() - newtelemetry.LoadIntegration("test-integration") + telemetry.LoadIntegration("test-integration") assert.Len(t, recorder.Integrations, 1) assert.Equal(t, "test-integration", recorder.Integrations[0].Name) }) t.Run("mark-integration-as-loaded", func(t *testing.T) { recorder := new(RecordClient) - defer newtelemetry.MockClient(recorder)() + defer telemetry.MockClient(recorder)() - newtelemetry.MarkIntegrationAsLoaded(newtelemetry.Integration{Name: "test-integration", Version: "1.0.0"}) + telemetry.MarkIntegrationAsLoaded(telemetry.Integration{Name: "test-integration", Version: "1.0.0"}) assert.Len(t, recorder.Integrations, 1) assert.Equal(t, "test-integration", recorder.Integrations[0].Name) assert.Equal(t, "1.0.0", recorder.Integrations[0].Version) @@ -89,42 +89,42 @@ func TestGlobalClient(t *testing.T) { t.Run("count", func(t *testing.T) { recorder := new(RecordClient) - defer newtelemetry.MockClient(recorder)() + defer telemetry.MockClient(recorder)() - newtelemetry.Count(newtelemetry.NamespaceTracers, "init_time", nil).Submit(1) + telemetry.Count(telemetry.NamespaceTracers, "init_time", nil).Submit(1) assert.Len(t, recorder.Metrics, 1) - require.Contains(t, recorder.Metrics, MetricKey{Name: "init_time", Namespace: newtelemetry.NamespaceTracers, Kind: string(transport.CountMetric)}) - assert.Equal(t, 1.0, recorder.Metrics[MetricKey{Name: "init_time", Namespace: newtelemetry.NamespaceTracers, Kind: string(transport.CountMetric)}].Get()) + require.Contains(t, recorder.Metrics, MetricKey{Name: "init_time", Namespace: telemetry.NamespaceTracers, Kind: string(transport.CountMetric)}) + assert.Equal(t, 1.0, recorder.Metrics[MetricKey{Name: "init_time", Namespace: telemetry.NamespaceTracers, Kind: string(transport.CountMetric)}].Get()) }) t.Run("gauge", func(t *testing.T) { recorder := new(RecordClient) - defer newtelemetry.MockClient(recorder)() + defer telemetry.MockClient(recorder)() - newtelemetry.Gauge(newtelemetry.NamespaceTracers, "init_time", nil).Submit(1) + telemetry.Gauge(telemetry.NamespaceTracers, "init_time", nil).Submit(1) assert.Len(t, recorder.Metrics, 1) - require.Contains(t, recorder.Metrics, MetricKey{Name: "init_time", Namespace: newtelemetry.NamespaceTracers, Kind: string(transport.GaugeMetric)}) - assert.Equal(t, 1.0, recorder.Metrics[MetricKey{Name: "init_time", Namespace: newtelemetry.NamespaceTracers, Kind: string(transport.GaugeMetric)}].Get()) + require.Contains(t, recorder.Metrics, MetricKey{Name: "init_time", Namespace: telemetry.NamespaceTracers, Kind: string(transport.GaugeMetric)}) + assert.Equal(t, 1.0, recorder.Metrics[MetricKey{Name: "init_time", Namespace: telemetry.NamespaceTracers, Kind: string(transport.GaugeMetric)}].Get()) }) t.Run("rate", func(t *testing.T) { recorder := new(RecordClient) - defer newtelemetry.MockClient(recorder)() + defer telemetry.MockClient(recorder)() - newtelemetry.Rate(newtelemetry.NamespaceTracers, "init_time", nil).Submit(1) + telemetry.Rate(telemetry.NamespaceTracers, "init_time", nil).Submit(1) assert.Len(t, recorder.Metrics, 1) - require.Contains(t, recorder.Metrics, MetricKey{Name: "init_time", Namespace: newtelemetry.NamespaceTracers, Kind: string(transport.RateMetric)}) - assert.False(t, math.IsNaN(recorder.Metrics[MetricKey{Name: "init_time", Namespace: newtelemetry.NamespaceTracers, Kind: string(transport.RateMetric)}].Get())) + require.Contains(t, recorder.Metrics, MetricKey{Name: "init_time", Namespace: telemetry.NamespaceTracers, Kind: string(transport.RateMetric)}) + assert.False(t, math.IsNaN(recorder.Metrics[MetricKey{Name: "init_time", Namespace: telemetry.NamespaceTracers, Kind: string(transport.RateMetric)}].Get())) }) t.Run("distribution", func(t *testing.T) { recorder := new(RecordClient) - defer newtelemetry.MockClient(recorder)() + defer telemetry.MockClient(recorder)() - newtelemetry.Distribution(newtelemetry.NamespaceGeneral, "init_time", nil).Submit(1) + telemetry.Distribution(telemetry.NamespaceGeneral, "init_time", nil).Submit(1) assert.Len(t, recorder.Metrics, 1) - require.Contains(t, recorder.Metrics, MetricKey{Name: "init_time", Namespace: newtelemetry.NamespaceGeneral, Kind: string(transport.DistMetric)}) - assert.Equal(t, 1.0, recorder.Metrics[MetricKey{Name: "init_time", Namespace: newtelemetry.NamespaceGeneral, Kind: string(transport.DistMetric)}].Get()) + require.Contains(t, recorder.Metrics, MetricKey{Name: "init_time", Namespace: telemetry.NamespaceGeneral, Kind: string(transport.DistMetric)}) + assert.Equal(t, 1.0, recorder.Metrics[MetricKey{Name: "init_time", Namespace: telemetry.NamespaceGeneral, Kind: string(transport.DistMetric)}].Get()) }) } diff --git a/internal/telemetry/telemetrytest/mock.go b/internal/telemetry/telemetrytest/mock.go new file mode 100644 index 0000000000..2991101f37 --- /dev/null +++ b/internal/telemetry/telemetrytest/mock.go @@ -0,0 +1,93 @@ +// 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 2025 Datadog, Inc. + +// Package telemetrytest provides a mock implementation of the telemetry client for testing purposes +package telemetrytest + +import ( + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" + + "github.com/stretchr/testify/mock" +) + +// MockClient implements Client and is used for testing purposes outside the telemetry package, +// e.g. the tracer and profiler. +type MockClient struct { + mock.Mock +} + +func (m *MockClient) Close() error { + return nil +} + +type MockMetricHandle struct { + mock.Mock +} + +func (m *MockMetricHandle) Submit(value float64) { + m.Called(value) +} + +func (m *MockMetricHandle) Get() float64 { + return m.Called().Get(0).(float64) +} + +func (m *MockClient) Count(namespace telemetry.Namespace, name string, tags []string) telemetry.MetricHandle { + return m.Called(namespace, name, tags).Get(0).(telemetry.MetricHandle) +} + +func (m *MockClient) Rate(namespace telemetry.Namespace, name string, tags []string) telemetry.MetricHandle { + return m.Called(namespace, name, tags).Get(0).(telemetry.MetricHandle) +} + +func (m *MockClient) Gauge(namespace telemetry.Namespace, name string, tags []string) telemetry.MetricHandle { + return m.Called(namespace, name, tags).Get(0).(telemetry.MetricHandle) +} + +func (m *MockClient) Distribution(namespace telemetry.Namespace, name string, tags []string) telemetry.MetricHandle { + return m.Called(namespace, name, tags).Get(0).(telemetry.MetricHandle) +} + +func (m *MockClient) Log(level telemetry.LogLevel, text string, options ...telemetry.LogOption) { + m.Called(level, text, options) +} + +func (m *MockClient) ProductStarted(product telemetry.Namespace) { + m.Called(product) +} + +func (m *MockClient) ProductStopped(product telemetry.Namespace) { + m.Called(product) +} + +func (m *MockClient) ProductStartError(product telemetry.Namespace, err error) { + m.Called(product, err) +} + +func (m *MockClient) RegisterAppConfig(key string, value any, origin telemetry.Origin) { + m.Called(key, value, origin) +} + +func (m *MockClient) RegisterAppConfigs(kvs ...telemetry.Configuration) { + m.Called(kvs) +} + +func (m *MockClient) MarkIntegrationAsLoaded(integration telemetry.Integration) { + m.Called(integration) +} + +func (m *MockClient) Flush() { + m.Called() +} + +func (m *MockClient) AppStart() { + m.Called() +} + +func (m *MockClient) AppStop() { + m.Called() +} + +var _ telemetry.Client = (*MockClient)(nil) diff --git a/internal/newtelemetry/telemetrytest/record.go b/internal/telemetry/telemetrytest/record.go similarity index 64% rename from internal/newtelemetry/telemetrytest/record.go rename to internal/telemetry/telemetrytest/record.go index b73d398f54..0e6a95361b 100644 --- a/internal/newtelemetry/telemetrytest/record.go +++ b/internal/telemetry/telemetrytest/record.go @@ -12,12 +12,12 @@ import ( "testing" "time" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry" - "gopkg.in/DataDog/dd-trace-go.v1/internal/newtelemetry/internal/transport" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/internal/transport" ) type MetricKey struct { - Namespace newtelemetry.Namespace + Namespace telemetry.Namespace Name string Tags string Kind string @@ -27,10 +27,10 @@ type RecordClient struct { mu sync.Mutex Started bool Stopped bool - Configuration []newtelemetry.Configuration - Logs map[newtelemetry.LogLevel]string - Integrations []newtelemetry.Integration - Products map[newtelemetry.Namespace]bool + Configuration []telemetry.Configuration + Logs map[telemetry.LogLevel]string + Integrations []telemetry.Integration + Products map[telemetry.Namespace]bool Metrics map[MetricKey]*RecordMetricHandle } @@ -62,7 +62,7 @@ func (m *RecordMetricHandle) Get() float64 { return m.get(m) } -func (r *RecordClient) metric(kind string, namespace newtelemetry.Namespace, name string, tags []string, submit func(handle *RecordMetricHandle, value float64), get func(handle *RecordMetricHandle) float64) *RecordMetricHandle { +func (r *RecordClient) metric(kind string, namespace telemetry.Namespace, name string, tags []string, submit func(handle *RecordMetricHandle, value float64), get func(handle *RecordMetricHandle) float64) *RecordMetricHandle { r.mu.Lock() defer r.mu.Unlock() if r.Metrics == nil { @@ -76,7 +76,7 @@ func (r *RecordClient) metric(kind string, namespace newtelemetry.Namespace, nam return r.Metrics[key] } -func (r *RecordClient) Count(namespace newtelemetry.Namespace, name string, tags []string) newtelemetry.MetricHandle { +func (r *RecordClient) Count(namespace telemetry.Namespace, name string, tags []string) telemetry.MetricHandle { return r.metric(string(transport.CountMetric), namespace, name, tags, func(handle *RecordMetricHandle, value float64) { handle.count += value }, func(handle *RecordMetricHandle) float64 { @@ -84,7 +84,7 @@ func (r *RecordClient) Count(namespace newtelemetry.Namespace, name string, tags }) } -func (r *RecordClient) Rate(namespace newtelemetry.Namespace, name string, tags []string) newtelemetry.MetricHandle { +func (r *RecordClient) Rate(namespace telemetry.Namespace, name string, tags []string) telemetry.MetricHandle { handle := r.metric(string(transport.RateMetric), namespace, name, tags, func(handle *RecordMetricHandle, value float64) { handle.count += value handle.rate = float64(handle.count) / time.Since(handle.rateStart).Seconds() @@ -96,7 +96,7 @@ func (r *RecordClient) Rate(namespace newtelemetry.Namespace, name string, tags return handle } -func (r *RecordClient) Gauge(namespace newtelemetry.Namespace, name string, tags []string) newtelemetry.MetricHandle { +func (r *RecordClient) Gauge(namespace telemetry.Namespace, name string, tags []string) telemetry.MetricHandle { return r.metric(string(transport.GaugeMetric), namespace, name, tags, func(handle *RecordMetricHandle, value float64) { handle.gauge = value }, func(handle *RecordMetricHandle) float64 { @@ -104,7 +104,7 @@ func (r *RecordClient) Gauge(namespace newtelemetry.Namespace, name string, tags }) } -func (r *RecordClient) Distribution(namespace newtelemetry.Namespace, name string, tags []string) newtelemetry.MetricHandle { +func (r *RecordClient) Distribution(namespace telemetry.Namespace, name string, tags []string) telemetry.MetricHandle { return r.metric(string(transport.DistMetric), namespace, name, tags, func(handle *RecordMetricHandle, value float64) { handle.distrib = append(handle.distrib, value) }, func(handle *RecordMetricHandle) float64 { @@ -116,59 +116,59 @@ func (r *RecordClient) Distribution(namespace newtelemetry.Namespace, name strin }) } -func (r *RecordClient) Log(level newtelemetry.LogLevel, text string, _ ...newtelemetry.LogOption) { +func (r *RecordClient) Log(level telemetry.LogLevel, text string, _ ...telemetry.LogOption) { r.mu.Lock() defer r.mu.Unlock() if r.Logs == nil { - r.Logs = make(map[newtelemetry.LogLevel]string) + r.Logs = make(map[telemetry.LogLevel]string) } r.Logs[level] = text } -func (r *RecordClient) ProductStarted(product newtelemetry.Namespace) { +func (r *RecordClient) ProductStarted(product telemetry.Namespace) { r.mu.Lock() defer r.mu.Unlock() if r.Products == nil { - r.Products = make(map[newtelemetry.Namespace]bool) + r.Products = make(map[telemetry.Namespace]bool) } r.Products[product] = true } -func (r *RecordClient) ProductStopped(product newtelemetry.Namespace) { +func (r *RecordClient) ProductStopped(product telemetry.Namespace) { r.mu.Lock() defer r.mu.Unlock() if r.Products == nil { - r.Products = make(map[newtelemetry.Namespace]bool) + r.Products = make(map[telemetry.Namespace]bool) } r.Products[product] = false } -func (r *RecordClient) ProductStartError(product newtelemetry.Namespace, _ error) { +func (r *RecordClient) ProductStartError(product telemetry.Namespace, _ error) { r.mu.Lock() defer r.mu.Unlock() if r.Products == nil { - r.Products = make(map[newtelemetry.Namespace]bool) + r.Products = make(map[telemetry.Namespace]bool) } r.Products[product] = false } -func (r *RecordClient) RegisterAppConfig(key string, value any, origin newtelemetry.Origin) { +func (r *RecordClient) RegisterAppConfig(key string, value any, origin telemetry.Origin) { r.mu.Lock() defer r.mu.Unlock() - r.Configuration = append(r.Configuration, newtelemetry.Configuration{Name: key, Value: value, Origin: origin}) + r.Configuration = append(r.Configuration, telemetry.Configuration{Name: key, Value: value, Origin: origin}) } -func (r *RecordClient) RegisterAppConfigs(kvs ...newtelemetry.Configuration) { +func (r *RecordClient) RegisterAppConfigs(kvs ...telemetry.Configuration) { r.mu.Lock() defer r.mu.Unlock() r.Configuration = append(r.Configuration, kvs...) } -func (r *RecordClient) MarkIntegrationAsLoaded(integration newtelemetry.Integration) { +func (r *RecordClient) MarkIntegrationAsLoaded(integration telemetry.Integration) { r.mu.Lock() defer r.mu.Unlock() r.Integrations = append(r.Integrations, integration) @@ -188,7 +188,7 @@ func (r *RecordClient) AppStop() { r.Stopped = true } -func CheckConfig(t *testing.T, cfgs []newtelemetry.Configuration, key string, value any) { +func CheckConfig(t *testing.T, cfgs []telemetry.Configuration, key string, value any) { for _, c := range cfgs { if c.Name == key && reflect.DeepEqual(c.Value, value) { return From 96233fb8fbae74dc54c68859addf07352c4b469b Mon Sep 17 00:00:00 2001 From: Eliott Bouhana Date: Thu, 30 Jan 2025 20:54:16 +0100 Subject: [PATCH 4/9] switch old telemetry client API to the new one Signed-off-by: Eliott Bouhana --- contrib/internal/httptrace/httptrace.go | 11 ++- ddtrace/tracer/dynamic_config.go | 7 +- ddtrace/tracer/otel_dd_mappings.go | 4 +- ddtrace/tracer/remote_config.go | 4 +- ddtrace/tracer/spancontext.go | 7 +- ddtrace/tracer/telemetry.go | 32 ++++--- ddtrace/tracer/telemetry_test.go | 89 +++++++++++-------- ddtrace/tracer/tracer.go | 4 +- internal/appsec/config/config.go | 2 +- internal/appsec/telemetry.go | 8 +- .../civisibility/integrations/civisibility.go | 2 +- internal/civisibility/utils/net/client.go | 36 ++++---- .../utils/telemetry/telemetry_count.go | 76 ++++++++-------- .../utils/telemetry/telemetry_distribution.go | 64 ++++++------- profiler/telemetry.go | 26 +++--- profiler/telemetry_test.go | 24 +++-- 16 files changed, 223 insertions(+), 173 deletions(-) diff --git a/contrib/internal/httptrace/httptrace.go b/contrib/internal/httptrace/httptrace.go index 22ac0306b4..b379b469cf 100644 --- a/contrib/internal/httptrace/httptrace.go +++ b/contrib/internal/httptrace/httptrace.go @@ -10,13 +10,14 @@ package httptrace import ( "context" "fmt" - "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" "net/http" "strconv" "strings" "sync" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" @@ -44,10 +45,8 @@ func StartRequestSpan(r *http.Request, opts ...ddtrace.StartSpanOption) (tracer. // we cannot track the configuration in newConfig because it's called during init() and the the telemetry client // is not initialized yet reportTelemetryConfigOnce.Do(func() { - telemetry.GlobalClient.ConfigChange([]telemetry.Configuration{ - {Name: "inferred_proxy_services_enabled", Value: cfg.inferredProxyServicesEnabled}, - }) - log.Debug("internal/httptrace: telemetry.ConfigChange called with cfg: %v:", cfg) + telemetry.RegisterAppConfig("inferred_proxy_services_enabled", cfg.inferredProxyServicesEnabled, telemetry.OriginEnvVar) + log.Debug("internal/httptrace: telemetry.RegisterAppConfig called with cfg: %v", cfg) }) var ipTags map[string]string diff --git a/ddtrace/tracer/dynamic_config.go b/ddtrace/tracer/dynamic_config.go index db48be5a73..67a3832a4c 100644 --- a/ddtrace/tracer/dynamic_config.go +++ b/ddtrace/tracer/dynamic_config.go @@ -79,11 +79,12 @@ func (dc *dynamicConfig[T]) handleRC(val *T) bool { func (dc *dynamicConfig[T]) toTelemetry() telemetry.Configuration { dc.RLock() defer dc.RUnlock() - return telemetry.Sanitize(telemetry.Configuration{ + value := telemetry.SanitizeConfigValue(dc.current) + return telemetry.Configuration{ Name: dc.cfgName, - Value: dc.current, + Value: value, Origin: dc.cfgOrigin, - }) + } } func equal[T comparable](x, y T) bool { diff --git a/ddtrace/tracer/otel_dd_mappings.go b/ddtrace/tracer/otel_dd_mappings.go index 2fe6278c20..681d7e9ba7 100644 --- a/ddtrace/tracer/otel_dd_mappings.go +++ b/ddtrace/tracer/otel_dd_mappings.go @@ -94,13 +94,13 @@ func getDDorOtelConfig(configName string) string { if val != "" { log.Warn("Both %v and %v are set, using %v=%v", config.ot, config.dd, config.dd, val) telemetryTags := []string{ddPrefix + strings.ToLower(config.dd), otelPrefix + strings.ToLower(config.ot)} - telemetry.GlobalClient.Count(telemetry.NamespaceTracers, "otel.env.hiding", 1.0, telemetryTags, true) + telemetry.Count(telemetry.NamespaceTracers, "otel.env.hiding", telemetryTags).Submit(1) } else { v, err := config.remapper(otVal) if err != nil { log.Warn("%v", err) telemetryTags := []string{ddPrefix + strings.ToLower(config.dd), otelPrefix + strings.ToLower(config.ot)} - telemetry.GlobalClient.Count(telemetry.NamespaceTracers, "otel.env.invalid", 1.0, telemetryTags, true) + telemetry.Count(telemetry.NamespaceTracers, "otel.env.invalid", telemetryTags).Submit(1) } val = v } diff --git a/ddtrace/tracer/remote_config.go b/ddtrace/tracer/remote_config.go index eaa8641730..ea5f6ff4bb 100644 --- a/ddtrace/tracer/remote_config.go +++ b/ddtrace/tracer/remote_config.go @@ -186,7 +186,7 @@ func (t *tracer) onRemoteConfigUpdate(u remoteconfig.ProductUpdate) map[string]s } if len(telemConfigs) > 0 { log.Debug("Reporting %d configuration changes to telemetry", len(telemConfigs)) - telemetry.GlobalClient.ConfigChange(telemConfigs) + telemetry.RegisterAppConfigs(telemConfigs...) } return statuses } @@ -244,7 +244,7 @@ func (t *tracer) onRemoteConfigUpdate(u remoteconfig.ProductUpdate) map[string]s } if len(telemConfigs) > 0 { log.Debug("Reporting %d configuration changes to telemetry", len(telemConfigs)) - telemetry.GlobalClient.ConfigChange(telemConfigs) + telemetry.RegisterAppConfigs(telemConfigs...) } return statuses } diff --git a/ddtrace/tracer/spancontext.go b/ddtrace/tracer/spancontext.go index 5b51677867..4a3b0cf9c0 100644 --- a/ddtrace/tracer/spancontext.go +++ b/ddtrace/tracer/spancontext.go @@ -497,7 +497,7 @@ func (t *trace) finishedOne(s *span) { return // The trace hasn't completed and partial flushing will not occur } log.Debug("Partial flush triggered with %d finished spans", t.finished) - telemetry.GlobalClient.Count(telemetry.NamespaceTracers, "trace_partial_flush.count", 1, []string{"reason:large_trace"}, true) + telemetry.Count(telemetry.NamespaceTracers, "trace_partial_flush.count", []string{"reason:large_trace"}).Submit(1) finishedSpans := make([]*span, 0, t.finished) leftoverSpans := make([]*span, 0, len(t.spans)-t.finished) for _, s2 := range t.spans { @@ -507,9 +507,8 @@ func (t *trace) finishedOne(s *span) { leftoverSpans = append(leftoverSpans, s2) } } - // TODO: (Support MetricKindDist) Re-enable these when we actually support `MetricKindDist` - //telemetry.GlobalClient.Record(telemetry.NamespaceTracers, telemetry.MetricKindDist, "trace_partial_flush.spans_closed", float64(len(finishedSpans)), nil, true) - //telemetry.GlobalClient.Record(telemetry.NamespaceTracers, telemetry.MetricKindDist, "trace_partial_flush.spans_remaining", float64(len(leftoverSpans)), nil, true) + telemetry.Distribution(telemetry.NamespaceTracers, "trace_partial_flush.spans_closed", nil).Submit(float64(len(finishedSpans))) + telemetry.Distribution(telemetry.NamespaceTracers, "trace_partial_flush.spans_remaining", nil).Submit(float64(len(leftoverSpans))) finishedSpans[0].setMetric(keySamplingPriority, *t.priority) if s != t.spans[0] { // Make sure the first span in the chunk has the trace-level tags diff --git a/ddtrace/tracer/telemetry.go b/ddtrace/tracer/telemetry.go index e4dc21fc4d..2ad5285bd8 100644 --- a/ddtrace/tracer/telemetry.go +++ b/ddtrace/tracer/telemetry.go @@ -7,8 +7,10 @@ package tracer import ( "fmt" + "os" "strings" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" ) @@ -31,15 +33,23 @@ func startTelemetry(c *config) { // Do not do extra work populating config data if instrumentation telemetry is disabled. return } - telemetry.GlobalClient.ApplyOps( - telemetry.WithService(c.serviceName), - telemetry.WithEnv(c.env), - telemetry.WithHTTPClient(c.httpClient), - // c.logToStdout is true if serverless is turned on - // c.ciVisibilityAgentless is true if ci visibility mode is turned on and agentless writer is configured - telemetry.WithURL(c.logToStdout || c.ciVisibilityAgentless, c.agentURL.String()), - telemetry.WithVersion(c.version), - ) + + if telemetry.GlobalClient() == nil { + cfg := telemetry.ClientConfig{ + HTTPClient: c.httpClient, + AgentURL: c.agentURL.String(), + } + if c.logToStdout || c.ciVisibilityAgentless { + cfg.APIKey = os.Getenv("DD_API_KEY") + } + client, err := telemetry.NewClient(c.serviceName, c.env, c.version, cfg) + if err != nil { + log.Debug("profiler: failed to create telemetry client: %v", err) + return + } + telemetry.StartApp(client) + } + telemetry.ProductStarted(telemetry.NamespaceTracers) telemetryConfigs := []telemetry.Configuration{ {Name: "trace_debug_enabled", Value: c.debug}, {Name: "agent_feature_drop_p0s", Value: c.agent.DropP0s}, @@ -70,7 +80,7 @@ func startTelemetry(c *config) { c.headerAsTags.toTelemetry(), c.globalTags.toTelemetry(), c.traceSampleRules.toTelemetry(), - telemetry.Sanitize(telemetry.Configuration{Name: "span_sample_rules", Value: c.spanRules}), + {Name: "span_sample_rules", Value: c.spanRules}, } var peerServiceMapping []string for key, value := range c.peerServiceMappings { @@ -114,5 +124,5 @@ func startTelemetry(c *config) { } } telemetryConfigs = append(telemetryConfigs, additionalConfigs...) - telemetry.GlobalClient.ProductChange(telemetry.NamespaceTracers, true, telemetryConfigs) + telemetry.RegisterAppConfigs(telemetryConfigs...) } diff --git a/ddtrace/tracer/telemetry_test.go b/ddtrace/tracer/telemetry_test.go index 90e82c851e..3d64863cd1 100644 --- a/ddtrace/tracer/telemetry_test.go +++ b/ddtrace/tracer/telemetry_test.go @@ -7,6 +7,7 @@ package tracer import ( "fmt" + "reflect" "testing" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" @@ -17,6 +18,24 @@ import ( "github.com/stretchr/testify/assert" ) +func mockGlobalClient(client telemetry.Client) func() { + orig := telemetry.GlobalClient() + telemetry.SwapClient(client) + return func() { + telemetry.SwapClient(orig) + } +} + +func checkConfig(t *testing.T, cfgs []telemetry.Configuration, key string, value any) { + for _, c := range cfgs { + if c.Name == key && reflect.DeepEqual(c.Value, value) { + return + } + } + + t.Fatalf("could not find configuration key %s with value %v", key, value) +} + func TestTelemetryEnabled(t *testing.T) { t.Run("tracer start", func(t *testing.T) { telemetryClient := new(telemetrytest.MockClient) @@ -43,30 +62,23 @@ func TestTelemetryEnabled(t *testing.T) { assert.True(t, telemetryClient.Started) telemetryClient.AssertNumberOfCalls(t, "ApplyOps", 1) - telemetry.Check(t, telemetryClient.Configuration, "trace_debug_enabled", true) - telemetry.Check(t, telemetryClient.Configuration, "service", "test-serv") - telemetry.Check(t, telemetryClient.Configuration, "env", "test-env") - telemetry.Check(t, telemetryClient.Configuration, "runtime_metrics_enabled", true) - telemetry.Check(t, telemetryClient.Configuration, "stats_computation_enabled", false) - telemetry.Check(t, telemetryClient.Configuration, "trace_enabled", true) - telemetry.Check(t, telemetryClient.Configuration, "trace_span_attribute_schema", 0) - telemetry.Check(t, telemetryClient.Configuration, "trace_peer_service_defaults_enabled", true) - telemetry.Check(t, telemetryClient.Configuration, "trace_peer_service_mapping", "key:val") - telemetry.Check(t, telemetryClient.Configuration, "debug_stack_enabled", false) - telemetry.Check(t, telemetryClient.Configuration, "orchestrion_enabled", false) - telemetry.Check(t, telemetryClient.Configuration, "trace_sample_rate", nil) // default value is NaN which is sanitized to nil - telemetry.Check(t, telemetryClient.Configuration, "trace_header_tags", "key:val,key2:val2") - telemetry.Check(t, telemetryClient.Configuration, "trace_sample_rules", + checkConfig(t, telemetryClient.Configuration, "trace_debug_enabled", true) + checkConfig(t, telemetryClient.Configuration, "service", "test-serv") + checkConfig(t, telemetryClient.Configuration, "env", "test-env") + checkConfig(t, telemetryClient.Configuration, "runtime_metrics_enabled", true) + checkConfig(t, telemetryClient.Configuration, "stats_computation_enabled", false) + checkConfig(t, telemetryClient.Configuration, "trace_enabled", true) + checkConfig(t, telemetryClient.Configuration, "trace_span_attribute_schema", 0) + checkConfig(t, telemetryClient.Configuration, "trace_peer_service_defaults_enabled", true) + checkConfig(t, telemetryClient.Configuration, "trace_peer_service_mapping", "key:val") + checkConfig(t, telemetryClient.Configuration, "debug_stack_enabled", false) + checkConfig(t, telemetryClient.Configuration, "orchestrion_enabled", false) + checkConfig(t, telemetryClient.Configuration, "trace_sample_rate", nil) // default value is NaN which is sanitized to nil + checkConfig(t, telemetryClient.Configuration, "trace_header_tags", "key:val,key2:val2") + checkConfig(t, telemetryClient.Configuration, "trace_sample_rules", `[{"service":"test-serv","name":"op-name","resource":"resource-*","sample_rate":0.1,"tags":{"tag-a":"tv-a??"}}]`) - telemetry.Check(t, telemetryClient.Configuration, "span_sample_rules", "[]") - if metrics, ok := telemetryClient.Metrics[telemetry.NamespaceGeneral]; ok { - if initTime, ok := metrics["init_time"]; ok { - assert.True(t, initTime > 0, "expected positive init time, but got %f", initTime) - return - } - t.Fatalf("could not find general init time in telemetry client metrics") - } - t.Fatalf("could not find tracer namespace in telemetry client metrics") + checkConfig(t, telemetryClient.Configuration, "span_sample_rules", "[]") + assert.NotZero(t, telemetryClient.Distribution(telemetry.NamespaceTracers, "init_time", nil).Get()) }) t.Run("telemetry customer or dynamic rules", func(t *testing.T) { @@ -79,7 +91,7 @@ func TestTelemetryEnabled(t *testing.T) { rule.Provenance = prov telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + defer mockGlobalClient(telemetryClient)() Start(WithService("test-serv"), WithSamplingRules([]SamplingRule{rule}), ) @@ -87,7 +99,7 @@ func TestTelemetryEnabled(t *testing.T) { defer Stop() assert.True(t, telemetryClient.Started) - telemetry.Check(t, telemetryClient.Configuration, "trace_sample_rules", + checkConfig(t, telemetryClient.Configuration, "trace_sample_rules", fmt.Sprintf(`[{"service":"test-serv","name":"op-name","resource":"resource-*","sample_rate":0.1,"tags":{"tag-a":"tv-a??"},"provenance":"%s"}]`, prov.String())) } }) @@ -104,7 +116,7 @@ func TestTelemetryEnabled(t *testing.T) { } telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + defer mockGlobalClient(telemetryClient)() Start(WithService("test-serv"), WithSamplingRules(rules), ) @@ -112,15 +124,15 @@ func TestTelemetryEnabled(t *testing.T) { defer Stop() assert.True(t, telemetryClient.Started) - telemetry.Check(t, telemetryClient.Configuration, "trace_sample_rules", + checkConfig(t, telemetryClient.Configuration, "trace_sample_rules", `[{"service":"test-serv","name":"op-name","resource":"resource-*","sample_rate":0.1,"tags":{"tag-a":"tv-a??"}}]`) - telemetry.Check(t, telemetryClient.Configuration, "span_sample_rules", + checkConfig(t, telemetryClient.Configuration, "span_sample_rules", `[{"service":"test-serv","name":"op-name","sample_rate":0.1}]`) }) t.Run("tracer start with empty rules", func(t *testing.T) { telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + defer mockGlobalClient(telemetryClient)() t.Setenv("DD_TRACE_SAMPLING_RULES", "") t.Setenv("DD_SPAN_SAMPLING_RULES", "") @@ -131,15 +143,16 @@ func TestTelemetryEnabled(t *testing.T) { assert.True(t, telemetryClient.Started) var cfgs []telemetry.Configuration for _, c := range telemetryClient.Configuration { - cfgs = append(cfgs, telemetry.Sanitize(c)) + c.Value = telemetry.SanitizeConfigValue(c.Value) + cfgs = append(cfgs) } - telemetry.Check(t, cfgs, "trace_sample_rules", "[]") - telemetry.Check(t, cfgs, "span_sample_rules", "[]") + checkConfig(t, cfgs, "trace_sample_rules", "[]") + checkConfig(t, cfgs, "span_sample_rules", "[]") }) t.Run("profiler start, tracer start", func(t *testing.T) { telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + defer mockGlobalClient(telemetryClient)() profiler.Start() defer profiler.Stop() Start( @@ -147,18 +160,18 @@ func TestTelemetryEnabled(t *testing.T) { ) defer globalconfig.SetServiceName("") defer Stop() - telemetry.Check(t, telemetryClient.Configuration, "service", "test-serv") + checkConfig(t, telemetryClient.Configuration, "service", "test-serv") telemetryClient.AssertNumberOfCalls(t, "ApplyOps", 2) }) t.Run("orchestrion telemetry", func(t *testing.T) { telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + defer mockGlobalClient(telemetryClient)() Start(WithOrchestrion(map[string]string{"k1": "v1", "k2": "v2"})) defer Stop() - telemetry.Check(t, telemetryClient.Configuration, "orchestrion_enabled", true) - telemetry.Check(t, telemetryClient.Configuration, "orchestrion_k1", "v1") - telemetry.Check(t, telemetryClient.Configuration, "orchestrion_k2", "v2") + checkConfig(t, telemetryClient.Configuration, "orchestrion_enabled", true) + checkConfig(t, telemetryClient.Configuration, "orchestrion_k1", "v1") + checkConfig(t, telemetryClient.Configuration, "orchestrion_k2", "v2") }) } diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index 316ed56270..f189d8f8aa 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -149,7 +149,9 @@ func Start(opts ...StartOption) { if internal.Testing { return // mock tracer active } - defer telemetry.Time(telemetry.NamespaceGeneral, "init_time", nil, true)() + defer func(now time.Time) { + telemetry.Distribution(telemetry.NamespaceGeneral, "init_time", nil).Submit(float64(time.Since(now).Milliseconds())) + }(time.Now()) t := newTracer(opts...) if !t.config.enabled.current { // TODO: instrumentation telemetry client won't get started diff --git a/internal/appsec/config/config.go b/internal/appsec/config/config.go index fe34fa4332..73b4dd659d 100644 --- a/internal/appsec/config/config.go +++ b/internal/appsec/config/config.go @@ -24,7 +24,7 @@ func init() { // Register the global app telemetry configuration. func registerAppConfigTelemetry() { - registerSCAAppConfigTelemetry(telemetry.GlobalClient) + registerSCAAppConfigTelemetry(telemetry.GlobalClient()) } // Register the global app telemetry configuration related to the Software Composition Analysis (SCA) product. diff --git a/internal/appsec/telemetry.go b/internal/appsec/telemetry.go index 2a8b0b42d3..9be83469cf 100644 --- a/internal/appsec/telemetry.go +++ b/internal/appsec/telemetry.go @@ -87,5 +87,11 @@ func (a *appsecTelemetry) emit() { return } - telemetry.GlobalClient.ProductChange(telemetry.NamespaceAppSec, a.enabled, a.configs) + if a.enabled { + telemetry.ProductStarted(telemetry.NamespaceAppSec) + } else { + telemetry.ProductStopped(telemetry.NamespaceAppSec) + } + + telemetry.RegisterAppConfigs(a.configs...) } diff --git a/internal/civisibility/integrations/civisibility.go b/internal/civisibility/integrations/civisibility.go index 6dd3caa3b4..cd5e8f6407 100644 --- a/internal/civisibility/integrations/civisibility.go +++ b/internal/civisibility/integrations/civisibility.go @@ -131,7 +131,7 @@ func ExitCiVisibility() { log.Debug("civisibility: flushing and stopping tracer") tracer.Flush() tracer.Stop() - telemetry.GlobalClient.Stop() + telemetry.StopApp() log.Debug("civisibility: done.") }() for _, v := range closeActions { diff --git a/internal/civisibility/utils/net/client.go b/internal/civisibility/utils/net/client.go index 3bcfcdd847..c823f6a0f6 100644 --- a/internal/civisibility/utils/net/client.go +++ b/internal/civisibility/utils/net/client.go @@ -126,11 +126,13 @@ func NewClientWithServiceNameAndSubdomain(serviceName, subdomain string) Client defaultHeaders := map[string]string{} var baseURL string var requestHandler *RequestHandler + var agentURL *url.URL + var APIKeyValue string agentlessEnabled := internal.BoolEnv(constants.CIVisibilityAgentlessEnabledEnvironmentVariable, false) if agentlessEnabled { // Agentless mode is enabled. - APIKeyValue := os.Getenv(constants.APIKeyEnvironmentVariable) + APIKeyValue = os.Getenv(constants.APIKeyEnvironmentVariable) if APIKeyValue == "" { log.Error("An API key is required for agentless mode. Use the DD_API_KEY env variable to set it") return nil @@ -159,7 +161,7 @@ func NewClientWithServiceNameAndSubdomain(serviceName, subdomain string) Client // Use agent mode with the EVP proxy. defaultHeaders["X-Datadog-EVP-Subdomain"] = subdomain - agentURL := internal.AgentURLFromEnv() + agentURL = internal.AgentURLFromEnv() if agentURL.Scheme == "unix" { // If we're connecting over UDS we can just rely on the agent to provide the hostname log.Debug("connecting to agent over unix, do not set hostname on any traces") @@ -205,19 +207,23 @@ func NewClientWithServiceNameAndSubdomain(serviceName, subdomain string) Client if !telemetry.Disabled() { telemetryInit.Do(func() { - telemetry.GlobalClient.ApplyOps( - telemetry.WithService(serviceName), - telemetry.WithEnv(environment), - telemetry.WithHTTPClient(requestHandler.Client), - telemetry.WithURL(agentlessEnabled, baseURL), - telemetry.SyncFlushOnStop(), - ) - telemetry.GlobalClient.ProductChange(telemetry.NamespaceCiVisibility, true, []telemetry.Configuration{ - telemetry.StringConfig("service", serviceName), - telemetry.StringConfig("env", environment), - telemetry.BoolConfig("agentless", agentlessEnabled), - telemetry.StringConfig("test_session_name", ciTags[constants.TestSessionName]), - }) + telemetry.ProductStarted(telemetry.NamespaceTracers) + if telemetry.GlobalClient() != nil { + return + } + cfg := telemetry.ClientConfig{ + HTTPClient: requestHandler.Client, + APIKey: APIKeyValue, + } + if agentURL != nil { + cfg.AgentURL = agentURL.String() + } + client, err := telemetry.NewClient(serviceName, environment, os.Getenv("DD_VERSION"), cfg) + if err != nil { + log.Debug("civisibility: failed to create telemetry client: %v", err) + return + } + telemetry.StartApp(client) }) } diff --git a/internal/civisibility/utils/telemetry/telemetry_count.go b/internal/civisibility/utils/telemetry/telemetry_count.go index 0e1bfaa931..6dca55279e 100644 --- a/internal/civisibility/utils/telemetry/telemetry_count.go +++ b/internal/civisibility/utils/telemetry/telemetry_count.go @@ -48,165 +48,165 @@ func GetErrorTypeFromStatusCode(statusCode int) ErrorType { func EventCreated(testingFramework string, eventType TestingEventType) { tags := []string{string(getTestingFramework(testingFramework))} tags = append(tags, eventType...) - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "event_created", 1.0, removeEmptyStrings(tags), true) + telemetry.Count(telemetry.NamespaceCIVisibility, "event_created", removeEmptyStrings(tags)).Submit(1.0) } // EventFinished the number of events finished by CI Visibility func EventFinished(testingFramework string, eventType TestingEventType) { tags := []string{string(getTestingFramework(testingFramework))} tags = append(tags, eventType...) - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "event_finished", 1.0, removeEmptyStrings(tags), true) + telemetry.Count(telemetry.NamespaceCIVisibility, "event_finished", removeEmptyStrings(tags)).Submit(1.0) } // CodeCoverageStarted the number of code coverage start calls by CI Visibility func CodeCoverageStarted(testingFramework string, coverageLibraryType CoverageLibraryType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "code_coverage_started", 1.0, removeEmptyStrings([]string{ + telemetry.Count(telemetry.NamespaceCIVisibility, "code_coverage_started", removeEmptyStrings([]string{ string(getTestingFramework(testingFramework)), string(coverageLibraryType), - }), true) + })).Submit(1.0) } // CodeCoverageFinished the number of code coverage finished calls by CI Visibility func CodeCoverageFinished(testingFramework string, coverageLibraryType CoverageLibraryType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "code_coverage_finished", 1.0, removeEmptyStrings([]string{ + telemetry.Count(telemetry.NamespaceCIVisibility, "code_coverage_finished", removeEmptyStrings([]string{ string(getTestingFramework(testingFramework)), string(coverageLibraryType), - }), true) + })).Submit(1.0) } // EventsEnqueueForSerialization the number of events enqueued for serialization by CI Visibility func EventsEnqueueForSerialization() { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "events_enqueued_for_serialization", 1.0, nil, true) + telemetry.Count(telemetry.NamespaceCIVisibility, "events_enqueued_for_serialization", nil).Submit(1.0) } // EndpointPayloadRequests the number of requests sent to the endpoint, regardless of success, tagged by endpoint type func EndpointPayloadRequests(endpointType EndpointType, requestCompressedType RequestCompressedType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "endpoint_payload.requests", 1.0, removeEmptyStrings([]string{ + telemetry.Count(telemetry.NamespaceCIVisibility, "endpoint_payload.requests", removeEmptyStrings([]string{ string(endpointType), string(requestCompressedType), - }), true) + })).Submit(1.0) } // EndpointPayloadRequestsErrors the number of requests sent to the endpoint that errored, tagget by the error type and endpoint type and status code func EndpointPayloadRequestsErrors(endpointType EndpointType, errorType ErrorType) { tags := []string{string(endpointType)} tags = append(tags, errorType...) - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "endpoint_payload.requests_errors", 1.0, removeEmptyStrings(tags), true) + telemetry.Count(telemetry.NamespaceCIVisibility, "endpoint_payload.requests_errors", removeEmptyStrings(tags)).Submit(1.0) } // EndpointPayloadDropped the number of payloads dropped after all retries by CI Visibility func EndpointPayloadDropped(endpointType EndpointType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "endpoint_payload.dropped", 1.0, removeEmptyStrings([]string{ + telemetry.Count(telemetry.NamespaceCIVisibility, "endpoint_payload.dropped", removeEmptyStrings([]string{ string(endpointType), - }), true) + })).Submit(1.0) } // GitCommand the number of git commands executed by CI Visibility func GitCommand(commandType CommandType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "git.command", 1.0, removeEmptyStrings([]string{ + telemetry.Count(telemetry.NamespaceCIVisibility, "git.command", removeEmptyStrings([]string{ string(commandType), - }), true) + })).Submit(1.0) } // GitCommandErrors the number of git command that errored by CI Visibility func GitCommandErrors(commandType CommandType, exitCode CommandExitCodeType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "git.command_errors", 1.0, removeEmptyStrings([]string{ + telemetry.Count(telemetry.NamespaceCIVisibility, "git.command_errors", removeEmptyStrings([]string{ string(commandType), string(exitCode), - }), true) + })).Submit(1.0) } // GitRequestsSearchCommits the number of requests sent to the search commit endpoint, regardless of success. func GitRequestsSearchCommits(requestCompressed RequestCompressedType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "git_requests.search_commits", 1.0, removeEmptyStrings([]string{ + telemetry.Count(telemetry.NamespaceCIVisibility, "git_requests.search_commits", removeEmptyStrings([]string{ string(requestCompressed), - }), true) + })).Submit(1.0) } // GitRequestsSearchCommitsErrors the number of requests sent to the search commit endpoint that errored, tagged by the error type. func GitRequestsSearchCommitsErrors(errorType ErrorType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "git_requests.search_commits_errors", 1.0, removeEmptyStrings(errorType), true) + telemetry.Count(telemetry.NamespaceCIVisibility, "git_requests.search_commits_errors", removeEmptyStrings(errorType)).Submit(1.0) } // GitRequestsObjectsPack the number of requests sent to the objects pack endpoint, tagged by the request compressed type. func GitRequestsObjectsPack(requestCompressed RequestCompressedType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "git_requests.objects_pack", 1.0, removeEmptyStrings([]string{ + telemetry.Count(telemetry.NamespaceCIVisibility, "git_requests.objects_pack", removeEmptyStrings([]string{ string(requestCompressed), - }), true) + })).Submit(1.0) } // GitRequestsObjectsPackErrors the number of requests sent to the objects pack endpoint that errored, tagged by the error type. func GitRequestsObjectsPackErrors(errorType ErrorType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "git_requests.objects_pack_errors", 1.0, removeEmptyStrings(errorType), true) + telemetry.Count(telemetry.NamespaceCIVisibility, "git_requests.objects_pack_errors", removeEmptyStrings(errorType)).Submit(1.0) } // GitRequestsSettings the number of requests sent to the settings endpoint, tagged by the request compressed type. func GitRequestsSettings(requestCompressed RequestCompressedType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "git_requests.settings", 1.0, removeEmptyStrings([]string{ + telemetry.Count(telemetry.NamespaceCIVisibility, "git_requests.settings", removeEmptyStrings([]string{ string(requestCompressed), - }), true) + })).Submit(1.0) } // GitRequestsSettingsErrors the number of requests sent to the settings endpoint that errored, tagged by the error type. func GitRequestsSettingsErrors(errorType ErrorType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "git_requests.settings_errors", 1.0, removeEmptyStrings(errorType), true) + telemetry.Count(telemetry.NamespaceCIVisibility, "git_requests.settings_errors", removeEmptyStrings(errorType)).Submit(1.0) } // GitRequestsSettingsResponse the number of settings responses received by CI Visibility, tagged by the settings response type. func GitRequestsSettingsResponse(settingsResponseType SettingsResponseType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "git_requests.settings_response", 1.0, removeEmptyStrings(settingsResponseType), true) + telemetry.Count(telemetry.NamespaceCIVisibility, "git_requests.settings_response", removeEmptyStrings(settingsResponseType)).Submit(1.0) } // ITRSkippableTestsRequest the number of requests sent to the ITR skippable tests endpoint, tagged by the request compressed type. func ITRSkippableTestsRequest(requestCompressed RequestCompressedType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "itr_skippable_tests.request", 1.0, removeEmptyStrings([]string{ + telemetry.Count(telemetry.NamespaceCIVisibility, "itr_skippable_tests.request", removeEmptyStrings([]string{ string(requestCompressed), - }), true) + })).Submit(1.0) } // ITRSkippableTestsRequestErrors the number of requests sent to the ITR skippable tests endpoint that errored, tagged by the error type. func ITRSkippableTestsRequestErrors(errorType ErrorType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "itr_skippable_tests.request_errors", 1.0, removeEmptyStrings(errorType), true) + telemetry.Count(telemetry.NamespaceCIVisibility, "itr_skippable_tests.request_errors", removeEmptyStrings(errorType)).Submit(1.0) } // ITRSkippableTestsResponseTests the number of tests received in the ITR skippable tests response by CI Visibility. func ITRSkippableTestsResponseTests(value float64) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "itr_skippable_tests.response_tests", value, nil, true) + telemetry.Count(telemetry.NamespaceCIVisibility, "itr_skippable_tests.response_tests", nil).Submit(value) } // ITRSkipped the number of ITR tests skipped by CI Visibility, tagged by the event type. func ITRSkipped(eventType TestingEventType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "itr_skipped", 1.0, removeEmptyStrings(eventType), true) + telemetry.Count(telemetry.NamespaceCIVisibility, "itr_skipped", removeEmptyStrings(eventType)).Submit(1.0) } // ITRUnskippable the number of ITR tests unskippable by CI Visibility, tagged by the event type. func ITRUnskippable(eventType TestingEventType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "itr_unskippable", 1.0, removeEmptyStrings(eventType), true) + telemetry.Count(telemetry.NamespaceCIVisibility, "itr_unskippable", removeEmptyStrings(eventType)).Submit(1.0) } // ITRForcedRun the number of tests or test suites that would've been skipped by ITR but were forced to run because of their unskippable status by CI Visibility. func ITRForcedRun(eventType TestingEventType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "itr_forced_run", 1.0, removeEmptyStrings(eventType), true) + telemetry.Count(telemetry.NamespaceCIVisibility, "itr_forced_run", removeEmptyStrings(eventType)).Submit(1.0) } // CodeCoverageIsEmpty the number of code coverage payloads that are empty by CI Visibility. func CodeCoverageIsEmpty() { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "code_coverage.is_empty", 1.0, nil, true) + telemetry.Count(telemetry.NamespaceCIVisibility, "code_coverage.is_empty", nil).Submit(1.0) } // CodeCoverageErrors the number of errors while processing code coverage by CI Visibility. func CodeCoverageErrors() { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "code_coverage.errors", 1.0, nil, true) + telemetry.Count(telemetry.NamespaceCIVisibility, "code_coverage.errors", nil).Submit(1.0) } // KnownTestsRequest the number of requests sent to the known tests endpoint, tagged by the request compressed type. func KnownTestsRequest(requestCompressed RequestCompressedType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "known_tests.request", 1.0, removeEmptyStrings([]string{ + telemetry.Count(telemetry.NamespaceCIVisibility, "known_tests.request", removeEmptyStrings([]string{ string(requestCompressed), - }), true) + })).Submit(1.0) } // KnownTestsRequestErrors the number of requests sent to the known tests endpoint that errored, tagged by the error type. func KnownTestsRequestErrors(errorType ErrorType) { - telemetry.GlobalClient.Count(telemetry.NamespaceCiVisibility, "known_tests.request_errors", 1.0, removeEmptyStrings(errorType), true) + telemetry.Count(telemetry.NamespaceCIVisibility, "known_tests.request_errors", removeEmptyStrings(errorType)).Submit(1.0) } diff --git a/internal/civisibility/utils/telemetry/telemetry_distribution.go b/internal/civisibility/utils/telemetry/telemetry_distribution.go index 20c5786eb0..adb489f7c8 100644 --- a/internal/civisibility/utils/telemetry/telemetry_distribution.go +++ b/internal/civisibility/utils/telemetry/telemetry_distribution.go @@ -9,96 +9,96 @@ import "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" // EndpointPayloadBytes records the size in bytes of the serialized payload by CI Visibility. func EndpointPayloadBytes(endpointType EndpointType, value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "endpoint_payload.bytes", value, removeEmptyStrings([]string{ - (string)(endpointType), - }), true) + telemetry.Distribution(telemetry.NamespaceCIVisibility, "endpoint_payload.bytes", removeEmptyStrings([]string{ + string(endpointType), + })).Submit(value) } // EndpointPayloadRequestsMs records the time it takes to send the payload sent to the endpoint in ms by CI Visibility. func EndpointPayloadRequestsMs(endpointType EndpointType, value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "endpoint_payload.requests_ms", value, removeEmptyStrings([]string{ - (string)(endpointType), - }), true) + telemetry.Distribution(telemetry.NamespaceCIVisibility, "endpoint_payload.requests_ms", removeEmptyStrings([]string{ + string(endpointType), + })).Submit(value) } // EndpointPayloadEventsCount records the number of events in the payload sent to the endpoint by CI Visibility. func EndpointPayloadEventsCount(endpointType EndpointType, value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "endpoint_payload.events_count", value, removeEmptyStrings([]string{ - (string)(endpointType), - }), true) + telemetry.Distribution(telemetry.NamespaceCIVisibility, "endpoint_payload.events_count", removeEmptyStrings([]string{ + string(endpointType), + })).Submit(value) } // EndpointEventsSerializationMs records the time it takes to serialize the events in the payload sent to the endpoint in ms by CI Visibility. func EndpointEventsSerializationMs(endpointType EndpointType, value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "endpoint_payload.events_serialization_ms", value, removeEmptyStrings([]string{ - (string)(endpointType), - }), true) + telemetry.Distribution(telemetry.NamespaceCIVisibility, "endpoint_payload.events_serialization_ms", removeEmptyStrings([]string{ + string(endpointType), + })).Submit(value) } // GitCommandMs records the time it takes to execute a git command in ms by CI Visibility. func GitCommandMs(commandType CommandType, value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "git.command_ms", value, removeEmptyStrings([]string{ + telemetry.Distribution(telemetry.NamespaceCIVisibility, "git.command_ms", removeEmptyStrings([]string{ (string)(commandType), - }), true) + })).Submit(value) } // GitRequestsSearchCommitsMs records the time it takes to get the response of the search commit quest in ms by CI Visibility. func GitRequestsSearchCommitsMs(responseCompressedType ResponseCompressedType, value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "git_requests.search_commits_ms", value, removeEmptyStrings([]string{ + telemetry.Distribution(telemetry.NamespaceCIVisibility, "git_requests.search_commits_ms", removeEmptyStrings([]string{ (string)(responseCompressedType), - }), true) + })).Submit(value) } // GitRequestsObjectsPackMs records the time it takes to get the response of the objects pack request in ms by CI Visibility. func GitRequestsObjectsPackMs(value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "git_requests.objects_pack_ms", value, nil, true) + telemetry.Distribution(telemetry.NamespaceCIVisibility, "git_requests.objects_pack_ms", nil).Submit(value) } // GitRequestsObjectsPackBytes records the sum of the sizes of the object pack files inside a single payload by CI Visibility func GitRequestsObjectsPackBytes(value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "git_requests.objects_pack_bytes", value, nil, true) + telemetry.Distribution(telemetry.NamespaceCIVisibility, "git_requests.objects_pack_bytes", nil).Submit(value) } // GitRequestsObjectsPackFiles records the number of files sent in the object pack payload by CI Visibility. func GitRequestsObjectsPackFiles(value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "git_requests.objects_pack_files", value, nil, true) + telemetry.Distribution(telemetry.NamespaceCIVisibility, "git_requests.objects_pack_files", nil).Submit(value) } // GitRequestsSettingsMs records the time it takes to get the response of the settings endpoint request in ms by CI Visibility. func GitRequestsSettingsMs(value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "git_requests.settings_ms", value, nil, true) + telemetry.Distribution(telemetry.NamespaceCIVisibility, "git_requests.settings_ms", nil).Submit(value) } // ITRSkippableTestsRequestMs records the time it takes to get the response of the itr skippable tests endpoint request in ms by CI Visibility. func ITRSkippableTestsRequestMs(value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "itr_skippable_tests.request_ms", value, nil, true) + telemetry.Distribution(telemetry.NamespaceCIVisibility, "itr_skippable_tests.request_ms", nil).Submit(value) } -// ITRSkippableTestsResponseBytes records the number of bytes received by the endpoint. Tagged with a boolean flag set to true if response body is compressed. +// ITRSkippableTestsResponseBytes records the number of bytes received by the endpoint. Tagged with a boolean flag set t if response body is compressed. func ITRSkippableTestsResponseBytes(responseCompressedType ResponseCompressedType, value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "itr_skippable_tests.response_bytes", value, removeEmptyStrings([]string{ + telemetry.Distribution(telemetry.NamespaceCIVisibility, "itr_skippable_tests.response_bytes", removeEmptyStrings([]string{ (string)(responseCompressedType), - }), true) + })).Submit(value) } // CodeCoverageFiles records the number of files in the code coverage report by CI Visibility. func CodeCoverageFiles(value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "code_coverage.files", value, nil, true) + telemetry.Distribution(telemetry.NamespaceCIVisibility, "code_coverage.files", nil).Submit(value) } -// KnownTestsRequestMs records the time it takes to get the response of the known tests endpoint request in ms by CI Visibility. -func KnownTestsRequestMs(value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "known_tests.request_ms", value, nil, true) +// EarlyFlakeDetectionRequestMs records the time it takes to get the response of the early flake detection endpoint request in ms by CI Visibility. +func EarlyFlakeDetectionRequestMs(value float64) { + telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "early_flake_detection.request_ms", value, nil, true) } // KnownTestsResponseBytes records the number of bytes received by the endpoint. Tagged with a boolean flag set to true if response body is compressed. func KnownTestsResponseBytes(responseCompressedType ResponseCompressedType, value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "known_tests.response_bytes", value, removeEmptyStrings([]string{ - (string)(responseCompressedType), - }), true) + telemetry.Distribution(telemetry.NamespaceCIVisibility, "known_tests.response_bytes", removeEmptyStrings([]string{ + string(responseCompressedType), + })).Submit(value) } // KnownTestsResponseTests records the number of tests in the response of the known tests endpoint by CI Visibility. func KnownTestsResponseTests(value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "known_tests.response_tests", value, nil, true) + telemetry.Distribution(telemetry.NamespaceCIVisibility, "known_tests.response_tests", nil).Submit(value) } diff --git a/profiler/telemetry.go b/profiler/telemetry.go index a53367f33a..780b2f3bf6 100644 --- a/profiler/telemetry.go +++ b/profiler/telemetry.go @@ -6,6 +6,7 @@ package profiler import ( + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" ) @@ -24,15 +25,20 @@ func startTelemetry(c *config) { _, ok := c.types[t] return ok } - telemetry.GlobalClient.ApplyOps( - telemetry.WithService(c.service), - telemetry.WithEnv(c.env), - telemetry.WithHTTPClient(c.httpClient), - telemetry.WithURL(c.agentless, c.agentURL), - ) - telemetry.GlobalClient.ProductChange( - telemetry.NamespaceProfilers, - true, + if telemetry.GlobalClient() == nil { + client, err := telemetry.NewClient(c.service, c.env, c.version, telemetry.ClientConfig{ + HTTPClient: c.httpClient, + APIKey: c.apiKey, + AgentURL: c.agentURL, + }) + if err != nil { + log.Debug("profiler: failed to create telemetry client: %v", err) + return + } + telemetry.StartApp(client) + } + telemetry.ProductStarted(telemetry.NamespaceProfilers) + telemetry.RegisterAppConfigs( []telemetry.Configuration{ {Name: "delta_profiles", Value: c.deltaProfiles}, {Name: "agentless", Value: c.agentless}, @@ -56,6 +62,6 @@ func startTelemetry(c *config) { {Name: "num_custom_profiler_label_keys", Value: len(c.customProfilerLabels)}, {Name: "enabled", Value: c.enabled}, {Name: "flush_on_exit", Value: c.flushOnExit}, - }, + }..., ) } diff --git a/profiler/telemetry_test.go b/profiler/telemetry_test.go index 155b16d33c..c88b726ce4 100644 --- a/profiler/telemetry_test.go +++ b/profiler/telemetry_test.go @@ -15,11 +15,19 @@ import ( "github.com/stretchr/testify/assert" ) +func mockGlobalClient(client telemetry.Client) func() { + orig := telemetry.GlobalClient() + telemetry.SwapClient(client) + return func() { + telemetry.SwapClient(orig) + } +} + // Test that the profiler sends the correct telemetry information func TestTelemetryEnabled(t *testing.T) { t.Run("tracer start, profiler start", func(t *testing.T) { telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + defer mockGlobalClient(telemetryClient)() tracer.Start() defer tracer.Stop() @@ -31,13 +39,13 @@ func TestTelemetryEnabled(t *testing.T) { ) defer Stop() - assert.True(t, telemetryClient.ProfilerEnabled) - telemetry.Check(t, telemetryClient.Configuration, "heap_profile_enabled", true) - telemetryClient.AssertNumberOfCalls(t, "ApplyOps", 2) + assert.True(t, telemetryClient.Products[telemetry.NamespaceProfilers]) + assert.Contains(t, telemetryClient.Configuration, telemetry.Configuration{Name: "heap_profile_enabled", Value: true}) + telemetryClient.AssertCalled(t, "ProductStarted", telemetry.NamespaceProfilers) }) t.Run("only profiler start", func(t *testing.T) { telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + defer mockGlobalClient(telemetryClient)() Start( WithProfileTypes( HeapProfile, @@ -45,8 +53,8 @@ func TestTelemetryEnabled(t *testing.T) { ) defer Stop() - assert.True(t, telemetryClient.ProfilerEnabled) - telemetry.Check(t, telemetryClient.Configuration, "heap_profile_enabled", true) - telemetryClient.AssertNumberOfCalls(t, "ApplyOps", 1) + assert.True(t, telemetryClient.Products[telemetry.NamespaceProfilers]) + assert.Contains(t, telemetryClient.Configuration, telemetry.Configuration{Name: "heap_profile_enabled", Value: true}) + telemetryClient.AssertCalled(t, "ProductStarted", telemetry.NamespaceProfilers) }) } From 842f9d7861f9501e90bec60106d1c7c2eb5df017 Mon Sep 17 00:00:00 2001 From: Eliott Bouhana Date: Fri, 31 Jan 2025 13:30:52 +0100 Subject: [PATCH 5/9] changes to make the telemetry client work Signed-off-by: Eliott Bouhana --- contrib/internal/httptrace/httptrace.go | 5 +- .../internal/telemetrytest/telemetry_test.go | 17 +- ddtrace/opentelemetry/telemetry_test.go | 9 +- ddtrace/opentelemetry/tracer.go | 2 +- ddtrace/opentelemetry/tracer_test.go | 7 +- ddtrace/opentracer/tracer.go | 4 +- ddtrace/opentracer/tracer_test.go | 7 +- ddtrace/tracer/dynamic_config.go | 14 +- ddtrace/tracer/log_test.go | 31 ++- ddtrace/tracer/otel_dd_mappings_test.go | 13 +- ddtrace/tracer/remote_config_test.go | 193 +++++++----------- ddtrace/tracer/spancontext_test.go | 22 +- ddtrace/tracer/telemetry.go | 28 ++- ddtrace/tracer/telemetry_test.go | 2 +- ddtrace/tracer/textmap_test.go | 10 +- ddtrace/tracer/tracer_test.go | 15 +- internal/appsec/config/config.go | 11 +- internal/appsec/config/config_test.go | 3 +- internal/appsec/features.go | 8 +- .../utils/telemetry/telemetry_count.go | 2 +- .../utils/telemetry/telemetry_distribution.go | 6 +- internal/telemetry/telemetrytest/record.go | 8 +- profiler/telemetry.go | 24 +-- profiler/telemetry_test.go | 18 +- 24 files changed, 199 insertions(+), 260 deletions(-) diff --git a/contrib/internal/httptrace/httptrace.go b/contrib/internal/httptrace/httptrace.go index b379b469cf..9b33c18105 100644 --- a/contrib/internal/httptrace/httptrace.go +++ b/contrib/internal/httptrace/httptrace.go @@ -15,15 +15,14 @@ import ( "strings" "sync" - "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener/httpsec" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/namingschema" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" ) var ( diff --git a/contrib/internal/telemetrytest/telemetry_test.go b/contrib/internal/telemetrytest/telemetry_test.go index 123b2a73ca..d82146d018 100644 --- a/contrib/internal/telemetrytest/telemetry_test.go +++ b/contrib/internal/telemetrytest/telemetry_test.go @@ -12,10 +12,12 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/gorilla/mux" "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/telemetrytest" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -23,13 +25,12 @@ import ( // sends the correct data to the telemetry client. func TestIntegrationInfo(t *testing.T) { // mux.NewRouter() uses the net/http and gorilla/mux integration - mux.NewRouter() - integrations := telemetry.Integrations() - require.Len(t, integrations, 2) - assert.Equal(t, integrations[0].Name, "net/http") - assert.True(t, integrations[0].Enabled) - assert.Equal(t, integrations[1].Name, "gorilla/mux") - assert.True(t, integrations[1].Enabled) + client := new(telemetrytest.RecordClient) + telemetry.StartApp(client) + _ = mux.NewRouter() + + assert.Contains(t, client.Integrations, telemetry.Integration{Name: "net/http", Version: "", Error: ""}) + assert.Contains(t, client.Integrations, telemetry.Integration{Name: "gorilla/mux", Version: "", Error: ""}) } type contribPkg struct { diff --git a/ddtrace/opentelemetry/telemetry_test.go b/ddtrace/opentelemetry/telemetry_test.go index 573d6ea699..05f6e62e20 100644 --- a/ddtrace/opentelemetry/telemetry_test.go +++ b/ddtrace/opentelemetry/telemetry_test.go @@ -73,16 +73,15 @@ func TestTelemetry(t *testing.T) { for k, v := range test.env { t.Setenv(k, v) } - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() p := NewTracerProvider() p.Tracer("") defer p.Shutdown() - assert.True(t, telemetryClient.Started) - telemetry.Check(t, telemetryClient.Configuration, "trace_propagation_style_inject", test.expectedInject) - telemetry.Check(t, telemetryClient.Configuration, "trace_propagation_style_extract", test.expectedExtract) + assert.Contains(t, telemetryClient.Configuration, telemetry.Configuration{Name: "trace_propagation_style_inject", Value: test.expectedInject}) + assert.Contains(t, telemetryClient.Configuration, telemetry.Configuration{Name: "trace_propagation_style_extract", Value: test.expectedExtract}) }) } diff --git a/ddtrace/opentelemetry/tracer.go b/ddtrace/opentelemetry/tracer.go index 42df8e2b6f..bf050b72bf 100644 --- a/ddtrace/opentelemetry/tracer.go +++ b/ddtrace/opentelemetry/tracer.go @@ -50,7 +50,7 @@ func (t *oteltracer) Start(ctx context.Context, spanName string, opts ...oteltra if k := ssConfig.SpanKind(); k != 0 { ddopts = append(ddopts, tracer.Tag(ext.SpanKind, k.String())) } - telemetry.GlobalClient.Count(telemetry.NamespaceTracers, "spans_created", 1.0, telemetryTags, true) + telemetry.Count(telemetry.NamespaceTracers, "spans_created", telemetryTags).Submit(1.0) var cfg ddtrace.StartSpanConfig cfg.Tags = make(map[string]interface{}) for _, attr := range ssConfig.Attributes() { diff --git a/ddtrace/opentelemetry/tracer_test.go b/ddtrace/opentelemetry/tracer_test.go index 9099fc132e..95982efc4f 100644 --- a/ddtrace/opentelemetry/tracer_test.go +++ b/ddtrace/opentelemetry/tracer_test.go @@ -195,14 +195,13 @@ func TestShutdownOnce(t *testing.T) { } func TestSpanTelemetry(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() tp := NewTracerProvider() otel.SetTracerProvider(tp) tr := otel.Tracer("") _, _ = tr.Start(context.Background(), "otel.span") - telemetryClient.AssertCalled(t, "Count", telemetry.NamespaceTracers, "spans_created", 1.0, telemetryTags, true) - telemetryClient.AssertNumberOfCalls(t, "Count", 1) + assert.NotZero(t, telemetryClient.Count(telemetry.NamespaceTracers, "spans_created", telemetryTags).Get()) } func TestConcurrentSetAttributes(_ *testing.T) { diff --git a/ddtrace/opentracer/tracer.go b/ddtrace/opentracer/tracer.go index d91191ebe0..8764d625b4 100644 --- a/ddtrace/opentracer/tracer.go +++ b/ddtrace/opentracer/tracer.go @@ -32,7 +32,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" - opentracing "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go" ) // New creates, instantiates and returns an Opentracing compatible version of the @@ -67,7 +67,7 @@ func (t *opentracer) StartSpan(operationName string, options ...opentracing.Star for k, v := range sso.Tags { opts = append(opts, tracer.Tag(k, v)) } - telemetry.GlobalClient.Count(telemetry.NamespaceTracers, "spans_created", 1.0, telemetryTags, true) + telemetry.Count(telemetry.NamespaceTracers, "spans_created", telemetryTags).Submit(1.0) return &span{ Span: t.Tracer.StartSpan(operationName, opts...), opentracer: t, diff --git a/ddtrace/opentracer/tracer_test.go b/ddtrace/opentracer/tracer_test.go index 6a57d9a6d3..4cb0ee4269 100644 --- a/ddtrace/opentracer/tracer_test.go +++ b/ddtrace/opentracer/tracer_test.go @@ -115,10 +115,9 @@ func TestExtractError(t *testing.T) { } func TestSpanTelemetry(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() opentracing.SetGlobalTracer(New()) _ = opentracing.StartSpan("opentracing.span") - telemetryClient.AssertCalled(t, "Count", telemetry.NamespaceTracers, "spans_created", 1.0, telemetryTags, true) - telemetryClient.AssertNumberOfCalls(t, "Count", 1) + assert.NotZero(t, telemetryClient.Count(telemetry.NamespaceTracers, "spans_created", telemetryTags).Get()) } diff --git a/ddtrace/tracer/dynamic_config.go b/ddtrace/tracer/dynamic_config.go index 67a3832a4c..e0bb4928ed 100644 --- a/ddtrace/tracer/dynamic_config.go +++ b/ddtrace/tracer/dynamic_config.go @@ -26,11 +26,12 @@ type dynamicConfig[T any] struct { func newDynamicConfig[T any](name string, val T, apply func(T) bool, equal func(x, y T) bool) dynamicConfig[T] { return dynamicConfig[T]{ - cfgName: name, - current: val, - startup: val, - apply: apply, - equal: equal, + cfgName: name, + current: val, + startup: val, + cfgOrigin: telemetry.OriginDefault, + apply: apply, + equal: equal, } } @@ -79,10 +80,9 @@ func (dc *dynamicConfig[T]) handleRC(val *T) bool { func (dc *dynamicConfig[T]) toTelemetry() telemetry.Configuration { dc.RLock() defer dc.RUnlock() - value := telemetry.SanitizeConfigValue(dc.current) return telemetry.Configuration{ Name: dc.cfgName, - Value: value, + Value: dc.current, Origin: dc.cfgOrigin, } } diff --git a/ddtrace/tracer/log_test.go b/ddtrace/tracer/log_test.go index 0b94b91494..b04fd8b2b3 100644 --- a/ddtrace/tracer/log_test.go +++ b/ddtrace/tracer/log_test.go @@ -14,7 +14,6 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -30,7 +29,7 @@ func TestStartupLog(t *testing.T) { defer stop() tp.Reset() - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") logStartup(tracer) require.Len(t, tp.Logs(), 2) assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test(\.exe)?","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sample_rate_limit":"disabled","trace_sampling_rules":null,"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"runtime_metrics_v2_enabled":false,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":((true)|(false)),"Stats":((true)|(false)),"StatsdPort":(0|8125)},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext","tracing_as_transport":false,"dogstatsd_address":"localhost:8125"}`, tp.Logs()[1]) @@ -62,7 +61,7 @@ func TestStartupLog(t *testing.T) { defer stop() tp.Reset() - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") logStartup(tracer) require.Len(t, tp.Logs(), 2) assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"configuredEnv","service":"configured.service","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":true,"analytics_enabled":true,"sample_rate":"0\.123000","sample_rate_limit":"100","trace_sampling_rules":\[{"service":"mysql","sample_rate":0\.75}\],"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":{"initial_service":"new_service"},"tags":{"runtime-id":"[^"]*","tag":"value","tag2":"NaN"},"runtime_metrics_enabled":true,"runtime_metrics_v2_enabled":false,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"2.3.4","architecture":"[^"]*","global_service":"configured.service","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"StatsdPort":(0|8125)},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":true,"metadata":{"version":"v1"}},"feature_flags":\["discovery"\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext","tracing_as_transport":false,"dogstatsd_address":"localhost:8125"}`, tp.Logs()[1]) @@ -92,7 +91,7 @@ func TestStartupLog(t *testing.T) { defer stop() tp.Reset() - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") logStartup(tracer) require.Len(t, tp.Logs(), 2) assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"configuredEnv","service":"configured.service","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":true,"analytics_enabled":true,"sample_rate":"0\.123000","sample_rate_limit":"1000.001","trace_sampling_rules":\[{"service":"mysql","sample_rate":0\.75}\],"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":{"initial_service":"new_service"},"tags":{"runtime-id":"[^"]*","tag":"value","tag2":"NaN"},"runtime_metrics_enabled":true,"runtime_metrics_v2_enabled":false,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"2.3.4","architecture":"[^"]*","global_service":"configured.service","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"StatsdPort":(0|8125)},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext","tracing_as_transport":false,"dogstatsd_address":"localhost:8125"}`, tp.Logs()[1]) @@ -106,7 +105,7 @@ func TestStartupLog(t *testing.T) { defer stop() tp.Reset() - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") logStartup(tracer) require.Len(t, tp.Logs(), 2) fmt.Println(tp.Logs()[1]) @@ -120,7 +119,7 @@ func TestStartupLog(t *testing.T) { defer stop() tp.Reset() - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") logStartup(tracer) assert.Len(tp.Logs(), 1) assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test(\.exe)?","agent_url":"http://localhost:9/v0.4/traces","agent_error":"","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sample_rate_limit":"disabled","trace_sampling_rules":null,"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"runtime_metrics_v2_enabled":false,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"true","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"StatsdPort":(0|8125)},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext","tracing_as_transport":false,"dogstatsd_address":"localhost:8125"}`, tp.Logs()[0]) @@ -132,7 +131,7 @@ func TestStartupLog(t *testing.T) { tracer, _, _, stop := startTestTracer(t, WithLogger(tp)) defer stop() tp.Reset() - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") logStartup(tracer) require.Len(t, tp.Logs(), 2) @@ -146,7 +145,7 @@ func TestStartupLog(t *testing.T) { func TestLogSamplingRules(t *testing.T) { assert := assert.New(t) tp := new(log.RecordLogger) - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") t.Setenv("DD_TRACE_SAMPLING_RULES", `[{"service": "some.service", "sample_rate": 0.234}, {"service": "other.service"}, {"service": "last.service", "sample_rate": 0.56}, {"odd": "pairs"}, {"sample_rate": 9.10}]`) _, _, _, stop := startTestTracer(t, WithLogger(tp), WithEnv("test")) defer stop() @@ -158,7 +157,7 @@ func TestLogSamplingRules(t *testing.T) { func TestLogDefaultSampleRate(t *testing.T) { assert := assert.New(t) tp := new(log.RecordLogger) - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") log.UseLogger(tp) t.Setenv("DD_TRACE_SAMPLE_RATE", ``) _, _, _, stop := startTestTracer(t, WithLogger(tp), WithEnv("test")) @@ -173,7 +172,7 @@ func TestLogAgentReachable(t *testing.T) { tracer, _, _, stop := startTestTracer(t, WithLogger(tp)) defer stop() tp.Reset() - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") logStartup(tracer) require.Len(t, tp.Logs(), 2) assert.Regexp(logPrefixRegexp+` WARN: DIAGNOSTICS Unable to reach agent intake: Post`, tp.Logs()[0]) @@ -186,7 +185,7 @@ func TestLogFormat(t *testing.T) { tracer, _, _, stop := startTestTracer(t, WithLogger(tp), WithRuntimeMetrics(), WithDebugMode(true)) defer stop() tp.Reset() - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") tracer.StartSpan("test", ServiceName("test-service"), ResourceName("/"), WithSpanID(12345)) assert.Len(tp.Logs(), 1) assert.Regexp(logPrefixRegexp+` DEBUG: Started Span: dd.trace_id="12345" dd.span_id="12345" dd.parent_id="0", Operation: test, Resource: /, Tags: map.*, map.*`, tp.Logs()[0]) @@ -257,7 +256,7 @@ func setup(t *testing.T, customProp Propagator) string { } defer stop() tp.Reset() - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") logStartup(tracer) require.Len(t, tp.Logs(), 2) return tp.Logs()[1] @@ -278,7 +277,7 @@ func TestAgentURL(t *testing.T) { tracer := newTracer(WithLogger(tp), WithUDS("var/run/datadog/apm.socket")) defer tracer.Stop() tp.Reset() - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") logStartup(tracer) logEntry, found := findLogEntry(tp.Logs(), `"agent_url":"unix://var/run/datadog/apm.socket"`) if !found { @@ -294,7 +293,7 @@ func TestAgentURLFromEnv(t *testing.T) { tracer := newTracer(WithLogger(tp)) defer tracer.Stop() tp.Reset() - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") logStartup(tracer) logEntry, found := findLogEntry(tp.Logs(), `"agent_url":"unix://var/run/datadog/apm.socket"`) if !found { @@ -311,7 +310,7 @@ func TestInvalidAgentURL(t *testing.T) { tracer := newTracer(WithLogger(tp)) defer tracer.Stop() tp.Reset() - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") logStartup(tracer) logEntry, found := findLogEntry(tp.Logs(), `"agent_url":"http://localhost:8126/v0.4/traces"`) if !found { @@ -328,7 +327,7 @@ func TestAgentURLConflict(t *testing.T) { tracer := newTracer(WithLogger(tp), WithUDS("var/run/datadog/apm.socket"), WithAgentAddr("localhost:8126")) defer tracer.Stop() tp.Reset() - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") logStartup(tracer) logEntry, found := findLogEntry(tp.Logs(), `"agent_url":"http://localhost:8126/v0.4/traces"`) if !found { diff --git a/ddtrace/tracer/otel_dd_mappings_test.go b/ddtrace/tracer/otel_dd_mappings_test.go index 3f85dffa50..4427a02a3b 100644 --- a/ddtrace/tracer/otel_dd_mappings_test.go +++ b/ddtrace/tracer/otel_dd_mappings_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/telemetrytest" ) @@ -29,21 +30,21 @@ func TestAssessSource(t *testing.T) { assert.Equal(t, "abc", v) }) t.Run("both", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() // DD_SERVICE prevails t.Setenv("DD_SERVICE", "abc") t.Setenv("OTEL_SERVICE_NAME", "123") v := getDDorOtelConfig("service") assert.Equal(t, "abc", v) - telemetryClient.AssertCalled(t, "Count", telemetry.NamespaceTracers, "otel.env.hiding", 1.0, []string{"config_datadog:dd_service", "config_opentelemetry:otel_service_name"}, true) + assert.NotZero(t, telemetryClient.Count(telemetry.NamespaceTracers, "otel.env.hiding", []string{"config_datadog:dd_service", "config_opentelemetry:otel_service_name"}).Get()) }) t.Run("invalid-ot", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() t.Setenv("OTEL_LOG_LEVEL", "nonesense") v := getDDorOtelConfig("debugMode") assert.Equal(t, "", v) - telemetryClient.AssertCalled(t, "Count", telemetry.NamespaceTracers, "otel.env.invalid", 1.0, []string{"config_datadog:dd_trace_debug", "config_opentelemetry:otel_log_level"}, true) + assert.NotZero(t, telemetryClient.Count(telemetry.NamespaceTracers, "otel.env.invalid", []string{"config_datadog:dd_trace_debug", "config_opentelemetry:otel_log_level"}).Get()) }) } diff --git a/ddtrace/tracer/remote_config_test.go b/ddtrace/tracer/remote_config_test.go index d64c2666fe..f54a6f21e0 100644 --- a/ddtrace/tracer/remote_config_test.go +++ b/ddtrace/tracer/remote_config_test.go @@ -22,10 +22,18 @@ import ( "github.com/stretchr/testify/require" ) +func assertCalled(t *testing.T, client *telemetrytest.RecordClient, cfgs []telemetry.Configuration) { + t.Helper() + + for _, cfg := range cfgs { + assert.Contains(t, client.Configuration, cfg) + } +} + func TestOnRemoteConfigUpdate(t *testing.T) { t.Run("RC sampling rate = 0.5 is applied and can be reverted", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() tracer, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) defer stop() @@ -45,12 +53,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { require.Equal(t, 0.5, s.Metrics[keyRulesSamplerAppliedRate]) // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 1) - telemetryClient.AssertCalled( - t, - "ConfigChange", - []telemetry.Configuration{{Name: "trace_sample_rate", Value: 0.5, Origin: telemetry.OriginRemoteConfig}}, - ) + assert.Contains(t, telemetryClient.Configuration, telemetry.Configuration{Name: "trace_sample_rate", Value: 0.5, Origin: telemetry.OriginRemoteConfig}) //Apply RC with sampling rules. Assert _dd.rule_psr shows the corresponding rule matched rate. input = remoteconfig.ProductUpdate{ @@ -88,15 +91,13 @@ func TestOnRemoteConfigUpdate(t *testing.T) { s.Finish() require.NotContains(t, keyRulesSamplerAppliedRate, s.Metrics) - // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 3) - // Not calling AssertCalled because the configuration contains a math.NaN() - // as value which cannot be asserted see https://github.com/stretchr/testify/issues/624 + // assert telemetry config contains trace_sample_rate with Nan (marshalled as nil) + assert.Contains(t, telemetryClient.Configuration, telemetry.Configuration{Name: "trace_sample_rate", Value: nil, Origin: telemetry.OriginDefault}) }) t.Run("DD_TRACE_SAMPLE_RATE=0.1 and RC sampling rate = 0.2", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() t.Setenv("DD_TRACE_SAMPLE_RATE", "0.1") tracer, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) @@ -116,13 +117,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { s.Finish() require.Equal(t, 0.2, s.Metrics[keyRulesSamplerAppliedRate]) - // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 1) - telemetryClient.AssertCalled( - t, - "ConfigChange", - []telemetry.Configuration{{Name: "trace_sample_rate", Value: 0.2, Origin: telemetry.OriginRemoteConfig}}, - ) + assert.Contains(t, telemetryClient.Configuration, telemetry.Configuration{Name: "trace_sample_rate", Value: 0.2, Origin: telemetry.OriginRemoteConfig}) // Unset RC. Assert _dd.rule_psr shows the previous sampling rate (0.1) is applied input = remoteconfig.ProductUpdate{ @@ -134,18 +129,12 @@ func TestOnRemoteConfigUpdate(t *testing.T) { s.Finish() require.Equal(t, 0.1, s.Metrics[keyRulesSamplerAppliedRate]) - // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 2) - telemetryClient.AssertCalled( - t, - "ConfigChange", - []telemetry.Configuration{{Name: "trace_sample_rate", Value: 0.1, Origin: telemetry.OriginDefault}}, - ) + assert.Contains(t, telemetryClient.Configuration, telemetry.Configuration{Name: "trace_sample_rate", Value: 0.1, Origin: telemetry.OriginDefault}) }) t.Run("DD_TRACE_SAMPLING_RULES rate=0.1 and RC trace sampling rules rate = 1.0", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() t.Setenv("DD_TRACE_SAMPLING_RULES", `[{ "service": "my-service", @@ -192,20 +181,17 @@ func TestOnRemoteConfigUpdate(t *testing.T) { require.Equal(t, samplerToDM(samplernames.RuleRate), s.context.trace.propagatingTags[keyDecisionMaker]) } - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 1) - telemetryClient.AssertCalled(t, "ConfigChange", []telemetry.Configuration{ - {Name: "trace_sample_rate", Value: 0.5, Origin: telemetry.OriginRemoteConfig}, - { - Name: "trace_sample_rules", - Value: `[{"service":"my-service","name":"web.request","resource":"abc","sample_rate":1,"provenance":"customer"}]`, - Origin: telemetry.OriginRemoteConfig, - }, + assert.Contains(t, telemetryClient.Configuration, telemetry.Configuration{Name: "trace_sample_rate", Value: 0.5, Origin: telemetry.OriginRemoteConfig}) + assert.Contains(t, telemetryClient.Configuration, telemetry.Configuration{ + Name: "trace_sample_rules", + Value: `[{"service":"my-service","name":"web.request","resource":"abc","sample_rate":1,"provenance":"customer"}]`, + Origin: telemetry.OriginRemoteConfig, }) }) t.Run("DD_TRACE_SAMPLING_RULES=0.1 and RC rule rate=1.0 and revert", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() t.Setenv("DD_TRACE_SAMPLING_RULES", `[{ "service": "my-service", @@ -270,13 +256,13 @@ func TestOnRemoteConfigUpdate(t *testing.T) { if p, ok := s.context.trace.samplingPriority(); ok && p > 0 { require.Equal(t, samplerToDM(samplernames.RuleRate), s.context.trace.propagatingTags[keyDecisionMaker]) } - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 2) - telemetryClient.AssertCalled(t, "ConfigChange", []telemetry.Configuration{ + + assertCalled(t, telemetryClient, []telemetry.Configuration{ {Name: "trace_sample_rate", Value: 0.5, Origin: telemetry.OriginRemoteConfig}, {Name: "trace_sample_rules", Value: `[{"service":"my-service","name":"web.request","resource":"abc","sample_rate":1,"provenance":"customer"} {"service":"my-service","name":"web.request","resource":"*","sample_rate":0.3,"provenance":"dynamic"}]`, Origin: telemetry.OriginRemoteConfig}, }) - telemetryClient.AssertCalled(t, "ConfigChange", []telemetry.Configuration{ + assertCalled(t, telemetryClient, []telemetry.Configuration{ {Name: "trace_sample_rate", Value: nil, Origin: telemetry.OriginDefault}, { Name: "trace_sample_rules", @@ -287,8 +273,8 @@ func TestOnRemoteConfigUpdate(t *testing.T) { }) t.Run("RC rule with tags", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() tracer, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) defer stop() @@ -301,7 +287,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { "provenance": "customer", "sample_rate": 1.0, "tags": [{"key": "tag-a", "value_glob": "tv-a??"}] - }]}, + }]}, "service_target": {"service": "my-service", "env": "my-env"}}`), } applyStatus := tracer.onRemoteConfigUpdate(input) @@ -320,8 +306,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { s.Finish() require.Equal(t, 0.5, s.Metrics[keyRulesSamplerAppliedRate]) - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 1) - telemetryClient.AssertCalled(t, "ConfigChange", []telemetry.Configuration{ + assertCalled(t, telemetryClient, []telemetry.Configuration{ {Name: "trace_sample_rate", Value: 0.5, Origin: telemetry.OriginRemoteConfig}, { Name: "trace_sample_rules", @@ -333,8 +318,8 @@ func TestOnRemoteConfigUpdate(t *testing.T) { t.Run("RC header tags = X-Test-Header:my-tag-name is applied and can be reverted", func(t *testing.T) { defer globalconfig.ClearHeaderTags() - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() tracer, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) defer stop() @@ -352,14 +337,9 @@ func TestOnRemoteConfigUpdate(t *testing.T) { require.Equal(t, 1, globalconfig.HeaderTagsLen()) require.Equal(t, "my-tag-name", globalconfig.HeaderTag("X-Test-Header")) - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 1) - telemetryClient.AssertCalled( - t, - "ConfigChange", - []telemetry.Configuration{ - {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name", Origin: telemetry.OriginRemoteConfig}, - }, - ) + assertCalled(t, telemetryClient, []telemetry.Configuration{ + {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name", Origin: telemetry.OriginRemoteConfig}, + }) // Unset RC. Assert header tags are not set input = remoteconfig.ProductUpdate{ @@ -370,17 +350,14 @@ func TestOnRemoteConfigUpdate(t *testing.T) { require.Equal(t, 0, globalconfig.HeaderTagsLen()) // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 2) - telemetryClient.AssertCalled( - t, - "ConfigChange", + assertCalled(t, telemetryClient, []telemetry.Configuration{{Name: "trace_header_tags", Value: "", Origin: telemetry.OriginDefault}}, ) }) t.Run("RC tracing_enabled = false is applied", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() tr, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) defer stop() @@ -428,16 +405,14 @@ func TestOnRemoteConfigUpdate(t *testing.T) { applyStatus = tr.onRemoteConfigUpdate(input) require.Equal(t, state.ApplyStateAcknowledged, applyStatus["path"].State) require.Equal(t, false, tr.config.enabled.current) - - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 1) }) t.Run( "DD_TRACE_HEADER_TAGS=X-Test-Header:my-tag-name-from-env and RC header tags = X-Test-Header:my-tag-name-from-rc", func(t *testing.T) { defer globalconfig.ClearHeaderTags() - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() t.Setenv("DD_TRACE_HEADER_TAGS", "X-Test-Header:my-tag-name-from-env") tracer, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) @@ -455,10 +430,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { require.Equal(t, "my-tag-name-from-rc", globalconfig.HeaderTag("X-Test-Header")) // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 1) - telemetryClient.AssertCalled( - t, - "ConfigChange", + assertCalled(t, telemetryClient, []telemetry.Configuration{ {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name-from-rc", Origin: telemetry.OriginRemoteConfig}, }, @@ -474,10 +446,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { require.Equal(t, "my-tag-name-from-env", globalconfig.HeaderTag("X-Test-Header")) // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 2) - telemetryClient.AssertCalled( - t, - "ConfigChange", + assertCalled(t, telemetryClient, []telemetry.Configuration{ {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name-from-env", Origin: telemetry.OriginDefault}, }, @@ -489,8 +458,8 @@ func TestOnRemoteConfigUpdate(t *testing.T) { "In code header tags = X-Test-Header:my-tag-name-in-code and RC header tags = X-Test-Header:my-tag-name-from-rc", func(t *testing.T) { defer globalconfig.ClearHeaderTags() - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() tracer, _, _, stop := startTestTracer( t, @@ -512,10 +481,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { require.Equal(t, "my-tag-name-from-rc", globalconfig.HeaderTag("X-Test-Header")) // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 1) - telemetryClient.AssertCalled( - t, - "ConfigChange", + assertCalled(t, telemetryClient, []telemetry.Configuration{ {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name-from-rc", Origin: telemetry.OriginRemoteConfig}, }, @@ -531,10 +497,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { require.Equal(t, "my-tag-name-in-code", globalconfig.HeaderTag("X-Test-Header")) // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 2) - telemetryClient.AssertCalled( - t, - "ConfigChange", + assertCalled(t, telemetryClient, []telemetry.Configuration{ {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-name-in-code", Origin: telemetry.OriginDefault}, }, @@ -543,8 +506,8 @@ func TestOnRemoteConfigUpdate(t *testing.T) { ) t.Run("Invalid payload", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() tracer, _, _, stop := startTestTracer(t, WithService("my-service"), WithEnv("my-env")) defer stop() @@ -561,12 +524,14 @@ func TestOnRemoteConfigUpdate(t *testing.T) { require.NotEmpty(t, applyStatus["path"].Error) // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 0) + for _, cfg := range telemetryClient.Configuration { + assert.NotEqual(t, "trace_sample_rate", cfg.Name) + } }) t.Run("Service mismatch", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() tracer, _, _, stop := startTestTracer(t, WithServiceName("my-service"), WithEnv("my-env")) defer stop() @@ -581,12 +546,14 @@ func TestOnRemoteConfigUpdate(t *testing.T) { require.Equal(t, "service mismatch", applyStatus["path"].Error) // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 0) + for _, cfg := range telemetryClient.Configuration { + assert.NotEqual(t, "trace_sample_rate", cfg.Name) + } }) t.Run("Env mismatch", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() tracer, _, _, stop := startTestTracer(t, WithServiceName("my-service"), WithEnv("my-env")) defer stop() @@ -601,12 +568,14 @@ func TestOnRemoteConfigUpdate(t *testing.T) { require.Equal(t, "env mismatch", applyStatus["path"].Error) // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 0) + for _, cfg := range telemetryClient.Configuration { + assert.NotEqual(t, "trace_sample_rate", cfg.Name) + } }) t.Run("DD_TAGS=key0:val0,key1:val1, WithGlobalTag=key2:val2 and RC tags = key3:val3,key4:val4", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() t.Setenv("DD_TAGS", "key0:val0,key1:val1") tracer, _, _, stop := startTestTracer( @@ -639,13 +608,9 @@ func TestOnRemoteConfigUpdate(t *testing.T) { runtimeIDTag := ext.RuntimeID + ":" + globalconfig.RuntimeID() // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 1) - telemetryClient.AssertCalled( - t, - "ConfigChange", - []telemetry.Configuration{ - {Name: "trace_tags", Value: "key3:val3,key4:val4," + runtimeIDTag, Origin: telemetry.OriginRemoteConfig}, - }, + assertCalled(t, telemetryClient, []telemetry.Configuration{ + {Name: "trace_tags", Value: "key3:val3,key4:val4," + runtimeIDTag, Origin: telemetry.OriginRemoteConfig}, + }, ) // Unset RC. Assert config shows the original DD_TAGS + WithGlobalTag + runtime ID @@ -664,20 +629,16 @@ func TestOnRemoteConfigUpdate(t *testing.T) { require.Equal(t, globalconfig.RuntimeID(), s.Meta[ext.RuntimeID]) // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 2) - telemetryClient.AssertCalled( - t, - "ConfigChange", - []telemetry.Configuration{ - {Name: "trace_tags", Value: "key0:val0,key1:val1,key2:val2," + runtimeIDTag, Origin: telemetry.OriginDefault}, - }, + assertCalled(t, telemetryClient, []telemetry.Configuration{ + {Name: "trace_tags", Value: "key0:val0,key1:val1,key2:val2," + runtimeIDTag, Origin: telemetry.OriginDefault}, + }, ) }) t.Run("Deleted config", func(t *testing.T) { defer globalconfig.ClearHeaderTags() - telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() t.Setenv("DD_TRACE_SAMPLE_RATE", "0.1") t.Setenv("DD_TRACE_HEADER_TAGS", "X-Test-Header:my-tag-from-env") @@ -702,8 +663,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { require.Equal(t, "from-rc", s.Meta["ddtag"]) // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 1) - telemetryClient.AssertCalled(t, "ConfigChange", []telemetry.Configuration{ + assertCalled(t, telemetryClient, []telemetry.Configuration{ {Name: "trace_sample_rate", Value: 0.2, Origin: telemetry.OriginRemoteConfig}, {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-from-rc", Origin: telemetry.OriginRemoteConfig}, { @@ -724,8 +684,7 @@ func TestOnRemoteConfigUpdate(t *testing.T) { require.Equal(t, "from-env", s.Meta["ddtag"]) // Telemetry - telemetryClient.AssertNumberOfCalls(t, "ConfigChange", 2) - telemetryClient.AssertCalled(t, "ConfigChange", []telemetry.Configuration{ + assertCalled(t, telemetryClient, []telemetry.Configuration{ {Name: "trace_sample_rate", Value: 0.1, Origin: telemetry.OriginDefault}, {Name: "trace_header_tags", Value: "X-Test-Header:my-tag-from-env", Origin: telemetry.OriginDefault}, {Name: "trace_tags", Value: "ddtag:from-env," + ext.RuntimeID + ":" + globalconfig.RuntimeID(), Origin: telemetry.OriginDefault}, diff --git a/ddtrace/tracer/spancontext_test.go b/ddtrace/tracer/spancontext_test.go index 7e86d9fdb1..0265fbd4a5 100644 --- a/ddtrace/tracer/spancontext_test.go +++ b/ddtrace/tracer/spancontext_test.go @@ -39,7 +39,7 @@ func TestNewSpanContextPushError(t *testing.T) { defer setupteardown(2, 2)() tp := new(log.RecordLogger) - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") _, _, _, stop := startTestTracer(t, WithLogger(tp), WithLambdaMode(true), WithEnv("testEnv")) defer stop() parent := newBasicSpan("test1") // 1st span in trace @@ -172,9 +172,9 @@ func TestPartialFlush(t *testing.T) { t.Setenv("DD_TRACE_PARTIAL_FLUSH_ENABLED", "true") t.Setenv("DD_TRACE_PARTIAL_FLUSH_MIN_SPANS", "2") t.Run("WithFlush", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - telemetryClient.ProductChange(telemetry.NamespaceTracers, true, nil) - defer telemetry.MockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + telemetryClient.ProductStarted(telemetry.NamespaceTracers) + defer telemetry.MockClient(telemetryClient)() tracer, transport, flush, stop := startTestTracer(t) defer stop() @@ -198,10 +198,9 @@ func TestPartialFlush(t *testing.T) { comparePayloadSpans(t, children[0], ts[0][0]) comparePayloadSpans(t, children[1], ts[0][1]) - telemetryClient.AssertCalled(t, "Count", telemetry.NamespaceTracers, "trace_partial_flush.count", 1.0, []string{"reason:large_trace"}, true) - // TODO: (Support MetricKindDist) Re-enable these when we actually support `MetricKindDist` - //telemetryClient.AssertCalled(t, "Record", telemetry.NamespaceTracers, "trace_partial_flush.spans_closed", 2.0, []string(nil), true) // Typed-nil here to not break usage of reflection in `mock` library. - //telemetryClient.AssertCalled(t, "Record", telemetry.NamespaceTracers, "trace_partial_flush.spans_remaining", 1.0, []string(nil), true) + assert.Equal(t, 1.0, telemetryClient.Count(telemetry.NamespaceTracers, "trace_partial_flush.count", []string{"reason:large_trace"}).Get()) + assert.Equal(t, 2.0, telemetryClient.Distribution(telemetry.NamespaceTracers, "trace_partial_flush.spans_closed", nil).Get()) + assert.Equal(t, 1.0, telemetryClient.Distribution(telemetry.NamespaceTracers, "trace_partial_flush.spans_remaining", nil).Get()) root.Finish() flush(1) @@ -214,9 +213,6 @@ func TestPartialFlush(t *testing.T) { assert.Equal(t, 1.0, ts[0][1].Metrics[keySamplingPriority]) // the tag should only be on the first span in the chunk comparePayloadSpans(t, root.(*span), tsRoot[0][0]) comparePayloadSpans(t, children[2], tsRoot[0][1]) - telemetryClient.AssertNumberOfCalls(t, "Count", 1) - // TODO: (Support MetricKindDist) Re-enable this when we actually support `MetricKindDist` - // telemetryClient.AssertNumberOfCalls(t, "Record", 2) }) // This test covers an issue where partial flushing + a rate sampler would panic @@ -242,7 +238,7 @@ func TestSpanTracePushNoFinish(t *testing.T) { assert := assert.New(t) tp := new(log.RecordLogger) - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") _, _, _, stop := startTestTracer(t, WithLogger(tp), WithLambdaMode(true), WithEnv("testEnv")) defer stop() @@ -756,7 +752,7 @@ func TestSpanContextPushFull(t *testing.T) { defer func(old int) { traceMaxSize = old }(traceMaxSize) traceMaxSize = 2 tp := new(log.RecordLogger) - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") _, _, _, stop := startTestTracer(t, WithLogger(tp), WithLambdaMode(true), WithEnv("testEnv")) defer stop() diff --git a/ddtrace/tracer/telemetry.go b/ddtrace/tracer/telemetry.go index 2ad5285bd8..632b0f1e50 100644 --- a/ddtrace/tracer/telemetry.go +++ b/ddtrace/tracer/telemetry.go @@ -34,21 +34,6 @@ func startTelemetry(c *config) { return } - if telemetry.GlobalClient() == nil { - cfg := telemetry.ClientConfig{ - HTTPClient: c.httpClient, - AgentURL: c.agentURL.String(), - } - if c.logToStdout || c.ciVisibilityAgentless { - cfg.APIKey = os.Getenv("DD_API_KEY") - } - client, err := telemetry.NewClient(c.serviceName, c.env, c.version, cfg) - if err != nil { - log.Debug("profiler: failed to create telemetry client: %v", err) - return - } - telemetry.StartApp(client) - } telemetry.ProductStarted(telemetry.NamespaceTracers) telemetryConfigs := []telemetry.Configuration{ {Name: "trace_debug_enabled", Value: c.debug}, @@ -125,4 +110,17 @@ func startTelemetry(c *config) { } telemetryConfigs = append(telemetryConfigs, additionalConfigs...) telemetry.RegisterAppConfigs(telemetryConfigs...) + cfg := telemetry.ClientConfig{ + HTTPClient: c.httpClient, + AgentURL: c.agentURL.String(), + } + if c.logToStdout || c.ciVisibilityAgentless { + cfg.APIKey = os.Getenv("DD_API_KEY") + } + client, err := telemetry.NewClient(c.serviceName, c.env, c.version, cfg) + if err != nil { + log.Debug("profiler: failed to create telemetry client: %v", err) + return + } + telemetry.StartApp(client) } diff --git a/ddtrace/tracer/telemetry_test.go b/ddtrace/tracer/telemetry_test.go index 3d64863cd1..a021185fbc 100644 --- a/ddtrace/tracer/telemetry_test.go +++ b/ddtrace/tracer/telemetry_test.go @@ -39,7 +39,7 @@ func checkConfig(t *testing.T, cfgs []telemetry.Configuration, key string, value func TestTelemetryEnabled(t *testing.T) { t.Run("tracer start", func(t *testing.T) { telemetryClient := new(telemetrytest.MockClient) - defer telemetry.MockGlobalClient(telemetryClient)() + defer mockGlobalClient(telemetryClient)() Start( WithDebugMode(true), diff --git a/ddtrace/tracer/textmap_test.go b/ddtrace/tracer/textmap_test.go index 0dd05124c0..90784852bc 100644 --- a/ddtrace/tracer/textmap_test.go +++ b/ddtrace/tracer/textmap_test.go @@ -16,16 +16,14 @@ import ( "sync" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/httpmem" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" - "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/DataDog/datadog-go/v5/statsd" ) @@ -2092,7 +2090,7 @@ func TestNonePropagator(t *testing.T) { t.Run("inject/none,b3", func(t *testing.T) { t.Setenv(headerPropagationStyleInject, "none,b3") tp := new(log.RecordLogger) - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") tracer := newTracer(WithLogger(tp), WithEnv("test")) defer tracer.Stop() // reinitializing to capture log output, since propagators are parsed before logger is set @@ -2421,7 +2419,7 @@ func FuzzComposeTracestate(f *testing.F) { if strings.HasSuffix(v, " ") { t.Skipf("Skipping invalid tags") } - totalLen += (len(k) + len(v)) + totalLen += len(k) + len(v) if totalLen > 128 { break } diff --git a/ddtrace/tracer/tracer_test.go b/ddtrace/tracer/tracer_test.go index dd1820621e..42658d6dc1 100644 --- a/ddtrace/tracer/tracer_test.go +++ b/ddtrace/tracer/tracer_test.go @@ -26,6 +26,10 @@ import ( pb "github.com/DataDog/datadog-agent/pkg/proto/pbgo/trace" "github.com/DataDog/datadog-go/v5/statsd" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tinylib/msgp/msgp" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal" @@ -33,11 +37,6 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/statsdtest" - "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/tinylib/msgp/msgp" ) func (t *tracer) newEnvSpan(service, env string) *span { @@ -712,7 +711,7 @@ func TestSamplingDecision(t *testing.T) { func TestTracerRuntimeMetrics(t *testing.T) { t.Run("on", func(t *testing.T) { tp := new(log.RecordLogger) - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") tracer := newTracer(WithRuntimeMetrics(), WithLogger(tp), WithDebugMode(true), WithEnv("test")) defer tracer.Stop() assert.Contains(t, tp.Logs()[0], "DEBUG: Runtime metrics enabled") @@ -721,7 +720,7 @@ func TestTracerRuntimeMetrics(t *testing.T) { t.Run("dd-env", func(t *testing.T) { t.Setenv("DD_RUNTIME_METRICS_ENABLED", "true") tp := new(log.RecordLogger) - tp.Ignore("appsec: ", telemetry.LogPrefix) + tp.Ignore("appsec: ", "telemetry") tracer := newTracer(WithLogger(tp), WithDebugMode(true), WithEnv("test")) defer tracer.Stop() assert.Contains(t, tp.Logs()[0], "DEBUG: Runtime metrics enabled") @@ -1486,7 +1485,7 @@ func TestTracerRace(t *testing.T) { // different orders, and modifying spans after creation. for n := 0; n < total; n++ { i := n // keep local copy - odd := ((i % 2) != 0) + odd := (i % 2) != 0 go func() { if i%11 == 0 { time.Sleep(time.Microsecond) diff --git a/internal/appsec/config/config.go b/internal/appsec/config/config.go index 73b4dd659d..d019ad9ccc 100644 --- a/internal/appsec/config/config.go +++ b/internal/appsec/config/config.go @@ -19,25 +19,20 @@ import ( ) func init() { - registerAppConfigTelemetry() -} - -// Register the global app telemetry configuration. -func registerAppConfigTelemetry() { - registerSCAAppConfigTelemetry(telemetry.GlobalClient()) + registerSCAAppConfigTelemetry() } // Register the global app telemetry configuration related to the Software Composition Analysis (SCA) product. // Report over telemetry whether SCA's enablement env var was set or not along with its value. Nothing is reported in // case of an error or if the env var is not set. -func registerSCAAppConfigTelemetry(client telemetry.Client) { +func registerSCAAppConfigTelemetry() { val, defined, err := parseBoolEnvVar(EnvSCAEnabled) if err != nil { log.Error("appsec: %v", err) return } if defined { - client.RegisterAppConfig(EnvSCAEnabled, val, telemetry.OriginEnvVar) + telemetry.RegisterAppConfig(EnvSCAEnabled, val, telemetry.OriginEnvVar) } } diff --git a/internal/appsec/config/config_test.go b/internal/appsec/config/config_test.go index 77d29e36df..9dc26262e0 100644 --- a/internal/appsec/config/config_test.go +++ b/internal/appsec/config/config_test.go @@ -51,8 +51,9 @@ func TestSCAEnabled(t *testing.T) { telemetryClient := new(telemetrytest.MockClient) telemetryClient.On("RegisterAppConfig", EnvSCAEnabled, tc.expectedValue, telemetry.OriginEnvVar).Return() + defer telemetry.MockClient(telemetryClient)() - registerSCAAppConfigTelemetry(telemetryClient) + registerSCAAppConfigTelemetry() if tc.telemetryExpected { telemetryClient.AssertCalled(t, "RegisterAppConfig", EnvSCAEnabled, tc.expectedValue, telemetry.OriginEnvVar) diff --git a/internal/appsec/features.go b/internal/appsec/features.go index ca286de742..e9ef073378 100644 --- a/internal/appsec/features.go +++ b/internal/appsec/features.go @@ -66,8 +66,12 @@ func (a *appsec) SwapRootOperation() error { oldFeatures := a.features a.features = newFeatures - log.Debug("appsec: stopping the following features: %v", oldFeatures) - log.Debug("appsec: starting the following features: %v", newFeatures) + if len(oldFeatures) > 0 { + log.Debug("appsec: stopping the following features: %v", oldFeatures) + } + if len(newFeatures) > 0 { + log.Debug("appsec: starting the following features: %v", newFeatures) + } dyngo.SwapRootOperation(newRoot) diff --git a/internal/civisibility/utils/telemetry/telemetry_count.go b/internal/civisibility/utils/telemetry/telemetry_count.go index 6dca55279e..d1b1e175a1 100644 --- a/internal/civisibility/utils/telemetry/telemetry_count.go +++ b/internal/civisibility/utils/telemetry/telemetry_count.go @@ -208,5 +208,5 @@ func KnownTestsRequest(requestCompressed RequestCompressedType) { // KnownTestsRequestErrors the number of requests sent to the known tests endpoint that errored, tagged by the error type. func KnownTestsRequestErrors(errorType ErrorType) { - telemetry.Count(telemetry.NamespaceCIVisibility, "known_tests.request_errors", removeEmptyStrings(errorType)).Submit(1.0) + telemetry.Count(telemetry.NamespaceCIVisibility, "known_tests.request_errors", removeEmptyStrings(errorType)).Submit(1.0) } diff --git a/internal/civisibility/utils/telemetry/telemetry_distribution.go b/internal/civisibility/utils/telemetry/telemetry_distribution.go index adb489f7c8..93b3228f5c 100644 --- a/internal/civisibility/utils/telemetry/telemetry_distribution.go +++ b/internal/civisibility/utils/telemetry/telemetry_distribution.go @@ -86,9 +86,9 @@ func CodeCoverageFiles(value float64) { telemetry.Distribution(telemetry.NamespaceCIVisibility, "code_coverage.files", nil).Submit(value) } -// EarlyFlakeDetectionRequestMs records the time it takes to get the response of the early flake detection endpoint request in ms by CI Visibility. -func EarlyFlakeDetectionRequestMs(value float64) { - telemetry.GlobalClient.Record(telemetry.NamespaceCiVisibility, telemetry.MetricKindDist, "early_flake_detection.request_ms", value, nil, true) +// KnownTestsRequestMs records the time it takes to get the response of the known tests endpoint request in ms by CI Visibility. +func KnownTestsRequestMs(value float64) { + telemetry.Distribution(telemetry.NamespaceCIVisibility, "known_tests.request_ms", nil).Submit(value) } // KnownTestsResponseBytes records the number of bytes received by the endpoint. Tagged with a boolean flag set to true if response body is compressed. diff --git a/internal/telemetry/telemetrytest/record.go b/internal/telemetry/telemetrytest/record.go index 0e6a95361b..daee5f3e4a 100644 --- a/internal/telemetry/telemetrytest/record.go +++ b/internal/telemetry/telemetrytest/record.go @@ -157,14 +157,15 @@ func (r *RecordClient) ProductStartError(product telemetry.Namespace, _ error) { } func (r *RecordClient) RegisterAppConfig(key string, value any, origin telemetry.Origin) { - r.mu.Lock() - defer r.mu.Unlock() - r.Configuration = append(r.Configuration, telemetry.Configuration{Name: key, Value: value, Origin: origin}) + r.RegisterAppConfigs(telemetry.Configuration{Name: key, Value: value, Origin: origin}) } func (r *RecordClient) RegisterAppConfigs(kvs ...telemetry.Configuration) { r.mu.Lock() defer r.mu.Unlock() + for i := range kvs { + kvs[i].Value = telemetry.SanitizeConfigValue(kvs[i].Value) + } r.Configuration = append(r.Configuration, kvs...) } @@ -189,6 +190,7 @@ func (r *RecordClient) AppStop() { } func CheckConfig(t *testing.T, cfgs []telemetry.Configuration, key string, value any) { + t.Helper() for _, c := range cfgs { if c.Name == key && reflect.DeepEqual(c.Value, value) { return diff --git a/profiler/telemetry.go b/profiler/telemetry.go index 780b2f3bf6..0fb63f9405 100644 --- a/profiler/telemetry.go +++ b/profiler/telemetry.go @@ -25,18 +25,6 @@ func startTelemetry(c *config) { _, ok := c.types[t] return ok } - if telemetry.GlobalClient() == nil { - client, err := telemetry.NewClient(c.service, c.env, c.version, telemetry.ClientConfig{ - HTTPClient: c.httpClient, - APIKey: c.apiKey, - AgentURL: c.agentURL, - }) - if err != nil { - log.Debug("profiler: failed to create telemetry client: %v", err) - return - } - telemetry.StartApp(client) - } telemetry.ProductStarted(telemetry.NamespaceProfilers) telemetry.RegisterAppConfigs( []telemetry.Configuration{ @@ -64,4 +52,16 @@ func startTelemetry(c *config) { {Name: "flush_on_exit", Value: c.flushOnExit}, }..., ) + if telemetry.GlobalClient() == nil { + client, err := telemetry.NewClient(c.service, c.env, c.version, telemetry.ClientConfig{ + HTTPClient: c.httpClient, + APIKey: c.apiKey, + AgentURL: c.agentURL, + }) + if err != nil { + log.Debug("profiler: failed to create telemetry client: %v", err) + return + } + telemetry.StartApp(client) + } } diff --git a/profiler/telemetry_test.go b/profiler/telemetry_test.go index c88b726ce4..81a4fca36e 100644 --- a/profiler/telemetry_test.go +++ b/profiler/telemetry_test.go @@ -15,19 +15,11 @@ import ( "github.com/stretchr/testify/assert" ) -func mockGlobalClient(client telemetry.Client) func() { - orig := telemetry.GlobalClient() - telemetry.SwapClient(client) - return func() { - telemetry.SwapClient(orig) - } -} - // Test that the profiler sends the correct telemetry information func TestTelemetryEnabled(t *testing.T) { t.Run("tracer start, profiler start", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer mockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() tracer.Start() defer tracer.Stop() @@ -41,11 +33,10 @@ func TestTelemetryEnabled(t *testing.T) { assert.True(t, telemetryClient.Products[telemetry.NamespaceProfilers]) assert.Contains(t, telemetryClient.Configuration, telemetry.Configuration{Name: "heap_profile_enabled", Value: true}) - telemetryClient.AssertCalled(t, "ProductStarted", telemetry.NamespaceProfilers) }) t.Run("only profiler start", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer mockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() Start( WithProfileTypes( HeapProfile, @@ -55,6 +46,5 @@ func TestTelemetryEnabled(t *testing.T) { assert.True(t, telemetryClient.Products[telemetry.NamespaceProfilers]) assert.Contains(t, telemetryClient.Configuration, telemetry.Configuration{Name: "heap_profile_enabled", Value: true}) - telemetryClient.AssertCalled(t, "ProductStarted", telemetry.NamespaceProfilers) }) } From 121ec679763af8878fbb95475d54d6b6f4e1982b Mon Sep 17 00:00:00 2001 From: Eliott Bouhana Date: Mon, 10 Feb 2025 14:17:04 +0100 Subject: [PATCH 6/9] send the startup log over as telemetry debug log Signed-off-by: Eliott Bouhana --- ddtrace/tracer/log.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ddtrace/tracer/log.go b/ddtrace/tracer/log.go index 14bf075a9d..5a2cdc156e 100644 --- a/ddtrace/tracer/log.go +++ b/ddtrace/tracer/log.go @@ -18,6 +18,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/osinfo" + telemetrylog "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/version" ) @@ -170,4 +171,5 @@ func logStartup(t *tracer) { return } log.Info("DATADOG TRACER CONFIGURATION %s\n", string(bs)) + telemetrylog.Debug("DATADOG TRACER CONFIGURATION %s\n", string(bs)) } From 5db05d4de562bc7e9ac6f21d0a90eca33d3f53b3 Mon Sep 17 00:00:00 2001 From: Eliott Bouhana Date: Mon, 10 Feb 2025 18:22:13 +0100 Subject: [PATCH 7/9] rename in tracer Signed-off-by: Eliott Bouhana --- ddtrace/tracer/telemetry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/tracer/telemetry.go b/ddtrace/tracer/telemetry.go index 632b0f1e50..8e809fabe5 100644 --- a/ddtrace/tracer/telemetry.go +++ b/ddtrace/tracer/telemetry.go @@ -119,7 +119,7 @@ func startTelemetry(c *config) { } client, err := telemetry.NewClient(c.serviceName, c.env, c.version, cfg) if err != nil { - log.Debug("profiler: failed to create telemetry client: %v", err) + log.Debug("tracer: failed to create telemetry client: %v", err) return } telemetry.StartApp(client) From 96bf15acf3f2864ab9de47c040e1d974ece6b99e Mon Sep 17 00:00:00 2001 From: Eliott Bouhana Date: Mon, 10 Feb 2025 18:38:36 +0100 Subject: [PATCH 8/9] fix rebasing issue Signed-off-by: Eliott Bouhana --- ddtrace/tracer/telemetry_test.go | 105 +++++++++++-------------------- 1 file changed, 38 insertions(+), 67 deletions(-) diff --git a/ddtrace/tracer/telemetry_test.go b/ddtrace/tracer/telemetry_test.go index a021185fbc..fc83df9e4b 100644 --- a/ddtrace/tracer/telemetry_test.go +++ b/ddtrace/tracer/telemetry_test.go @@ -7,7 +7,6 @@ package tracer import ( "fmt" - "reflect" "testing" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" @@ -18,28 +17,10 @@ import ( "github.com/stretchr/testify/assert" ) -func mockGlobalClient(client telemetry.Client) func() { - orig := telemetry.GlobalClient() - telemetry.SwapClient(client) - return func() { - telemetry.SwapClient(orig) - } -} - -func checkConfig(t *testing.T, cfgs []telemetry.Configuration, key string, value any) { - for _, c := range cfgs { - if c.Name == key && reflect.DeepEqual(c.Value, value) { - return - } - } - - t.Fatalf("could not find configuration key %s with value %v", key, value) -} - func TestTelemetryEnabled(t *testing.T) { t.Run("tracer start", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer mockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() Start( WithDebugMode(true), @@ -60,25 +41,24 @@ func TestTelemetryEnabled(t *testing.T) { defer globalconfig.SetServiceName("") defer Stop() - assert.True(t, telemetryClient.Started) - telemetryClient.AssertNumberOfCalls(t, "ApplyOps", 1) - checkConfig(t, telemetryClient.Configuration, "trace_debug_enabled", true) - checkConfig(t, telemetryClient.Configuration, "service", "test-serv") - checkConfig(t, telemetryClient.Configuration, "env", "test-env") - checkConfig(t, telemetryClient.Configuration, "runtime_metrics_enabled", true) - checkConfig(t, telemetryClient.Configuration, "stats_computation_enabled", false) - checkConfig(t, telemetryClient.Configuration, "trace_enabled", true) - checkConfig(t, telemetryClient.Configuration, "trace_span_attribute_schema", 0) - checkConfig(t, telemetryClient.Configuration, "trace_peer_service_defaults_enabled", true) - checkConfig(t, telemetryClient.Configuration, "trace_peer_service_mapping", "key:val") - checkConfig(t, telemetryClient.Configuration, "debug_stack_enabled", false) - checkConfig(t, telemetryClient.Configuration, "orchestrion_enabled", false) - checkConfig(t, telemetryClient.Configuration, "trace_sample_rate", nil) // default value is NaN which is sanitized to nil - checkConfig(t, telemetryClient.Configuration, "trace_header_tags", "key:val,key2:val2") - checkConfig(t, telemetryClient.Configuration, "trace_sample_rules", + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "trace_debug_enabled", true) + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "service", "test-serv") + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "env", "test-env") + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "runtime_metrics_enabled", true) + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "stats_computation_enabled", false) + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "trace_enabled", true) + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "trace_span_attribute_schema", 0) + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "trace_peer_service_defaults_enabled", true) + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "trace_peer_service_mapping", "key:val") + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "debug_stack_enabled", false) + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "orchestrion_enabled", false) + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "trace_sample_rate", nil) // default value is NaN which is sanitized to nil + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "trace_header_tags", "key:val,key2:val2") + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "trace_sample_rules", `[{"service":"test-serv","name":"op-name","resource":"resource-*","sample_rate":0.1,"tags":{"tag-a":"tv-a??"}}]`) - checkConfig(t, telemetryClient.Configuration, "span_sample_rules", "[]") - assert.NotZero(t, telemetryClient.Distribution(telemetry.NamespaceTracers, "init_time", nil).Get()) + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "span_sample_rules", "[]") + + assert.NotZero(t, telemetryClient.Distribution(telemetry.NamespaceGeneral, "init_time", nil).Get()) }) t.Run("telemetry customer or dynamic rules", func(t *testing.T) { @@ -90,16 +70,15 @@ func TestTelemetryEnabled(t *testing.T) { } rule.Provenance = prov - telemetryClient := new(telemetrytest.MockClient) - defer mockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() Start(WithService("test-serv"), WithSamplingRules([]SamplingRule{rule}), ) defer globalconfig.SetServiceName("") defer Stop() - assert.True(t, telemetryClient.Started) - checkConfig(t, telemetryClient.Configuration, "trace_sample_rules", + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "trace_sample_rules", fmt.Sprintf(`[{"service":"test-serv","name":"op-name","resource":"resource-*","sample_rate":0.1,"tags":{"tag-a":"tv-a??"},"provenance":"%s"}]`, prov.String())) } }) @@ -115,24 +94,23 @@ func TestTelemetryEnabled(t *testing.T) { rules[i].Provenance = Local } - telemetryClient := new(telemetrytest.MockClient) - defer mockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() Start(WithService("test-serv"), WithSamplingRules(rules), ) defer globalconfig.SetServiceName("") defer Stop() - assert.True(t, telemetryClient.Started) - checkConfig(t, telemetryClient.Configuration, "trace_sample_rules", + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "trace_sample_rules", `[{"service":"test-serv","name":"op-name","resource":"resource-*","sample_rate":0.1,"tags":{"tag-a":"tv-a??"}}]`) - checkConfig(t, telemetryClient.Configuration, "span_sample_rules", + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "span_sample_rules", `[{"service":"test-serv","name":"op-name","sample_rate":0.1}]`) }) t.Run("tracer start with empty rules", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer mockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() t.Setenv("DD_TRACE_SAMPLING_RULES", "") t.Setenv("DD_SPAN_SAMPLING_RULES", "") @@ -140,19 +118,13 @@ func TestTelemetryEnabled(t *testing.T) { defer globalconfig.SetServiceName("") defer Stop() - assert.True(t, telemetryClient.Started) - var cfgs []telemetry.Configuration - for _, c := range telemetryClient.Configuration { - c.Value = telemetry.SanitizeConfigValue(c.Value) - cfgs = append(cfgs) - } - checkConfig(t, cfgs, "trace_sample_rules", "[]") - checkConfig(t, cfgs, "span_sample_rules", "[]") + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "trace_sample_rules", "[]") + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "span_sample_rules", "[]") }) t.Run("profiler start, tracer start", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer mockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() profiler.Start() defer profiler.Stop() Start( @@ -160,18 +132,17 @@ func TestTelemetryEnabled(t *testing.T) { ) defer globalconfig.SetServiceName("") defer Stop() - checkConfig(t, telemetryClient.Configuration, "service", "test-serv") - telemetryClient.AssertNumberOfCalls(t, "ApplyOps", 2) + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "service", "test-serv") }) t.Run("orchestrion telemetry", func(t *testing.T) { - telemetryClient := new(telemetrytest.MockClient) - defer mockGlobalClient(telemetryClient)() + telemetryClient := new(telemetrytest.RecordClient) + defer telemetry.MockClient(telemetryClient)() Start(WithOrchestrion(map[string]string{"k1": "v1", "k2": "v2"})) defer Stop() - checkConfig(t, telemetryClient.Configuration, "orchestrion_enabled", true) - checkConfig(t, telemetryClient.Configuration, "orchestrion_k1", "v1") - checkConfig(t, telemetryClient.Configuration, "orchestrion_k2", "v2") + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "orchestrion_enabled", true) + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "orchestrion_k1", "v1") + telemetrytest.CheckConfig(t, telemetryClient.Configuration, "orchestrion_k2", "v2") }) } From 4ea2847927c0f7a30d867550fd78c0a03b86f8e1 Mon Sep 17 00:00:00 2001 From: Eliott Bouhana Date: Thu, 13 Feb 2025 14:01:55 +0100 Subject: [PATCH 9/9] fix mistake in civis telemetry product start Signed-off-by: Eliott Bouhana --- internal/civisibility/utils/net/client.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/internal/civisibility/utils/net/client.go b/internal/civisibility/utils/net/client.go index c823f6a0f6..694b3b0b23 100644 --- a/internal/civisibility/utils/net/client.go +++ b/internal/civisibility/utils/net/client.go @@ -127,18 +127,18 @@ func NewClientWithServiceNameAndSubdomain(serviceName, subdomain string) Client var baseURL string var requestHandler *RequestHandler var agentURL *url.URL - var APIKeyValue string + var apiKeyValue string agentlessEnabled := internal.BoolEnv(constants.CIVisibilityAgentlessEnabledEnvironmentVariable, false) if agentlessEnabled { // Agentless mode is enabled. - APIKeyValue = os.Getenv(constants.APIKeyEnvironmentVariable) - if APIKeyValue == "" { + apiKeyValue = os.Getenv(constants.APIKeyEnvironmentVariable) + if apiKeyValue == "" { log.Error("An API key is required for agentless mode. Use the DD_API_KEY env variable to set it") return nil } - defaultHeaders["dd-api-key"] = APIKeyValue + defaultHeaders["dd-api-key"] = apiKeyValue // Check for a custom agentless URL. agentlessURL := os.Getenv(constants.CIVisibilityAgentlessURLEnvironmentVariable) @@ -207,13 +207,19 @@ func NewClientWithServiceNameAndSubdomain(serviceName, subdomain string) Client if !telemetry.Disabled() { telemetryInit.Do(func() { - telemetry.ProductStarted(telemetry.NamespaceTracers) + telemetry.ProductStarted(telemetry.NamespaceCIVisibility) + telemetry.RegisterAppConfigs( + telemetry.Configuration{Name: "service", Value: serviceName}, + telemetry.Configuration{Name: "env", Value: environment}, + telemetry.Configuration{Name: "agentless", Value: agentlessEnabled}, + telemetry.Configuration{Name: "test_session_name", Value: ciTags[constants.TestSessionName]}, + ) if telemetry.GlobalClient() != nil { return } cfg := telemetry.ClientConfig{ HTTPClient: requestHandler.Client, - APIKey: APIKeyValue, + APIKey: apiKeyValue, } if agentURL != nil { cfg.AgentURL = agentURL.String()