Skip to content
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
8 changes: 6 additions & 2 deletions src/common/core/docgen/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from pathlib import Path
from typing import Iterable, Iterator

from common.core.otel import get_otel_event_name


class DocgenEventsWarning(UserWarning):
"""Raised by the events scanner when a call site can't be resolved."""
Expand Down Expand Up @@ -317,9 +319,11 @@ def _build_entry_from_emit_call(
)
return None
attributes = scope.bound_attrs | _kwargs_as_attributes(node.keywords)
name = f"{scope.domain}.{event_arg.value}" if scope.domain else event_arg.value
return EventEntry(
name=name,
name=get_otel_event_name(
logger_name=scope.domain or None,
body=event_arg.value,
),
level=func.attr,
attributes=attributes,
locations=[SourceLocation(path=path, line=node.lineno)],
Expand Down
21 changes: 17 additions & 4 deletions src/common/core/otel.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@
)


def get_otel_event_name(*, logger_name: str | None, body: str) -> str:
"""Build the event name that reaches OTel from a structlog `(logger, event)`.

The body is normalised via `inflection.underscore` so hyphens and CamelCase
collapse to snake_case. Empty bodies fall back to ``"unknown"``. The logger
name, when present, is prefixed verbatim.
"""
normalised = inflection.underscore(body) if body else "unknown"
if logger_name:
return f"{logger_name}.{normalised}"
return normalised


def add_otel_trace_context(
logger: structlog.types.WrappedLogger,
method_name: str,
Expand Down Expand Up @@ -95,10 +108,10 @@ def processor(
attributes[key] = str(value)

body = event_dict.get("event", "")
logger_name = event_dict.get("logger")
event_name = inflection.underscore(body) if body else "unknown"
if logger_name:
event_name = f"{logger_name}.{event_name}"
event_name = get_otel_event_name(
logger_name=event_dict.get("logger"),
body=body,
)

# Some observability platforms don't surface OTel's EventName.
# Keep a custom attribute for better visibility.
Expand Down
36 changes: 36 additions & 0 deletions tests/unit/common/core/test_docgen_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,42 @@
"""\
import structlog

logger = structlog.get_logger("app_analytics")
logger.warning("no-analytics-database-configured")
""",
[
EventEntry(
name="app_analytics.no_analytics_database_configured",
level="warning",
attributes=frozenset(),
locations=[SourceLocation(path=PATH, line=4)],
),
],
[],
id="hyphenated-event-body-normalised-to-snake_case",
),
pytest.param(
"""\
import structlog

logger = structlog.get_logger("segments")
logger.info("NewSegmentRevision")
""",
[
EventEntry(
name="segments.new_segment_revision",
level="info",
attributes=frozenset(),
locations=[SourceLocation(path=PATH, line=4)],
),
],
[],
id="camel-case-event-body-normalised-to-snake_case",
),
pytest.param(
"""\
import structlog

logger = structlog.get_logger("sentry_change_tracking")


Expand Down
Loading