Skip to content

Commit d998966

Browse files
committed
first commit
0 parents  commit d998966

13 files changed

+994
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea

clean.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package logparser
2+
3+
import (
4+
"regexp"
5+
"strings"
6+
)
7+
8+
var (
9+
timestampRegexes = []*regexp.Regexp{
10+
regexp.MustCompile(`(^|\s)\d{2}:\d{2}(:\d{2}[^\s"']*)?`),
11+
regexp.MustCompile(`\d{2} [A-Z][a-z]{2} \d{4}`),
12+
regexp.MustCompile(`\d{4}-\d{2}-\d{2}`),
13+
regexp.MustCompile(`\d{4}/\d{2}/\d{2}`),
14+
regexp.MustCompile(`\d{4}\.\d{2}\.\d{2}`),
15+
regexp.MustCompile(`[A-Z][a-z]{2} \d{2}`),
16+
regexp.MustCompile(`\d{2}-\d{2}-\d{4}`),
17+
regexp.MustCompile(`\d{2}/\d{2}/\d{4}`),
18+
regexp.MustCompile(`\d{2}\.\d{2}\.\d{4}`),
19+
}
20+
extraSpaces = regexp.MustCompile(`\s+`)
21+
)
22+
23+
func clean(line string) string {
24+
for _, r := range timestampRegexes {
25+
line = r.ReplaceAllString(line, "")
26+
}
27+
return strings.TrimSpace(extraSpaces.ReplaceAllString(line, " "))
28+
}
29+
30+
func containsTimestamp(line string) bool {
31+
for _, re := range timestampRegexes {
32+
if re.MatchString(line) {
33+
return true
34+
}
35+
}
36+
return false
37+
}

clean_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package logparser
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"testing"
6+
)
7+
8+
func Test_containsTimestamp(t *testing.T) {
9+
assert.True(t, containsTimestamp("2005-08-09"))
10+
assert.True(t, containsTimestamp("2020/06/26"))
11+
assert.True(t, containsTimestamp("02/17/2009"))
12+
assert.True(t, containsTimestamp("25.02.2013"))
13+
assert.True(t, containsTimestamp("2013.25.02"))
14+
assert.True(t, containsTimestamp("18:31"))
15+
assert.True(t, containsTimestamp("18:31:42"))
16+
assert.True(t, containsTimestamp("18:31:42+03"))
17+
assert.True(t, containsTimestamp("18:31:42-03"))
18+
assert.True(t, containsTimestamp("18:31:42+03:30"))
19+
assert.True(t, containsTimestamp("18:31:42-03:30"))
20+
assert.True(t, containsTimestamp("2005-08-09T18:31:42"))
21+
assert.True(t, containsTimestamp("2005-08-09T18:31:42+03"))
22+
assert.True(t, containsTimestamp("2005-08-09T18:31:42-03"))
23+
assert.True(t, containsTimestamp("2005-08-09T18:31:42+03:30"))
24+
assert.True(t, containsTimestamp("2005-08-09T18:31:42-03:30"))
25+
assert.True(t, containsTimestamp("2005-08-09T18:31:42"))
26+
assert.True(t, containsTimestamp("2005-08-09T18:31:42.201"))
27+
}

decoder.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package logparser
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
)
8+
9+
type DockerLogJson struct {
10+
Log string
11+
}
12+
13+
type Decoder interface {
14+
Decode(string) (string, error)
15+
}
16+
17+
type DockerJsonDecoder struct{}
18+
19+
func (d DockerJsonDecoder) Decode(src string) (string, error) {
20+
obj := DockerLogJson{}
21+
if err := json.Unmarshal([]byte(src), &obj); err != nil {
22+
return "", fmt.Errorf(`failed to unmarshal docker log entry "%s": %s`, src, err)
23+
}
24+
return obj.Log, nil
25+
}
26+
27+
type CriDecoder struct{}
28+
29+
func (d CriDecoder) Decode(src string) (string, error) {
30+
c := 0
31+
i := strings.IndexFunc(src, func(r rune) bool {
32+
if r == ' ' {
33+
c++
34+
}
35+
return c == 3
36+
})
37+
if i < 0 {
38+
return "", fmt.Errorf("unexpected entry format: %s", src)
39+
}
40+
return src[i+1:], nil
41+
}

