Skip to content

Commit d05e364

Browse files
authored
Only set up exporters if providers are properly set (#530)
This PR fixes the issue where DBOS would throw an exception if `enable_otlp` is set to `True` but the logger/tracer providers weren't set. Instead of crashing the entire app, it'll log a warning after this change. Also fixed an issue where `DBOS.trace` being `None` (when OTLP tracer wasn't set) could crash the FastAPI app. It should only set the status code if it can find an active span.
1 parent 2e15ab5 commit d05e364

File tree

5 files changed

+26
-11
lines changed

5 files changed

+26
-11
lines changed

dbos/_dbos.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,11 +1326,10 @@ def step_status(cls) -> Optional[StepStatus]:
13261326
return None
13271327

13281328
@classproperty
1329-
def span(cls) -> "Span":
1329+
def span(cls) -> Optional["Span"]:
13301330
"""Return the tracing `Span` associated with the current context."""
13311331
ctx = assert_current_dbos_context()
13321332
span = ctx.get_current_active_span()
1333-
assert span
13341333
return span
13351334

13361335
@classproperty

dbos/_fastapi.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,6 @@ async def dbos_fastapi_middleware(
8787
and not dbos._config["telemetry"]["disable_otlp"]
8888
and hasattr(response, "status_code")
8989
):
90-
DBOS.span.set_attribute("responseCode", response.status_code)
90+
if DBOS.span is not None:
91+
DBOS.span.set_attribute("responseCode", response.status_code)
9192
return response

dbos/_logger.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def config_logger(config: "ConfigFile") -> None:
7171
if not disable_otlp:
7272

7373
from opentelemetry._logs import get_logger_provider, set_logger_provider
74+
from opentelemetry._logs._internal import ProxyLoggerProvider
7475
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
7576
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
7677
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
@@ -80,7 +81,8 @@ def config_logger(config: "ConfigFile") -> None:
8081
# Only set up OTLP provider and exporter if endpoints are provided
8182
log_provider = get_logger_provider()
8283
if otlp_logs_endpoints is not None and len(otlp_logs_endpoints) > 0:
83-
if not isinstance(log_provider, LoggerProvider):
84+
if isinstance(log_provider, ProxyLoggerProvider):
85+
# Set a real LoggerProvider if it was previously a ProxyLoggerProvider
8486
log_provider = LoggerProvider(
8587
Resource.create(
8688
attributes={
@@ -91,7 +93,7 @@ def config_logger(config: "ConfigFile") -> None:
9193
set_logger_provider(log_provider)
9294

9395
for e in otlp_logs_endpoints:
94-
log_provider.add_log_record_processor(
96+
log_provider.add_log_record_processor( # type: ignore
9597
BatchLogRecordProcessor(
9698
OTLPLogExporter(endpoint=e),
9799
export_timeout_millis=5000,
@@ -101,10 +103,14 @@ def config_logger(config: "ConfigFile") -> None:
101103
# Even if no endpoints are provided, we still need a LoggerProvider to create the LoggingHandler
102104
global _otlp_handler
103105
if _otlp_handler is None:
104-
_otlp_handler = LoggingHandler(logger_provider=log_provider)
105-
106-
# Direct DBOS logs to OTLP
107-
dbos_logger.addHandler(_otlp_handler)
106+
if isinstance(log_provider, ProxyLoggerProvider):
107+
dbos_logger.warning(
108+
"OTLP is enabled but logger provider not set, skipping log exporter setup."
109+
)
110+
else:
111+
_otlp_handler = LoggingHandler(logger_provider=log_provider)
112+
# Direct DBOS logs to OTLP
113+
dbos_logger.addHandler(_otlp_handler)
108114

109115
# Attach DBOS-specific attributes to all log entries.
110116
global _dbos_log_transformer

dbos/_tracer.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from dbos._utils import GlobalParams
99

1010
from ._dbos_config import ConfigFile
11+
from ._logger import dbos_logger
1112

1213
if TYPE_CHECKING:
1314
from ._context import TracedAttributes
@@ -46,7 +47,8 @@ def config(self, config: ConfigFile) -> None:
4647

4748
# Only set up OTLP provider and exporter if endpoints are provided
4849
if otlp_traces_endpoints is not None and len(otlp_traces_endpoints) > 0:
49-
if not isinstance(tracer_provider, TracerProvider):
50+
if isinstance(tracer_provider, trace.ProxyTracerProvider):
51+
# Set a real TracerProvider if it was previously a ProxyTracerProvider
5052
resource = Resource(
5153
attributes={
5254
SERVICE_NAME: config["name"],
@@ -61,7 +63,12 @@ def config(self, config: ConfigFile) -> None:
6163

6264
for e in otlp_traces_endpoints:
6365
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint=e))
64-
tracer_provider.add_span_processor(processor)
66+
tracer_provider.add_span_processor(processor) # type: ignore
67+
68+
if isinstance(tracer_provider, trace.ProxyTracerProvider):
69+
dbos_logger.warning(
70+
"OTLP is enabled but tracer provider not set, skipping trace exporter setup."
71+
)
6572

6673
def set_provider(self, provider: "Optional[TracerProvider]") -> None:
6774
self.provider = provider

tests/test_fastapi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def test_endpoint(var1: str, var2: str) -> str:
3434
@app.get("/workflow/{var1}/{var2}")
3535
@DBOS.workflow()
3636
def test_workflow(var1: str, var2: str) -> str:
37+
assert DBOS.span is not None
3738
DBOS.span.set_attribute("test_key", "test_value")
3839
res1 = test_transaction(var1)
3940
res2 = test_step(var2)
@@ -85,6 +86,7 @@ def test_endpoint(var1: str, var2: str) -> str:
8586

8687
@DBOS.workflow()
8788
def test_workflow(var1: str, var2: str) -> str:
89+
assert DBOS.span is not None
8890
DBOS.span.set_attribute("test_key", "test_value")
8991
res1 = test_transaction(var1)
9092
res2 = test_step(var2)

0 commit comments

Comments
 (0)