forked from Devatoria/go-graylog
/
graylog.go
137 lines (112 loc) · 3.28 KB
/
graylog.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
package graylog
import (
"crypto/tls"
"encoding/json"
"fmt"
"math"
"net"
"time"
"github.com/Jeffail/gabs"
)
// Transport represents a transport type enum
type Transport string
const (
UDP Transport = "udp"
TCP Transport = "tcp"
)
// Endpoint represents a graylog endpoint
type Endpoint struct {
Transport Transport
Address string
Port uint
}
// Graylog represents an established graylog connection
type Graylog struct {
Client *net.Conn
TLSClient *tls.Conn
}
// Message represents a GELF formated message
type Message struct {
Version string `json:"version"`
Host string `json:"host"`
ShortMessage string `json:"short_message"`
FullMessage string `json:"full_message,omitempty"`
Timestamp time.Time `json:"-"`
Level uint `json:"level,omitempty"`
Extra map[string]interface{} `json:"-"`
}
// NewGraylog instanciates a new graylog connection using the given endpoint
func NewGraylog(e Endpoint) (*Graylog, error) {
c, err := net.Dial(string(e.Transport), fmt.Sprintf("%s:%d", e.Address, e.Port))
if err != nil {
return nil, err
}
return &Graylog{Client: &c}, nil
}
// NewGraylogTLS instanciates a new graylog connection with TLS, using the given endpoint
func NewGraylogTLS(e Endpoint, timeout time.Duration, config *tls.Config) (*Graylog, error) {
c, err := tls.DialWithDialer(&net.Dialer{Timeout: timeout}, string(e.Transport), fmt.Sprintf("%s:%d", e.Address, e.Port), config)
if err != nil {
return nil, err
}
return &Graylog{TLSClient: c}, nil
}
// Send writes the given message to the given graylog client
func (g *Graylog) Send(m Message) error {
data, err := m.prepare()
if err != nil {
return err
}
// Check if TLS client is instanciated, otherwise send using the classic client
if g.TLSClient != nil {
_, err = (*g.TLSClient).Write(data)
} else {
_, err = (*g.Client).Write(data)
}
return err
}
// Close closes the opened connections of the given client
func (g *Graylog) Close() error {
if g.TLSClient != nil {
if err := (*g.TLSClient).Close(); err != nil {
return err
}
}
if g.Client != nil {
if err := (*g.Client).Close(); err != nil {
return err
}
}
return nil
}
// prepare marshal the given message, add extra fields and append EOL symbols
func (m Message) prepare() ([]byte, error) {
// Marshal the GELF message in order to get base JSON
jsonMessage, err := json.Marshal(m)
if err != nil {
return []byte{}, err
}
// Parse JSON in order to dynamically edit it
c, err := gabs.ParseJSON(jsonMessage)
if err != nil {
return []byte{}, err
}
// Loop on extra fields and inject them into JSON
for key, value := range m.Extra {
_, err = c.Set(value, fmt.Sprintf("_%s", key))
if err != nil {
return []byte{}, err
}
}
// add milliseconds per GELF Payload Specification
ts := float64(m.Timestamp.Unix())
// truncate rather than round milliseconds to allow matches
// when searching external logs that store more precision
ms := float64(m.Timestamp.Nanosecond()) / 1000000
ts += math.Floor(ms) / 1000
c.Set(ts, "timestamp")
// Append the \n\0 sequence to the given message in order to indicate
// to graylog the end of the message
data := append(c.Bytes(), '\n', byte(0))
return data, nil
}