Files
unpoller-unpoller-3/pkg/lokiunifi/report_protect.go
Cody Lee cedc52fc89 feat(lokiunifi): add richer low-cardinality stream labels (#932) (#975)
- Add job=unpoller to every Loki stream (alarm, anomaly, event, ids,
  system_log, protect_log, protect_thumbnail) for standard Grafana/Loki
  source filtering with {job="unpoller"}
- Add event_type and inner_alert_action labels to IDS streams using
  EventType and InnerAlertAction fields
- Add event_type and inner_alert_action labels to Alarm streams using
  Key and InnerAlertAction fields
- Skip severity/category on Anomaly: the unifi.Anomaly struct has no
  such fields

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

72 lines
2.2 KiB
Go

package lokiunifi
import (
"encoding/json"
"strconv"
"github.com/unpoller/unifi/v5"
)
const typeProtectLog = "ProtectLog"
const typeProtectThumbnail = "ProtectThumbnail"
// ProtectLogEvent stores a structured UniFi Protect Log Entry for batch sending to Loki.
// Logs the raw JSON for parsing with Loki's `| json` pipeline.
// If the event has a thumbnail, it's sent as a separate log line.
func (r *Report) ProtectLogEvent(event *unifi.ProtectLogEntry, logs *Logs) {
if event.Datetime().Before(r.Oldest) {
return
}
r.Counts[typeProtectLog]++ // increase counter and append new log line.
// Store thumbnail separately before marshaling (it's excluded from JSON by default now)
thumbnailBase64 := event.ThumbnailBase64
// Marshal event to JSON for the log line (without thumbnail to keep it small)
event.ThumbnailBase64 = "" // Temporarily clear for marshaling
msg, err := json.Marshal(event)
if err != nil {
msg = []byte(event.Msg())
}
event.ThumbnailBase64 = thumbnailBase64 // Restore
// Add event log line
logs.Streams = append(logs.Streams, LogStream{
Entries: [][]string{{strconv.FormatInt(event.Datetime().UnixNano(), 10), string(msg)}},
Labels: CleanLabels(MergeLabels(map[string]string{
"application": "unifi_protect_log",
"job": "unpoller",
"source": event.SourceName,
"event_type": event.GetEventType(),
"category": event.GetCategory(),
"severity": event.GetSeverity(),
"camera": event.Camera,
}, r.ExtraLabels)),
})
// Add thumbnail as separate log line if present
if thumbnailBase64 != "" {
r.Counts[typeProtectThumbnail]++
thumbnailJSON, _ := json.Marshal(map[string]string{
"event_id": event.ID,
"thumbnail_base64": thumbnailBase64,
"mime_type": "image/jpeg",
})
// Use timestamp + 1 nanosecond to ensure ordering (thumbnail after event)
logs.Streams = append(logs.Streams, LogStream{
Entries: [][]string{{strconv.FormatInt(event.Datetime().UnixNano()+1, 10), string(thumbnailJSON)}},
Labels: CleanLabels(MergeLabels(map[string]string{
"application": "unifi_protect_thumbnail",
"job": "unpoller",
"source": event.SourceName,
"event_id": event.ID,
"camera": event.Camera,
}, r.ExtraLabels)),
})
}
}