Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 60fa493

Browse files
authoredDec 20, 2021
Improve opentracing for incoming HTTP requests (#11618)
* remove `start_active_span_from_request` Instead, pull out a separate function, `span_context_from_request`, to extract the parent span, which we can then pass into `start_active_span` as normal. This seems to be clearer all round. * Remove redundant tags from `incoming-federation-request` These are all wrapped up inside a parent span generated in AsyncResource, so there's no point duplicating all the tags that are set there. * Leave request spans open until the request completes It may take some time for the response to be encoded into JSON, and that JSON to be streamed back to the client, and really we want that inside the top-level span, so let's hand responsibility for closure to the SynapseRequest. * opentracing logs for HTTP request events * changelog
1 parent 8e4083e commit 60fa493

File tree

4 files changed

+65
-73
lines changed

4 files changed

+65
-73
lines changed
 

‎changelog.d/11618.misc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve opentracing support for incoming HTTP requests.

‎synapse/federation/transport/server/_base.py

+13-26
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,11 @@
2222
from synapse.http.server import HttpServer, ServletCallback
2323
from synapse.http.servlet import parse_json_object_from_request
2424
from synapse.http.site import SynapseRequest
25-
from synapse.logging import opentracing
2625
from synapse.logging.context import run_in_background
2726
from synapse.logging.opentracing import (
28-
SynapseTags,
29-
start_active_span,
30-
start_active_span_from_request,
31-
tags,
27+
set_tag,
28+
span_context_from_request,
29+
start_active_span_follows_from,
3230
whitelisted_homeserver,
3331
)
3432
from synapse.server import HomeServer
@@ -279,30 +277,19 @@ async def new_func(
279277
logger.warning("authenticate_request failed: %s", e)
280278
raise
281279

282-
request_tags = {
283-
SynapseTags.REQUEST_ID: request.get_request_id(),
284-
tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER,
285-
tags.HTTP_METHOD: request.get_method(),
286-
tags.HTTP_URL: request.get_redacted_uri(),
287-
tags.PEER_HOST_IPV6: request.getClientIP(),
288-
"authenticated_entity": origin,
289-
"servlet_name": request.request_metrics.name,
290-
}
291-
292-
# Only accept the span context if the origin is authenticated
293-
# and whitelisted
280+
# update the active opentracing span with the authenticated entity
281+
set_tag("authenticated_entity", origin)
282+
283+
# if the origin is authenticated and whitelisted, link to its span context
284+
context = None
294285
if origin and whitelisted_homeserver(origin):
295-
scope = start_active_span_from_request(
296-
request, "incoming-federation-request", tags=request_tags
297-
)
298-
else:
299-
scope = start_active_span(
300-
"incoming-federation-request", tags=request_tags
301-
)
286+
context = span_context_from_request(request)
302287

303-
with scope:
304-
opentracing.inject_response_headers(request.responseHeaders)
288+
scope = start_active_span_follows_from(
289+
"incoming-federation-request", contexts=(context,) if context else ()
290+
)
305291

292+
with scope:
306293
if origin and self.RATELIMIT:
307294
with ratelimiter.ratelimit(origin) as d:
308295
await d

‎synapse/http/site.py

+29-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import contextlib
1515
import logging
1616
import time
17-
from typing import Any, Generator, Optional, Tuple, Union
17+
from typing import TYPE_CHECKING, Any, Generator, Optional, Tuple, Union
1818

1919
import attr
2020
from zope.interface import implementer
@@ -35,6 +35,9 @@
3535
)
3636
from synapse.types import Requester
3737

38+
if TYPE_CHECKING:
39+
import opentracing
40+
3841
logger = logging.getLogger(__name__)
3942

4043
_next_request_seq = 0
@@ -81,6 +84,10 @@ def __init__(
8184
# server name, for client requests this is the Requester object.
8285
self._requester: Optional[Union[Requester, str]] = None
8386

87+
# An opentracing span for this request. Will be closed when the request is
88+
# completely processed.
89+
self._opentracing_span: "Optional[opentracing.Span]" = None
90+
8491
# we can't yet create the logcontext, as we don't know the method.
8592
self.logcontext: Optional[LoggingContext] = None
8693

@@ -148,6 +155,13 @@ def requester(self, value: Union[Requester, str]) -> None:
148155
# If there's no authenticated entity, it was the requester.
149156
self.logcontext.request.authenticated_entity = authenticated_entity or requester
150157

158+
def set_opentracing_span(self, span: "opentracing.Span") -> None:
159+
"""attach an opentracing span to this request
160+
161+
Doing so will cause the span to be closed when we finish processing the request
162+
"""
163+
self._opentracing_span = span
164+
151165
def get_request_id(self) -> str:
152166
return "%s-%i" % (self.get_method(), self.request_seq)
153167

@@ -286,6 +300,9 @@ async def handle_request(request):
286300
self._processing_finished_time = time.time()
287301
self._is_processing = False
288302

303+
if self._opentracing_span:
304+
self._opentracing_span.log_kv({"event": "finished processing"})
305+
289306
# if we've already sent the response, log it now; otherwise, we wait for the
290307
# response to be sent.
291308
if self.finish_time is not None:
@@ -299,6 +316,8 @@ def finish(self) -> None:
299316
"""
300317
self.finish_time = time.time()
301318
Request.finish(self)
319+
if self._opentracing_span:
320+
self._opentracing_span.log_kv({"event": "response sent"})
302321
if not self._is_processing:
303322
assert self.logcontext is not None
304323
with PreserveLoggingContext(self.logcontext):
@@ -333,6 +352,11 @@ def connectionLost(self, reason: Union[Failure, Exception]) -> None:
333352
with PreserveLoggingContext(self.logcontext):
334353
logger.info("Connection from client lost before response was sent")
335354

355+
if self._opentracing_span:
356+
self._opentracing_span.log_kv(
357+
{"event": "client connection lost", "reason": str(reason.value)}
358+
)
359+
336360
if not self._is_processing:
337361
self._finished_processing()
338362

@@ -421,6 +445,10 @@ def _finished_processing(self) -> None:
421445
usage.evt_db_fetch_count,
422446
)
423447

448+
# complete the opentracing span, if any.
449+
if self._opentracing_span:
450+
self._opentracing_span.finish()
451+
424452
try:
425453
self.request_metrics.stop(self.finish_time, self.code, self.sentLength)
426454
except Exception as e:

‎synapse/logging/opentracing.py

+22-46
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ def set_fates(clotho, lachesis, atropos, father="Zues", mother="Themis"):
173173
import attr
174174

175175
from twisted.internet import defer
176+
from twisted.web.http import Request
176177
from twisted.web.http_headers import Headers
177178

178179
from synapse.config import ConfigError
@@ -490,48 +491,6 @@ def start_active_span_follows_from(
490491
return scope
491492

492493

493-
def start_active_span_from_request(
494-
request,
495-
operation_name,
496-
references=None,
497-
tags=None,
498-
start_time=None,
499-
ignore_active_span=False,
500-
finish_on_close=True,
501-
):
502-
"""
503-
Extracts a span context from a Twisted Request.
504-
args:
505-
headers (twisted.web.http.Request)
506-
507-
For the other args see opentracing.tracer
508-
509-
returns:
510-
span_context (opentracing.span.SpanContext)
511-
"""
512-
# Twisted encodes the values as lists whereas opentracing doesn't.
513-
# So, we take the first item in the list.
514-
# Also, twisted uses byte arrays while opentracing expects strings.
515-
516-
if opentracing is None:
517-
return noop_context_manager() # type: ignore[unreachable]
518-
519-
header_dict = {
520-
k.decode(): v[0].decode() for k, v in request.requestHeaders.getAllRawHeaders()
521-
}
522-
context = opentracing.tracer.extract(opentracing.Format.HTTP_HEADERS, header_dict)
523-
524-
return opentracing.tracer.start_active_span(
525-
operation_name,
526-
child_of=context,
527-
references=references,
528-
tags=tags,
529-
start_time=start_time,
530-
ignore_active_span=ignore_active_span,
531-
finish_on_close=finish_on_close,
532-
)
533-
534-
535494
def start_active_span_from_edu(
536495
edu_content,
537496
operation_name,
@@ -743,6 +702,20 @@ def active_span_context_as_string():
743702
return json_encoder.encode(carrier)
744703

745704

705+
def span_context_from_request(request: Request) -> "Optional[opentracing.SpanContext]":
706+
"""Extract an opentracing context from the headers on an HTTP request
707+
708+
This is useful when we have received an HTTP request from another part of our
709+
system, and want to link our spans to those of the remote system.
710+
"""
711+
if not opentracing:
712+
return None
713+
header_dict = {
714+
k.decode(): v[0].decode() for k, v in request.requestHeaders.getAllRawHeaders()
715+
}
716+
return opentracing.tracer.extract(opentracing.Format.HTTP_HEADERS, header_dict)
717+
718+
746719
@only_if_tracing
747720
def span_context_from_string(carrier):
748721
"""
@@ -882,10 +855,13 @@ def trace_servlet(request: "SynapseRequest", extract_context: bool = False):
882855
}
883856

884857
request_name = request.request_metrics.name
885-
if extract_context:
886-
scope = start_active_span_from_request(request, request_name)
887-
else:
888-
scope = start_active_span(request_name)
858+
context = span_context_from_request(request) if extract_context else None
859+
860+
# we configure the scope not to finish the span immediately on exit, and instead
861+
# pass the span into the SynapseRequest, which will finish it once we've finished
862+
# sending the response to the client.
863+
scope = start_active_span(request_name, child_of=context, finish_on_close=False)
864+
request.set_opentracing_span(scope.span)
889865

890866
with scope:
891867
inject_response_headers(request.responseHeaders)

0 commit comments

Comments
 (0)
This repository has been archived.