-
Notifications
You must be signed in to change notification settings - Fork 2
/
parser.go
163 lines (139 loc) · 3.3 KB
/
parser.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
package snitch
import (
"bytes"
"errors"
"strconv"
"strings"
"github.com/quipo/statsd"
"github.com/xjewer/snitch/lib/config"
"github.com/xjewer/snitch/lib/stats"
)
var (
ErrProcessorIsFinished = errors.New("processor is finished")
ErrEmptyVarName = errors.New("empty var name")
)
// Parser parses log text from reader and sends statistics
type Parser interface {
HandleLine(*Line) error
}
type handler struct {
reader LogReader
statsd statsd.Statsd
metrics []*metric
cfg config.Source
}
// NewParser makes new Parser
func NewParser(r LogReader, s statsd.Statsd, cfg config.Source) (Parser, error) {
m, err := makeMetrics(cfg.Keys, cfg.Prefix)
if err != nil {
return nil, err
}
return &handler{
statsd: s,
reader: r,
metrics: m,
cfg: cfg,
}, nil
}
// handleLine handles log text and sends statistics to statsd
func (h *handler) HandleLine(l *Line) error {
l.Split(h.cfg.Delimiter)
for _, m := range h.metrics {
key, err := makeKeyFromPaths(l, m)
if err != nil {
return err
}
if m.count {
stats.SendEvent(h.statsd, key)
}
if m.timing {
f, err := getElementAmount(l, m.timingData, m.delimiter)
if err != nil && err != ErrEmptyString {
return err
}
// statsd wants milliseconds
stats.SendTiming(h.statsd, key, int64(1000*f))
}
}
return nil
}
// makeKeyFromPaths makes statsd key from keyPath
// key keyPaths sets the order and type of each sequences
func makeKeyFromPaths(l *Line, m *metric) (string, error) {
//todo use sync.Pool
var buffer bytes.Buffer
for i, k := range m.keyPaths {
if i != 0 {
buffer.WriteString(".")
}
if k.isVar {
m, err := getElementString(l, k.match, m.delimiter, true)
if err != nil {
return "", err
}
buffer.WriteString(substituteDots(m))
} else {
buffer.WriteString(k.val)
}
}
return buffer.String(), nil
}
// getElementString returns specific entry from entries, id last value pass,
// it returns the last from chain of ", "
func getElementString(l *Line, i int, sep string, last bool) (string, error) {
c, err := l.GetEntry(i)
if err != nil {
return "", err
}
if last && sep != "" {
return getLastMatch(c, sep), nil
}
return c, nil
}
// getElementAmount returns amount of values from specific entry
func getElementAmount(l *Line, i int, sep string) (float32, error) {
c, err := l.GetEntry(i)
if err != nil {
return 0.0, err
}
return getAmount(c, sep)
}
// getLastMatch returns the last columns after string separator
func getLastMatch(s string, sep string) string {
index := strings.LastIndex(s, sep)
if index >= 0 && index+1 < len(s) {
return s[index+len(sep):]
}
return s
}
// getAmount returns amount of numbers in column, if error happen - returns it
func getAmount(s string, sep string) (float32, error) {
var result float32
if s == "-" {
return result, ErrEmptyString
}
if sep == "" {
f, err := strconv.ParseFloat(s, 32)
if err != nil {
return result, err
}
return float32(f), nil
}
columns := strings.Split(s, sep)
for _, c := range columns {
f, err := strconv.ParseFloat(c, 32)
if err != nil {
return result, err
}
if f == 0 {
// avoid needless addition
continue
}
result += float32(f)
}
return result, nil
}
// substituteDots replaces dots in string
func substituteDots(s string) string {
return strings.Replace(s, ".", "_", -1)
}