Files
Cody Lee 521c2f88bc feat(otelunifi): add OpenTelemetry output plugin (#978)
* feat(otelunifi): add OpenTelemetry output plugin

Adds a new push-based output plugin that exports UniFi metrics to any
OTLP-compatible backend (Grafana Alloy/Mimir, Honeycomb, Datadog via
OTel, New Relic, etc.) using the Go OpenTelemetry SDK v1.42.

Config (default disabled):
  [otel]
  url      = "http://localhost:4318"
  protocol = "http"   # or "grpc"
  interval = "30s"
  timeout  = "10s"
  disable  = false
  api_key  = ""       # optional Bearer auth

Env var prefix: UP_OTEL_*

Exported metrics:
- Sites:   user/guest/IoT counts, AP/GW/SW counts, latency, uptime,
           tx/rx rates per subsystem
- Clients: uptime, rx/tx bytes & rates; signal/noise/RSSI for wireless
- UAP:     up, uptime, CPU/mem, load, per-radio channel/power,
           per-VAP station count/satisfaction/bytes
- USW:     up, uptime, CPU/mem, load, aggregate rx/tx bytes,
           per-port up/speed/bytes/packets/errors/dropped/PoE
- USG:     up, uptime, CPU/mem, load, per-WAN rx/tx bytes/packets/errors
- UDM/UXG: up, uptime, CPU/mem, load averages

Closes #933

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(otelunifi): rename unused ctx parameter to _ in recordGauge

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(otelunifi): replace Disable with Enable (default false)

Plugin is opt-in: set enable=true / UP_OTEL_ENABLE=true to activate.
Closes part of #933.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 18:19:18 -05:00

88 lines
2.9 KiB
Go

package otelunifi
import (
"context"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"github.com/unpoller/unifi/v5"
)
// exportUSG emits metrics for a UniFi Security Gateway.
func (u *OtelOutput) exportUSG(ctx context.Context, meter metric.Meter, r *Report, s *unifi.USG) {
if !s.Adopted.Val || s.Locating.Val {
return
}
attrs := attribute.NewSet(
attribute.String("mac", s.Mac),
attribute.String("site_name", s.SiteName),
attribute.String("source", s.SourceName),
attribute.String("name", s.Name),
attribute.String("model", s.Model),
attribute.String("version", s.Version),
attribute.String("type", s.Type),
attribute.String("ip", s.IP),
)
up := 0.0
if s.State.Val == 1 {
up = 1.0
}
u.recordGauge(ctx, meter, r, "unifi_device_usg_up",
"Whether USG is up (1) or down (0)", up, attrs)
u.recordGauge(ctx, meter, r, "unifi_device_usg_uptime_seconds",
"USG uptime in seconds", s.Uptime.Val, attrs)
u.recordGauge(ctx, meter, r, "unifi_device_usg_cpu_utilization",
"USG CPU utilization percentage", s.SystemStats.CPU.Val, attrs)
u.recordGauge(ctx, meter, r, "unifi_device_usg_mem_utilization",
"USG memory utilization percentage", s.SystemStats.Mem.Val, attrs)
u.recordGauge(ctx, meter, r, "unifi_device_usg_load_avg_1",
"USG load average 1-minute", s.SysStats.Loadavg1.Val, attrs)
// Export WAN1 metrics
u.exportUSGWan(ctx, meter, r, s, s.Wan1, "wan1")
// Export WAN2 metrics if present
u.exportUSGWan(ctx, meter, r, s, s.Wan2, "wan2")
}
// exportUSGWan emits metrics for a single WAN interface on a USG.
func (u *OtelOutput) exportUSGWan(
ctx context.Context,
meter metric.Meter,
r *Report,
s *unifi.USG,
wan unifi.Wan,
ifaceName string,
) {
if wan.IP == "" {
return
}
wanAttrs := attribute.NewSet(
attribute.String("mac", s.Mac),
attribute.String("site_name", s.SiteName),
attribute.String("source", s.SourceName),
attribute.String("name", s.Name),
attribute.String("iface", ifaceName),
attribute.String("ip", wan.IP),
)
u.recordGauge(ctx, meter, r, "unifi_device_usg_wan_rx_bytes",
"USG WAN interface receive bytes total", wan.RxBytes.Val, wanAttrs)
u.recordGauge(ctx, meter, r, "unifi_device_usg_wan_tx_bytes",
"USG WAN interface transmit bytes total", wan.TxBytes.Val, wanAttrs)
u.recordGauge(ctx, meter, r, "unifi_device_usg_wan_rx_packets",
"USG WAN interface receive packets total", wan.RxPackets.Val, wanAttrs)
u.recordGauge(ctx, meter, r, "unifi_device_usg_wan_tx_packets",
"USG WAN interface transmit packets total", wan.TxPackets.Val, wanAttrs)
u.recordGauge(ctx, meter, r, "unifi_device_usg_wan_rx_errors",
"USG WAN interface receive errors total", wan.RxErrors.Val, wanAttrs)
u.recordGauge(ctx, meter, r, "unifi_device_usg_wan_tx_errors",
"USG WAN interface transmit errors total", wan.TxErrors.Val, wanAttrs)
u.recordGauge(ctx, meter, r, "unifi_device_usg_wan_speed_mbps",
"USG WAN interface link speed in Mbps", wan.Speed.Val, wanAttrs)
}