@@ -17,9 +17,33 @@ import (
1717 "github.com/DataDog/dd-trace-go/v2/internal/telemetry/internal/transport"
1818)
1919
20+ // configuration is a data source that tracks SDK configuration key-value pairs
21+ // (e.g. DD_ENV, DD_SERVICE) reported by products via RegisterAppConfig/RegisterAppConfigs.
22+ //
23+ // Flow:
24+ // - Products call Add() to register configs. Each config is stored in config and
25+ // its key is marked in pending.
26+ // - On each flush tick (~60s), the client calls Payload() which returns an
27+ // AppClientConfigurationChange containing only new/updated configs since the
28+ // last flush, then clears pending. The config map itself is never cleared,
29+ // so it accumulates the full state from app-started through any subsequent
30+ // config changes.
31+ // - When heartbeatEnricher emits an extended heartbeat (~24h), it calls All()
32+ // which returns the full accumulated config state from config. Since Add()
33+ // overwrites previous values for the same key, All() always reflects the
34+ // correct current state — merging startup configs with any subsequent updates.
35+ // This also ensures startup configs consumed by appStartedReducer (and
36+ // otherwise invisible to the mapper pipeline) are included.
37+ // - Both Payload() and All() normalize configs via normalize() which applies
38+ // default origin, sanitizes values, and assigns fallback seqIDs.
2039type configuration struct {
21- mu sync.Mutex
40+ mu sync.Mutex
41+ // config holds all registered configs for the lifetime of the SDK. Entries are
42+ // never removed; updated configs overwrite previous values for the same key.
2243 config map [configKey ]transport.ConfKeyValue
44+ // pending tracks which keys in config have been added or updated since the last
45+ // Payload() call. Cleared after each flush so only deltas are reported.
46+ pending map [configKey ]struct {}
2347 // fallbackSeqID is used only for legacy configs that don't already have a seqID.
2448 // New code should report configs with seqIDs via config/configProvider.
2549 fallbackSeqID uint64
@@ -43,6 +67,7 @@ func (c *configuration) Add(kv Configuration) {
4367
4468 if c .config == nil {
4569 c .config = make (map [configKey ]transport.ConfKeyValue )
70+ c .pending = make (map [configKey ]struct {})
4671 }
4772
4873 ID := idOrEmpty (kv .ID )
@@ -55,39 +80,56 @@ func (c *configuration) Add(kv Configuration) {
5580 ID : ID ,
5681 SeqID : kv .SeqID ,
5782 }
83+ c .pending [key ] = struct {}{}
84+ }
85+
86+ // normalize applies default origin, sanitizes the value, and assigns a fallback
87+ // seqID if needed. The normalized conf is written back to c.config[key].
88+ func (c * configuration ) normalize (key configKey ) transport.ConfKeyValue {
89+ conf := c .config [key ]
90+ if conf .Origin == "" {
91+ conf .Origin = transport .OriginDefault
92+ }
93+ conf .Value = SanitizeConfigValue (conf .Value )
94+ if conf .SeqID == 0 {
95+ c .fallbackSeqID ++
96+ conf .SeqID = c .fallbackSeqID
97+ }
98+ c .config [key ] = conf
99+ return conf
58100}
59101
60102func (c * configuration ) Payload () transport.Payload {
61103 c .mu .Lock ()
62104 defer c .mu .Unlock ()
63- if len (c .config ) == 0 {
105+ if len (c .pending ) == 0 {
64106 return nil
65107 }
66108
67- configs := make ([]transport.ConfKeyValue , len (c .config ))
68- idx := 0
69- for key , conf := range c .config {
70- if conf .Origin == "" {
71- conf .Origin = transport .OriginDefault
72- }
73- conf .Value = SanitizeConfigValue (conf .Value )
74-
75- // Fallback seqID for legacy code that doesn't report via config/configProvider
76- if conf .SeqID == 0 {
77- c .fallbackSeqID ++
78- conf .SeqID = c .fallbackSeqID
79- }
80-
81- configs [idx ] = conf
82- idx ++
83- delete (c .config , key )
109+ configs := make ([]transport.ConfKeyValue , 0 , len (c .pending ))
110+ for key := range c .pending {
111+ configs = append (configs , c .normalize (key ))
84112 }
113+ clear (c .pending )
85114
86115 return transport.AppClientConfigurationChange {
87116 Configuration : configs ,
88117 }
89118}
90119
120+ // All returns a sanitized snapshot of all accumulated configs. Used by
121+ // heartbeatEnricher to populate the configuration field in extended heartbeats.
122+ func (c * configuration ) All () []transport.ConfKeyValue {
123+ c .mu .Lock ()
124+ defer c .mu .Unlock ()
125+
126+ configs := make ([]transport.ConfKeyValue , 0 , len (c .config ))
127+ for key := range c .config {
128+ configs = append (configs , c .normalize (key ))
129+ }
130+ return configs
131+ }
132+
91133// SanitizeConfigValue sanitizes the value of a configuration key to ensure it can be marshalled.
92134func SanitizeConfigValue (value any ) any {
93135 if value == nil {
0 commit comments