open-telemetry / open-telemetry/opentelemetry-python-contrib
auto-instrument hypercorn
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 1.1k
- Avg merge
- 4d 15h
- Merged PRs (30d)
- 16
Description
What problem do you want to solve?
I am using otel's python auto-instrumentation with my Flask app, and my Flask app is getting instrumented as expected (e.g. my app's logs are able to include the current trace and span so they can be correlated).
Like many users, I am using hypercorn to serve my Flask app. Because otel python auto-instrumentation is currently missing support for hypercorn, hypercorn's access logs are not able to pick up the current trace and span.
Describe the solution you'd like
Here is some code that solved the problem for me, in case you can incorporate something like this here:
from opentelemetry import trace
from opentelemetry.propagate import extract
from opentelemetry.trace import SpanKind
# Patch target
import hypercorn.app_wrappers
_OTEL_PATCHED = False
def extract_headers(scope): # type: ignore
# ASGI scope headers are a list of (bytes, bytes)
return {k.decode("latin1").lower(): v.decode("latin1") for k, v in scope.get("headers", [])}
async def otel_asgi_call_wrapper(self, scope, receive, send, sync_spawn, call_soon): # type: ignore
if scope.get("type") != "http":
return await self._original_asgi_call(scope, receive, send, sync_spawn, call_soon)
tracer = trace.get_tracer("hypercorn.instrumentation")
headers = extract_headers(scope) # type: ignore
from opentelemetry.propagators.textmap import DefaultGetter
ctx = extract(headers, getter=DefaultGetter())
span_name = f"{scope.get('method', 'HTTP')} {scope.get('path', '/') }"
from opentelemetry.context import attach, detach
token = attach(ctx)
try:
with tracer.start_as_current_span(span_name, kind=SpanKind.SERVER) as span:
span.set_attribute("http.scheme", scope.get("scheme", "http"))
span.set_attribute("http.host", headers.get("host", ""))
if "method" in scope:
span.set_attribute("http.method", scope["method"])
if "path" in scope:
span.set_attribute("http.target", scope["path"])
return await self._original_asgi_call(scope, receive, send, sync_spawn, call_soon)
finally:
detach(token)
def patch_hypercorn_asgi() -> None:
global _OTEL_PATCHED
if _OTEL_PATCHED:
return
cls = hypercorn.app_wrappers.ASGIWrapper
if not hasattr(cls, "_original_asgi_call"):
cls._original_asgi_call = cls.__call__ # type: ignore
cls.__call__ = otel_asgi_call_wrapper # type: ignore
_OTEL_PATCHED = True
Describe alternatives you've considered
No response
Additional Context
No response
Would you like to implement a fix?
None
Tip
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The issue targets hypercorn.app_wrappers.ASGIWrapper and proposes wrapping its ASGI call to extract context and create server spans. Start by reviewing the existing Python auto-instrumentation entry points for comparable server integrations, then verify that Hypercorn access logs can correlate traces and spans. Done means Hypercorn is supported by auto-instrumentation with HTTP requests instrumented as described.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- observability
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100