@@ -10,11 +10,14 @@ import (
1010 "path/filepath"
1111 "sync"
1212 "testing"
13+ "time"
1314
1415 "github.com/stretchr/testify/require"
1516
1617 pkgconfigsetup "github.com/DataDog/datadog-agent/pkg/config/setup"
1718 "github.com/DataDog/datadog-agent/pkg/util/log"
19+ "github.com/DataDog/datadog-agent/pkg/util/log/errortracking"
20+ pkgslog "github.com/DataDog/datadog-agent/pkg/util/log/slog"
1821)
1922
2023func BenchmarkSlogParallel (b * testing.B ) {
@@ -76,3 +79,125 @@ func runLog(b *testing.B) {
7679 }
7780 log .Flush ()
7881}
82+
83+ // --- Errortracking handler-chain tests ---------------------------------
84+ //
85+ // These tests previously lived in log_errortracking_test.go; folded
86+ // here per iglendd's "filename should match the source file" comment
87+ // on PR #50607. log.go's tests live in log_test.go.
88+
89+ // recordingSubmitter captures every ErrorLog routed through the chain.
90+ // Tests use it as the registered Submitter to assert the chain forwards
91+ // (or filters) records.
92+ type recordingSubmitter struct {
93+ mu sync.Mutex
94+ logs []errortracking.ErrorLog
95+ }
96+
97+ func (r * recordingSubmitter ) submit (e errortracking.ErrorLog ) {
98+ r .mu .Lock ()
99+ defer r .mu .Unlock ()
100+ r .logs = append (r .logs , e )
101+ }
102+
103+ func (r * recordingSubmitter ) snapshot () []errortracking.ErrorLog {
104+ r .mu .Lock ()
105+ defer r .mu .Unlock ()
106+ out := make ([]errortracking.ErrorLog , len (r .logs ))
107+ copy (out , r .logs )
108+ return out
109+ }
110+
111+ func resetErrortrackingSlot (t * testing.T ) {
112+ t .Helper ()
113+ t .Cleanup (func () {
114+ RegisterErrortrackingSubmitter (nil )
115+ RegisterErrortrackingBouncer (nil )
116+ })
117+ RegisterErrortrackingSubmitter (nil )
118+ RegisterErrortrackingBouncer (nil )
119+ }
120+
121+ // TestRegisterErrortrackingSubmitter_NilResets verifies the on/off contract:
122+ // after registering and then clearing, the slot must return to a no-op
123+ // state. Tests rely on this for cleanup between cases.
124+ func TestRegisterErrortrackingSubmitter_NilResets (t * testing.T ) {
125+ resetErrortrackingSlot (t )
126+
127+ rec := & recordingSubmitter {}
128+ RegisterErrortrackingSubmitter (rec .submit )
129+ require .NotNil (t , loadErrortrackingSubmitter ())
130+
131+ RegisterErrortrackingSubmitter (nil )
132+ require .Nil (t , loadErrortrackingSubmitter ())
133+ }
134+
135+ // TestBuildSlogLogger_ForwardsErrorRecord asserts that the chain assembled
136+ // by buildSlogLogger fans error records out to the registered Submitter
137+ // while routing non-error records only to the formatted writer branch.
138+ func TestBuildSlogLogger_ForwardsErrorRecord (t * testing.T ) {
139+ resetErrortrackingSlot (t )
140+
141+ rec := & recordingSubmitter {}
142+ RegisterErrortrackingSubmitter (rec .submit )
143+ // A Bouncer must be registered alongside the Submitter: when loadBouncer
144+ // is set but returns nil the handler drops the record (safe default for
145+ // the Fx startup window). The production wiring registers both together.
146+ RegisterErrortrackingBouncer (errortracking .NewBouncer (15 * time .Minute , 0 ))
147+
148+ dir := t .TempDir ()
149+ ddCfg := pkgconfigsetup .Datadog ()
150+ logger , levelVar , err := buildSlogLogger (
151+ log .DebugLvl ,
152+ false ,
153+ filepath .Join (dir , "test.log" ), 1000 , 2 ,
154+ "" ,
155+ commonFormatter ("TEST" , ddCfg ), nil ,
156+ )
157+ require .NoError (t , err )
158+ t .Cleanup (logger .Close )
159+ levelVar .Set (slog .LevelDebug )
160+
161+ wrapper , ok := logger .(* pkgslog.Wrapper )
162+ require .True (t , ok , "expected *pkgslog.Wrapper, got %T" , logger )
163+ sl := slog .New (wrapper .Handler ())
164+
165+ sl .Info ("info message - should not reach errortracking" )
166+ sl .Warn ("warn message - should not reach errortracking" )
167+ sl .Error ("error message - should reach errortracking" )
168+ logger .Flush ()
169+
170+ got := rec .snapshot ()
171+ require .Len (t , got , 1 , "exactly one Error record must reach the registered Submitter" )
172+ require .NotZero (t , got [0 ].PC , "captured record must carry a call-site PC" )
173+ require .Greater (t , got [0 ].PCsLen , 0 , "captured record must carry stack PCs" )
174+ require .Equal (t , uint32 (1 ), got [0 ].Count , "first sighting always has Count=1" )
175+ }
176+
177+ // TestBuildSlogLogger_NoForwardingWhenUnregistered asserts that the chain
178+ // built by buildSlogLogger remains a no-op for the errortracking branch
179+ // when no Submitter has been registered (the common path before opt-in).
180+ func TestBuildSlogLogger_NoForwardingWhenUnregistered (t * testing.T ) {
181+ resetErrortrackingSlot (t )
182+
183+ dir := t .TempDir ()
184+ ddCfg := pkgconfigsetup .Datadog ()
185+ logger , levelVar , err := buildSlogLogger (
186+ log .DebugLvl ,
187+ false ,
188+ filepath .Join (dir , "test.log" ), 1000 , 2 ,
189+ "" ,
190+ commonFormatter ("TEST" , ddCfg ), nil ,
191+ )
192+ require .NoError (t , err )
193+ t .Cleanup (logger .Close )
194+ levelVar .Set (slog .LevelDebug )
195+
196+ wrapper := logger .(* pkgslog.Wrapper )
197+ sl := slog .New (wrapper .Handler ())
198+
199+ // Should not panic; should produce nothing observable on the
200+ // errortracking side.
201+ sl .Error ("error with nobody listening" )
202+ logger .Flush ()
203+ }
0 commit comments