Skip to main content

SNMP Trap Profile Format

Overview

An SNMP trap profile defines how a specific class of SNMP trap notifications is decoded, classified, and rendered by the Netdata SNMP trap subsystem.

info

Trap profiles are reusable and declarative — you never need to modify the collector source code to support a new device or vendor.

A profile tells the Netdata SNMP trap plugin:

  • which trap OIDs carry well-known notifications
  • which varbinds ship with each trap (their OIDs, MIB types, value enums)
  • how to classify each trap (category + severity)
  • how to render each trap into a human-readable journal MESSAGE
  • which optional metrics and charts can be derived from selected traps

Trap profiles do not define journal field names manually. The plugin derives indexed TRAP_VAR_* journal fields from received non-sensitive, non-redundant event varbinds, and also keeps the complete structured audit copy in TRAP_JSON (see the SNMP trap subsystem design doc). Profiles may define optional trap-to-metric rules and chart definitions. Listener jobs still decide whether to enable those rules through profile_metrics.

A profile is a single YAML file. One file per vendor by convention; stock profiles ship under default/ and are organized by inferred enterprise-PEN vendor (ciscosystems.yaml, huawei-technology-co-ltd.yaml, juniper-networks-inc.yaml, …) plus the IETF-standard file (standard.yaml) and the IEEE LLDP file (ieee-lldp.yaml).

How profiles are loaded

The plugin reads stock profiles from the go.d stock config directory snmp.trap-profiles/default/ subdirectory (typically /usr/lib/netdata/conf.d/go.d/snmp.trap-profiles/default/) and operator profiles from the go.d user config directory snmp.trap-profiles/ subdirectory (typically /etc/netdata/go.d/snmp.trap-profiles/). Stock files are plain .yaml in the source repository for reviewability; installed packages ship them as .yaml.zst. The loader accepts .yaml, .yml, .yaml.zst, and .yml.zst.

Profiles are loaded only when the first runnable SNMP trap job is created — Netdata agents that do not receive traps never pay the memory footprint.

Profile loading is shared across trap jobs created by one plugin registration. The first runnable job acquires a catalog lease, eagerly loads and validates every operator profile, and validates the stock file inventory against exactly one catalogue.json or catalogue.json.zst. This happens in Collector.Init(); Collector.Check() is a no-op. The manifest supplies exact routes for trap OIDs and metric rule names, plus a deterministic candidate-file set for each MIB. The runtime does not fall back to parsing every stock profile when the manifest is missing or invalid.

Stock profile definitions are loaded and validated only when a matching trap OID, MIB-qualified name, or selected metric rule needs that file. A MIB-qualified lookup hydrates all candidates for its MIB and then requires exactly one matching trap name. Different stock files hydrate independently; concurrent requests for the same file coalesce. A failed eager validation is a creation-time job failure surfaced to DynCfg, while a failed lazy stock load is reported by the matching operation and cached only for the current catalog epoch. The final lease release drops the epoch, so the next runnable job rebuilds it from disk.

Profile identity is the extensionless filename. For example, an operator ciscosystems.yaml replaces stock ciscosystems.yaml.zst. Identities must match ^[a-z0-9][a-z0-9_-]*$; duplicate operator identities and invalid operator profiles fail the complete load.

File layout

# Optional file-scope metadata
vendor: cisco
mib_count: 390
trap_count: 1584

# File-scoped table of varbind metadata, name-keyed.
# Defined once per file; every trap below references entries by name.
varbinds:
ifIndex:
oid: 1.3.6.1.2.1.2.2.1.1
type: INTEGER
ifAdminStatus:
oid: 1.3.6.1.2.1.2.2.1.7
type: INTEGER
enum:
'1': up
'2': down
'3': testing
cospfConfigErrorType:
oid: 1.3.6.1.4.1.9.10.101.1.1.2
type: INTEGER
enum:
'1': badVersion
'2': areaMismatch
'6': authFailure
# …

