Feature Request
Proposal to add browse-based node discovery for the OPC-UA input plugins (inputs.opcua and inputs.opcua_listener), allowing users to specify glob patterns over browse paths instead of enumerating every node explicitly. This is one of the most requested features for large-scale OPC-UA deployments.
Addresses #8275
Motivation
Users managing factories or industrial environments often have thousands of OPC-UA nodes (e.g., MV01..MV99 across dozens of devices). Today, each node must be listed individually in the config:
[[inputs.opcua.group.nodes]]
name = "host_MV01"
namespace = "2"
identifier_type = "s"
identifier = "Plant.Device1.MV01"
[[inputs.opcua.group.nodes]]
name = "host_MV02"
namespace = "2"
identifier_type = "s"
identifier = "Plant.Device1.MV02"
# ... repeated hundreds of times
This is error-prone, hard to maintain, and doesn't adapt when new nodes appear on the server.
Proposed Configuration
Existing nodes and group config remains fully backwards compatible. A new [inputs.opcua.browse] section enables pattern-based discovery:
[[inputs.opcua]]
endpoint = "opc.tcp://factory-server:4840"
name = "opcua"
# Existing explicit nodes still work
[[inputs.opcua.group.nodes]]
name = "server_time"
namespace = "0"
identifier_type = "i"
identifier = "2258"
# NEW: Browse-based discovery
[inputs.opcua.browse]
# Starting node for browsing (default: Objects folder ns=0;i=85)
# browse_root = "ns=0;i=85"
# Max tree depth to prevent runaway traversal (default: 0 = unlimited)
# browse_depth = 10
# Max total discovered nodes as a safety limit (default: 10000)
# browse_max_nodes = 10000
# Node classes to match as leaf nodes (default: ["Variable"])
# browse_node_classes = ["Variable"]
[[inputs.opcua.browse.browse_path]]
# Glob pattern over browse path segments
# Supports: * (any single segment), ** (recursive descent),
# ? (single char), [abc] (character class)
pattern = "Objects/Plant1/*/MV*"
name = "motor_valves"
# How to derive the metric field name:
# "browse_name" (default), "display_name", or "path"
# field_name_source = "browse_name"
[inputs.opcua.browse.browse_path.default_tags]
plant = "plant1"
# Extract browse path segments as tag values (key = tag name, value = segment index)
[inputs.opcua.browse.browse_path.tags_from_path]
device = 2 # "Objects/Plant1/Device1/MV01" -> device="Device1"
[[inputs.opcua.browse.browse_path]]
pattern = "Objects/**/Temperature"
name = "temperatures"
Pattern Examples
| Pattern |
Matches |
Objects/Plant1/*/MV* |
Any MV-prefixed Variable one level under Plant1 children |
Objects/**/Temperature |
Temperature nodes at any depth under Objects |
Objects/Plant[12]/Device?/Status |
Status under Device + single char, under Plant1 or Plant2 |
Objects/*/Sensors/* |
All sensor values across all top-level children |
Design Approach
Browse, Don't Glob Strings
OPC-UA's address space is a graph, not a flat namespace. Node IDs are opaque identifiers that can't be pattern-matched without first knowing what exists. The implementation uses the standard OPC-UA Browse service to walk the address space, then matches the resulting browse paths against user patterns.
Preserve Hierarchy as a Tree
The browse result is maintained as a tree (BrowsedNode with Children) until the final metric-mapping step. This avoids:
- Losing parent-child context needed for path-based filtering
- Expensive re-browsing when multiple patterns match overlapping subtrees
- Ambiguity from premature path flattening
Segment-Level Glob Matching (Not Regex)
Patterns operate on browse path segments (split by /), not on flattened strings. This aligns with the tree structure and is more intuitive for operators. Go's path.Match handles per-segment *, ?, [class]; custom logic handles the ** recursive wildcard.
Feed Into Existing Pipeline
Discovered nodes are converted to NodeSettings and appended to the existing RootNodes list before InitNodeMetricMapping() runs. All downstream code (read client, subscribe client, metric generation, node registration) works unchanged.
Cycle Detection
OPC-UA address spaces can be cyclic. The BFS traversal tracks visited node IDs to avoid infinite loops.
Batched Browse Requests
The gopcua Browse API accepts multiple BrowseDescription entries per request. The implementation batches children lookups to reduce round-trips for large address spaces.
Implementation Plan
Phase 1
- Path matcher (
plugins/common/opcua/pathmatch.go) - glob matching over []string segments
- Address space browser (
plugins/common/opcua/browse.go) - BFS traversal with cycle detection, depth/node limits, batched Browse requests
- Browse resolver (
plugins/common/opcua/input/browse_resolver.go) - convert matched nodes to NodeSettings with field name derivation and tag extraction
- Integration - wire browse into
readClient.connect() and subscribeClient.connect()
- Config,
sample.conf, README
Phase 2 (future)
browse_refresh_interval - periodic re-browsing to discover new nodes without restart
TranslateBrowsePathsToNodeIDs optimization for known path prefixes
Safety Mechanisms
browse_max_nodes prevents OOM on huge address spaces (default: 10,000)
browse_depth prevents deep traversal (default: unlimited, but recommended)
- Cycle detection via visited-node tracking during BFS
- Per-node browse errors are logged but don't fail the entire discovery
- Zero matches produce a warning, not an error (server may not have those nodes yet)
- Existing
validateNodeToAdd detects duplicate nodes from overlapping patterns
Compatibility
- Fully backwards compatible: no changes to existing nodes/group config behavior
- Users can mix explicit nodes and browse patterns
- Works with both
inputs.opcua (polling) and inputs.opcua_listener (subscription)
References
Feature Request
Proposal to add browse-based node discovery for the OPC-UA input plugins (
inputs.opcuaandinputs.opcua_listener), allowing users to specify glob patterns over browse paths instead of enumerating every node explicitly. This is one of the most requested features for large-scale OPC-UA deployments.Addresses #8275
Motivation
Users managing factories or industrial environments often have thousands of OPC-UA nodes (e.g.,
MV01..MV99across dozens of devices). Today, each node must be listed individually in the config:This is error-prone, hard to maintain, and doesn't adapt when new nodes appear on the server.
Proposed Configuration
Existing nodes and group config remains fully backwards compatible. A new
[inputs.opcua.browse]section enables pattern-based discovery:Pattern Examples
Objects/Plant1/*/MV*Objects/**/TemperatureObjects/Plant[12]/Device?/StatusObjects/*/Sensors/*Design Approach
Browse, Don't Glob Strings
OPC-UA's address space is a graph, not a flat namespace. Node IDs are opaque identifiers that can't be pattern-matched without first knowing what exists. The implementation uses the standard OPC-UA Browse service to walk the address space, then matches the resulting browse paths against user patterns.
Preserve Hierarchy as a Tree
The browse result is maintained as a tree (
BrowsedNodewithChildren) until the final metric-mapping step. This avoids:Segment-Level Glob Matching (Not Regex)
Patterns operate on browse path segments (split by
/), not on flattened strings. This aligns with the tree structure and is more intuitive for operators. Go'spath.Matchhandles per-segment*,?,[class]; custom logic handles the**recursive wildcard.Feed Into Existing Pipeline
Discovered nodes are converted to
NodeSettingsand appended to the existingRootNodeslist beforeInitNodeMetricMapping()runs. All downstream code (read client, subscribe client, metric generation, node registration) works unchanged.Cycle Detection
OPC-UA address spaces can be cyclic. The BFS traversal tracks visited node IDs to avoid infinite loops.
Batched Browse Requests
The
gopcuaBrowse API accepts multipleBrowseDescriptionentries per request. The implementation batches children lookups to reduce round-trips for large address spaces.Implementation Plan
Phase 1
plugins/common/opcua/pathmatch.go) - glob matching over[]stringsegmentsplugins/common/opcua/browse.go) - BFS traversal with cycle detection, depth/node limits, batched Browse requestsplugins/common/opcua/input/browse_resolver.go) - convert matched nodes toNodeSettingswith field name derivation and tag extractionreadClient.connect()andsubscribeClient.connect()sample.conf, READMEPhase 2 (future)
browse_refresh_interval- periodic re-browsing to discover new nodes without restartTranslateBrowsePathsToNodeIDsoptimization for known path prefixesSafety Mechanisms
browse_max_nodesprevents OOM on huge address spaces (default: 10,000)browse_depthprevents deep traversal (default: unlimited, but recommended)validateNodeToAdddetects duplicate nodes from overlapping patternsCompatibility
inputs.opcua(polling) andinputs.opcua_listener(subscription)References