Repository: caddyserver/caddy (language: go)

Issue:
[opentelemetry] add ability to contextualize span name
Since caddy is often the entry point to an application, it's generally the root span and it's name becomes the operation name of the entire trace. As a result, tools like Jaeger show a list of various operations that all have the same name.

It would be great if the span name could be customized on a "per request" basis with string interpolation in the span name, eg.

```
tracing {
  span '{request>uri}'
}
```
or even get the operation name from the upstream like this:
```
tracing {
  span '{response>headers>X-Trace-Operation-Name}'
}
```



Discussion / hints:
/cc @andriikushch @cedricziel 

Sounds like we just need placeholder support for the span name. Is it possible to run the replacer at runtime, or must the span name be known at provision-time?

FYI @cdaguerre the syntax would be more like this:

```
tracing {
	span {uri}
}
```

Where `{uri}` is a Caddyfile shortcut for `{http.request.uri}` as per https://caddyserver.com/docs/caddyfile/concepts#placeholders

For response header values, that's trickier because AFAIK the trace is set up at the start of the request, before handling, not after, on the way out. Definitely possible to wire up with a request header though, I think. But I might be wrong here.
Thanks for clarifying the syntax!

I don't know the first thing about Go but [this](https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/tracing/tracer.go#L46) looks "provision-time".
Since the trace handler is the very first to be setup in the stack, it's probably also the last to handle the response, right?
It should take all other handler processing times into account and so on so my guess is yes...

For most frameworks operation naming in line with OpenTelemetry's semantic conventions can not be achieved through mere request manipulation and is probably much easier to handle at framework level then with request info manipulation on caddy level, hence the suggestion, although I'm not sure it's very clean ;)
Any news on the implementation of this feature? Sounds quite interesting.
The Caddy maintainers don't use tracing or metrics ourselves, so we have very little incentive to spend time working on it. So we're asking for the community to help maintain those parts.
Understandable.
I have a working solution for this, I can submit a PR.
(as in, supporting placeholders in the span name, for http request only)

Fix patch:
diff --git a/modules/caddyhttp/tracing/tracer.go b/modules/caddyhttp/tracing/tracer.go
index ddb01e82fc5..e5c51073230 100644
--- a/modules/caddyhttp/tracing/tracer.go
+++ b/modules/caddyhttp/tracing/tracer.go
@@ -69,7 +69,13 @@ func newOpenTelemetryWrapper(
 		sdktrace.WithResource(res),
 	)
 
-	ot.handler = otelhttp.NewHandler(http.HandlerFunc(ot.serveHTTP), ot.spanName, otelhttp.WithTracerProvider(tracerProvider), otelhttp.WithPropagators(ot.propagators))
+	ot.handler = otelhttp.NewHandler(http.HandlerFunc(ot.serveHTTP),
+		ot.spanName,
+		otelhttp.WithTracerProvider(tracerProvider),
+		otelhttp.WithPropagators(ot.propagators),
+		otelhttp.WithSpanNameFormatter(ot.spanNameFormatter),
+	)
+
 	return ot, nil
 }
 
@@ -106,3 +112,8 @@ func (ot *openTelemetryWrapper) newResource(
 		semconv.WebEngineVersionKey.String(webEngineVersion),
 	))
 }
+
+// spanNameFormatter performs the replacement of placeholders in the span name
+func (ot *openTelemetryWrapper) spanNameFormatter(operation string, r *http.Request) string {
+	return r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer).ReplaceAll(operation, "")
+}


Test patch:
diff --git a/modules/caddyhttp/tracing/module_test.go b/modules/caddyhttp/tracing/module_test.go
index 0fbc05bbd6c..2a775fc18b2 100644
--- a/modules/caddyhttp/tracing/module_test.go
+++ b/modules/caddyhttp/tracing/module_test.go
@@ -98,7 +98,7 @@ func TestTracing_ServeHTTP_Propagation_Without_Initial_Headers(t *testing.T) {
 		SpanName: "mySpan",
 	}
 
-	req := httptest.NewRequest("GET", "https://example.com/foo", nil)
+	req := createRequestWithContext("GET", "https://example.com/foo")
 	w := httptest.NewRecorder()
 
 	var handler caddyhttp.HandlerFunc = func(writer http.ResponseWriter, request *http.Request) error {
@@ -128,7 +128,7 @@ func TestTracing_ServeHTTP_Propagation_With_Initial_Headers(t *testing.T) {
 		SpanName: "mySpan",
 	}
 
-	req := httptest.NewRequest("GET", "https://example.com/foo", nil)
+	req := createRequestWithContext("GET", "https://example.com/foo")
 	req.Header.Set("traceparent", "00-11111111111111111111111111111111-1111111111111111-01")
 	w := httptest.NewRecorder()
 
@@ -159,7 +159,7 @@ func TestTracing_ServeHTTP_Next_Error(t *testing.T) {
 		SpanName: "mySpan",
 	}
 
-	req := httptest.NewRequest("GET", "https://example.com/foo", nil)
+	req := createRequestWithContext("GET", "https://example.com/foo")
 	w := httptest.NewRecorder()
 
 	expectErr := errors.New("test error")
@@ -180,3 +180,11 @@ func TestTracing_ServeHTTP_Next_Error(t *testing.T) {
 		t.Errorf("expected error, got: %v", err)
 	}
 }
+
+func createRequestWithContext(method string, url string) *http.Request {
+	r, _ := http.NewRequest(method, url, nil)
+	repl := caddy.NewReplacer()
+	ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, repl)
+	r = r.WithContext(ctx)
+	return r
+}