Skip to content

Derive best-effort service.name and service.version from installed package metadata / pyproject.toml #39

Description

@mmanciop

Motivation

Our distro currently derives service.name as a best-effort fallback from sys.argv[0] (entry-point script basename, stripped of .py) when the user hasn't set OTEL_SERVICE_NAME and hasn't put service.name in OTEL_RESOURCE_ATTRIBUTES. See resource.py:131-143 and resource.py:197-239.

We can do better by reading the app's declarative metadata — the Python analog of what our Node.js distro already does with package.json.

State of the art (as of 2026-07)

Where the pattern has converged (adjacent languages):

  • Node.js: community opentelemetry-resource-detector-service reads package.jsonname/version. Our own Dash0 Node distro follows this.
  • Java / Spring Boot Starter: reads META-INF/build-info.properties (build.project.artifactservice.name, build.versionservice.version), then MANIFEST.MF, then spring.application.name.
  • .NET auto-instrumentation: entry assembly reflection.

Both Java and Node deliberately read build-time-generated metadata, not source manifests, because installed apps often don't ship the source manifest. Python has a direct analog: importlib.metadata reads the installed .dist-info/METADATA, which is derived from pyproject.toml [project] at build time.

Proposed field mapping

Source OTel attribute
[project].name / dist-info Name service.name
[project].version / dist-info Version service.version
service.namespace (no cross-ecosystem precedent — skip)

Do not map [project].description, authors, or other fields — no ecosystem precedent.

Proposed precedence (mirrors Java Spring Boot Starter)

```
OTEL_SERVICE_NAME env var

service.name in OTEL_RESOURCE_ATTRIBUTES env var
> importlib.metadata Name (for installed packages)
> pyproject.toml [project].name (walk-up from sys.argv[0])
> sys.argv[0] basename without .py (current fallback)
> unknown_service:{process.executable.name} (SDK default)
```

Same stack for service.version (without the env-var layers, which OTel doesn't standardize for version).

Two-tier discovery strategy

Primary — importlib.metadata for `pip install .` deployments. Use packages_distributions() + metadata(dist_name) to read the installed .dist-info/METADATA. No file-path discovery, no tomllib dependency, works in containers where source isn't shipped. Direct analog of what Java does with build-info.properties.

Secondary — pyproject.toml walk-up for `python src/app.py`-style source runs. Walk up from Path(sys.argv[0]).resolve().parent (not cwd — that's wrong for the common invocation pattern), stopping at .git or filesystem root. tomllib is 3.11+ stdlib; make this branch conditional on availability rather than pulling tomli as a hard dep.

Edge cases

Scenario Behavior
App installed via `pip install .` importlib.metadata succeeds; no file walk needed
App run from source, single pyproject.toml File walk finds it
Monorepo, multiple pyproject.toml files First walk-up wins = the one owning the entry-point file
App has no pyproject.toml (legacy setup.py only) Fall through to current sys.argv[0] heuristic
pyproject.toml has no [project] table (tool-only config) Fall through gracefully
Container/deployed app — pyproject.toml not shipped importlib.metadata still works
Python 3.8–3.10 without tomli installed Skip pyproject.toml file-reading path; importlib.metadata still works

What to avoid

  • No hyphen-to-underscore normalization on project.name (Node community doesn't do it; no reason to diverge)
  • No default `service.version = "0.0.0"` — absent is better than wrong
  • Don't walk up from cwd; wrong for python path/to/app.py invocations
  • Don't add tomli as a hard dependency for 3.8–3.10 support

Primary sources

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions