open-telemetry / open-telemetry/opentelemetry-python-contrib
Add instrumentation for asyncua (OPC UA client)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 1.1k
- Avg merge
- 4d 15h
- Merged PRs (30d)
- 16
Description
What problem do you want to solve?
Python applications communicating with industrial control systems (PLCs, SCADA, MES) via OPC UA currently have no first-class OpenTelemetry instrumentation. The most popular Python OPC UA library, asyncua (1.4k GitHub stars, actively maintained, used in industrial IoT gateways, MES integrations, and data historian pipelines), has no instrumentor in opentelemetry-python-contrib. Client-side Python instrumentation for asyncua would enable observability for Python-based industrial integrations.
Describe the solution you'd like
A new package opentelemetry-instrumentation-asyncua in opentelemetry-python-contrib, extending BaseInstrumentor and following the established patterns in this repo (structurally modeled on asyncpg, redis, and similar).
MVP scope (initial PR)
Traces for client-initiated OPC UA operations:
Client.connect→opcua.connectspanClient.disconnect→opcua.disconnectspanNode.read_value→opcua.readspanNode.write_value→opcua.writespanNode.call_method→opcua.call_methodspan
All spans are SpanKind.CLIENT. Attributes include:
opcua.operation(read / write / connect / etc.)opcua.endpoint,server.address,server.port(target identification)opcua.node_id(canonical OPC UA form viaNodeId.to_string())opcua.security_policy,opcua.security_mode(security posture)opcua.method_id,opcua.method_args_count(for method calls)opcua.variant_type(for writes)error.typeand span status on exceptions
asyncua's Node class is shared between client and server code paths, so wrappers filter out server-internal operations (e.g., address space setup reads/writes) by inspecting the node's session type — ensuring only genuine client-initiated calls produce spans.
Deferred to follow-up work
- Subscriptions and monitored items (design question: data change notifications are long-lived server-push via client-initiated polling; likely modeled as
CONSUMERspans with links to the subscription span, similar to existing messaging conventions) - Browse / address space navigation
- Metrics (operation duration histogram, active subscriptions gauge)
- OPC UA
StatusCodeextraction from responseDataValue(would require wrapping lower-level methods where the full response is visible) - OPC UA-specific semantic conventions proposal (separate work in
semantic-conventions)
Questions for maintainers
Before opening a PR, I'd appreciate guidance on three points:
-
Semantic conventions approach: There are no stable OTel semantic conventions for OPC UA yet. Would you prefer:
- (a) A provisional
opcua.*attribute namespace, documented as experimental, with parallel work to propose stable conventions insemantic-conventions? - (b) Generic
rpc.*conventions withrpc.system = "opcua"? - (c) Something else?
My current prototype uses (a). Happy to change direction.
- (a) A provisional
-
Scope of MVP: Is the scope above acceptable for an initial PR, or would you expect subscriptions to be covered in the first drop? Subscriptions add design complexity (long-lived notifications, span/metric split) that I'd prefer to address in a focused follow-up.
-
Package naming: I'm proposing
opentelemetry-instrumentation-asyncua(library-specific, following thepymysql/mysql/mysqlclientprecedent) rather thanopentelemetry-instrumentation-opcua(protocol-specific). Does that work?
Describe alternatives you've considered
- Manual instrumentation with the OTel SDK — works but requires every user to reimplement. A standard instrumentation in this repo is more valuable for the ecosystem.
- Third-party PyPI package outside contrib — would fragment the ecosystem and reduce discoverability. Hosting in
opentelemetry-python-contribsignals community ownership and ensures maintenance continuity alongside the rest of the instrumentation catalog. - Waiting for stable OPC UA semantic conventions before implementing — this could take a long time and leaves Python users without a standard instrumentation indefinitely. A provisional
opcua.*namespace now, adjusted later to match stabilized conventions, seems more pragmatic.
Additional Context
I have a working local prototype implementing the MVP scope described above. It passes lint (ruff) and produces clean, verified spans end-to-end against a local asyncua server:
Span: opcua.connect (kind=CLIENT, duration=3.46 ms)
opcua.operation = connect
opcua.endpoint = opc.tcp://localhost:14840/test/
server.address = localhost
server.port = 14840
opcua.security_policy = None
opcua.security_mode = None
Span: opcua.read (kind=CLIENT, duration=0.54 ms)
opcua.operation = read
opcua.node_id = ns=2;i=2
Span: opcua.write (kind=CLIENT, duration=0.36 ms)
opcua.operation = write
opcua.node_id = ns=2;i=2
Span: opcua.disconnect (kind=CLIENT, duration=0.52 ms)
opcua.operation = disconnect
opcua.endpoint = opc.tcp://localhost:14840/test/
Would you like to implement a fix?
Yes
Tip
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reviewing BaseInstrumentor and the established asyncpg and redis instrumentation patterns, then inspect asyncua's Client and Node methods listed in the MVP. Confirm the package produces the requested CLIENT spans and attributes, filters server-internal operations, and verifies the results end-to-end against a local asyncua server.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- observability
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100