-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathwriter.go
More file actions
162 lines (137 loc) · 3.78 KB
/
writer.go
File metadata and controls
162 lines (137 loc) · 3.78 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
package graylog
import (
"compress/gzip"
"crypto/rand"
"encoding/json"
"errors"
"net"
"os"
"strings"
"golang.org/x/exp/slog"
)
// Logger is an io.Logger for sending log messages to the Graylog server.
type Logger struct {
conn net.Conn
*slog.Logger
host string
facility string
isUDP bool
}
// Dial establishes a connection to the Graylog server and returns Logger to
// send log messages.
//
// Supported networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), "udp",
// "udp4" (IPv4-only), "udp6" (IPv6-only).
// When using UDP as a transport layer, the messages sent are compressed using
// GZIP and automatically chunked.
//
// Attrs specify a list of attributes that will be added to all messages sent
// to Graylog server.
func Dial(network, address string, attrs ...slog.Attr) (*Logger, error) {
host, err := os.Hostname()
if err != nil {
return nil, err
}
conn, err := net.Dial(network, address)
if err != nil {
return nil, err
}
w := Logger{
conn: conn,
isUDP: strings.HasPrefix(network, "udp"),
host: host,
facility: strings.TrimSpace(facility), // copy to handler,
}
var handler slog.Handler = handler{w: w}
if len(attrs) > 0 {
handler = handler.WithAttrs(attrs)
}
w.Logger = slog.New(handler)
return &w, nil
}
// Close closes a connection to the Graylog server.
func (w Logger) Close() error {
return w.conn.Close()
}
// LogValue return log value with connection info (network & address).
func (w Logger) LogValue() slog.Value {
if w.conn == nil {
return slog.Value{}
}
addr := w.conn.RemoteAddr()
return slog.GroupValue(
slog.String("network", addr.Network()),
slog.String("address", addr.String()),
)
}
// ErrMessageToLarge returns when trying to send too long message other UDP.
var ErrMessageToLarge = errors.New("message too large")
var debugOutput = false // output gelf json before send
// write send a GELF message to the server.
func (w Logger) write(b []byte) error {
if len(b) == 0 {
return nil
}
// debug output
if debugOutput {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(json.RawMessage(b)); err != nil {
return err
}
}
// send other TCP
if !w.isUDP {
_, err := w.conn.Write(append(b, '\x00'))
return err
}
// else send other UDP
// Compress message
buf := newBuffer()
defer buf.Free()
zw := gzip.NewWriter(buf)
if _, err := zw.Write(b); err != nil {
zw.Close()
return err
}
if err := zw.Close(); err != nil {
return err
}
// Some Graylog components are limited to processing up to 8192 bytes.
const maxSize = 8192
if len(*buf) <= maxSize {
_, err := w.conn.Write(*buf)
return err
}
// A message MUST NOT consist of more than 128 chunks.
count := uint8((len(*buf)-1)/(maxSize-12) + 1)
if count > 128 {
return ErrMessageToLarge
}
// Prepend the following structure to your GELF message to make it chunked:
// - Chunked GELF magic bytes - 2 bytes: 0x1e 0x0f
// - Message ID - 8 bytes: Must be the same for every chunk of this
// message. Identifies the whole message and is used to reassemble the
// chunks later. Generate from millisecond timestamp + hostname, for
// example.
// - Sequence number - 1 byte: The sequence number of this chunk starts
// at 0 and is always less than the sequence count.
// - Sequence count - 1 byte: Total number of chunks this message has.
header := [maxSize]byte{0x1e, 0x0f, 11: count}
if _, err := rand.Read(header[2:10]); err != nil {
return err
}
// All chunks MUST arrive within 5 seconds or the server will discard all
// chunks that have arrived or are in the process of arriving.
for i := uint8(0); i < count; i++ {
header[10] = i
n := copy(header[12:], *buf)
if n == 0 {
break
}
if _, err := w.conn.Write(header[:n+12]); err != nil {
return err
}
}
return nil
}