go.mod

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/coroot/logparser
2+
3+
go 1.16
4+
5+
require github.com/stretchr/testify v1.7.0

go.sum

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
7+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
8+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
9+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
10+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
11+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

level.go

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package logparser
2+
3+
import (
4+
"strings"
5+
"unicode"
6+
)
7+
8+
type Level string
9+
10+
const (
11+
LevelUnknown Level = "unknown"
12+
LevelDebug Level = "debug"
13+
LevelInfo Level = "info"
14+
LevelWarning Level = "warning"
15+
LevelError Level = "error"
16+
LevelCritical Level = "critical"
17+
18+
maxLineLenForGuessingLevel = 255
19+
guessLevelInFields = 5
20+
)
21+
22+
var (
23+
glogLevelsMapping = map[byte]Level{
24+
'I': LevelInfo,
25+
'W': LevelWarning,
26+
'E': LevelError,
27+
'F': LevelCritical,
28+
}
29+
priority2Levels = map[string]Level{
30+
"0": LevelCritical,
31+
"1": LevelCritical,
32+
"2": LevelCritical,
33+
"3": LevelError,
34+
"4": LevelWarning,
35+
"5": LevelInfo,
36+
"6": LevelInfo,
37+
"7": LevelDebug,
38+
}
39+
)
40+
41+
func LevelByPriority(priority string) Level {
42+
if level, ok := priority2Levels[priority]; ok {
43+
return level
44+
}
45+
return LevelUnknown
46+
}
47+
48+
func GuessLevel(line string) Level {
49+
if len(line) > maxLineLenForGuessingLevel {
50+
line = line[:maxLineLenForGuessingLevel]
51+
}
52+
fields := strings.Fields(line)
53+
if len(fields) == 0 {
54+
return LevelUnknown
55+
}
56+
limit := len(fields)
57+
if limit > guessLevelInFields {
58+
limit = guessLevelInFields
59+
}
60+
61+
if l := tryGlog(fields); l != LevelUnknown {
62+
return l
63+
}
64+
65+
for _, f := range fields[:limit] {
66+
subfields := strings.FieldsFunc(f, func(r rune) bool {
67+
return r == ']' || r == ')' || r == ';' || r == '|' || r == ':' || r == ','
68+
})
69+
for _, sf := range subfields {
70+
sf = strings.TrimLeft(strings.ToLower(sf), "\"[('")
71+
sf = strings.TrimPrefix(sf, "level=")
72+
if len(sf) < 4 {
73+
continue
74+
}
75+
switch sf[:4] {
76+
case "debu":
77+
return LevelDebug
78+
case "info":
79+
return LevelInfo
80+
case "warn":
81+
return LevelWarning
82+
case "erro":
83+
return LevelError
84+
case "crit", "fata":
85+
return LevelCritical
86+
}
87+
}
88+
}
89+
if l := guessRedisLevel(fields); l != LevelUnknown {
90+
return l
91+
}
92+
return LevelUnknown
93+
}
94+
95+
func tryGlog(fields []string) Level {
96+
firstField := fields[0]
97+
if len(firstField) != 5 {
98+
return LevelUnknown
99+
}
100+
level, ok := glogLevelsMapping[firstField[0]]
101+
if !ok {
102+
return LevelUnknown
103+
}
104+
for _, r := range firstField[1:] {
105+
if !unicode.IsDigit(r) {
106+
return LevelUnknown
107+
}
108+
}
109+
return level
110+
}
111+
112+
// redis 2.x
113+
// [pid] date loglevel message
114+
// [4018] 14 Nov 07:01:22.119 * Background saving terminated with success
115+
116+
// redis 3.x+
117+
// pid:role timestamp loglevel message
118+
// 1:S 12 Nov 07:52:11.999 * FAIL message received from X about Y
119+
120+
// redis 5.x: the year was added
121+
// 1:S 12 Nov 2019 07:52:11.999 * FAIL message received from X about Y
122+
func guessRedisLevel(fields []string) Level {
123+
if len(fields) < 6 {
124+
return LevelUnknown
125+
}
126+
if strings.HasPrefix(fields[0], "[") && strings.HasSuffix(fields[0], "]") {
127+
return redisCharToLevel(fields[4])
128+
}
129+
if len(strings.Split(fields[0], ":")) == 2 {
130+
if len(fields[3]) == 4 { //redis 5.x+
131+
return redisCharToLevel(fields[5])
132+
} else {
133+
return redisCharToLevel(fields[4])
134+
}
135+
}
136+
return LevelUnknown
137+
}
138+
139+
func redisCharToLevel(level string) Level {
140+
switch level {
141+
case ".":
142+
return LevelDebug
143+
case "-":
144+
return LevelInfo
145+
case "*", "#":
146+
return LevelWarning
147+
}
148+
return LevelUnknown
149+
}
150+
151+
// todo
152+
//
153+
// python
154+
//
155+
// loglevels: DEBUG, INFO, WARNING, ERROR,CRITICAL
156+
// pylogging:
157+
// default "%(levelname)s:%(name)s:%(message)s" https://github.com/python/cpython/blob/master/Lib/logging/__init__.py#L502
158+
// django:
159+
// {levelname} {message}
160+
// {asctime} {module} [{levelname}] okserver
161+
// [%(asctime)s] %(levelname)s
162+
// %(levelname)s %(asctime)s %(module)s: %(message)s
163+
// asctime: %(asctime)s Human-readable time when the LogRecord was created. By default this is of the form ‘2003-07-08 16:49:45,896’ (the numbers after the comma are millisecond portion of the time).
164+
//

