• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 25565075335

08 May 2026 03:47PM UTC coverage: 92.787% (-0.1%) from 92.887%
25565075335

push

github

web-flow
add OpenTelemetry backend for work unit reporting (#23284)

# Overview

Add a new `pants.backend.observability.opentelemetry` backend to report
work unit tracing to OpenTelemetry. The backend is based on
[shoalsoft-pants-opentelemetry-plugin](https://github.com/shoalsoft/shoalsoft-pants-opentelemetry-plugin)
with unnecessary compatibility code and "shoalsoft" branding removed.

Notes:
- This backend only reports Pants engine work units to OpenTelemetry; it
does not report tracing data for Pants rule code or Rust code.
- This backend does not support gRPC export due to fork safety issues
with the gRPC C library and Python. See
https://github.com/shoalsoft/shoalsoft-pants-opentelemetry-plugin/issues/84
and https://github.com/grpc/grpc/blob/master/doc/fork_support.md for
additional details.

# Lockfile

```
    Lockfile diff: 3rdparty/python/user_reqs.lock [python-default]

    ==                    Upgraded dependencies                     ==

      anyio                          4.12.1       -->   4.13.0
      certifi                        2026.1.4     -->   2026.4.22
      charset-normalizer             3.4.4        -->   3.4.7
      click                          8.3.1        -->   8.3.2
      cross-web                      0.4.1        -->   0.6.0
      cryptography                   46.0.5       -->   46.0.7
      graphql-core                   3.2.7        -->   3.2.8
      idna                           3.11         -->   3.12
      librt                          0.8.1        -->   0.9.0
      pydantic                       2.12.5       -->   2.13.3
      pydantic-core                  2.41.5       -->   2.46.3
      pygments                       2.19.2       -->   2.20.0
      pyjwt                          2.11.0       -->   2.12.1
      python-dotenv                  1.2.1        -->   1.2.2
      python-multipart               0.0.22       -->   0.0.26
      ujson                          5.11.0       -->   5.12.0

    ==                   ... (continued)

564 of 740 new or added lines in 12 files covered. (76.22%)

1 existing line in 1 file now uncovered.

92944 of 100169 relevant lines covered (92.79%)

4.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.35
/src/python/pants/backend/observability/opentelemetry/register.py
1
# Copyright 2026 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
1✔
5

6
import datetime
1✔
7
import logging
1✔
8

9
from pants.backend.observability.opentelemetry.exception_logging_processor import (
1✔
10
    ExceptionLoggingProcessor,
11
)
12
from pants.backend.observability.opentelemetry.opentelemetry_config import OtlpParameters
1✔
13
from pants.backend.observability.opentelemetry.opentelemetry_processor import get_processor
1✔
14
from pants.backend.observability.opentelemetry.single_threaded_processor import (
1✔
15
    SingleThreadedProcessor,
16
)
17
from pants.backend.observability.opentelemetry.subsystem import TelemetrySubsystem
1✔
18
from pants.backend.observability.opentelemetry.workunit_handler import TelemetryWorkunitsCallback
1✔
19
from pants.base.build_root import BuildRoot
1✔
20
from pants.core.util_rules.env_vars import (
1✔
21
    environment_vars_subset,
22
)
23
from pants.engine.env_vars import EnvironmentVarsRequest
1✔
24
from pants.engine.rules import collect_rules, implicitly, rule
1✔
25
from pants.engine.streaming_workunit_handler import (
1✔
26
    WorkunitsCallback,
27
    WorkunitsCallbackFactory,
28
    WorkunitsCallbackFactoryRequest,
29
)
30
from pants.engine.unions import UnionRule
1✔
31

32
logger = logging.getLogger(__name__)
1✔
33

34

35
class TelemetryWorkunitsCallbackFactoryRequest(WorkunitsCallbackFactoryRequest):
1✔
36
    pass
1✔
37

38

39
@rule
1✔
40
async def telemetry_workunits_callback_factory_request(
1✔
41
    _: TelemetryWorkunitsCallbackFactoryRequest,
42
    telemetry: TelemetrySubsystem,
43
    build_root: BuildRoot,
44
) -> WorkunitsCallbackFactory:
45
    logger.debug(
1✔
46
        f"telemetry_workunits_callback_factory_request: telemetry.enabled={telemetry.enabled}; telemetry.exporter={telemetry.exporter}; "
47
        f"bool(telemetry.exporter)={bool(telemetry.exporter)}"
48
    )
49

50
    traceparent_env_var: str | None = None
1✔
51
    otel_resource_attributes: str | None = None
1✔
52
    if telemetry.enabled and telemetry.exporter:
1✔
53
        env_vars = await environment_vars_subset(
1✔
54
            EnvironmentVarsRequest(["TRACEPARENT", "OTEL_RESOURCE_ATTRIBUTES"]), **implicitly()
55
        )  # type: ignore[arg-type,unused-ignore]
56
        if telemetry.parse_traceparent:
1✔
57
            traceparent_env_var = env_vars.get("TRACEPARENT")
1✔
58
            logger.debug(f"Found TRACEPARENT: {traceparent_env_var}")
1✔
59
        otel_resource_attributes = env_vars.get("OTEL_RESOURCE_ATTRIBUTES")
1✔
60
        logger.debug(f"Found OTEL_RESOURCE_ATTRIBUTES: {otel_resource_attributes}")
1✔
61

62
    def workunits_callback_factory() -> WorkunitsCallback | None:
1✔
63
        if not telemetry.enabled or not telemetry.exporter:
1✔
NEW
64
            logger.debug("Skipping enabling OpenTelemetry work unit handler.")
×
NEW
65
            return None
×
66

67
        logger.debug("Enabling OpenTelemetry work unit handler.")
1✔
68

69
        otel_processor = get_processor(
1✔
70
            span_exporter_name=telemetry.exporter,
71
            otlp_parameters=OtlpParameters(
72
                endpoint=telemetry.exporter_endpoint,
73
                traces_endpoint=telemetry.exporter_traces_endpoint,
74
                certificate_file=telemetry.exporter_certificate_file,
75
                client_key_file=telemetry.exporter_client_key_file,
76
                client_certificate_file=telemetry.exporter_client_certificate_file,
77
                headers=telemetry.exporter_headers,
78
                timeout=telemetry.exporter_timeout,
79
                compression=(
80
                    telemetry.exporter_compression.value if telemetry.exporter_compression else None
81
                ),
82
            ),
83
            build_root=build_root.pathlib_path,
84
            traceparent_env_var=traceparent_env_var,
85
            otel_resource_attributes=otel_resource_attributes,
86
            json_file=telemetry.json_file,
87
            trace_link_template=telemetry.trace_link_template,
88
        )
89

90
        processor = SingleThreadedProcessor(
1✔
91
            ExceptionLoggingProcessor(otel_processor, name="OpenTelemetry")
92
        )
93

94
        processor.initialize()
1✔
95

96
        return TelemetryWorkunitsCallback(
1✔
97
            processor=processor,
98
            finish_timeout=finish_timeout,
99
            async_completion=telemetry.async_completion,
100
        )
101

102
    finish_timeout = datetime.timedelta(seconds=telemetry.finish_timeout)
1✔
103
    return WorkunitsCallbackFactory(
1✔
104
        callback_factory=workunits_callback_factory,
105
    )
106

107

108
def rules():
1✔
109
    return (
1✔
110
        *collect_rules(),
111
        UnionRule(WorkunitsCallbackFactoryRequest, TelemetryWorkunitsCallbackFactoryRequest),
112
    )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc