Skip to content

Commit f9a4ce6

Browse files
authored
#402: add opentelemetry tracing and collector (#403)
* #402: add opentelemetry tracing and collector * fix style
1 parent 52a86dd commit f9a4ce6

13 files changed

Lines changed: 92 additions & 5 deletions

File tree

app/commands/adminapi/command.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
from pathlib import Path
22
from typing import final
33

4+
import pydantic
45
import pydantic_settings as settings
56
import structlog
67
import yaml
78

89
from app.data import repositories
910
from app.domain import adminapi as domain
10-
from app.lib import auth, clients, commands, config
11+
from app.lib import auth, clients, commands, config, tracing
1112
from app.lib.storage import postgres
13+
from app.lib.tracing import TracingConfig
1214
from app.lib.web import middlewares, server
1315
from app.presentation import adminapi as presentation
1416

@@ -27,6 +29,8 @@ def __init__(self, config_path: str):
2729
def prepare(self):
2830
cfg = parse_config(self.config_path)
2931

32+
tracing.setup_tracing("adminapi", cfg.tracing)
33+
3034
self.pg_storage = postgres.PgStorage(cfg.storage, log)
3135
self.pg_storage.connect()
3236

@@ -65,6 +69,9 @@ class Config(config.ConfigSettings):
6569
storage: postgres.PgStorageConfig
6670
clients: ClientsConfig
6771
auth_enabled: bool
72+
tracing: TracingConfig = pydantic.Field(
73+
default_factory=lambda: TracingConfig(endpoint="localhost:4317", enabled=False)
74+
)
6875

6976

7077
def parse_config(path: str) -> Config:

app/commands/dataapi/command.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
from pathlib import Path
22
from typing import final
33

4+
import pydantic
45
import structlog
56
import yaml
67

78
from app.data import repositories
89
from app.domain import dataapi as domain
910
from app.domain import responders
10-
from app.lib import commands, config
11+
from app.lib import commands, config, tracing
1112
from app.lib.storage import postgres
13+
from app.lib.tracing import TracingConfig
1214
from app.lib.web import server
1315
from app.presentation import dataapi as presentation
1416

@@ -28,6 +30,8 @@ def __init__(self, config_path: str) -> None:
2830
def prepare(self):
2931
self.config = parse_config(self.config_path)
3032

33+
tracing.setup_tracing("dataapi", self.config.tracing)
34+
3135
self.pg_storage = postgres.PgStorage(self.config.storage, log)
3236
self.pg_storage.connect()
3337

@@ -50,6 +54,9 @@ class Config(config.ConfigSettings):
5054
server: server.ServerConfig
5155
storage: postgres.PgStorageConfig
5256
catalogs: responders.CatalogConfig
57+
tracing: TracingConfig = pydantic.Field(
58+
default_factory=lambda: TracingConfig(endpoint="localhost:4317", enabled=False)
59+
)
5360

5461

5562
def parse_config(path: str) -> Config:

app/lib/concurrency/errorgroup.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import contextvars
12
import threading
23
from collections.abc import Callable
34
from concurrent.futures import Future, ThreadPoolExecutor
@@ -23,14 +24,21 @@ def __init__(self) -> None:
2324
self._error_lock = threading.Lock()
2425

2526
def run[T, **P](self, fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> TaskResult[T]:
26-
future = self._executor.submit(self._run_with_error_handling, fn, *args, **kwargs)
27+
ctx = contextvars.copy_context()
28+
future = self._executor.submit(self._run_with_error_handling, ctx, fn, *args, **kwargs)
2729
self._futures.append(future)
2830

2931
return TaskResult[T](future)
3032

31-
def _run_with_error_handling[T, **P](self, fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T | None:
33+
def _run_with_error_handling[T, **P](
34+
self,
35+
ctx: contextvars.Context,
36+
fn: Callable[P, T],
37+
*args: P.args,
38+
**kwargs: P.kwargs,
39+
) -> T | None:
3240
try:
33-
return fn(*args, **kwargs)
41+
return ctx.run(fn, *args, **kwargs)
3442
except Exception as e:
3543
with self._error_lock:
3644
if self._error is None:

app/lib/tracing.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import pydantic
2+
from opentelemetry import trace
3+
from opentelemetry.exporter.otlp.proto.grpc import trace_exporter
4+
from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor
5+
from opentelemetry.sdk import trace as sdk_trace
6+
from opentelemetry.sdk.resources import Resource
7+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
8+
9+
10+
class TracingConfig(pydantic.BaseModel):
11+
endpoint: str
12+
enabled: bool = False
13+
14+
15+
def setup_tracing(service_name: str, cfg: TracingConfig) -> None:
16+
if not cfg.enabled:
17+
return
18+
19+
provider = sdk_trace.TracerProvider(
20+
resource=Resource.create({"service.name": service_name}),
21+
)
22+
provider.add_span_processor(
23+
BatchSpanProcessor(trace_exporter.OTLPSpanExporter(endpoint=cfg.endpoint, insecure=True))
24+
)
25+
trace.set_tracer_provider(provider)
26+
27+
PsycopgInstrumentor().instrument()

app/lib/web/server/server.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import uvicorn
1010
from fastapi import exceptions, responses
1111
from fastapi.middleware import cors
12+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
1213
from starlette.middleware import base as smiddlewares
1314

1415
from app.lib.web import errors, middlewares
@@ -88,6 +89,8 @@ def __init__(
8889

8990
app.add_exception_handler(exceptions.RequestValidationError, validation_exception_handler)
9091

92+
FastAPIInstrumentor.instrument_app(app)
93+
9194
self.app = app
9295
self.config = cfg
9396
self.logger = logger

configs/dev/adminapi.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ storage:
1515

1616
clients:
1717
ads_token: token
18+
19+
tracing:
20+
endpoint: localhost:4317
21+
enabled: true

configs/dev/dataapi.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ storage:
99
user: hyperleda
1010
password: password
1111

12+
tracing:
13+
endpoint: localhost:4317
14+
enabled: true
15+
1216
catalogs:
1317
velocity:
1418
apexes:

configs/prod/adminapi.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@ storage:
1414

1515
clients:
1616
ads_token: fake
17+
18+
tracing:
19+
endpoint: tracing-collector:4317
20+
enabled: true

configs/prod/dataapi.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ storage:
99
user: hyperleda
1010
password: fake
1111

12+
tracing:
13+
endpoint: tracing-collector:4317
14+
enabled: true
15+
1216
catalogs:
1317
velocity:
1418
apexes:

configs/test/adminapi.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@ storage:
1414

1515
clients:
1616
ads_token: fake
17+
18+
tracing:
19+
endpoint: tracing-collector:4317
20+
enabled: true

0 commit comments

Comments
 (0)