@@ -18,12 +18,17 @@ package otel_test
1818
1919import (
2020 "context"
21+ "errors"
2122 "io"
23+ "slices"
2224 "testing"
25+ "time"
2326
27+ "github.com/containerd/log"
2428 "github.com/containerd/log/otel"
2529 "github.com/sirupsen/logrus"
2630 "github.com/sirupsen/logrus/hooks/test"
31+ "go.opentelemetry.io/otel/codes"
2732 "go.opentelemetry.io/otel/trace"
2833)
2934
3439 testSpanID = trace.SpanID {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 }
3540)
3641
42+ // testSpan is a minimal recording span used to test hook behavior without
43+ // depending on the OpenTelemetry SDK.
44+ type testSpan struct {
45+ trace.Span
46+ status codes.Code
47+ }
48+
49+ func (s * testSpan ) SpanContext () trace.SpanContext {
50+ return trace .NewSpanContext (trace.SpanContextConfig {TraceID : testTraceID , SpanID : testSpanID })
51+ }
52+
53+ func (s * testSpan ) IsRecording () bool { return true }
54+ func (s * testSpan ) AddEvent (string , ... trace.EventOption ) {}
55+ func (s * testSpan ) SetStatus (code codes.Code , _ string ) { s .status = code }
56+
3757func TestLogrusHookTraceID (t * testing.T ) {
3858 tests := []struct {
3959 name string
@@ -106,3 +126,133 @@ func TestLogrusHookTraceID(t *testing.T) {
106126 })
107127 }
108128}
129+
130+ // TestLogrusHookLevels verifies that [WithLevel] limits the levels handled by
131+ // the hook while preserving all levels by default.
132+ func TestLogrusHookLevels (t * testing.T ) {
133+ tests := []struct {
134+ name string
135+ opts []otel.HookOpt
136+ want []log.Level
137+ }{
138+ {
139+ name : "default" ,
140+ want : []log.Level {
141+ log .PanicLevel ,
142+ log .FatalLevel ,
143+ log .ErrorLevel ,
144+ log .WarnLevel ,
145+ log .InfoLevel ,
146+ log .DebugLevel ,
147+ log .TraceLevel ,
148+ },
149+ },
150+ {
151+ name : "warn" ,
152+ opts : []otel.HookOpt {
153+ otel .WithLevel (log .WarnLevel ),
154+ },
155+ want : []log.Level {
156+ log .PanicLevel ,
157+ log .FatalLevel ,
158+ log .ErrorLevel ,
159+ log .WarnLevel ,
160+ },
161+ },
162+ {
163+ name : "error" ,
164+ opts : []otel.HookOpt {
165+ otel .WithLevel (log .ErrorLevel ),
166+ },
167+ want : []log.Level {
168+ log .PanicLevel ,
169+ log .FatalLevel ,
170+ log .ErrorLevel ,
171+ },
172+ },
173+ }
174+
175+ for _ , tc := range tests {
176+ t .Run (tc .name , func (t * testing.T ) {
177+ hook := otel .NewLogrusHook (tc .opts ... )
178+ if got := hook .Levels (); ! slices .Equal (got , tc .want ) {
179+ t .Errorf ("Levels() = %v; want %v" , got , tc .want )
180+ }
181+ })
182+ }
183+ }
184+
185+ // TestLogrusHookErrorStatusLevel verifies that [WithErrorStatusLevel] marks
186+ // spans as errors based on log severity and leaves span status unchanged by
187+ // default.
188+ func TestLogrusHookErrorStatusLevel (t * testing.T ) {
189+ tests := []struct {
190+ name string
191+ opts []otel.HookOpt
192+ level log.Level
193+ fields log.Fields
194+ wantError bool
195+ }{
196+ {
197+ name : "default" ,
198+ level : log .ErrorLevel ,
199+ },
200+ {
201+ name : "below threshold" ,
202+ opts : []otel.HookOpt {
203+ otel .WithErrorStatusLevel (log .ErrorLevel ),
204+ },
205+ level : log .WarnLevel ,
206+ },
207+ {
208+ name : "at threshold" ,
209+ opts : []otel.HookOpt {
210+ otel .WithErrorStatusLevel (log .ErrorLevel ),
211+ },
212+ level : log .ErrorLevel ,
213+ wantError : true ,
214+ },
215+ {
216+ name : "above threshold" ,
217+ opts : []otel.HookOpt {
218+ otel .WithErrorStatusLevel (log .ErrorLevel ),
219+ },
220+ level : log .FatalLevel ,
221+ wantError : true ,
222+ },
223+ {
224+ name : "error field below threshold" ,
225+ opts : []otel.HookOpt {
226+ otel .WithErrorStatusLevel (log .ErrorLevel ),
227+ },
228+ level : log .DebugLevel ,
229+ fields : log.Fields {
230+ "error" : errors .New ("ignored" ),
231+ },
232+ },
233+ }
234+
235+ for _ , tc := range tests {
236+ t .Run (tc .name , func (t * testing.T ) {
237+ span := & testSpan {}
238+ ctx := trace .ContextWithSpan (context .Background (), span )
239+
240+ hook := otel .NewLogrusHook (tc .opts ... )
241+ err := hook .Fire (& log.Entry {
242+ Context : ctx ,
243+ Data : tc .fields ,
244+ Level : tc .level ,
245+ Message : "message" ,
246+ Time : time .Now (),
247+ })
248+ if err != nil {
249+ t .Fatal (err )
250+ }
251+
252+ gotError := span .status == codes .Error
253+ if gotError != tc .wantError {
254+ t .Errorf ("span error status = %v; want %v" , gotError , tc .wantError )
255+ }
256+ })
257+ }
258+ }
0 commit comments