fluent-plugin-sensu 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +13 -0
- data/README.md +344 -0
- data/Rakefile +13 -0
- data/fluent-plugin-sensu.gemspec +28 -0
- data/lib/fluent/plugin/out_sensu.rb +288 -0
- data/test/helper.rb +33 -0
- data/test/plugin/test_out_sensu.rb +671 -0
- metadata +143 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2015- MIYAKAWA Taku
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,344 @@
|
|
1
|
+
# fluent-plugin-sensu
|
2
|
+
|
3
|
+
[Fluentd](http://fluentd.org) output plugin to send check results to
|
4
|
+
[sensu-client](https://sensuapp.org/docs/latest/clients),
|
5
|
+
which is an agent process of Sensu monitoring framework.
|
6
|
+
|
7
|
+
## Configuration
|
8
|
+
|
9
|
+
### Example configuration
|
10
|
+
|
11
|
+
```apache
|
12
|
+
<match ddos>
|
13
|
+
type sensu
|
14
|
+
|
15
|
+
# Connection settings
|
16
|
+
server localhost
|
17
|
+
port 3030
|
18
|
+
|
19
|
+
# Payload settings
|
20
|
+
|
21
|
+
## The check is named "ddos_detection"
|
22
|
+
check_name ddos_detection
|
23
|
+
|
24
|
+
## The severity is read from the field "level"
|
25
|
+
check_status_field level
|
26
|
+
</match>
|
27
|
+
```
|
28
|
+
|
29
|
+
### Plugin type
|
30
|
+
|
31
|
+
Tye type of this plugin is `sensu`.
|
32
|
+
Specify `type sensu` in the match section.
|
33
|
+
|
34
|
+
### Connection setting
|
35
|
+
|
36
|
+
* `server` (default is "localhost")
|
37
|
+
* The IP address or the hostname of the host running sensu-client daemon.
|
38
|
+
* `port` (default is 3030)
|
39
|
+
* The TCP port number of the [Sensu client socket](
|
40
|
+
https://sensuapp.org/docs/latest/clients#client-socket-input)
|
41
|
+
on which sensu-client daemon is listening.
|
42
|
+
|
43
|
+
### Check payload setting
|
44
|
+
|
45
|
+
The payload of a check result is a JSON object
|
46
|
+
which contains attributes as follows.
|
47
|
+
Attributes are indicated
|
48
|
+
by [JSONPath](http://goessner.net/articles/JsonPath/) expressions.
|
49
|
+
|
50
|
+
* `$.name`
|
51
|
+
* The check name to identify the check.
|
52
|
+
* `$.output`
|
53
|
+
* An arbitrary string to describe the check result.
|
54
|
+
* This attribute is often used to contain metric values.
|
55
|
+
* `$.status`
|
56
|
+
* The severity of the check result.
|
57
|
+
* 0 (OK), 1 (WARNING), 2 (CRITICAL) or 3 (UNKNOWN or CUSTOM).
|
58
|
+
|
59
|
+
The check result can also contain other attributes.
|
60
|
+
This plugin supports the attributes below.
|
61
|
+
|
62
|
+
* `$.type`
|
63
|
+
* Either "standard" or "metric".
|
64
|
+
* If the attribute is set to "standard", the sensu-server creates an event
|
65
|
+
only when the status is not OK or when the status is changed to OK.
|
66
|
+
* If the attribute is set to "metric", the sensu-server creates an event
|
67
|
+
even if the status is OK.
|
68
|
+
It is useful when Sensu sends check results to metrics collectors
|
69
|
+
such as [Graphite](http://graphite.wikidot.com/).
|
70
|
+
* `$.ttl`
|
71
|
+
* The time to live (TTL) in seconds,
|
72
|
+
until the check result is considered stale.
|
73
|
+
If TTL expires, sensu-server creates an event.
|
74
|
+
* This attribute is useful when you want to be notified
|
75
|
+
when logs are not output for a certain period.
|
76
|
+
* Same as `freshness_threshold` in Nagios.
|
77
|
+
* `$.handlers`
|
78
|
+
* The names of handlers which process events created for the check.
|
79
|
+
* `$.low_flap_threshold` and `$.high_flap_threshold`
|
80
|
+
* Threshold percentages to determine the status is considered "flapping,"
|
81
|
+
or the state is changed too frequently.
|
82
|
+
* Same as the options in Nagios.
|
83
|
+
See the description of [Flap Detection](
|
84
|
+
https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/3/en/flapping.html)
|
85
|
+
in Nagios.
|
86
|
+
* `$.source`
|
87
|
+
* The source of the check, such as servers or network switches.
|
88
|
+
* If this attribute is not specified,
|
89
|
+
the host of sensu-client is considered as the source.
|
90
|
+
* `$.executed`
|
91
|
+
* The timestamp on which the check is executed.
|
92
|
+
* Note that there is also another timestamp attribute named `issued`,
|
93
|
+
which is automatically measured by sensu-client process.
|
94
|
+
Uchiwa, the default dashboard of Sensu, displays `issued`
|
95
|
+
as the timestamp of check results.
|
96
|
+
|
97
|
+
This plugin additionally adds "fluentd" attribute to the check result.
|
98
|
+
The value of the attribute is a JSON object
|
99
|
+
whoes elements are input to the plugin.
|
100
|
+
|
101
|
+
* `$.fluentd.tag`
|
102
|
+
* The tag of the Fluentd data.
|
103
|
+
* `$.fluentd.time`
|
104
|
+
* The time of the Fluentd data, in seconds since the Unix epoch.
|
105
|
+
* `$.fluentd.record`
|
106
|
+
* The record of the Fluentd data.
|
107
|
+
|
108
|
+
#### `name` attribute
|
109
|
+
|
110
|
+
The check name is determined as below.
|
111
|
+
|
112
|
+
1. The field specified by `check_name_field` option,
|
113
|
+
if present and valid (highest priority)
|
114
|
+
* The valid values are strings composed of ASCII alphanumerics,
|
115
|
+
underscores, periods, and hyphens.
|
116
|
+
2. or `check_name` option, if present
|
117
|
+
* The valid values are same as above.
|
118
|
+
3. or the tag name, if valid
|
119
|
+
* The valid values are same as above.
|
120
|
+
4. or "fluent-plugin-sensu" (lowest priority)
|
121
|
+
|
122
|
+
#### `output` attribute
|
123
|
+
|
124
|
+
The check output is determined as below.
|
125
|
+
|
126
|
+
1. The field specified by `check_output_field` option,
|
127
|
+
if present (highest priority)
|
128
|
+
2. or `check_output` option, if present
|
129
|
+
3. or JSON notation of the record (lowest priority)
|
130
|
+
|
131
|
+
#### `status` attribute
|
132
|
+
|
133
|
+
The severity of the check result is determined as below.
|
134
|
+
|
135
|
+
1. The field specified by `check_status_field` option,
|
136
|
+
if present and permitted (highest priority)
|
137
|
+
* The values permitted to the field for each status (case insensitive):
|
138
|
+
* status 0: an integer ``0``
|
139
|
+
and strings ``"0"``, ``"OK"``
|
140
|
+
* status 1: an integer ``1``
|
141
|
+
and strings ``"1"``, ``"WARNING"``, ``"warn"``
|
142
|
+
* status 2: an integer ``2``
|
143
|
+
and strings ``"2"``, ``"CRITICAL"``, ``"crit"``
|
144
|
+
* status 3: an integer ``3``
|
145
|
+
and strings ``"3"``, ``"UNKNOWN"``, ``"CUSTOM"``
|
146
|
+
2. or `check_status` option, if present
|
147
|
+
* The permitted values for each status (case insensitive):
|
148
|
+
* status 0: ``0`` and ``OK``
|
149
|
+
* status 1: ``1``, ``WARNING``, ``warn``
|
150
|
+
* status 2: ``2``, ``CRITICAL``, ``crit``
|
151
|
+
* status 3: ``3``, ``UNKNOWN``, ``CUSTOM``
|
152
|
+
* If the value is not permitted, it causes a configuration error.
|
153
|
+
3. or `3`, which means UNKNOWN or CUSTOM (lowest priority)
|
154
|
+
|
155
|
+
"warn" and "crit" come from
|
156
|
+
[fluent-plugin-notifier](https://github.com/tagomoris/fluent-plugin-notifier).
|
157
|
+
|
158
|
+
#### `type` attribute
|
159
|
+
|
160
|
+
The check type is determined as below.
|
161
|
+
|
162
|
+
1. `check_type` option (highest priority)
|
163
|
+
* The value must be a string ``"standard"`` or ``"metric"``.
|
164
|
+
2. or "standard" (lowest priority)
|
165
|
+
|
166
|
+
#### `ttl` attribute
|
167
|
+
|
168
|
+
The TTL seconds till expiration is determined as below.
|
169
|
+
|
170
|
+
1. `check_ttl` option (highest priority)
|
171
|
+
* The value must be an integer which represents the TTL seconds.
|
172
|
+
2. or N/A (lowest priority)
|
173
|
+
* It means no expiration detection is performed.
|
174
|
+
|
175
|
+
#### `handlers` attributes
|
176
|
+
|
177
|
+
The handlers which process check results are determined as below.
|
178
|
+
|
179
|
+
1. `check_handlers` option (highest priority)
|
180
|
+
* The value must be an array of strings which represent handler names.
|
181
|
+
2. or `["default"]` (lowest priority)
|
182
|
+
|
183
|
+
#### `low_flap_threshold` and `high_flap_threshold` attributes
|
184
|
+
|
185
|
+
The threshold percentages for flap detection are determined as below.
|
186
|
+
|
187
|
+
1. `check_low_flap_threshold`
|
188
|
+
and `check_high_flap_threshold` options (highest priority)
|
189
|
+
* The values must be integers of threshold percentages.
|
190
|
+
2. or N/A (lowest priority)
|
191
|
+
* It means no flap detection is performed.
|
192
|
+
|
193
|
+
The two options either must be specified together,
|
194
|
+
not specified at all.
|
195
|
+
|
196
|
+
If the options are specified,
|
197
|
+
the following condition must be true:
|
198
|
+
`0 <= check_low_flap_threshold <= check_high_flap_threshold <= 100`.
|
199
|
+
|
200
|
+
#### `source` attribute
|
201
|
+
|
202
|
+
The source of the checks is determined as below.
|
203
|
+
|
204
|
+
1. The field specified by `check_source_field` option,
|
205
|
+
if present and valid (highest priority)
|
206
|
+
2. or `check_source` option
|
207
|
+
3. or N/A (lowest priority)
|
208
|
+
* It means the host of sensu-client is considered as the check source.
|
209
|
+
|
210
|
+
#### `executed` attribute
|
211
|
+
|
212
|
+
The executed timestamp is determined as below.
|
213
|
+
|
214
|
+
1. The field specified by `check_executed_field`
|
215
|
+
if present and valid (highest priority)
|
216
|
+
* The value must be an integer
|
217
|
+
which represents seconds since the Unix epoch.
|
218
|
+
2. The time of the Fluentd record (lowest priority)
|
219
|
+
|
220
|
+
### Buffering
|
221
|
+
|
222
|
+
The default value of `flush_interval` option is set to 1 second.
|
223
|
+
It means that check results are delayed at most 1 second
|
224
|
+
before being sent.
|
225
|
+
|
226
|
+
Except for `flush_interval`,
|
227
|
+
the plugin uses default options
|
228
|
+
for buffered output plugins (defined in Fluent::BufferedOutput class).
|
229
|
+
|
230
|
+
You can override buffering options in the configuration.
|
231
|
+
For example:
|
232
|
+
|
233
|
+
```apache
|
234
|
+
<match ddos>
|
235
|
+
type sensu
|
236
|
+
...snip...
|
237
|
+
buffer_type file
|
238
|
+
buffer_path /var/lib/fluentd/buffer/ddos
|
239
|
+
flush_interval 0.1
|
240
|
+
try_flush_interval 0.1
|
241
|
+
</match>
|
242
|
+
```
|
243
|
+
|
244
|
+
## Use case: "too many server errors" alert
|
245
|
+
|
246
|
+
#### Situation
|
247
|
+
|
248
|
+
Assume you have a web server which runs:
|
249
|
+
|
250
|
+
* Apache HTTP server
|
251
|
+
* Fluentd
|
252
|
+
* sensu-client
|
253
|
+
* which listens to the TCP port 3030 for [Sensu client socket](
|
254
|
+
https://sensuapp.org/docs/latest/clients#client-socket-input).
|
255
|
+
|
256
|
+
You want to be notified when Apache responds too many server errors,
|
257
|
+
for example 5 errors per minute as WARNING,
|
258
|
+
and 50 errors per minute as CRITICAL.
|
259
|
+
|
260
|
+
#### Configuration
|
261
|
+
|
262
|
+
The setting for Fluentd utilizes
|
263
|
+
[fluent-plugin-datacounter](https://github.com/tagomoris/fluent-plugin-datacounter),
|
264
|
+
[fluent-plugin-record-reformer](https://github.com/sonots/fluent-plugin-record-reformer),
|
265
|
+
and of course [fluent-plugin-sensu](https://github.com/miyakawataku/fluent-plugin-sensu).
|
266
|
+
Install those plugins and add configuration as below.
|
267
|
+
|
268
|
+
```apache
|
269
|
+
# Parse Apache access log
|
270
|
+
<source>
|
271
|
+
type tail
|
272
|
+
tag access
|
273
|
+
format apache2
|
274
|
+
|
275
|
+
# The paths vary by setup
|
276
|
+
path /var/log/httpd/access_log
|
277
|
+
pos_file /var/lib/fluentd/pos/httpd-access_log.pos
|
278
|
+
</source>
|
279
|
+
|
280
|
+
# Count 5xx errors per minute
|
281
|
+
<match access>
|
282
|
+
type datacounter
|
283
|
+
tag count.access
|
284
|
+
unit minute
|
285
|
+
aggregate all
|
286
|
+
count_key code
|
287
|
+
pattern1 error ^5\d\d$
|
288
|
+
</match>
|
289
|
+
|
290
|
+
# Calculate the severity level
|
291
|
+
<match count.access>
|
292
|
+
type record_reformer
|
293
|
+
tag server_errors
|
294
|
+
enable_ruby true
|
295
|
+
<record>
|
296
|
+
level ${error_count < 5 ? 'OK' : error_count < 50 ? 'WARNING' : 'CRITICAL'}
|
297
|
+
</record>
|
298
|
+
</match>
|
299
|
+
|
300
|
+
# Send checks to sensu-client
|
301
|
+
<match server_errors>
|
302
|
+
type sensu
|
303
|
+
server localhost
|
304
|
+
port 3030
|
305
|
+
|
306
|
+
check_name server_errors
|
307
|
+
check_type standard
|
308
|
+
check_status_field level
|
309
|
+
check_ttl 100
|
310
|
+
</match>
|
311
|
+
```
|
312
|
+
|
313
|
+
The TTL is set to 100 seconds here,
|
314
|
+
because the check must be sent for each 60 seconds,
|
315
|
+
plus 40 seconds as a margin.
|
316
|
+
|
317
|
+
### Alternatives
|
318
|
+
|
319
|
+
You can use [record\_transformer](
|
320
|
+
http://docs.fluentd.org/articles/filter_record_transformer) filter
|
321
|
+
instead of `fluent-plugin-record-reformer`
|
322
|
+
on Fluentd 0.12.0 and above.
|
323
|
+
|
324
|
+
If you are concerned with scalability,
|
325
|
+
[fluent-plugin-norikra](https://github.com/norikra/fluent-plugin-norikra)
|
326
|
+
may be a better option than datacounter and record\_reformer.
|
327
|
+
|
328
|
+
Another alternative configuration for the use case is
|
329
|
+
sending the error count to Graphite using [fluent-plugin-graphite](
|
330
|
+
https://github.com/studio3104/fluent-plugin-graphite),
|
331
|
+
and making Sensu monitor the value on Graphite with [check-data.rb](
|
332
|
+
https://github.com/sensu/sensu-community-plugins/blob/master/plugins/graphite/check-data.rb).
|
333
|
+
|
334
|
+
## Installation
|
335
|
+
|
336
|
+
Install `fluent-plugin-sensu` gem.
|
337
|
+
|
338
|
+
# Contributing
|
339
|
+
|
340
|
+
Submit an [issue](https://github.com/miyakawataku/fluent-plugin-sensu/issues)
|
341
|
+
or a [pull request](https://github.com/miyakawataku/fluent-plugin-sensu/pulls).
|
342
|
+
|
343
|
+
Feedback to [@miyakawa\_taku](https://twitter.com/miyakawa_taku) on Twitter
|
344
|
+
is also welcome.
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# vim: et sw=2 sts=2
|
3
|
+
|
4
|
+
require "bundler/gem_tasks"
|
5
|
+
require 'rake/testtask'
|
6
|
+
|
7
|
+
Rake::TestTask.new(:test) do |test|
|
8
|
+
test.libs << 'lib' << 'test'
|
9
|
+
test.pattern = 'test/**/test_*.rb'
|
10
|
+
test.verbose = true
|
11
|
+
end
|
12
|
+
|
13
|
+
task :default => :test
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# vim: et sw=2 sts=2
|
3
|
+
|
4
|
+
lib = File.expand_path('../lib', __FILE__)
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "fluent-plugin-sensu"
|
9
|
+
spec.version = '0.0.1'
|
10
|
+
spec.authors = ["MIYAKAWA Taku"]
|
11
|
+
spec.email = ["miyakawa.taku@gmail.com"]
|
12
|
+
spec.description = 'Fluentd output plugin to send checks' +
|
13
|
+
' to sensu-client.'
|
14
|
+
spec.summary = spec.description
|
15
|
+
spec.homepage = ""
|
16
|
+
spec.license = "Apache License, v2.0"
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split($/)
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rr"
|
27
|
+
spec.add_runtime_dependency "fluentd"
|
28
|
+
end
|
@@ -0,0 +1,288 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# vim: et sw=2 sts=2
|
3
|
+
|
4
|
+
module Fluent
|
5
|
+
|
6
|
+
# Fluentd output plugin to send checks to sensu-client.
|
7
|
+
class SensuOutput < Fluent::BufferedOutput
|
8
|
+
Fluent::Plugin.register_output('sensu', self)
|
9
|
+
|
10
|
+
#
|
11
|
+
# Config parameters
|
12
|
+
#
|
13
|
+
|
14
|
+
# The IP address or the hostname of the host running sensu-client.
|
15
|
+
config_param :server, :string, :default => 'localhost'
|
16
|
+
|
17
|
+
# The port to which sensu-client is listening.
|
18
|
+
config_param :port, :integer, :default => 3030
|
19
|
+
|
20
|
+
# Options for "name" attribute.
|
21
|
+
config_param :check_name, :string, :default => nil
|
22
|
+
config_param :check_name_field, :string, :default => nil
|
23
|
+
|
24
|
+
# Pattern for check names.
|
25
|
+
CHECK_NAME_PATTERN = /\A[\w.-]+\z/
|
26
|
+
|
27
|
+
# Options for "output" attribute.
|
28
|
+
config_param :check_output_field, :string, :default => nil
|
29
|
+
config_param :check_output, :string, :default => nil
|
30
|
+
|
31
|
+
# Options for "status" attribute.
|
32
|
+
config_param :check_status_field, :string, :default => nil
|
33
|
+
config_param(:check_status, :default => 3) { |status_str|
|
34
|
+
check_status = SensuOutput.normalize_status(status_str)
|
35
|
+
if not check_status
|
36
|
+
raise Fluent::ConfigError,
|
37
|
+
"invalid 'check_status': #{status_str}; 'check_status' must be" +
|
38
|
+
" 0/1/2/3, OK/WARNING/CRITICAL/CUSTOM, warn/crit"
|
39
|
+
end
|
40
|
+
check_status
|
41
|
+
}
|
42
|
+
|
43
|
+
# Pattern for OK status.
|
44
|
+
OK_PATTERN = /\A(0|OK)\z/i
|
45
|
+
|
46
|
+
# Pattern for WARNING status.
|
47
|
+
WARNING_PATTERN = /\A(1|WARNING|warn)\z/i
|
48
|
+
|
49
|
+
# Pattern for CRITICAL status.
|
50
|
+
CRITICAL_PATTERN = /\A(2|CRITICAL|crit)\z/i
|
51
|
+
|
52
|
+
# Pattern for UNKNOWN status.
|
53
|
+
UNKNOWN_PATTERN = /\A(3|UNKNOWN|CUSTOM)\z/i
|
54
|
+
|
55
|
+
# Option for "type" attribute.
|
56
|
+
config_param(:check_type, :default => 'standard') { |check_type|
|
57
|
+
if not ['standard', 'metric'].include?(check_type)
|
58
|
+
raise Fluent::ConfigError,
|
59
|
+
"invalid 'check_type': #{check_type}; 'check_type' must be" +
|
60
|
+
' either "standard" or "metric".'
|
61
|
+
end
|
62
|
+
check_type
|
63
|
+
}
|
64
|
+
|
65
|
+
# Option for "ttl" attribute.
|
66
|
+
config_param :check_ttl, :integer, :default => nil
|
67
|
+
|
68
|
+
# Option for "handlers" attribute.
|
69
|
+
config_param(:check_handlers, :default => ['default']) { |handlers_str|
|
70
|
+
error_message = "invalid 'check_handlers': #{handlers_str};" +
|
71
|
+
" 'check_handlers' must be an array of strings."
|
72
|
+
obj = JSON.load(handlers_str)
|
73
|
+
raise Fluent::ConfigError, error_message if not obj.is_a?(Array)
|
74
|
+
array = obj
|
75
|
+
all_string_elements = array.all? { |e| e.is_a?(String) }
|
76
|
+
raise Fluent::ConfigError, error_message if not all_string_elements
|
77
|
+
array
|
78
|
+
}
|
79
|
+
|
80
|
+
# Options for flapping thresholds.
|
81
|
+
config_param :check_low_flap_threshold, :integer, :default => nil
|
82
|
+
config_param :check_high_flap_threshold, :integer, :default => nil
|
83
|
+
|
84
|
+
# Options for "source" attribute.
|
85
|
+
config_param :check_source_field, :string, :default => nil
|
86
|
+
config_param :check_source, :string, :default => nil
|
87
|
+
|
88
|
+
# Options for "executed" attribute.
|
89
|
+
config_param :check_executed_field, :string, :default => nil
|
90
|
+
|
91
|
+
# Load modules.
|
92
|
+
private
|
93
|
+
def initialize
|
94
|
+
super
|
95
|
+
require 'json'
|
96
|
+
end
|
97
|
+
|
98
|
+
# Read and validate the configuration.
|
99
|
+
public
|
100
|
+
def configure(conf)
|
101
|
+
super
|
102
|
+
reject_invalid_check_name
|
103
|
+
reject_invalid_flapping_thresholds
|
104
|
+
end
|
105
|
+
|
106
|
+
# Reject check_name option if invalid
|
107
|
+
private
|
108
|
+
def reject_invalid_check_name
|
109
|
+
if @check_name && @check_name !~ CHECK_NAME_PATTERN
|
110
|
+
raise ConfigError,
|
111
|
+
"check_name must be a string consisting of one or more" +
|
112
|
+
" ASCII alphanumerics, underscores, periods, and hyphens"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Reject invalid check_low_flap_threshold and check_high_flap_threshold
|
117
|
+
private
|
118
|
+
def reject_invalid_flapping_thresholds
|
119
|
+
if @check_low_flap_threshold.nil? ^ @check_high_flap_threshold.nil?
|
120
|
+
raise ConfigError,
|
121
|
+
"'check_low_flap_threshold' and 'check_high_flap_threshold'" +
|
122
|
+
" specified togher, or not specified at all."
|
123
|
+
end
|
124
|
+
if @check_low_flap_threshold
|
125
|
+
in_order = 0 <= @check_low_flap_threshold &&
|
126
|
+
@check_low_flap_threshold <= @check_high_flap_threshold &&
|
127
|
+
@check_high_flap_threshold <= 100
|
128
|
+
if not in_order
|
129
|
+
raise ConfigError,
|
130
|
+
"the following condition must be true:" +
|
131
|
+
" 0 <= check_low_flap_threshold <= check_high_flap_threshold <= 100"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Pack the tuple (tag, time, record).
|
137
|
+
public
|
138
|
+
def format(tag, time, record)
|
139
|
+
[tag, time, record].to_msgpack
|
140
|
+
end
|
141
|
+
|
142
|
+
# Send a check
|
143
|
+
public
|
144
|
+
def write(chunk)
|
145
|
+
results = []
|
146
|
+
chunk.msgpack_each { |(tag, time, record)|
|
147
|
+
payload = {
|
148
|
+
'name' => determine_check_name(tag, record),
|
149
|
+
'output' => determine_output(tag, record),
|
150
|
+
'status' => determine_status(tag, record),
|
151
|
+
'type' => @check_type,
|
152
|
+
'handlers' => @check_handlers,
|
153
|
+
'executed' => determine_executed_time(tag, time, record),
|
154
|
+
'fluentd' => {
|
155
|
+
'tag' => tag,
|
156
|
+
'time' => time.to_i,
|
157
|
+
'record' => record,
|
158
|
+
},
|
159
|
+
}
|
160
|
+
add_attribute_if_present(
|
161
|
+
payload, 'ttl', @check_ttl)
|
162
|
+
add_attribute_if_present(
|
163
|
+
payload, 'low_flap_threshold', @check_low_flap_threshold)
|
164
|
+
add_attribute_if_present(
|
165
|
+
payload, 'high_flap_threshold', @check_high_flap_threshold)
|
166
|
+
add_attribute_if_present(
|
167
|
+
payload, 'source', determine_source(tag, record))
|
168
|
+
send_check(@server, @port, payload)
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
# Send a check to sensu-client.
|
173
|
+
private
|
174
|
+
def send_check(server, port, payload)
|
175
|
+
json = payload.to_json
|
176
|
+
sensu_client = TCPSocket.open(@server, @port)
|
177
|
+
begin
|
178
|
+
sensu_client.puts(json)
|
179
|
+
ensure
|
180
|
+
sensu_client.close
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Adds an attribute to the payload if present.
|
185
|
+
private
|
186
|
+
def add_attribute_if_present(payload, name, value)
|
187
|
+
payload[name] = value if value
|
188
|
+
end
|
189
|
+
|
190
|
+
# Determines "name" attribute of a check.
|
191
|
+
private
|
192
|
+
def determine_check_name(tag, record)
|
193
|
+
# Field specified by check_name_field option
|
194
|
+
if @check_name_field
|
195
|
+
check_name = record[@check_name_field]
|
196
|
+
return check_name if check_name =~ CHECK_NAME_PATTERN
|
197
|
+
log.warn('Invalid check name in the field.' +
|
198
|
+
' Fallback to check_name option, tag,' +
|
199
|
+
' or constant "fluent-plugin-sensu".',
|
200
|
+
:tag => tag,
|
201
|
+
:check_name_field => @check_name_field,
|
202
|
+
:value => check_name)
|
203
|
+
# Fall through
|
204
|
+
end
|
205
|
+
# check_name option
|
206
|
+
return @check_name if @check_name
|
207
|
+
# Tag
|
208
|
+
return tag if tag =~ CHECK_NAME_PATTERN
|
209
|
+
# Default value
|
210
|
+
log.warn('Invalid check name in the tag.' +
|
211
|
+
'Fallback to the constant "fluent-plugin-sensu".',
|
212
|
+
:tag => tag)
|
213
|
+
return 'fluent-plugin-sensu'
|
214
|
+
end
|
215
|
+
|
216
|
+
# Determines "output" attribute of a check.
|
217
|
+
private
|
218
|
+
def determine_output(tag, record)
|
219
|
+
# Read from the field
|
220
|
+
if @check_output_field
|
221
|
+
check_output = record[@check_output_field]
|
222
|
+
return check_output if check_output
|
223
|
+
log.warn('the field for "output" attribute is absent',
|
224
|
+
:tag => tag, :check_output_field => @check_output_field)
|
225
|
+
# Fall through
|
226
|
+
end
|
227
|
+
# Returns the option value
|
228
|
+
return @check_output if @check_output
|
229
|
+
# Default to JSON notation of the record
|
230
|
+
return record.to_json
|
231
|
+
end
|
232
|
+
|
233
|
+
# Determines "status" attribute of a check.
|
234
|
+
private
|
235
|
+
def determine_status(tag, record)
|
236
|
+
# Read from the field
|
237
|
+
if @check_status_field
|
238
|
+
status_field_val = record[@check_status_field]
|
239
|
+
if status_field_val
|
240
|
+
check_status = SensuOutput.normalize_status(status_field_val)
|
241
|
+
return check_status if check_status
|
242
|
+
end
|
243
|
+
log.warn('the field for "status" attribute is invalid',
|
244
|
+
:tag => tag, :check_status_field => @check_status_field,
|
245
|
+
:value => status_field_val)
|
246
|
+
end
|
247
|
+
# Returns the default
|
248
|
+
return @check_status
|
249
|
+
end
|
250
|
+
|
251
|
+
# Converts variations of status values to an integer.
|
252
|
+
private
|
253
|
+
def self.normalize_status(status_val)
|
254
|
+
status_str = status_val.to_s
|
255
|
+
return 0 if status_str =~ OK_PATTERN
|
256
|
+
return 1 if status_str =~ WARNING_PATTERN
|
257
|
+
return 2 if status_str =~ CRITICAL_PATTERN
|
258
|
+
return 3 if status_str =~ UNKNOWN_PATTERN
|
259
|
+
return nil
|
260
|
+
end
|
261
|
+
|
262
|
+
# Determines "source" attribute of a check.
|
263
|
+
private
|
264
|
+
def determine_source(tag, record)
|
265
|
+
if @check_source_field
|
266
|
+
source = record[@check_source_field]
|
267
|
+
return source if source
|
268
|
+
log.warn('the field for "source" attribute is absent',
|
269
|
+
:tag => tag, :check_source_field => @check_source_field)
|
270
|
+
end
|
271
|
+
return @check_source
|
272
|
+
end
|
273
|
+
|
274
|
+
# Determines "executed" attribute of a check.
|
275
|
+
private
|
276
|
+
def determine_executed_time(tag, time, record)
|
277
|
+
if @check_executed_field
|
278
|
+
executed = record[@check_executed_field]
|
279
|
+
return executed if executed.is_a?(Integer)
|
280
|
+
log.warn('the field for "executed" attribute is absent',
|
281
|
+
:tag => tag, :check_executed_field => @check_executed_field)
|
282
|
+
end
|
283
|
+
return time
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|