135 lines
3.7 KiB
Go
135 lines
3.7 KiB
Go
package lokiunifi
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/unifi-poller/poller"
|
|
"github.com/unifi-poller/unifi"
|
|
)
|
|
|
|
// LogStream contains a stream of logs (like a log file).
|
|
// This app uses one stream per log entry because each log may have different labels.
|
|
type LogStream struct {
|
|
Labels map[string]string `json:"stream"` // "the file name"
|
|
Entries [][]string `json:"values"` // "the log lines"
|
|
}
|
|
|
|
// Logs is the main logs-holding structure.
|
|
type Logs struct {
|
|
Streams []LogStream `json:"streams"` // "multiple files"
|
|
}
|
|
|
|
// Report is the temporary data generated and sent to Loki at every interval.
|
|
type Report struct {
|
|
Eve int // Total count of Events.
|
|
IDS int // Total count of IDS/IPS Events.
|
|
Start time.Time
|
|
Last time.Time
|
|
Loki *Client
|
|
Events *poller.Events
|
|
Logs
|
|
poller.Logger
|
|
}
|
|
|
|
// SendEvents loops the event Logs, matches the interface
|
|
// type, calls the appropriate method for the data, and completes the report.
|
|
// This runs once per interval, if there was no collection error.
|
|
func (r *Report) SendEvents() error {
|
|
for _, e := range r.Events.Logs {
|
|
switch event := e.(type) {
|
|
case *unifi.IDS:
|
|
r.SaveIDS(event)
|
|
case *unifi.Event:
|
|
r.SaveEvent(event)
|
|
default: // unlikely.
|
|
r.LogErrorf("unknown event type: %T", e)
|
|
}
|
|
}
|
|
|
|
return r.Loki.Send(r.Logs)
|
|
}
|
|
|
|
// SaveIDS stores a structured IDS Event for batch sending to Loki.
|
|
func (r *Report) SaveIDS(ids *unifi.IDS) {
|
|
if ids.Datetime.Before(r.Last) {
|
|
return
|
|
}
|
|
|
|
r.IDS++ // increase counter and append new log line.
|
|
r.Streams = append(r.Streams, LogStream{
|
|
Entries: [][]string{{strconv.FormatInt(ids.Datetime.UnixNano(), 10), ids.Msg}},
|
|
Labels: cleanLabels(map[string]string{
|
|
"application": "unifi_ids",
|
|
"source": ids.SourceName,
|
|
"site_name": ids.SiteName,
|
|
"subsystem": ids.Subsystem,
|
|
"category": ids.Catname,
|
|
"event_type": ids.EventType,
|
|
"key": ids.Key,
|
|
"app_protocol": ids.AppProto,
|
|
"protocol": ids.Proto,
|
|
"interface": ids.InIface,
|
|
"src_country": ids.SrcIPCountry,
|
|
"usgip": ids.USGIP,
|
|
"action": ids.InnerAlertAction,
|
|
}),
|
|
})
|
|
}
|
|
|
|
// SaveEvent stores a structured UniFi Event for batch sending to Loki.
|
|
func (r *Report) SaveEvent(event *unifi.Event) {
|
|
if event.Datetime.Before(r.Last) {
|
|
return
|
|
}
|
|
|
|
r.Eve++ // increase counter and append new log line.
|
|
r.Streams = append(r.Streams, LogStream{
|
|
Entries: [][]string{{strconv.FormatInt(event.Datetime.UnixNano(), 10), event.Msg}},
|
|
Labels: cleanLabels(map[string]string{
|
|
"application": "unifi_event",
|
|
"admin": event.Admin, // username
|
|
"site_name": event.SiteName,
|
|
"source": event.SourceName,
|
|
"subsystem": event.Subsystem,
|
|
"ap_from": event.ApFrom,
|
|
"ap_to": event.ApTo,
|
|
"ap": event.Ap,
|
|
"ap_name": event.ApName,
|
|
"gw": event.Gw,
|
|
"gw_name": event.GwName,
|
|
"sw": event.Sw,
|
|
"sw_name": event.SwName,
|
|
"category": event.Catname,
|
|
"radio": event.Radio,
|
|
"radio_from": event.RadioFrom,
|
|
"radio_to": event.RadioTo,
|
|
"key": event.Key,
|
|
"interface": event.InIface,
|
|
"event_type": event.EventType,
|
|
"ssid": event.SSID,
|
|
"channel": event.Channel.Txt,
|
|
"channel_from": event.ChannelFrom.Txt,
|
|
"channel_to": event.ChannelTo.Txt,
|
|
"usgip": event.USGIP,
|
|
"network": event.Network,
|
|
"app_protocol": event.AppProto,
|
|
"protocol": event.Proto,
|
|
"action": event.InnerAlertAction,
|
|
"src_country": event.SrcIPCountry,
|
|
}),
|
|
})
|
|
}
|
|
|
|
// cleanLabels removes any tag that is empty.
|
|
func cleanLabels(labels map[string]string) map[string]string {
|
|
for i := range labels {
|
|
if strings.TrimSpace(labels[i]) == "" {
|
|
delete(labels, i)
|
|
}
|
|
}
|
|
|
|
return labels
|
|
}
|