Merge branch 'master' of ../lokiunifi into merge-them-all

This commit is contained in:
Cody Lee 2022-11-23 21:12:08 -06:00
commit 24595013fa
No known key found for this signature in database
13 changed files with 645 additions and 0 deletions

View File

@ -0,0 +1,9 @@
language: go
go:
- 1.16.x
before_install:
# download super-linter: golangci-lint
- curl -sL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin latest
script:
- golangci-lint run --enable-all -D exhaustivestruct,nlreturn
- go test ./...

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2021 David Newhall II
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,26 @@
# lokiunifi
Loki Output Plugin for UnPoller
This plugin writes UniFi Events and IDS data to Loki. Maybe Alarms too.
Example Config:
```toml
[loki]
# URL is the only required setting for Loki.
url = "http://192.168.3.2:3100"
# How often to poll UniFi and report to Loki.
interval = "2m"
# How long to wait for Loki responses.
timeout = "5s"
# Set these to use basic auth.
#user = ""
#pass = ""
# Used for auth-less multi-tenant.
#tenant_id = ""
```

View File

@ -0,0 +1,101 @@
package lokiunifi
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
)
const (
lokiPushPath = "/loki/api/v1/push"
)
var errStatusCode = fmt.Errorf("unexpected HTTP status code")
// Client holds the http client for contacting Loki.
type Client struct {
*Config
*http.Client
}
func (l *Loki) httpClient() *Client {
return &Client{
Config: l.Config,
Client: &http.Client{
Timeout: l.Timeout.Duration,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: !l.VerifySSL, // nolint: gosec
},
},
},
}
}
// Post marshals and posts a batch of log messages.
func (c *Client) Post(logs interface{}) error {
msg, err := json.Marshal(logs)
if err != nil {
return fmt.Errorf("json marshal: %w", err)
}
u := strings.TrimSuffix(c.URL, lokiPushPath) + lokiPushPath
req, err := c.NewRequest(u, "POST", "application/json", msg)
if err != nil {
return err
}
if code, body, err := c.Do(req); err != nil {
return err
} else if code != http.StatusNoContent {
m := fmt.Sprintf("%s (%d/%s) %s, msg: %s", u, code, http.StatusText(code),
strings.TrimSpace(strings.ReplaceAll(string(body), "\n", " ")), msg)
return fmt.Errorf("%s: %w", m, errStatusCode)
}
return nil
}
// NewRequest creates the http request based on input data.
func (c *Client) NewRequest(url, method, cType string, msg []byte) (*http.Request, error) {
req, err := http.NewRequest(method, url, bytes.NewBuffer(msg)) //nolint:noctx
if err != nil {
return nil, fmt.Errorf("creating request: %w", err)
}
if cType != "" {
req.Header.Set("Content-Type", cType)
}
if c.Username != "" || c.Password != "" {
req.SetBasicAuth(c.Username, c.Password)
}
if c.TenantID != "" {
req.Header.Set("X-Scope-OrgID", c.TenantID)
}
return req, nil
}
// Do makes an http request and returns the status code, body and/or an error.
func (c *Client) Do(req *http.Request) (int, []byte, error) {
resp, err := c.Client.Do(req)
if err != nil {
return 0, nil, fmt.Errorf("making request: %w", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return resp.StatusCode, body, fmt.Errorf("reading body: %w", err)
}
return resp.StatusCode, body, nil
}

View File

@ -0,0 +1,11 @@
module github.com/unpoller/lokiunifi
go 1.16
require (
github.com/unpoller/poller v0.0.0-20210623101401-f12841d79a28
github.com/unpoller/unifi v0.0.9-0.20210623100314-3dccfdbc4c80
github.com/unpoller/webserver v0.0.0-20210623101543-90d89bb0acdf
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golift.io/cnfg v0.0.7
)

View File

@ -0,0 +1,58 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/pflag v1.0.6-0.20201009195203-85dd5c8bc61c h1:zqmyTlQyufRC65JnImJ6H1Sf7BDj8bG31EV919NVEQc=
github.com/spf13/pflag v1.0.6-0.20201009195203-85dd5c8bc61c/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/unpoller/poller v0.0.0-20210623101401-f12841d79a28 h1:YAv5naMdpOFahnxteFFRidZlrSEwLv8V2nBKJKmLmHg=
github.com/unpoller/poller v0.0.0-20210623101401-f12841d79a28/go.mod h1:AbDp60t5WlLSRELAliMJ0RFQpm/0yXpyolVSZqNtero=
github.com/unpoller/unifi v0.0.9-0.20210623100314-3dccfdbc4c80 h1:XjHGfJhMwnB63DYHgtWGJgDxLhxVcAOtf+cfuvpGoyo=
github.com/unpoller/unifi v0.0.9-0.20210623100314-3dccfdbc4c80/go.mod h1:K9QFFGfZws4gzB+Popix19S/rBKqrtqI+tyPORyg3F0=
github.com/unpoller/webserver v0.0.0-20210623101543-90d89bb0acdf h1:HhXi3qca3kyFEFPh0mqdr0bpQs94hJvMbUJztwPtf2A=
github.com/unpoller/webserver v0.0.0-20210623101543-90d89bb0acdf/go.mod h1:77PywuUvspdtoRuH1htFhR3Tp0pLyWj6kJlYR4tBYho=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210406210042-72f3dc4e9b72/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golift.io/cnfg v0.0.7 h1:qkNpP5Bq+5Gtoc6HcI8kapMD5zFOVan6qguxqBQF3OY=
golift.io/cnfg v0.0.7/go.mod h1:AsB0DJe7nv0bizKaoy3e3MjjOF7upTpMOMvsfv4CNNk=
golift.io/version v0.0.2 h1:i0gXRuSDHKs4O0sVDUg4+vNIuOxYoXhaxspftu2FRTE=
golift.io/version v0.0.2/go.mod h1:76aHNz8/Pm7CbuxIsDi97jABL5Zui3f2uZxDm4vB6hU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -0,0 +1,38 @@
package lokiunifi
import (
"fmt"
"time"
"github.com/unpoller/webserver"
)
// Logf logs a message.
func (l *Loki) Logf(msg string, v ...interface{}) {
webserver.NewOutputEvent(PluginName, PluginName, &webserver.Event{
Ts: time.Now(),
Msg: fmt.Sprintf(msg, v...),
Tags: map[string]string{"type": "info"},
})
l.Collect.Logf(msg, v...)
}
// LogErrorf logs an error message.
func (l *Loki) LogErrorf(msg string, v ...interface{}) {
webserver.NewOutputEvent(PluginName, PluginName, &webserver.Event{
Ts: time.Now(),
Msg: fmt.Sprintf(msg, v...),
Tags: map[string]string{"type": "error"},
})
l.Collect.LogErrorf(msg, v...)
}
// LogDebugf logs a debug message.
func (l *Loki) LogDebugf(msg string, v ...interface{}) {
webserver.NewOutputEvent(PluginName, PluginName, &webserver.Event{
Ts: time.Now(),
Msg: fmt.Sprintf(msg, v...),
Tags: map[string]string{"type": "debug"},
})
l.Collect.LogDebugf(msg, v...)
}

View File

@ -0,0 +1,143 @@
package lokiunifi
import (
"fmt"
"io/ioutil"
"strconv"
"strings"
"time"
"github.com/unpoller/poller"
"github.com/unpoller/webserver"
"golift.io/cnfg"
)
const (
maxInterval = 10 * time.Minute
minInterval = 10 * time.Second
defaultTimeout = 10 * time.Second
defaultInterval = 2 * time.Minute
)
const (
// InputName is the name of plugin that gives us data.
InputName = "unifi"
// PluginName is the name of this plugin.
PluginName = "loki"
)
// Config is the plugin's input data.
type Config struct {
Disable bool `json:"disable" toml:"disable" xml:"disable" yaml:"disable"`
VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"`
URL string `json:"url" toml:"url" xml:"url" yaml:"url"`
Username string `json:"user" toml:"user" xml:"user" yaml:"user"`
Password string `json:"pass" toml:"pass" xml:"pass" yaml:"pass"`
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"`
}
// Loki is the main library struct. This satisfies the poller.Output interface.
type Loki struct {
Collect poller.Collect
*Config `json:"loki" toml:"loki" xml:"loki" yaml:"loki"`
client *Client
last time.Time
}
// init is how this modular code is initialized by the main app.
// This module adds itself as an output module to the poller core.
func init() { // nolint: gochecknoinits
l := &Loki{Config: &Config{
Interval: cnfg.Duration{Duration: defaultInterval},
Timeout: cnfg.Duration{Duration: defaultTimeout},
}}
poller.NewOutput(&poller.Output{
Name: PluginName,
Config: l,
Method: l.Run,
})
}
// Run is fired from the poller library after the Config is unmarshalled.
func (l *Loki) Run(collect poller.Collect) error {
if l.Collect = collect; l.Config == nil || l.URL == "" || l.Disable {
l.Logf("Loki config missing (or disabled), Loki output disabled!")
return nil
}
l.ValidateConfig()
fake := *l.Config
fake.Password = strconv.FormatBool(fake.Password != "")
webserver.UpdateOutput(&webserver.Output{Name: PluginName, Config: fake})
l.PollController()
l.LogErrorf("Loki Output Plugin Stopped!")
return nil
}
// ValidateConfig sets initial "last" update time. Also creates an http client,
// makes sure URL is sane, and sets interval within min/max limits.
func (l *Loki) ValidateConfig() {
if l.Interval.Duration > maxInterval {
l.Interval.Duration = maxInterval
} else if l.Interval.Duration < minInterval {
l.Interval.Duration = minInterval
}
if strings.HasPrefix(l.Password, "file://") {
pass, err := ioutil.ReadFile(strings.TrimPrefix(l.Password, "file://"))
if err != nil {
l.LogErrorf("Reading Loki Password File: %v", err)
}
l.Password = strings.TrimSpace(string(pass))
}
l.last = time.Now().Add(-l.Interval.Duration)
l.client = l.httpClient()
l.URL = strings.TrimRight(l.URL, "/") // gets a path appended to it later.
}
// PollController runs forever, polling UniFi for events and pushing them to Loki.
// This is started by Run().
func (l *Loki) PollController() {
interval := l.Interval.Round(time.Second)
l.Logf("Loki Event collection started, interval: %v, URL: %s", interval, l.URL)
ticker := time.NewTicker(interval)
for start := range ticker.C {
events, err := l.Collect.Events(&poller.Filter{Name: InputName})
if err != nil {
l.LogErrorf("event fetch for Loki failed: %v", err)
continue
}
err = l.ProcessEvents(l.NewReport(start), events)
if err != nil {
l.LogErrorf("%v", err)
}
}
}
// ProcessEvents offloads some of the loop from PollController.
func (l *Loki) ProcessEvents(report *Report, events *poller.Events) error {
// Sometimes it gets stuck on old messages. This gets it past that.
if time.Since(l.last) > 4*l.Interval.Duration {
l.last = time.Now().Add(-4 * l.Interval.Duration)
}
logs := report.ProcessEventLogs(events)
if err := l.client.Post(logs); err != nil {
return fmt.Errorf("sending to Loki failed: %w", err)
}
l.last = report.Start
l.Logf("Events sent to Loki. %v", report)
return nil
}

View File

@ -0,0 +1,82 @@
package lokiunifi
import (
"fmt"
"strings"
"time"
"github.com/unpoller/poller"
"github.com/unpoller/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. This is the Loki-output format.
type Logs struct {
Streams []LogStream `json:"streams"` // "multiple files"
}
// Report is the temporary data generated by processing events.
type Report struct {
Start time.Time
Oldest time.Time
poller.Logger
Counts map[string]int
}
// NewReport makes a new report.
func (l *Loki) NewReport(start time.Time) *Report {
return &Report{
Start: start,
Oldest: l.last,
Logger: l,
Counts: make(map[string]int),
}
}
// ProcessEventLogs loops the event Logs, matches the interface type, calls the
// appropriate method for the data, and compiles the Logs into a Loki format.
// This runs once per interval, if there was no collection error.
func (r *Report) ProcessEventLogs(events *poller.Events) *Logs {
logs := &Logs{}
for _, e := range events.Logs {
switch event := e.(type) {
case *unifi.IDS:
r.IDS(event, logs)
case *unifi.Event:
r.Event(event, logs)
case *unifi.Alarm:
r.Alarm(event, logs)
case *unifi.Anomaly:
r.Anomaly(event, logs)
default: // unlikely.
r.LogErrorf("unknown event type: %T", e)
}
}
return logs
}
func (r *Report) String() string {
return fmt.Sprintf("%s: %d, %s: %d, %s: %d, %s: %d, Dur: %v",
typeEvent, r.Counts[typeEvent], typeIDS, r.Counts[typeIDS],
typeAlarm, r.Counts[typeAlarm], typeAnomaly, r.Counts[typeAnomaly],
time.Since(r.Start).Round(time.Millisecond))
}
// 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
}

View File

@ -0,0 +1,37 @@
package lokiunifi
import (
"strconv"
"github.com/unpoller/unifi"
)
const typeAlarm = "Alarm"
// Alarm stores a structured Alarm for batch sending to Loki.
func (r *Report) Alarm(event *unifi.Alarm, logs *Logs) {
if event.Datetime.Before(r.Oldest) {
return
}
r.Counts[typeAlarm]++ // increase counter and append new log line.
logs.Streams = append(logs.Streams, LogStream{
Entries: [][]string{{strconv.FormatInt(event.Datetime.UnixNano(), 10), event.Msg}},
Labels: CleanLabels(map[string]string{
"application": "unifi_alarm",
"source": event.SourceName,
"site_name": event.SiteName,
"subsystem": event.Subsystem,
"category": event.Catname,
"event_type": event.EventType,
"key": event.Key,
"app_protocol": event.AppProto,
"protocol": event.Proto,
"interface": event.InIface,
"src_country": event.SrcIPCountry,
"usgip": event.USGIP,
"action": event.InnerAlertAction,
}),
})
}

View File

@ -0,0 +1,28 @@
package lokiunifi
import (
"strconv"
"github.com/unpoller/unifi"
)
const typeAnomaly = "Anomaly"
// Anomaly stores a structured Anomaly for batch sending to Loki.
func (r *Report) Anomaly(event *unifi.Anomaly, logs *Logs) {
if event.Datetime.Before(r.Oldest) {
return
}
r.Counts[typeAnomaly]++ // increase counter and append new log line.
logs.Streams = append(logs.Streams, LogStream{
Entries: [][]string{{strconv.FormatInt(event.Datetime.UnixNano(), 10), event.Anomaly}},
Labels: CleanLabels(map[string]string{
"application": "unifi_anomaly",
"source": event.SourceName,
"site_name": event.SiteName,
"device_mac": event.DeviceMAC,
}),
})
}

View File

@ -0,0 +1,54 @@
package lokiunifi
import (
"strconv"
"github.com/unpoller/unifi"
)
const typeEvent = "Event"
// Event stores a structured UniFi Event for batch sending to Loki.
func (r *Report) Event(event *unifi.Event, logs *Logs) {
if event.Datetime.Before(r.Oldest) {
return
}
r.Counts[typeEvent]++ // increase counter and append new log line.
logs.Streams = append(logs.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,
}),
})
}

View File

@ -0,0 +1,37 @@
package lokiunifi
import (
"strconv"
"github.com/unpoller/unifi"
)
const typeIDS = "IDS"
// event stores a structured event Event for batch sending to Loki.
func (r *Report) IDS(event *unifi.IDS, logs *Logs) {
if event.Datetime.Before(r.Oldest) {
return
}
r.Counts[typeIDS]++ // increase counter and append new log line.
logs.Streams = append(logs.Streams, LogStream{
Entries: [][]string{{strconv.FormatInt(event.Datetime.UnixNano(), 10), event.Msg}},
Labels: CleanLabels(map[string]string{
"application": "unifi_ids",
"source": event.SourceName,
"site_name": event.SiteName,
"subsystem": event.Subsystem,
"category": event.Catname,
"event_type": event.EventType,
"key": event.Key,
"app_protocol": event.AppProto,
"protocol": event.Proto,
"interface": event.InIface,
"src_country": event.SrcIPCountry,
"usgip": event.USGIP,
"action": event.InnerAlertAction,
}),
})
}