Add instrumentation for asyncua (OPC UA client)
feature-request
### 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`](https://github.com/FreeOpcUa/opcua-asyncio) (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.connect` span
- `Client.disconnect` → `opcua.disconnect` span
- `Node.read_value` → `opcua.read` span
- `Node.write_value` → `opcua.write` span
- `Node.call_method` → `opcua.call_method` span
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 via `NodeId.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.type` and 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 `CONSUMER` spans with links to the subscription span, similar to existing messaging conventions)
- Browse / address space navigation
- Metrics (operation duration histogram, active subscriptions gauge)
- OPC UA `StatusCode` extraction from response `DataValue` (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:
1. **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 in `semantic-conventions`?
- (b) Generic `rpc.*` conventions with `rpc.system = "opcua"`?
- (c) Something else?
My current prototype uses (a). Happy to change direction.
2. **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.
3. **Package naming**: I'm proposing `opentelemetry-instrumentation-asyncua` (library-specific, following the `pymysql`/`mysql`/`mysqlclient` precedent) rather than `opentelemetry-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-contrib` signals 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:
```console
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
<sub>[React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) 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](https://opentelemetry.io/community/end-user/issue-participation/).</sub>
0 条评论