@@ -2,6 +2,7 @@ package tracing
22
33import (
44 "context"
5+ "encoding/json"
56 "errors"
67 "net/http"
78 "net/http/httptest"
@@ -15,17 +16,26 @@ import (
1516
1617func TestTracing_UnmarshalCaddyfile (t * testing.T ) {
1718 tests := []struct {
18- name string
19- spanName string
20- d * caddyfile.Dispenser
21- wantErr bool
19+ name string
20+ spanName string
21+ spanAttributes map [string ]string
22+ d * caddyfile.Dispenser
23+ wantErr bool
2224 }{
2325 {
2426 name : "Full config" ,
2527 spanName : "my-span" ,
28+ spanAttributes : map [string ]string {
29+ "attr1" : "value1" ,
30+ "attr2" : "value2" ,
31+ },
2632 d : caddyfile .NewTestDispenser (`
2733tracing {
2834 span my-span
35+ span_attributes {
36+ attr1 value1
37+ attr2 value2
38+ }
2939}` ),
3040 wantErr : false ,
3141 },
@@ -42,6 +52,21 @@ tracing {
4252 name : "Empty config" ,
4353 d : caddyfile .NewTestDispenser (`
4454tracing {
55+ }` ),
56+ wantErr : false ,
57+ },
58+ {
59+ name : "Only span attributes" ,
60+ spanAttributes : map [string ]string {
61+ "service.name" : "my-service" ,
62+ "service.version" : "1.0.0" ,
63+ },
64+ d : caddyfile .NewTestDispenser (`
65+ tracing {
66+ span_attributes {
67+ service.name my-service
68+ service.version 1.0.0
69+ }
4570}` ),
4671 wantErr : false ,
4772 },
@@ -56,6 +81,20 @@ tracing {
5681 if ot .SpanName != tt .spanName {
5782 t .Errorf ("UnmarshalCaddyfile() SpanName = %v, want SpanName %v" , ot .SpanName , tt .spanName )
5883 }
84+
85+ if len (tt .spanAttributes ) > 0 {
86+ if ot .SpanAttributes == nil {
87+ t .Errorf ("UnmarshalCaddyfile() SpanAttributes is nil, expected %v" , tt .spanAttributes )
88+ } else {
89+ for key , expectedValue := range tt .spanAttributes {
90+ if actualValue , exists := ot .SpanAttributes [key ]; ! exists {
91+ t .Errorf ("UnmarshalCaddyfile() SpanAttributes missing key %v" , key )
92+ } else if actualValue != expectedValue {
93+ t .Errorf ("UnmarshalCaddyfile() SpanAttributes[%v] = %v, want %v" , key , actualValue , expectedValue )
94+ }
95+ }
96+ }
97+ }
5998 })
6099 }
61100}
@@ -79,6 +118,26 @@ func TestTracing_UnmarshalCaddyfile_Error(t *testing.T) {
79118 d : caddyfile .NewTestDispenser (`
80119tracing {
81120 span
121+ }` ),
122+ wantErr : true ,
123+ },
124+ {
125+ name : "Span attributes missing value" ,
126+ d : caddyfile .NewTestDispenser (`
127+ tracing {
128+ span_attributes {
129+ key
130+ }
131+ }` ),
132+ wantErr : true ,
133+ },
134+ {
135+ name : "Span attributes too many arguments" ,
136+ d : caddyfile .NewTestDispenser (`
137+ tracing {
138+ span_attributes {
139+ key value extra
140+ }
82141}` ),
83142 wantErr : true ,
84143 },
@@ -181,6 +240,123 @@ func TestTracing_ServeHTTP_Next_Error(t *testing.T) {
181240 }
182241}
183242
243+ func TestTracing_Span_Attributes_With_Placeholders (t * testing.T ) {
244+ ot := & Tracing {
245+ SpanName : "test-span" ,
246+ SpanAttributes : map [string ]string {
247+ "http.method" : "{http.request.method}" ,
248+ "service.name" : "test-service" ,
249+ "mixed.attribute" : "prefix-{http.request.method}-suffix" ,
250+ },
251+ }
252+
253+ // Create a specific request to test against
254+ req , _ := http .NewRequest ("POST" , "https://api.example.com/v1/users?id=123&action=create" , nil )
255+ req .Host = "api.example.com"
256+
257+ // Set up the request context with proper replacer and vars
258+ repl := caddy .NewReplacer ()
259+ ctx := context .WithValue (req .Context (), caddy .ReplacerCtxKey , repl )
260+ ctx = context .WithValue (ctx , caddyhttp .VarsCtxKey , make (map [string ]any ))
261+ req = req .WithContext (ctx )
262+
263+ // Manually populate the HTTP variables that would normally be set by the server
264+ // This simulates what addHTTPVarsToReplacer would do
265+ repl .Set ("http.request.method" , req .Method )
266+
267+ w := httptest .NewRecorder ()
268+
269+ // Handler that can verify the context and span attributes
270+ var handler caddyhttp.HandlerFunc = func (writer http.ResponseWriter , request * http.Request ) error {
271+ // Just ensure the request gets processed
272+ writer .WriteHeader (200 )
273+ return nil
274+ }
275+
276+ caddyCtx , cancel := caddy .NewContext (caddy.Context {Context : context .Background ()})
277+ defer cancel ()
278+
279+ if err := ot .Provision (caddyCtx ); err != nil {
280+ t .Errorf ("Provision error: %v" , err )
281+ t .FailNow ()
282+ }
283+
284+ // Execute the request
285+ if err := ot .ServeHTTP (w , req , handler ); err != nil {
286+ t .Errorf ("ServeHTTP error: %v" , err )
287+ }
288+
289+ // Verify that the span attributes were configured correctly in the otel wrapper
290+ expectedRawAttrs := map [string ]string {
291+ "http.method" : "{http.request.method}" ,
292+ "service.name" : "test-service" ,
293+ "mixed.attribute" : "prefix-{http.request.method}-suffix" ,
294+ }
295+
296+ for key , expectedValue := range expectedRawAttrs {
297+ if actualValue , exists := ot .otel .spanAttributes [key ]; ! exists {
298+ t .Errorf ("Expected span attribute %s to exist" , key )
299+ } else if actualValue != expectedValue {
300+ t .Errorf ("Expected span attribute %s = %s, got %s" , key , expectedValue , actualValue )
301+ }
302+ }
303+
304+ // Now test that the replacement would work correctly if called directly
305+ // This verifies that our placeholder values would be replaced correctly
306+ expectedReplacements := map [string ]string {
307+ "{http.request.method}" : "POST" ,
308+ "service.name" : "service.name" ,
309+ "prefix-{http.request.method}-suffix" : "prefix-POST-suffix" ,
310+ }
311+
312+ for placeholder , expected := range expectedReplacements {
313+ replaced := repl .ReplaceAll (placeholder , "" )
314+ if replaced != expected {
315+ t .Errorf ("Expected %s to be replaced with %s, got %s" , placeholder , expected , replaced )
316+ }
317+ }
318+ }
319+
320+ func TestTracing_JSON_Configuration (t * testing.T ) {
321+ // Test that our struct correctly marshals to and from JSON
322+ original := & Tracing {
323+ SpanName : "test-span" ,
324+ SpanAttributes : map [string ]string {
325+ "service.name" : "test-service" ,
326+ "service.version" : "1.0.0" ,
327+ "env" : "test" ,
328+ },
329+ }
330+
331+ jsonData , err := json .Marshal (original )
332+ if err != nil {
333+ t .Fatalf ("Failed to marshal to JSON: %v" , err )
334+ }
335+
336+ var unmarshaled Tracing
337+ if err := json .Unmarshal (jsonData , & unmarshaled ); err != nil {
338+ t .Fatalf ("Failed to unmarshal from JSON: %v" , err )
339+ }
340+
341+ if unmarshaled .SpanName != original .SpanName {
342+ t .Errorf ("Expected SpanName %s, got %s" , original .SpanName , unmarshaled .SpanName )
343+ }
344+
345+ if len (unmarshaled .SpanAttributes ) != len (original .SpanAttributes ) {
346+ t .Errorf ("Expected %d span attributes, got %d" , len (original .SpanAttributes ), len (unmarshaled .SpanAttributes ))
347+ }
348+
349+ for key , expectedValue := range original .SpanAttributes {
350+ if actualValue , exists := unmarshaled .SpanAttributes [key ]; ! exists {
351+ t .Errorf ("Expected span attribute %s to exist" , key )
352+ } else if actualValue != expectedValue {
353+ t .Errorf ("Expected span attribute %s = %s, got %s" , key , expectedValue , actualValue )
354+ }
355+ }
356+
357+ t .Logf ("JSON representation: %s" , string (jsonData ))
358+ }
359+
184360func createRequestWithContext (method string , url string ) * http.Request {
185361 r , _ := http .NewRequest (method , url , nil )
186362 repl := caddy .NewReplacer ()
0 commit comments