Skip to content

Commit d66a4ab

Browse files
authored
multiindex search for elastiwatch (#365)
* multiindex search for elastiwatch * x
1 parent 435f096 commit d66a4ab

4 files changed

Lines changed: 94 additions & 21 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package elastiwatch
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestNewEntryGregor(t *testing.T) {
10+
e, err := newEntry([]byte(`{"message":"boom","severity":"ERROR","program":"dbcleand","time":"2026-10-22T15:00:00Z"}`))
11+
require.NoError(t, err)
12+
require.Equal(t, "boom", e.Message)
13+
require.Equal(t, "ERROR", e.Severity)
14+
require.Equal(t, "dbcleand", e.Program)
15+
}
16+
17+
func TestNewEntryKBFSLogrus(t *testing.T) {
18+
e, err := newEntry([]byte(`{"msg":"block get failed","level":"error","time":"2026-10-22T15:00:00.123456789Z"}`))
19+
require.NoError(t, err)
20+
require.Equal(t, "block get failed", e.Message)
21+
require.Equal(t, "ERROR", e.Severity)
22+
}
23+
24+
func TestNewEntryIced(t *testing.T) {
25+
e, err := newEntry([]byte(`{"message":"node version: v22.0.0","level":"info","program":"logdumpd","pid":13,"@timestamp":"2026-10-22T15:00:00.000Z"}`))
26+
require.NoError(t, err)
27+
require.Equal(t, "node version: v22.0.0", e.Message)
28+
require.Equal(t, "INFO", e.Severity)
29+
require.Equal(t, "logdumpd", e.Program)
30+
require.Equal(t, "2026-10-22T15:00:00.000Z", e.Time)
31+
}
32+
33+
func TestNormalizeSeverityWarnFatal(t *testing.T) {
34+
require.Equal(t, "WARNING", normalizeSeverity("warn"))
35+
require.Equal(t, "CRITICAL", normalizeSeverity("fatal"))
36+
}

‎elastiwatch/elastiwatch/grouper.go‎

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,49 @@ import (
99
)
1010

1111
type entry struct {
12-
Message string `json:"message"`
13-
Severity string `json:"severity"`
14-
UID string `json:"uid"`
15-
Time string `json:"time"`
16-
Program string `json:"program"`
17-
parts []string
12+
Message string `json:"message"`
13+
Msg string `json:"msg"`
14+
Severity string `json:"severity"`
15+
Level string `json:"level"`
16+
UID string `json:"uid"`
17+
Time string `json:"time"`
18+
Timestamp string `json:"@timestamp"`
19+
Program string `json:"program"`
20+
parts []string
21+
}
22+
23+
func normalizeSeverity(s string) string {
24+
switch strings.ToUpper(strings.TrimSpace(s)) {
25+
case "WARN", "WARNING":
26+
return "WARNING"
27+
case "ERR", "ERROR":
28+
return "ERROR"
29+
case "FATAL", "PANIC", "CRIT", "CRITICAL":
30+
return "CRITICAL"
31+
case "INFO", "INFORMATION", "NOTICE":
32+
return "INFO"
33+
case "DEBUG", "TRACE":
34+
return "DEBUG"
35+
default:
36+
return strings.ToUpper(strings.TrimSpace(s))
37+
}
1838
}
1939

2040
func newEntry(dat []byte) (*entry, error) {
2141
entry := new(entry)
2242
if err := json.Unmarshal(dat, entry); err != nil {
2343
return nil, err
2444
}
45+
if entry.Message == "" {
46+
entry.Message = entry.Msg
47+
}
48+
if entry.Severity == "" {
49+
entry.Severity = entry.Level
50+
}
51+
entry.Severity = normalizeSeverity(entry.Severity)
52+
if entry.Time == "" {
53+
entry.Time = entry.Timestamp
54+
}
2555
entry.parts = append(entry.parts, entry.Program)
2656
entry.parts = append(entry.parts, entry.UID)
2757
entry.parts = append(entry.parts, strings.Split(entry.Message, " ")...)

‎elastiwatch/elastiwatch/logs.go‎

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"regexp"
77
"strings"
88
"time"
9+
"unicode"
910

1011
"github.com/keybase/go-keybase-chat-bot/kbchat/types/chat1"
1112
"github.com/keybase/managed-bots/base"
@@ -14,25 +15,28 @@ import (
1415

1516
type LogWatch struct {
1617
*base.DebugOutput
17-
db *DB
18-
cli *opensearchapi.Client
19-
index, email string
20-
entries []*entry
21-
emailer base.Emailer
22-
sendCount int
23-
lastSend time.Time
24-
alertConvID chat1.ConvIDStr
25-
emailConvID chat1.ConvIDStr
26-
shutdownCh chan struct{}
27-
peekCh chan struct{}
18+
db *DB
19+
cli *opensearchapi.Client
20+
indices []string
21+
email string
22+
entries []*entry
23+
emailer base.Emailer
24+
sendCount int
25+
lastSend time.Time
26+
alertConvID chat1.ConvIDStr
27+
emailConvID chat1.ConvIDStr
28+
shutdownCh chan struct{}
29+
peekCh chan struct{}
2830
}
2931

3032
func NewLogWatch(cli *opensearchapi.Client, db *DB, index, email string, emailer base.Emailer, alertConvID, emailConvID chat1.ConvIDStr, debugConfig *base.ChatDebugOutputConfig) *LogWatch {
3133
return &LogWatch{
3234
DebugOutput: base.NewDebugOutput("LogWatch", debugConfig),
3335
cli: cli,
3436
db: db,
35-
index: index,
37+
indices: strings.FieldsFunc(index, func(r rune) bool {
38+
return r == ',' || unicode.IsSpace(r)
39+
}),
3640
email: email,
3741
emailer: emailer,
3842
lastSend: time.Now(),
@@ -168,9 +172,9 @@ func (l *LogWatch) generateAndSend(entries []*entry) {
168172

169173
func (l *LogWatch) runOnce() {
170174
res, err := l.cli.Search(context.Background(), &opensearchapi.SearchReq{
171-
Indices: []string{l.index},
175+
Indices: l.indices,
172176
Params: opensearchapi.SearchParams{
173-
Query: `NOT severity:debug AND @timestamp:[now-1m TO now]`,
177+
Query: `NOT severity:debug AND NOT level:debug AND @timestamp:[now-1m TO now]`,
174178
Sort: []string{"@timestamp:desc"},
175179
Size: new(10000),
176180
Pretty: true,
@@ -190,6 +194,9 @@ func (l *LogWatch) runOnce() {
190194
l.Errorf("failed to unmarshal log entry: %s", err)
191195
continue
192196
}
197+
if entry.Severity == "DEBUG" {
198+
continue
199+
}
193200
entries = append(entries, entry)
194201
}
195202
} else {

‎elastiwatch/main.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ func mainInner() int {
176176
fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
177177
var alertConvID, emailConvID string
178178
fs.StringVar(&opts.ESAddress, "esaddress", os.Getenv("ESADDRESS"), "Elasticsearch address")
179-
fs.StringVar(&opts.Index, "index", os.Getenv("INDEX"), "Elasticsearch index")
179+
fs.StringVar(&opts.Index, "index", os.Getenv("INDEX"), "OpenSearch index pattern(s), comma-separated")
180180
fs.StringVar(&opts.Email, "email", os.Getenv("EMAIL"), "Destination email address")
181181
fs.StringVar(&opts.SenderEmail, "sender-email", os.Getenv("SENDER_EMAIL"), "Sourceemail address")
182182
fs.StringVar(&opts.Team, "team", os.Getenv("TEAM"), "Team")

0 commit comments

Comments
 (0)