feat(lokiunifi): add extra_labels config for custom Loki stream labels (#691) (#973)

Add an ExtraLabels map[string]string field to the Loki Config struct so
users can define static key=value labels that are merged into the stream
labels of every log line sent to Loki. This allows users to distinguish
streams (e.g., by environment or datacenter) without hardcoding values.

Built-in dynamic labels (application, site_name, source, etc.) always
take precedence over extra labels to preserve existing behavior.

Example config (TOML):
  [loki.extra_labels]
  environment = "production"
  datacenter  = "us-east-1"

Closes #691

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cody Lee
2026-03-23 15:25:32 -05:00
committed by GitHub
parent 6c5ff5482d
commit a95804743d
7 changed files with 38 additions and 24 deletions

View File

@@ -36,6 +36,7 @@ type Config struct {
TenantID string `json:"tenant_id" toml:"tenant_id" xml:"tenant_id" yaml:"tenant_id"`
Interval cnfg.Duration `json:"interval" toml:"interval" xml:"interval" yaml:"interval"`
Timeout cnfg.Duration `json:"timeout" toml:"timeout" xml:"timeout" yaml:"timeout"`
ExtraLabels map[string]string `json:"extra_labels" toml:"extra_labels" xml:"extra_labels" yaml:"extra_labels"`
}
// Loki is the main library struct. This satisfies the poller.Output interface.

View File

@@ -26,6 +26,7 @@ type Report struct {
Start time.Time
Oldest time.Time
Collect poller.Collect
ExtraLabels map[string]string
poller.Logger
Counts map[string]int
}
@@ -36,6 +37,7 @@ func (l *Loki) NewReport(start time.Time) *Report {
Start: start,
Oldest: l.last,
Collect: l.Collect,
ExtraLabels: l.ExtraLabels,
Logger: l,
Counts: make(map[string]int),
}
@@ -95,3 +97,14 @@ func CleanLabels(labels map[string]string) map[string]string {
return labels
}
// MergeLabels merges extra into labels. Keys already present in labels are not overwritten.
func MergeLabels(labels, extra map[string]string) map[string]string {
for k, v := range extra {
if _, exists := labels[k]; !exists {
labels[k] = v
}
}
return labels
}

View File

@@ -26,10 +26,10 @@ func (r *Report) Alarm(event *unifi.Alarm, logs *Logs) {
logs.Streams = append(logs.Streams, LogStream{
Entries: [][]string{{strconv.FormatInt(event.Datetime.UnixNano(), 10), string(msg)}},
Labels: CleanLabels(map[string]string{
Labels: CleanLabels(MergeLabels(map[string]string{
"application": "unifi_alarm",
"source": event.SourceName,
"site_name": event.SiteName,
}),
}, r.ExtraLabels)),
})
}

View File

@@ -26,10 +26,10 @@ func (r *Report) Anomaly(event *unifi.Anomaly, logs *Logs) {
logs.Streams = append(logs.Streams, LogStream{
Entries: [][]string{{strconv.FormatInt(event.Datetime.UnixNano(), 10), string(msg)}},
Labels: CleanLabels(map[string]string{
Labels: CleanLabels(MergeLabels(map[string]string{
"application": "unifi_anomaly",
"source": event.SourceName,
"site_name": event.SiteName,
}),
}, r.ExtraLabels)),
})
}

View File

@@ -27,11 +27,11 @@ func (r *Report) Event(event *unifi.Event, logs *Logs) {
logs.Streams = append(logs.Streams, LogStream{
Entries: [][]string{{strconv.FormatInt(event.Datetime.UnixNano(), 10), string(msg)}},
Labels: CleanLabels(map[string]string{
Labels: CleanLabels(MergeLabels(map[string]string{
"application": "unifi_event",
"site_name": event.SiteName,
"source": event.SourceName,
}),
}, r.ExtraLabels)),
})
}
@@ -52,12 +52,12 @@ func (r *Report) SystemLogEvent(event *unifi.SystemLogEntry, logs *Logs) {
logs.Streams = append(logs.Streams, LogStream{
Entries: [][]string{{strconv.FormatInt(event.Datetime().UnixNano(), 10), string(msg)}},
Labels: CleanLabels(map[string]string{
Labels: CleanLabels(MergeLabels(map[string]string{
"application": "unifi_system_log",
"site_name": event.SiteName,
"source": event.SourceName,
"category": event.Category,
"severity": event.Severity,
}),
}, r.ExtraLabels)),
})
}

View File

@@ -26,10 +26,10 @@ func (r *Report) IDs(event *unifi.IDS, logs *Logs) {
logs.Streams = append(logs.Streams, LogStream{
Entries: [][]string{{strconv.FormatInt(event.Datetime.UnixNano(), 10), string(msg)}},
Labels: CleanLabels(map[string]string{
Labels: CleanLabels(MergeLabels(map[string]string{
"application": "unifi_ids",
"source": event.SourceName,
"site_name": event.SiteName,
}),
}, r.ExtraLabels)),
})
}

View File

@@ -34,14 +34,14 @@ func (r *Report) ProtectLogEvent(event *unifi.ProtectLogEntry, logs *Logs) {
// Add event log line
logs.Streams = append(logs.Streams, LogStream{
Entries: [][]string{{strconv.FormatInt(event.Datetime().UnixNano(), 10), string(msg)}},
Labels: CleanLabels(map[string]string{
Labels: CleanLabels(MergeLabels(map[string]string{
"application": "unifi_protect_log",
"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
@@ -57,12 +57,12 @@ func (r *Report) ProtectLogEvent(event *unifi.ProtectLogEntry, logs *Logs) {
// 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(map[string]string{
Labels: CleanLabels(MergeLabels(map[string]string{
"application": "unifi_protect_thumbnail",
"source": event.SourceName,
"event_id": event.ID,
"camera": event.Camera,
}),
}, r.ExtraLabels)),
})
}
}