forked from digitalocean/captainslog
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsyslogmsg.go
More file actions
273 lines (238 loc) · 6.93 KB
/
syslogmsg.go
File metadata and controls
273 lines (238 loc) · 6.93 KB
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
package captainslog
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"sync"
"time"
)
// SyslogMsg holds an Unmarshaled rfc3164 message.
type SyslogMsg struct {
Pri Priority
Time time.Time
Host string
Tag Tag
Cee string
IsJSON bool
IsCee bool
optionDontParseJSON bool
optionUseLocalFormat bool
Content string
timeFormat string
JSONValues map[string]interface{}
mutex *sync.Mutex
}
// Content holds the Content of a syslog message,
// including the Content as a string, and a struct of
// the JSONValues of appropriate.
type Content struct {
Content string
JSONValues map[string]interface{}
}
// Time holds both the time derviced from a
// syslog message along with the time format string
// used to parse it.
type Time struct {
Time time.Time
TimeFormat string
}
// Tag holds the data derviced from a
// syslog message's tag, including the full
// tag, the program name and the pid.
type Tag struct {
Program string
Pid string
HasColon bool
StartsWithBracket bool
}
// NewTag constructs a new empty captainslog.Tag
func NewTag() *Tag {
return &Tag{
HasColon: true,
}
}
// String converts the specified Tag into a string.
func (t Tag) String() string {
colon := ""
if t.HasColon {
colon = ":"
}
p := t.Program
if t.StartsWithBracket {
p = fmt.Sprintf("[%s]", t.Program)
}
if len(t.Pid) == 0 {
return p + colon
}
return fmt.Sprintf("%s[%s]%s", p, t.Pid, colon)
}
// NewSyslogMsg creates a new empty SyslogMsg.
func NewSyslogMsg(options ...SyslogMsgOption) SyslogMsg {
s := SyslogMsg{
JSONValues: make(map[string]interface{}),
mutex: &sync.Mutex{},
}
for _, option := range options {
option(&s)
}
return s
}
// NewSyslogMsgFromBytes accepts a []byte containing an RFC3164
// message and returns a SyslogMsg. If the original RFC3164
// message is a CEE enhanced message, the JSON will be
// parsed into the JSONValues map[string]inferface{}
func NewSyslogMsgFromBytes(b []byte, options ...func(*Parser)) (SyslogMsg, error) {
p := NewParser(options...)
msg, err := p.ParseBytes(b)
return msg, err
}
// SetFacility accepts a captainslog.Facility to
// set the facility of the SyslogMsg
func (s *SyslogMsg) SetFacility(f Facility) error {
return s.Pri.SetFacility(f)
}
// SetSeverity accepts a captainslog.Severity to set the
// severity of the SyslogMsg
func (s *SyslogMsg) SetSeverity(sv Severity) error {
return s.Pri.SetSeverity(sv)
}
// SetTime accepts a time.Time to set the time
// of the SyslogMsg
func (s *SyslogMsg) SetTime(t time.Time) {
s.Time = t
}
// SetProgram accepts a string to set the programname
// of the SyslogMsg
func (s *SyslogMsg) SetProgram(p string) {
s.Tag.Program = p
}
// SetPid accepts a string to set the pid of
// the SyslogMsg
func (s *SyslogMsg) SetPid(p string) {
s.Tag.Pid = p
s.Tag.HasColon = true
}
// SetHost accepts a string to set the host
// of the SyslogMsg
func (s *SyslogMsg) SetHost(h string) {
s.Host = h
}
// SetContent accepts a string to set the content
// of the SyslogMsg. It will check to see if the
// string is JSON and try to parse it if so.
func (s *SyslogMsg) SetContent(c string) error {
_, content, err := ParseContent([]byte(c), ContentOptionParseJSON)
s.Content = content.Content
s.JSONValues = content.JSONValues
if len(s.JSONValues) > 0 {
s.IsJSON = true
}
return err
}
// AddTagArray adds a tag to an array of tags at the key. If the key
// does not already exist, it will create the key and initially it
// to a []interface{}.
func (s *SyslogMsg) AddTagArray(key string, value interface{}) error {
if _, ok := s.JSONValues[key]; !ok {
s.JSONValues[key] = make([]interface{}, 0)
}
switch val := s.JSONValues[key].(type) {
case []interface{}:
s.JSONValues[key] = append(val, value)
if !s.IsCee {
s.IsCee = true
s.Cee = " @cee:"
s.JSONValues["msg"] = s.Content[1:]
}
return nil
default:
return fmt.Errorf("tags key in message was not an array")
}
}
// AddTag adds a tag to the value at key. If the key exists,
// the value currently at the key will be overwritten.
func (s *SyslogMsg) AddTag(key string, value interface{}) {
s.JSONValues[key] = value
}
// SyslogMsgOption can be passed to SyslogMsg.String(), SyslogMsg.Byte(), or
// NewSyslogMsg to control formatting
type SyslogMsgOption func(*SyslogMsg)
// OptionUseLocalFormat tells SyslogMsg.String() and SyslogMsg.Byte() to format the
// message to be compatible with writing to /dev/log rather than over the wire.
func OptionUseLocalFormat(s *SyslogMsg) {
s.optionUseLocalFormat = true
}
// OptionUseRemoteFormat tells SyslogMsg.String() and SyslogMsg.Byte() to use wire
// format for the message instead of local format
func OptionUseRemoteFormat(s *SyslogMsg) {
s.optionUseLocalFormat = false
}
// String returns the SyslogMsg as an RFC3164 string.
func (s *SyslogMsg) String(options ...SyslogMsgOption) string {
for _, option := range options {
option(s)
}
var content string
if s.IsJSON && !s.optionDontParseJSON {
b, err := json.Marshal(s.JSONValues)
if err != nil {
panic(err)
}
// Prepend a leading space before the JSON bytes
b = append([]byte(" "), b...)
content = string(b)
} else {
if len(s.JSONValues) > 0 {
s.JSONValues["msg"] = strings.TrimLeft(s.Content, " ")
s.IsCee = true
s.Cee = " @cee:"
b, err := json.Marshal(s.JSONValues)
if err != nil {
panic(err)
}
content = string(b)
} else {
content = s.Content
}
}
if s.optionUseLocalFormat {
return fmt.Sprintf("<%s>%s %s%s%s\n", s.Pri.String(), s.Time.Format(time.Stamp), s.Tag.String(), s.Cee, content)
}
if s.timeFormat == "" {
s.timeFormat = rsyslogTimeFormat
}
return fmt.Sprintf("<%s>%s %s %s%s%s\n", s.Pri.String(), s.Time.Format(s.timeFormat), s.Host, s.Tag.String(), s.Cee, content)
}
// Bytes returns the SyslogMsg as RFC3164 []byte.
func (s *SyslogMsg) Bytes(options ...SyslogMsgOption) []byte {
return []byte(s.String(options...))
}
// JSON returns a JSON representation of the message encoded in a []byte. Syslog fields are named with
// a "syslog_" prefix to avoid potential collision with fields from the message body.
func (s *SyslogMsg) JSON() ([]byte, error) {
content := make(map[string]interface{})
if s.optionDontParseJSON && s.IsCee {
decoder := json.NewDecoder(bytes.NewBuffer([]byte(s.Content)))
decoder.UseNumber()
err := decoder.Decode(&content)
if err != nil {
return []byte(""), err
}
}
for key, value := range s.JSONValues {
content[key] = value
}
content["syslog_time"] = s.Time
content["syslog_host"] = s.Host
content["syslog_tag"] = s.Tag.String()
content["syslog_programname"] = s.Tag.Program
content["syslog_pid"] = s.Tag.Pid
content["syslog_facilitytext"] = s.Pri.Facility.String()
content["syslog_severitytext"] = s.Pri.Severity.String()
if !s.IsCee {
content["syslog_content"] = s.Content
}
b, err := json.Marshal(content)
return b, err
}