Skip to content

Commit df5630c

Browse files
authored
Custom Trace Attributes (#384)
Optionally define custom attributes that are added to every span.
1 parent 249eeba commit df5630c

File tree

4 files changed

+22
-5
lines changed

4 files changed

+22
-5
lines changed

dbos/_dbos_config.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class DBOSConfig(TypedDict, total=False):
3131
otlp_logs_endpoints: List[str]: OTLP logs endpoints
3232
admin_port (int): Admin port
3333
run_admin_server (bool): Whether to run the DBOS admin server
34+
otlp_attributes (dict[str, str]): A set of custom attributes to apply OTLP-exported logs and traces
3435
"""
3536

3637
name: str
@@ -43,6 +44,7 @@ class DBOSConfig(TypedDict, total=False):
4344
otlp_logs_endpoints: Optional[List[str]]
4445
admin_port: Optional[int]
4546
run_admin_server: Optional[bool]
47+
otlp_attributes: Optional[dict[str, str]]
4648

4749

4850
class RuntimeConfig(TypedDict, total=False):
@@ -84,6 +86,7 @@ class LoggerConfig(TypedDict, total=False):
8486
class TelemetryConfig(TypedDict, total=False):
8587
logs: Optional[LoggerConfig]
8688
OTLPExporter: Optional[OTLPExporterConfig]
89+
otlp_attributes: Optional[dict[str, str]]
8790

8891

8992
class ConfigFile(TypedDict, total=False):
@@ -145,7 +148,8 @@ def translate_dbos_config_to_config_file(config: DBOSConfig) -> ConfigFile:
145148

146149
# Telemetry config
147150
telemetry: TelemetryConfig = {
148-
"OTLPExporter": {"tracesEndpoint": [], "logsEndpoint": []}
151+
"OTLPExporter": {"tracesEndpoint": [], "logsEndpoint": []},
152+
"otlp_attributes": config.get("otlp_attributes", {}),
149153
}
150154
# For mypy
151155
assert telemetry["OTLPExporter"] is not None

dbos/_logger.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@
2020

2121

2222
class DBOSLogTransformer(logging.Filter):
23-
def __init__(self) -> None:
23+
def __init__(self, config: "ConfigFile") -> None:
2424
super().__init__()
2525
self.app_id = os.environ.get("DBOS__APPID", "")
26+
self.otlp_attributes: dict[str, str] = config.get("telemetry", {}).get("otlp_attributes", {}) # type: ignore
2627

2728
def filter(self, record: Any) -> bool:
2829
record.applicationID = self.app_id
2930
record.applicationVersion = GlobalParams.app_version
3031
record.executorID = GlobalParams.executor_id
32+
for k, v in self.otlp_attributes.items():
33+
setattr(record, k, v)
3134

3235
# If available, decorate the log entry with Workflow ID and Trace ID
3336
from dbos._context import get_local_dbos_context
@@ -98,7 +101,7 @@ def config_logger(config: "ConfigFile") -> None:
98101

99102
# Attach DBOS-specific attributes to all log entries.
100103
global _dbos_log_transformer
101-
_dbos_log_transformer = DBOSLogTransformer()
104+
_dbos_log_transformer = DBOSLogTransformer(config)
102105
dbos_logger.addFilter(_dbos_log_transformer)
103106

104107

dbos/_tracer.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919

2020
class DBOSTracer:
2121

22+
otlp_attributes: dict[str, str] = {}
23+
2224
def __init__(self) -> None:
2325
self.app_id = os.environ.get("DBOS__APPID", None)
2426
self.provider: Optional[TracerProvider] = None
2527

2628
def config(self, config: ConfigFile) -> None:
29+
self.otlp_attributes = config.get("telemetry", {}).get("otlp_attributes", {}) # type: ignore
2730
if not isinstance(trace.get_tracer_provider(), TracerProvider):
2831
resource = Resource(
2932
attributes={
@@ -63,6 +66,8 @@ def start_span(
6366
for k, v in attributes.items():
6467
if k != "name" and v is not None and isinstance(v, (str, bool, int, float)):
6568
span.set_attribute(k, v)
69+
for k, v in self.otlp_attributes.items():
70+
span.set_attribute(k, v)
6671
return span
6772

6873
def end_span(self, span: Span) -> None:

tests/test_spans.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@
77
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
88
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
99

10-
from dbos import DBOS
10+
from dbos import DBOS, DBOSConfig
1111
from dbos._tracer import dbos_tracer
1212
from dbos._utils import GlobalParams
1313

1414

15-
def test_spans(dbos: DBOS) -> None:
15+
def test_spans(config: DBOSConfig) -> None:
16+
DBOS.destroy(destroy_registry=True)
17+
config["otlp_attributes"] = {"foo": "bar"}
18+
DBOS(config=config)
19+
DBOS.launch()
1620

1721
@DBOS.workflow()
1822
def test_workflow() -> None:
@@ -44,6 +48,7 @@ def test_step() -> None:
4448
assert span.attributes["applicationVersion"] == GlobalParams.app_version
4549
assert span.attributes["executorID"] == GlobalParams.executor_id
4650
assert span.context is not None
51+
assert span.attributes["foo"] == "bar"
4752

4853
assert spans[0].name == test_step.__name__
4954
assert spans[1].name == "a new span"

0 commit comments

Comments
 (0)