-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.go
142 lines (119 loc) · 3.9 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"os/signal"
"regexp"
"syscall"
"net/http"
"github.com/coreos/go-systemd/sdjournal"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// variables for Prometheus metrics
var (
metricSudoCount = promauto.NewCounter(prometheus.CounterOpts{
Name: "sudo_count_total",
Help: "The total number of sudo events",
})
verbose bool
)
// sdjournal.JournalReader.Follow requires a custom type with a Write method (io.Write).
type JournalWriter struct{}
// Write method implementing the io.Write interface with JournalWriter pointer receiver
func (p *JournalWriter) Write(data []byte) (n int, err error) {
// call JournalParser function with address of data to parse journald messages and process metrics
JournalParser(&data)
return len(data), nil
}
// JournalParser parses journal entry address and processes Prometheus metrics (passed from Write method).
func JournalParser(entry *[]byte) {
e := fmt.Sprintf("%s", *entry) // convert pointer value entry to string
if verbose {
fmt.Printf("%s", e)
}
// Check entry using regexp and update Prometheus metrics
r, _ := regexp.Compile("sudo:session")
matched := r.MatchString(e)
if matched {
metricSudoCount.Inc() // increment Prometheus counter
if verbose {
fmt.Println("incremented prometheus counter")
}
}
}
func main() {
var (
listenHTTP string
syslog_id string
ctx, cancel = context.WithCancel(context.Background())
)
defer func() {
// Close database, redis, truncate message queues, etc.
fmt.Println("Running cancel()")
cancel()
}()
// command line (flag) variables and defaults
flag.StringVar(&listenHTTP, "listenHTTP", ":9101", "ip:port to listen for http requests")
flag.BoolVar(&verbose, "verbose", false, "Enable verbose output with filtered log entries")
flag.StringVar(&syslog_id, "syslogIdentifier", "sudo", "syslog identifier used to filter journald (empty string for no filtering")
flag.Parse()
go read_journal(ctx, syslog_id) // go routine to follow/tail new journald logs and process metrics
go prom_http(ctx, listenHTTP) // start Prometheus http endpoint
// Wait for SIGINT.
sig := make(chan os.Signal, 3)
signal.Notify(sig, syscall.SIGHUP)
signal.Notify(sig, syscall.SIGINT)
signal.Notify(sig, syscall.SIGTERM)
<-sig
// Shutdown. Cancel application context will kill all attached tasks.
cancel()
}
// read_journal tails (follows) the journald log using the sdjournal package.
func read_journal(ctx context.Context, syslogIdentifier string) {
defer func() {
fmt.Println("running deferred ctx.Done")
<-ctx.Done()
}()
// links for more information on sdjournal
// https://pkg.go.dev/github.com/coreos/go-systemd/[email protected]/sdjournal#JournalReader
// https://github.com/coreos/go-systemd/blob/v22.3.2/sdjournal/read.go
// journal config
jconf := sdjournal.JournalReaderConfig{
Since: -1,
}
// Add Match rule
if syslogIdentifier != "" {
jconf.Matches = []sdjournal.Match{
{
Field: sdjournal.SD_JOURNAL_FIELD_SYSLOG_IDENTIFIER,
Value: syslogIdentifier, // ${APPNAME}.service
},
}
}
// journal reader
jr, err := sdjournal.NewJournalReader(jconf)
if err != nil {
panic(err)
}
defer jr.Close() // close JournalReader when done
jrw := JournalWriter{} // create variable of type JournalWriter (only implements Write method)
err = jr.Follow(nil, &jrw) // follow journal and pass address of custom writer to parse new entries
if err != nil {
panic(err)
}
}
// prom_http starts the Prometheus HTTP listener on the specified listen string at /metrics.
func prom_http(ctx context.Context, listen string) {
defer func() {
fmt.Println("running deferred ctx.Done")
<-ctx.Done()
}()
fmt.Println("listening on", listen, "/metrics")
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(listen, nil))
}