From 3bc47f4cde82260380a2ac5e737c411460357a43 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sun, 4 Oct 2026 19:23:51 +0300 Subject: [PATCH] docs: document resource detectors and Sentry profiling on free-threaded CPython Closes #181. --- docs/introduction/configuration.md | 35 ++++++++++++++++ .../test_opentelemetry_instrument.py | 41 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/docs/introduction/configuration.md b/docs/introduction/configuration.md index ed93ee5..5285ff6 100644 --- a/docs/introduction/configuration.md +++ b/docs/introduction/configuration.md @@ -170,6 +170,24 @@ For FastStream there is additionally: health-check span is recorded unless `opentelemetry_generate_health_check_spans` is False, and any `opentelemetry_instrumentors` are applied. +### Resource + +The `Resource` is built with the SDK's `Resource.create`, so OpenTelemetry resource detectors work +with no lite-bootstrap setting. Name them, comma-separated, in `OTEL_EXPERIMENTAL_RESOURCE_DETECTORS`; +each name is an entry point in the `opentelemetry_resource_detector` group. The SDK registers +`process`, `os` and `host`; separately installed detector packages register their own names: + +```bash +OTEL_EXPERIMENTAL_RESOURCE_DETECTORS=process,os +``` + +`OTEL_RESOURCE_ATTRIBUTES` is honoured as well. + +The attributes lite-bootstrap sets (`service.name`, `service.namespace`, `service.version`, +`container.name`) are merged last and override anything a detector or environment variable supplies. +`service_name` defaults to `"micro-service"`, so `OTEL_SERVICE_NAME` is overridden unless `service_name` +is empty: set `opentelemetry_service_name` or `service_name` instead. + ## Pyroscope @@ -187,6 +205,23 @@ Additional parameters: When OpenTelemetry is also enabled, a `PyroscopeSpanProcessor` is automatically added to the tracer provider. It tags root spans with a `pyroscope.profile.id` attribute and sets Pyroscope thread tags so that traces and profiles can be linked in the Grafana UI. +`pyroscope-io` publishes no free-threaded wheels, so the `pyroscope` extra cannot install on +free-threaded CPython ([#171](https://github.com/modern-python/lite-bootstrap/issues/171)). Sentry's +continuous profiler is pure Python and runs there; enable it through `sentry_additional_params`: + +```python +import sentry_sdk + +config = FreeConfig( + sentry_dsn="https://key@sentry.example.com/1", + sentry_additional_params={"profile_session_sample_rate": 1.0, "profile_lifecycle": "manual"}, +) +FreeBootstrapper(config).bootstrap() +sentry_sdk.profiler.start_profiler() +``` + +Read more about Sentry's profiling options [here](https://docs.sentry.io/platforms/python/profiling/). + ## Structlog Structlog is bootstrapped by default. To opt out, set `logging_enabled=False`. diff --git a/tests/instruments/test_opentelemetry_instrument.py b/tests/instruments/test_opentelemetry_instrument.py index dcb9282..6b0c72a 100644 --- a/tests/instruments/test_opentelemetry_instrument.py +++ b/tests/instruments/test_opentelemetry_instrument.py @@ -1,4 +1,5 @@ import logging +import os import re import sys import typing @@ -122,6 +123,46 @@ def test_opentelemetry_sampler_wins_over_sampler_env_vars(monkeypatch: pytest.Mo instrument.teardown() +@pytest.mark.parametrize( + ("config", "expected_service_name"), + [ + (OpenTelemetryConfig(opentelemetry_log_traces=True), "micro-service"), + (OpenTelemetryConfig(opentelemetry_log_traces=True, service_name="configured"), "configured"), + ( + OpenTelemetryConfig( + opentelemetry_log_traces=True, service_name="configured", opentelemetry_service_name="otel-configured" + ), + "otel-configured", + ), + ], +) +def test_opentelemetry_configured_service_name_wins_over_env( + monkeypatch: pytest.MonkeyPatch, config: OpenTelemetryConfig, expected_service_name: str +) -> None: + monkeypatch.setenv("OTEL_SERVICE_NAME", "from-env") + instrument = OpenTelemetryInstrument(bootstrap_config=config) + try: + instrument.bootstrap() + assert instrument._tracer_provider is not None # noqa: SLF001 + assert instrument._tracer_provider.resource.attributes["service.name"] == expected_service_name # noqa: SLF001 + finally: + instrument.teardown() + + +def test_opentelemetry_resource_detectors_enrich_resource(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("OTEL_EXPERIMENTAL_RESOURCE_DETECTORS", "process") + monkeypatch.setenv("OTEL_RESOURCE_ATTRIBUTES", "deployment.environment=from-env") + instrument = OpenTelemetryInstrument(bootstrap_config=OpenTelemetryConfig(opentelemetry_log_traces=True)) + try: + instrument.bootstrap() + assert instrument._tracer_provider is not None # noqa: SLF001 + attributes = instrument._tracer_provider.resource.attributes # noqa: SLF001 + assert attributes["process.pid"] == os.getpid() + assert attributes["deployment.environment"] == "from-env" + finally: + instrument.teardown() + + def test_opentelemetry_instrument_teardown_shuts_down_tracer_provider() -> None: instrument = OpenTelemetryInstrument( bootstrap_config=OpenTelemetryConfig(opentelemetry_log_traces=True),