# Trap definitions — each entry references varbinds by name.
traps:
- oid: 1.3.6.1.4.1.9.10.101.0.1
name: CISCO-OSPF-TRAP-MIB::cospfIfConfigError # MIB-qualified, globally unique
category: state_change
severity: warning
description: |
OSPF configuration mismatch on {{hostname}}
local router ID: {{value "ospfRouterId"}}
interface IP: {{value "ospfIfIpAddress"}}
source of mismatched packet: {{value "cospfPacketSrc"}}
error type: {{value "cospfConfigErrorType"}}
packet type: {{value "cospfPacketType"}}
status: current
varbinds:
- ospfRouterId
- ospfIfIpAddress
- cospfPacketSrc
- cospfConfigErrorType
- cospfPacketType

# Optional chart definitions for profile metric rules.
charts:
- id: ospf_config_errors
title: OSPF configuration errors
context: snmp.trap.ospf.config.errors
units: events/s
algorithm: incremental

# Optional trap-to-metric rules. Jobs enable these with profile_metrics.
metrics:
- name: cisco.ospf.config_error
type: counter
on_trap: CISCO-OSPF-TRAP-MIB::cospfIfConfigError
output:
metric: snmp_trap_ospf_config_error_events
dimension: events
chart: ospf_config_errors

Schema reference

File-level varbinds: table

Name-keyed map of varbind metadata shared across every trap in the file. Each entry is the slim shipping form of a single MIB object:

FieldRequiredTypeNotes
oidyesstringNumeric OID of the varbind
typeyesstringMIB syntax (e.g., INTEGER, OctetString, Counter32, IpAddress, DisplayString, ZeroBasedCounter32) or a TEXTUAL-CONVENTION name from the source MIB
enumnomapNumeric-string → label for INTEGER enumerations
constraintsnostringSize/range constraint expression (e.g., SIZE(0..16), (1..65535))

Future field: a display_hint member (render hint such as 1x: for MAC or 1d.1d.1d.1d for IPv4) will be added when the plugin's renderer needs it. The current extractor keeps DISPLAY-HINT data in intermediate JSONL when gomib exposes it, but the stock profile emitter does not write display_hint; the field is reserved for the renderer work that will consume it.

Plugin behaviour: varbinds that the plugin sees on a trap but cannot resolve via the file table fall back to raw OID-keyed rendering — there is no runtime MIB compilation in the plugin. Operators convert custom MIBs to profile YAMLs offline with the installed helper /usr/libexec/netdata/plugins.d/snmp-trap-profile-gen and drop the generated YAML files under /etc/netdata/go.d/snmp.trap-profiles/. The varbind still lands in the TRAP_JSON journal field with its OID, ASN.1-decoded type, and value. If it is not a sensitive or protocol-control varbind, the plugin also emits an indexed TRAP_VAR_OID_<NUMERIC_OID> journal field.

Duplicate symbolic name handling in TRAP_JSON and TRAP_VAR_*