level_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package logparser
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"testing"
6+
)
7+
8+
func TestGuessLevelGlog(t *testing.T) {
9+
//glog & klog
10+
assert.Equal(t, LevelUnknown, GuessLevel(`11002 a msg`))
11+
assert.Equal(t, LevelUnknown, GuessLevel(`WHAT1 a msg`))
12+
assert.Equal(t, LevelInfo, GuessLevel(`I0430 11:58:31.792717 1 cluster.go:337] memberlist 2020/04/30 11:58:31 [DEBUG] memberlist: Initiating push/pull sync with: 127.0.0.1:4000`))
13+
assert.Equal(t, LevelWarning, GuessLevel(`W0430 11:29:23.177635 1 nanny.go:120] Got EOF from stdout`))
14+
assert.Equal(t, LevelError, GuessLevel(`E0504 07:38:36.184861 1 replica_set.go:450] Sync "monitoring/prometheus-operator-5cfbdc9b67" failed with pods "prometheus-operator-5cfbdc9b67-" is forbidden: error looking up service account monitoring/prometheus-operator: serviceaccount "prometheus-operator" not found`))
15+
assert.Equal(t, LevelCritical, GuessLevel(`F0825 185142 test.cc:22] Check failed: write(1, NULL, 2) >= 0 Write NULL failed: Bad address [14]`))
16+
}
17+
18+
func TestGuessLevelRedis(t *testing.T) {
19+
assert.Equal(t, LevelWarning, GuessLevel(`[4018] 14 Nov 07:01:22.119 * Background saving terminated with success`))
20+
assert.Equal(t, LevelInfo, GuessLevel(`1:S 12 Nov 07:52:11.999 - some msg`))
21+
assert.Equal(t, LevelDebug, GuessLevel(`1:S 12 Nov 2019 07:52:11.999 . verbosed`))
22+
}
23+
24+
func TestGuessLevel(t *testing.T) {
25+
assert.Equal(t, LevelInfo, GuessLevel(`[info:2016-02-16T16:04:05.930-08:00] Some log text here`))
26+
assert.Equal(t, LevelInfo, GuessLevel(`2016-02-04T06:51:03.053580605Z" level=info msg="GET /containers/json`))
27+
assert.Equal(t, LevelError, GuessLevel(`2016-02-04T07:53:57.505612354Z" level=error msg="HTTP Error" err="No such image: -f" statusCode=404`))
28+
assert.Equal(t, LevelDebug, GuessLevel(`[2020-06-25 17:35:37,609][DEBUG][action.search ] [srv] [tweets-100][6]`))
29+
}

0 commit comments

Comments
 (0)