forked from influxdata/telegraf
-
Notifications
You must be signed in to change notification settings - Fork 0
/
socket_writer.go
186 lines (160 loc) · 4.26 KB
/
socket_writer.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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//go:generate ../../../tools/readme_config_includer/generator
package socket_writer
import (
"crypto/tls"
_ "embed"
"errors"
"fmt"
"math"
"net"
"strconv"
"strings"
"time"
"github.com/mdlayher/vsock"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/internal"
common_tls "github.com/influxdata/telegraf/plugins/common/tls"
"github.com/influxdata/telegraf/plugins/outputs"
"github.com/influxdata/telegraf/plugins/serializers"
)
//go:embed sample.conf
var sampleConfig string
type SocketWriter struct {
ContentEncoding string `toml:"content_encoding"`
Address string
KeepAlivePeriod *config.Duration
common_tls.ClientConfig
Log telegraf.Logger `toml:"-"`
serializers.Serializer
encoder internal.ContentEncoder
net.Conn
}
func (*SocketWriter) SampleConfig() string {
return sampleConfig
}
func (sw *SocketWriter) SetSerializer(s serializers.Serializer) {
sw.Serializer = s
}
func (sw *SocketWriter) Connect() error {
spl := strings.SplitN(sw.Address, "://", 2)
if len(spl) != 2 {
return fmt.Errorf("invalid address: %s", sw.Address)
}
tlsCfg, err := sw.ClientConfig.TLSConfig()
if err != nil {
return err
}
var c net.Conn
if spl[0] == "vsock" {
addrTuple := strings.SplitN(spl[1], ":", 2)
// Check address string for containing two
if len(addrTuple) < 2 {
return errors.New("port and/or CID number missing")
}
// Parse CID and port number from address string both being 32-bit
// source: https://man7.org/linux/man-pages/man7/vsock.7.html
cid, err := strconv.ParseUint(addrTuple[0], 10, 32)
if err != nil {
return fmt.Errorf("failed to parse CID %s: %w", addrTuple[0], err)
}
if (cid >= uint64(math.Pow(2, 32))-1) && (cid <= 0) {
return fmt.Errorf("value of CID %d is out of range", cid)
}
port, err := strconv.ParseUint(addrTuple[1], 10, 32)
if err != nil {
return fmt.Errorf("failed to parse port number %s: %w", addrTuple[1], err)
}
if (port >= uint64(math.Pow(2, 32))-1) && (port <= 0) {
return fmt.Errorf("port number %d is out of range", port)
}
c, err = vsock.Dial(uint32(cid), uint32(port), nil)
if err != nil {
return err
}
} else {
if tlsCfg == nil {
c, err = net.Dial(spl[0], spl[1])
} else {
c, err = tls.Dial(spl[0], spl[1], tlsCfg)
}
if err != nil {
return err
}
}
if err := sw.setKeepAlive(c); err != nil {
sw.Log.Debugf("Unable to configure keep alive (%s): %s", sw.Address, err)
}
// set encoder
sw.encoder, err = internal.NewContentEncoder(sw.ContentEncoding)
if err != nil {
return err
}
sw.Conn = c
return nil
}
func (sw *SocketWriter) setKeepAlive(c net.Conn) error {
if sw.KeepAlivePeriod == nil {
return nil
}
tcpc, ok := c.(*net.TCPConn)
if !ok {
return fmt.Errorf("cannot set keep alive on a %s socket", strings.SplitN(sw.Address, "://", 2)[0])
}
if *sw.KeepAlivePeriod == 0 {
return tcpc.SetKeepAlive(false)
}
if err := tcpc.SetKeepAlive(true); err != nil {
return err
}
return tcpc.SetKeepAlivePeriod(time.Duration(*sw.KeepAlivePeriod))
}
// Write writes the given metrics to the destination.
// If an error is encountered, it is up to the caller to retry the same write again later.
// Not parallel safe.
func (sw *SocketWriter) Write(metrics []telegraf.Metric) error {
if sw.Conn == nil {
// previous write failed with permanent error and socket was closed.
if err := sw.Connect(); err != nil {
return err
}
}
for _, m := range metrics {
bs, err := sw.Serialize(m)
if err != nil {
sw.Log.Debugf("Could not serialize metric: %v", err)
continue
}
bs, err = sw.encoder.Encode(bs)
if err != nil {
sw.Log.Debugf("Could not encode metric: %v", err)
continue
}
if _, err := sw.Conn.Write(bs); err != nil {
// TODO log & keep going with remaining strings
var netErr net.Error
if errors.As(err, &netErr) {
// permanent error. close the connection
sw.Close()
sw.Conn = nil
return fmt.Errorf("closing connection: %w", netErr)
}
return err
}
}
return nil
}
// Close closes the connection. Noop if already closed.
func (sw *SocketWriter) Close() error {
if sw.Conn == nil {
return nil
}
err := sw.Conn.Close()
sw.Conn = nil
return err
}
func init() {
outputs.Add("socket_writer", func() telegraf.Output {
return &SocketWriter{}
})
}