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

Gracefully handle log context slips and missing opentracing import errors. #5988

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/5988.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix invalid references to None while opentracing if the log context slips.
82 changes: 58 additions & 24 deletions synapse/logging/opentracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,7 @@ class _DummyTagNames(object):


def only_if_tracing(func):
"""Executes the function only if we're tracing. Otherwise return.
Assumes the function wrapped may return None"""
"""Executes the function only if we're tracing. Otherwise returns None."""

@wraps(func)
def _only_if_tracing_inner(*args, **kwargs):
Expand All @@ -252,6 +251,41 @@ def _only_if_tracing_inner(*args, **kwargs):
return _only_if_tracing_inner


def ensure_active_span(message, ret=None):
"""Executes the operation only if opentracing is enabled and there is an active span.
If there is no active span it logs message at the error level.

Args:
message (str): Message which fills in "There was no active span when trying to %s"
in the error log if there is no active span and opentracing is enabled.
ret (object): return value if opentracing is None or there is no active span.

Returns (object): The result of the func or ret if opentracing is disabled or there
was no active span.
"""

def ensure_active_span_inner_1(func):
@wraps(func)
def ensure_active_span_inner_2(*args, **kwargs):
if not opentracing:
return ret

if not opentracing.tracer.active_span:
logger.error(
"There was no active span when trying to %s."
" Did you forget to start one or did a context slip?",
message,
JorikSchellekens marked this conversation as resolved.
Show resolved Hide resolved
)

return ret

return func(*args, **kwargs)

return ensure_active_span_inner_2

return ensure_active_span_inner_1


@contextlib.contextmanager
def _noop_context_manager(*args, **kwargs):
"""Does exactly what it says on the tin"""
Expand Down Expand Up @@ -349,26 +383,24 @@ def start_active_span(
if opentracing is None:
return _noop_context_manager()

else:
# We need to enter the scope here for the logcontext to become active
return opentracing.tracer.start_active_span(
operation_name,
child_of=child_of,
references=references,
tags=tags,
start_time=start_time,
ignore_active_span=ignore_active_span,
finish_on_close=finish_on_close,
)
return opentracing.tracer.start_active_span(
operation_name,
child_of=child_of,
references=references,
tags=tags,
start_time=start_time,
ignore_active_span=ignore_active_span,
finish_on_close=finish_on_close,
)


def start_active_span_follows_from(operation_name, contexts):
if opentracing is None:
return _noop_context_manager()
else:
references = [opentracing.follows_from(context) for context in contexts]
scope = start_active_span(operation_name, references=references)
return scope

references = [opentracing.follows_from(context) for context in contexts]
scope = start_active_span(operation_name, references=references)
return scope


def start_active_span_from_request(
Expand Down Expand Up @@ -465,19 +497,19 @@ def start_active_span_from_edu(
# Opentracing setters for tags, logs, etc


@only_if_tracing
@ensure_active_span("set a tag")
def set_tag(key, value):
"""Sets a tag on the active span"""
opentracing.tracer.active_span.set_tag(key, value)


@only_if_tracing
@ensure_active_span("log")
def log_kv(key_values, timestamp=None):
"""Log to the active span"""
opentracing.tracer.active_span.log_kv(key_values, timestamp)


@only_if_tracing
@ensure_active_span("set the traces operation name")
def set_operation_name(operation_name):
"""Sets the operation name of the active span"""
opentracing.tracer.active_span.set_operation_name(operation_name)
Expand All @@ -486,7 +518,7 @@ def set_operation_name(operation_name):
# Injection and extraction


@only_if_tracing
@ensure_active_span("inject the span into a header")
def inject_active_span_twisted_headers(headers, destination, check_destination=True):
"""
Injects a span context into twisted headers in-place
Expand Down Expand Up @@ -522,7 +554,7 @@ def inject_active_span_twisted_headers(headers, destination, check_destination=T
headers.addRawHeaders(key, value)


@only_if_tracing
@ensure_active_span("inject the span into a byte dict")
def inject_active_span_byte_dict(headers, destination, check_destination=True):
"""
Injects a span context into a dict where the headers are encoded as byte
Expand Down Expand Up @@ -559,7 +591,7 @@ def inject_active_span_byte_dict(headers, destination, check_destination=True):
headers[key.encode()] = [value.encode()]


@only_if_tracing
@ensure_active_span("inject the span into a text map")
def inject_active_span_text_map(carrier, destination, check_destination=True):
"""
Injects a span context into a dict
Expand Down Expand Up @@ -591,6 +623,7 @@ def inject_active_span_text_map(carrier, destination, check_destination=True):
)


@ensure_active_span("get the active span context as a dict", ret={})
def get_active_span_text_map(destination=None):
"""
Gets a span context as a dict. This can be used instead of manually
Expand All @@ -603,7 +636,7 @@ def get_active_span_text_map(destination=None):
dict: the active span's context if opentracing is enabled, otherwise empty.
"""

if not opentracing or (destination and not whitelisted_homeserver(destination)):
if destination and not whitelisted_homeserver(destination):
return {}

carrier = {}
Expand All @@ -614,6 +647,7 @@ def get_active_span_text_map(destination=None):
return carrier


@ensure_active_span("get the span context as a string.", ret={})
JorikSchellekens marked this conversation as resolved.
Show resolved Hide resolved
def active_span_context_as_string():
"""
Returns:
Expand Down