TRAP_JSON is an object keyed by varbind symbolic name. In the rare case where two varbinds in a single trap share the same symbolic name (e.g. two vendor MIBs each import a varbind that resolved to the same display name), later entries use deterministic suffixes (#2, #3, ...) to avoid duplicate JSON object keys. Example:

{
"ifDescr": {"oid": "1.3.6.1.2.1.31.1.1.1.1", "type": "OctetString", "value": "Gi0/1"},
"ifDescr#2": {"oid": "1.3.6.1.4.1.99.1.1", "type": "OctetString", "value": "Gi0/2"}
}

The first occurrence wins the symbolic name; subsequent duplicates use suffixed keys. Profile authors should avoid this by giving the conflicting varbinds distinct symbolic names in the file-scoped varbinds: table; the fallback exists only for cases where no such authoring control is possible at extraction time or where malformed senders repeat OIDs.

Indexed journal fields use a separate collision-safe namespace. A received symbolic varbind ifOperStatus emits TRAP_VAR_IFOPERSTATUS; if its value has an enum label, the label is written to TRAP_VAR_IFOPERSTATUS and the numeric value is written to TRAP_VAR_IFOPERSTATUS_RAW. Duplicate/sanitized field-name collisions receive deterministic suffixes (_2, _3, ...). Protocol-control varbinds that duplicate first-class fields (sysUpTime.0, snmpTrapOID.0, snmpTrapAddress.0, snmpTrapEnterprise.0) are kept in TRAP_JSON but not emitted as TRAP_VAR_*; the sensitive SNMP community varbind is omitted. Generated field names are capped to journald's 64-byte field-name limit. Long symbolic/OID-derived names keep a readable prefix plus a stable hash suffix; the complete varbind identity stays in TRAP_JSON.

Generator rule: a varbind record produced by the MIB extractor that does NOT have both a resolvable oid AND a type (MIB syntax) is dropped at emit time — it never enters the varbinds: table, and any reference to it from a trap entry's varbinds: list is removed in lockstep. This keeps the shipped pack free of dangling references; description templates can only reference varbinds that survive to disk.

Optional metrics: rules and charts:

Profiles may define trap-to-metric rules next to trap decode information. These rules are inert until a listener job enables them with profile_metrics.

Metric rule names and chart IDs are file-local definitions. Rule names must remain unique across the effective profile catalogue, and a rule may reference only a chart defined in the same file.

Custom profile files under /etc/netdata/go.d/snmp.trap-profiles/ are loaded eagerly. Stock trap profiles remain lazy: the stock catalogue routes a received trap OID or selected metric rule to its owning profile file. When a custom metric rule references a stock trap, initialization parses only the stock profile candidates for that numeric OID or MIB-qualified trap name so the reference can be validated. Unrelated stock profile files remain unparsed.

Enable profile metrics in the listener job, not in the profile file:

profile_metrics:
enabled: true
include:
- cisco.config.changed
  • profile_metrics.enabled defaults to false; profile rules are inactive until the job enables them.
  • profile_metrics.include lists every rule enabled for the job. At least one rule is required when profile metrics are enabled. Missing or profile-disabled rule names fail job creation.
  • A profile may set one of its own rules to enabled: false; explicitly selecting that rule then fails job creation.

Identity behavior:

  • Device-attributable metrics are emitted under vnode host scope when trap enrichment supplies an unambiguous SourceVnodeID.
  • When no vnode is known, the metric is emitted under the listener job with bounded source_id and source_kind labels. The source ID is a stable local hash derived from the selected source value, job name, and SDK local Agent identity; it is not a portable join key across Agents, listeners, or reinstalls.
  • Ambiguous or conflicting vnode enrichment falls back to source-label metrics and increments attribution diagnostics instead of creating or migrating vnode metrics.
  • Vnode-scoped profile metric series also include source_id and source_kind; generated chart instances use the same label identity in both vnode and fallback modes.
  • source_kind is a closed label set: vnode, listener, trusted_trap_address, udp_peer, entry_source, hostname_or_ip, trap_varbind, topology_ifindex, source, or other.

Cardinality behavior:

  • Fixed safety limits cap each job at 500 selected rules, 2,000 source identities, 512 resources per source and resource class by default, and 50,000 profile-derived chart instances.
  • identity.resource.max_per_source can lower the fixed default for one rule's resource class.
  • identity.resource.key_from_varbind must reference an integer-like bounded varbind (INTEGER, Integer32, Unsigned32, or Gauge32). String, MAC, username, address, and payload-like fields are rejected as resource keys to protect metric cardinality. Counter32, Counter64, and TimeTicks are valid sample values, not resource identity keys.
  • identity.resource.class must be one of the stock classes interface, peer, neighbor, sensor, alarm, pool, l2_topology, component, or a site-specific lowercase class beginning with site_, such as site_foo_sensor.
  • Over-cap metric instances are skipped and diagnostics increment; accepted traps are still committed.
  • charts.lifecycle.expire_after_cycles counts go.d collection cycles, so changing the listener job update_every changes its wall-clock expiry time. State rule state.ttl uses a positive Go duration; expiration emits a zero once, then removes the series after that successful collection.
  • Expired series are removed. If the same source/resource appears again later, the next committed trap creates a fresh series; counter charts may show a reset after idle expiry.
  • All rules that share one chart must have the same label shape. Do not mix resource and non-resource rules in one chart, and do not mix multiple identity.resource.class values in one chart.

Supported rule types:

  • counter: increments a counter when on_trap is committed.
  • sample: reads a numeric varbind from value_from_varbind on on_trap.
  • state: sets or clears a gauge using either problem_trap/clear_trap or on_trap plus state.set_when and state.clear_when. For same-OID state rules, state.set_when takes precedence when both state.set_when and state.clear_when match the same trap.

Canonical rule fields:

FieldRequiredNotes
nameyesStable rule identity, unique across the effective profile catalogue
enablednoDefaults to enabled; set false to keep this file's rule unavailable for job selection
typeyescounter, sample, or state
on_trapyes for counter/sampleTrap name or numeric OID
problem_trap / clear_trapyes for separate-trap stateTrap names or numeric OIDs
wherenoPredicate list for filtering
identity.resourcenoBounded resource identity, such as interface index
value_from_varbindyes for sampleSymbolic varbind name; must be numeric
scale.multiplier / scale.divisornoApplied to sample values before emission
missingnodrop, zero, unknown_dimension, or error
output.metricnoDefaults to a deterministic snmp_trap_* name
output.dimensionnoDefaults to events, value, or state by rule type
output.chartnoChart ID; defaults from rule name
state.set_when / state.clear_whenyes for same-OID statePredicate objects used with on_trap; set_when takes precedence if both match
state.problem_value / state.clear_valuenoState values; defaults are 1 and 0
state.ttlnoPositive duration after which state is cleared once and then expired during Collect()

Predicate behavior:

  • Multiple where predicates are ANDed.
  • Each predicate must select exactly one string-valued source: varbind or field. Both, neither, and non-string selectors are invalid; express additional constraints as separate where predicates.
  • Predicates may reference one of the synthetic fields category, severity, trap_name, or trap_oid.
  • Varbind predicates must reference varbinds declared on every trap the rule can process; typos fail profile validation instead of silently missing.
  • Each predicate must include at least one condition operator: equals, in, exists, absent, greater_than, less_than, or range.
  • equals and in match enum labels when the referenced varbind has an enum.
  • Numeric predicates support greater_than, less_than, and inclusive two-value range; bounds must be finite numbers and range must have exactly two values ordered with lower \<= upper.
  • Non-finite runtime varbind values such as NaN or Inf do not match numeric predicates and increment the rule-miss diagnostic.
  • exists and absent match varbind presence.
  • not: true negates a predicate only when the referenced varbind is present; use absent: true to match missing varbinds. not cannot be combined with exists or absent.

Missing-value behavior:

  • drop skips the metric update and increments the rule-miss diagnostic.
  • zero is supported only for sample rules when absence explicitly means zero. It is not valid for counter or state; state rules must use explicit set/clear predicates or trap pairs.
  • unknown_dimension is supported only with identity.resource; it emits the bounded resource ID unknown.
  • error skips the metric update and increments the extraction-failure diagnostic.

Numeric sample behavior:

  • Accepted numeric source types are INTEGER, Integer32, Unsigned32, Gauge32, Counter32, Counter64, and TimeTicks.
  • Counter32, Counter64, and TimeTicks are accepted only for sample rules; they are not valid identity.resource.key_from_varbind types.
  • TimeTicks values are converted from hundredths of seconds to seconds before scale is applied.
  • scale.multiplier and scale.divisor are applied before the metric is emitted. Use chart units and metric names that describe the scaled value.
  • Counter32 and Counter64 trap varbinds are treated as absolute trap-reported snapshots. They are not converted into rates from sparse trap arrivals.

Diagnostics are emitted as continuous receiver metrics on the snmp.trap.profile_metric_diagnostics chart with these dimensions:

  • rule_missed
  • extraction_failed
  • attribution_failed
  • overflow_dropped
  • source_transitions

Diagnostics are listener-scoped receiver metrics. They are not emitted under per-device vnode scopes. attribution_failed counts events for which no usable source identity is available. overflow_dropped is reserved for source, resource, chart, or job cardinality caps. source_transitions counts when the same raw source maps to a different metric identity, for example from fallback source labels to vnode scope after enrichment starts resolving the device.

Runtime ordering:

  • Profile metrics update only after the trap is successfully committed to the authoritative output backend. When journal and OTLP are both enabled, journal commitment is authoritative and OTLP failures are otlp_export_failed export errors. When OTLP is the only output backend, OTLP export failures are terminal write failures and also increment the job-level pipeline.write_failed counter.
  • Dedup-suppressed traps and write-failed traps do not update profile metrics.
  • Trap-derived samples and states are last-reported values. They are not continuously polled measurements.

Chart metadata defaults and validation:

  • charts[].type defaults to line.
  • Supported chart types are line, area, stacked, and heatmap.
  • charts[].algorithm defaults to incremental.
  • sample and state rule charts must use algorithm: absolute.
  • counter rule charts must use algorithm: incremental.

Example counter rule and chart:

metrics:
- name: cisco.config.changed
type: counter
on_trap: CISCO-CONFIG-MAN-MIB::ccmCLIRunningConfigChanged
output:
metric: snmp_trap_cisco_config_events
dimension: changes
chart: cisco_config_changes

charts:
- id: cisco_config_changes
title: Cisco configuration changes
context: snmp.trap.cisco.config.changes
units: events/s
algorithm: incremental

Example per-resource counter rule:

metrics:
- name: cisco.port_security.ifindex
type: counter
on_trap: CISCO-PORT-SECURITY-MIB::cpsSecureMacAddrViolation
identity:
resource:
class: interface
key_from_varbind: ifIndex
max_per_source: 48
output:
metric: snmp_trap_cisco_port_security_violations
dimension: violations
chart: port_security_violations

charts:
- id: port_security_violations
title: Port security violations
context: snmp.trap.cisco.port.security.violations
units: events/s
algorithm: incremental

Profile metric validation rejects:

  • unknown metrics: or charts: fields;
  • duplicate rule names or chart IDs after merge;
  • unknown traps or varbind references;
  • sample rules that read non-numeric varbinds;
  • negative scale.multiplier and non-positive scale.divisor;
  • invalid predicate combinations such as not with exists or absent;
  • metric names that collide with built-in SNMP trap metric prefixes;
  • chart IDs or contexts that collide with built-in SNMP trap charts.
  • chart algorithms other than incremental or absolute.

Reserved metric name prefixes:

  • snmp_trap_events_
  • snmp_trap_severity_
  • snmp_trap_errors_
  • snmp_trap_dedup_
  • snmp_trap_pipeline_
  • snmp_trap_metric_
  • snmp_trap_profile_metrics_

Built-in receiver health is job-scoped. Profile authors should not recreate receiver pipeline health with profile rules; use profile metrics for vendor or site semantics, such as trap-derived state, counters, and bounded resource samples.

Trap entries (traps: list)

Each list entry defines one trap notification.

FieldRequiredTypeNotes
oidyesstringNumeric OID of the trap. Use the canonical OID form produced by the source MIB/tooling; the receiver tolerates the SMIv1 / SMIv2 .0. trap-OID ambiguity described below.
nameyesstringMIB-qualified canonical form <MIB-MODULE>::<symbol> (e.g. IF-MIB::linkDown, CISCO-CONFIG-MAN-MIB::ccmCLIRunningConfigChanged). Globally unique — different OIDs MUST have different names. Mirrors the canonical SMI form produced by snmptranslate/snmptrapd/MIB browsers. The plugin writes this exact string to the TRAP_NAME journal field.
categoryyesstringOne of the 8 canonical categories — see below
severityyesstringOne of the 8 syslog severities — see below
descriptionrec.stringTemplate rendered into the journal MESSAGE field
statusnostringMIB status: current, deprecated, mandatory, obsolete, or optional; unknown values are profile validation errors
varbindsnolistNames referencing the file-level table, or inline dicts (see below)
labelsnomapOperator-overridable: key → template producing a TRAP_TAG_<KEY> journal field. Dynamic references must be bounded-cardinality.
dedup_key_varbindsnolistNames of varbinds that participate in the deduplication fingerprint; every name must resolve to the file-scoped varbinds: table

Note: the name: field encodes the source MIB module already. There is no separate mib: field on a trap entry — that would be redundant.

Trap OID .0. tolerance

SMIv1 TRAP-TYPE notifications use the RFC 3584 enterprise.0.specific notification OID form. SMIv2 NOTIFICATION-TYPE notifications often use parent.specific without the inserted .0. segment. Some MIB conversion tools can emit either form for the same trap family.

The plugin matches trap profile entries exact-first. If exact lookup misses, it tries one alternate trap OID by adding or removing a single .0. immediately before the final OID arc. For example, a received 1.3.6.1.4.1.14179.2.6.3.0.24 can match a profile oid: of 1.3.6.1.4.1.14179.2.6.3.24, and the reverse is also true. These spellings are one logical trap identity: defining both forms anywhere in the effective profile set is a profile validation error.

This tolerance applies only to trap OID lookup.

Varbind OID resolution is exact-match-first. If a profile varbind OID does not exactly match a received PDU varbind OID, it also matches received varbind OIDs under profile_oid + ".". This covers SMI table cells such as ifIndex.1 for a profile column OID ifIndex, and scalar .0 instances for profiles that use the base scalar OID. When mapping a received PDU varbind OID back to profile metadata, exact match wins; otherwise the longest matching profile varbind OID prefix wins. The trap-OID .0. alternate spelling rule is not applied to varbind OIDs.

When deduplication includes configured dedup_key_varbinds and one of those varbinds is absent from a received PDU, the fingerprint uses a missing-value sentinel distinct from the empty string. Profile authors should list only varbinds normally present on every PDU for that trap OID.

Varbind references

varbinds: on a trap entry is a list. Each element is either:

  • a string — the varbind name, resolved via the file-level varbinds: table (the normal case for stock profiles), or

  • a dict — a full inline varbind definition for varbinds that are not in the table (operator additions, vendor-specific one-offs):

    varbinds:
    - ifIndex # reference into the file table
    - oid: 1.3.6.1.4.1.99.0.42 # inline definition
    name: customIndex
    type: Counter32

The order of varbinds: on a trap entry is not significant — the plugin matches arriving varbinds by OID at runtime.

Description template

description: is a restricted Go text/template string substituted at render time. Supported functions:

ReferenceResolved to
{{hostname}}Resolved device hostname from enrichment, or source IP fallback; the writer does not perform DNS lookup
{{source_ip}}Authoritative trap source address after trusted-relay handling
{{trap_name}}The trap's symbolic name
{{vendor}}Inferred device vendor slug
{{trap_interface}}Topology-resolved interface, when topology is co-located
{{trap_neighbors}}Topology-resolved upstream neighbours, when topology is co-located
{{value "varbindName"}}Varbind value, formatted per its enum (and future display_hint)
{{raw "varbindName"}}Varbind raw value (numeric for enums, undecoded bytes for OctetString)
{{first ...}}First non-empty argument, for optional-varbind fallback

Supported control flow is limited to {{with ...}}{{else}}{{end}}, using the same restricted function calls allowed for plain actions. Known-but-absent varbinds render as an empty string, not <missing>, so use with or first when optional context is included:

description: '{{with first (value "ifDescr") (value "ifName") (value "ifIndex")}}Interface {{.}} went down{{else}}Interface went down{{end}} on {{hostname}}.'

Unknown functions, unknown varbind names, malformed templates, variables, assignments, if, range, arbitrary pipelines, and template inclusion actions fail profile loading. Eager operator-profile errors surface at job creation; lazy stock errors surface when the first trap or enabled metric rule routes to that stock file.

If description: is absent the plugin renders the default template "{{trap_name}} on {{hostname}}.".

The rendered MESSAGE is capped at 512 bytes, including the ASCII ... truncation marker when truncation is needed. Multi-line MESSAGE values are written using systemd-journal's binary field encoding so embedded newlines never inject other journal fields.

The status field is informational. The plugin does not filter, drop, or warn on deprecated / mandatory / obsolete / optional traps. Known status values are still validated so typos fail at profile load instead of silently entering the shipped pack.

Categories — closed set

category MUST be one of these 8 canonical slugs. Stock profiles use this set; operator overrides cannot extend it. Cross-cutting concerns (compliance scope, tenant, datacenter, change window…) are expressed as labels, not as new categories.

SlugMeaning
state_changeInterface/port state, system lifecycle, routing protocol state, environmental state transitions (linkDown, coldStart, BGP transitions, fan/PSU/temp)
config_changeConfiguration change audit (ccmCLIRunningConfigChanged and analogues — who / what / when / from-where)
securitySecurity violations with per-event detail (port-security MAC violations, DHCP-snooping drops, DAI drops, ACL hits, IPS hits)
authAuthentication events with source identity (authenticationFailure with source IP, user attempt)
licenseLicense / compliance events (expired, violated, feature unlocked)
mobilityMAC mobility / topology events with the actor (macAddressMoved, STP newRoot)
diagnosticVendor diagnostic events with device-determined context (reboot reasons, module insertion, RAID array, optical transceiver)
unknownNo profile coverage — default for OIDs not in the catalogue, or vendor user-defined trap slots whose semantics are operator-determined

Severities — closed set, mapped to syslog PRIORITY

severity MUST be one of these 8 syslog levels. The plugin maps each to a numeric PRIORITY field on the journal entry:

SlugPRIORITYUse it for
emerg0System is unusable — exceptional vendor catastrophe
alert1Action must be taken immediately
crit2Critical conditions: hardware failure, security breach in progress
err3Error conditions: failure, fault, denial
warning4Warning conditions: threshold breach, degradation, recoverable error
notice5Normal-but-significant: routine state changes, completed operations
info6Informational: status updates, periodic events
debug7Debug-level: rare; reserved for traps the MIB explicitly marks debug

Cardinality discipline

The trap subsystem enforces cardinality discipline at the metric / label surface only — the journal (including the rendered MESSAGE) has no cardinality restriction. See the trap subsystem design doc §4 for full rationale.

In profile terms:

  • description: and varbinds: references — no restriction. Free use of any varbind including high-cardinality data (MAC, IP, username, packet content). These land in MESSAGE, indexed TRAP_VAR_* journal fields, and TRAP_JSON unless the varbind is sensitive or a protocol-control duplicate.
  • labels:bounded-cardinality only. Templates that reference unbounded varbinds (MAC, IP, username) are rejected at profile load with a clear error. Labels become TRAP_TAG_<KEY>=<VALUE> journal fields and propagate to metric labels.

Operator overrides

Operators place profile files under /etc/netdata/go.d/snmp.trap-profiles/ and choose one of these composition modes:

  1. Same filename in higher-priority directory replaces the lower-priority one entirely. Operator ciscosystems.yaml fully replaces stock ciscosystems.yaml or installed ciscosystems.yaml.zst — copy + edit the whole file to customize one vendor.
  2. Different filename adds entries. Operator site-additions.yaml (different filename) merges its traps: into the loaded set without touching stock files. Metric-only site profiles can also use a different filename and reference stock traps by MIB-qualified trap name in on_trap, problem_trap, or clear_trap.

Partial profile inheritance is not supported. The extends: key is rejected. Use listener-job overrides for category/severity/label policy changes on known OIDs, a complete same-name replacement for decode-definition changes, or a different filename for independent traps and metric rules.

TRAP_TAG_* label namespace and collision-free design

Operator labels — labels: { tenant: acme, oper_status: '{{value "ifOperStatus"}}' } — always emit as TRAP_TAG_<KEY_UPPERCASE> journal fields (e.g. TRAP_TAG_TENANT=acme, TRAP_TAG_OPER_STATUS=down). The dedicated TRAP_TAG_* namespace structurally prevents collisions with plugin-controlled TRAP_* fields, even when an operator label key happens to match a plugin field name. For example, a profile with labels: { interface_state: '{{value "ifOperStatus"}}' } and a co-located topology plugin both populate journal fields, but in different namespaces: the operator label becomes TRAP_TAG_INTERFACE_STATE, the topology field becomes TRAP_INTERFACE. Both can co-exist on the same trap entry without conflict.

Dynamic label references must be bounded-cardinality at profile-load time. Labels accept static strings, {{trap_name}}, {{vendor}}, enum-backed varbinds, booleans, and small numeric ranges. They reject unbounded values such as hostnames, source IPs, interface descriptions, MAC addresses, usernames, packet contents, and raw numeric OID references without profile metadata.

# /etc/netdata/go.d/snmp.trap-profiles/site-additions.yaml
traps:
- oid: 1.3.6.1.4.1.9.9.43.2.0.1 # Cisco config change
labels:
compliance: pci
tenant: acme
change_window: business_hours

Per-OID metric extraction lives in profile metrics: and charts: sections. Listener jobs only enable and select those rules through profile_metrics. Source identity, fallback privacy, and job-level cardinality limits use fixed safe policies; canonical rule-local identity.device and identity.resource fields control rule attribution and resources.

Generated stock profiles

Stock profiles under default/ are generated by src/go/cmd/snmptrapprofilegen, shipped as /usr/libexec/netdata/plugins.d/snmp-trap-profile-gen, from public MIB sources and an LLM classification step. The helper can also convert operator MIBs offline:

/usr/libexec/netdata/plugins.d/snmp-trap-profile-gen generate \
--source-dir ./mibs \
--all \
--out-dir ./snmp-trap-profile-gen-output

By default the helper reads the bundled IANA PEN snapshot from /usr/lib/netdata/conf.d/go.d/snmp.profiles/metadata/iana-enterprise-numbers.txt. Pass --refresh-pen to fetch the current IANA registry before emission. The run also writes review artifacts under --out-dir: traps.jsonl, extraction-report.json, conflicts.json for duplicate trap OIDs, and source-conflicts.json when multiple MIB files define the same module name.

The generated catalogue.json is a required runtime manifest. Each entry records the raw source filename plus its exact mibs, trap_oids, and, when present, metric_rule_names. It also records a required sha256: exactly 64 lowercase hexadecimal characters computed over the exact decompressed YAML bytes, including comments and the final newline. Lazy hydration reads and decompresses the profile once, verifies that digest, and parses the same byte slice. This binds lazy content to one immutable catalog epoch; it is not a signature or package-authenticity mechanism. If a package update changes a profile while an old epoch is live, that epoch rejects the changed body. A new epoch accepts it only with the matching new manifest.

Packaged installations may compress the manifest as catalogue.json.zst; gzip manifests are unsupported, and shipping both raw and Zstandard forms is an error.

Edits to files under default/ are overwritten on regeneration; operator modifications belong in the user override directory, not in stock files.


Do you have any feedback for this page? If so, you can open a new issue on our netdata/learn repository.