-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
206 lines (171 loc) · 4.58 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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package main
import (
"errors"
"fmt"
"os"
"regexp"
"time"
"github.com/BurntSushi/toml"
"github.com/abdfnx/gosh"
"github.com/nxadm/tail"
)
// regex pattern
type Pattern struct {
Name string
Regex string // actual regex
Command string
Alert string
}
type Config struct {
Logging string
Log_file string
Files []string
Regex []Pattern
}
func canAccessFile(filepath string) error {
_, err := os.Stat(filepath)
if err == nil {
return nil
} else if errors.Is(err, os.ErrNotExist) {
return err
}
// other error (e.g. insufficient permissions)
return err
}
func main() {
// Help screen
if len(os.Args) != 2 {
fmt.Printf("\nRun commands based on RegEx matches in continuously read files\n\nUsage: logspark [configuration file]\n\n")
return
}
if err := canAccessFile(os.Args[1]); err != nil {
fmt.Println(err)
os.Exit(1)
}
var config Config
if _, err := toml.DecodeFile(os.Args[1], &config); err != nil {
fmt.Println(err)
os.Exit(1)
}
// check for empty logging configuration
if config.Logging == "" {
fmt.Printf("Warning! No logging method specified, defaulting to verbose.\n\n")
} else if config.Logging != "verbose" && config.Logging != "minimal" && config.Logging != "none" {
fmt.Printf("Warning! Invalid logging method, defaulting to verbose.\n\n")
}
// check for duplicate values
var duplicates []string
for i := 0; i+1 < len(config.Regex); i++ {
// name
if config.Regex[i].Name == config.Regex[i+1].Name {
duplicates = append([]string(duplicates), config.Regex[i].Name)
}
// regex
if config.Regex[i].Regex == config.Regex[i+1].Regex {
duplicates = append([]string(duplicates), config.Regex[i].Regex)
}
// command
if config.Regex[i].Command == config.Regex[i+1].Command {
duplicates = append([]string(duplicates), config.Regex[i].Command)
}
// alert
if config.Regex[i].Alert == config.Regex[i+1].Alert {
duplicates = append([]string(duplicates), config.Regex[i].Alert)
}
}
for _, element := range duplicates {
fmt.Printf("Error! Duplicate value: %s\n", element)
os.Exit(1)
}
// check for empty regex
for i := 0; i < len(config.Regex); i++ {
if len(config.Regex[i].Regex) == 0 {
var input string
for input == "" || input != "y" && input != "n" {
fmt.Printf("Regex patterns empty, are you sure you want to continue? [y/n] ")
fmt.Scanf("%s", &input)
}
if input == "n" {
os.Exit(0)
}
}
}
// tails array (tail for each file)
tails := make([]*tail.Tail, len(config.Files))
// for each file
for i := 0; i < len(config.Files); i++ {
// check if the file exists
if err := canAccessFile(config.Files[i]); err != nil {
fmt.Println(err)
os.Exit(1)
} else {
// if it does, tail the file
t, err := tail.TailFile(config.Files[i], tail.Config{
Follow: true,
ReOpen: true,
Poll: true,
Logger: tail.DiscardingLogger,
})
if err != nil {
fmt.Println(err)
os.Exit(1)
}
tails[i] = t // add the tail to the array
}
if config.Log_file == config.Files[i] {
fmt.Println("Error: cannot tail log file (possible loop)")
os.Exit(1)
}
}
for i := 0; i < len(tails); i++ {
// tail each file concurrently
go func(i int) {
for line := range tails[i].Lines {
// ignore empty
if line.Text == "\n" {
continue
}
// for each [[regex]] in the TOML file
for j := 0; j < len(config.Regex); j++ {
regex, err := regexp.Compile(config.Regex[j].Regex)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// check for the regex
if regex.MatchString(line.Text) {
var output string
// log verbosity
switch config.Logging {
default:
fallthrough
case "verbose":
output = fmt.Sprintf("%s | %s | %s | %s | %s | %s | %s", time.Now().Format(time.RFC1123), tails[i].Filename, config.Regex[j].Name, config.Regex[j].Regex, config.Regex[j].Command, config.Regex[j].Alert, line.Text)
case "minimal":
output = fmt.Sprintf("%s | %s | %s | %s", time.Now().Format(time.DateTime), tails[i].Filename, config.Regex[j].Name, line.Text)
}
// log accordingly
switch config.Log_file {
default:
file, err := os.OpenFile(config.Log_file, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Fprintf(file, "%s\n", output)
case "stdout":
fmt.Println(output)
case "":
case "none":
}
// run the command
gosh.Run(config.Regex[j].Command)
// run the "alert" command
gosh.Run(config.Regex[j].Alert)
}
}
}
}(i)
}
select {}
}