logstash-output-graphite-mavlyutov 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 136822ceb4a9a17016f9d77316efa454d78d1409
4
+ data.tar.gz: 74637f2414cfe17add60878e2806b3210915af42
5
+ SHA512:
6
+ metadata.gz: 9327d6ba1e419f985ec0cc4f21ec99f963f43b1a3434b6785f780edabe56b257494b0eb2b6792db2d1f3fbe8195cc90f9567d3f1c68f87d0dbfbe8fb190640b8
7
+ data.tar.gz: 014ddb627b0fe85dc74ec4849710c39ee2c4d8f56bbe68d95af394b849dd6cd3dc02a5a2499281ce29ec1ae702784b8be294fd2a771905e91fb67eaa5eb18563
data/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
1
+ ## 3.1.1
2
+ - Relax constraint on logstash-core-plugin-api to >= 1.60 <= 2.99
3
+
4
+ ## 3.1.0
5
+ - breaking,config: Remove deprecated config `debug`.
6
+
7
+ ## 3.0.1
8
+ - Republish all the gems under jruby.
9
+
10
+ ## 3.0.0
11
+ - Update the plugin to the version 2.0 of the plugin api, this change is required for Logstash 5.0 compatibility. See https://github.com/elastic/logstash/issues/5141
12
+
13
+ ## 2.0.5
14
+ - Depend on logstash-core-plugin-api instead of logstash-core, removing the need to mass update plugins on major releases of logstash
15
+
16
+ ## 2.0.4
17
+ - New dependency requirements for logstash-core for the 5.0 release
18
+
19
+ ## 2.0.3
20
+ - Fixed empty/nil messages handling
21
+
22
+ ## 2.0.0
23
+ - Plugins were updated to follow the new shutdown semantic, this mainly allows Logstash to instruct input plugins to terminate gracefully,
24
+ instead of using Thread.raise on the plugins' threads. Ref: https://github.com/elastic/logstash/pull/3895
25
+ - Dependency on logstash-core update to 2.0
26
+
27
+ ## 1.0.2
28
+ - Added support for sprintf in field formatting
29
+
30
+ ## 1.0.1
31
+ - Added support for nested hashes as values
32
+
data/CONTRIBUTORS ADDED
@@ -0,0 +1,22 @@
1
+ The following is a list of people who have contributed ideas, code, bug
2
+ reports, or in general have helped logstash along its way.
3
+
4
+ Contributors:
5
+ * Colin Surprenant (colinsurprenant)
6
+ * Dave Snigier (LambdaDriver)
7
+ * John E. Vincent (lusis)
8
+ * Jordan Sissel (jordansissel)
9
+ * Kurt Hurtado (kurtado)
10
+ * Michael Leinartas (mleinart)
11
+ * Pere Urbón (purbon)
12
+ * Pete Fritchman (fetep)
13
+ * Pier-Hugues Pellerin (ph)
14
+ * Rene Lengwinat (invadersmustdie)
15
+ * Richard Pijnenburg (electrical)
16
+ * eravenelle
17
+ * piavlo
18
+
19
+ Note: If you've sent us patches, bug reports, or otherwise contributed to
20
+ Logstash, and you aren't on the list above and want to be, please let us know
21
+ and we'll make sure you're here. Contributions from folks like you are what make
22
+ open source awesome.
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ logstash_path = ENV["LOGSTASH_PATH"] || "../../logstash"
6
+ use_logstash_source = ENV["LOGSTASH_SOURCE"] && ENV["LOGSTASH_SOURCE"].to_s == "1"
7
+
8
+ if Dir.exist?(logstash_path) && use_logstash_source
9
+ gem 'logstash-core', :path => "#{logstash_path}/logstash-core"
10
+ gem 'logstash-core-plugin-api', :path => "#{logstash_path}/logstash-core-plugin-api"
11
+ end
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2012–2016 Elasticsearch <http://www.elastic.co>
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/NOTICE.TXT ADDED
@@ -0,0 +1,5 @@
1
+ Elasticsearch
2
+ Copyright 2012-2015 Elasticsearch
3
+
4
+ This product includes software developed by The Apache Software
5
+ Foundation (http://www.apache.org/).
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # Logstash Plugin
2
+
3
+ [![Travis Build Status](https://travis-ci.org/logstash-plugins/logstash-output-graphite.svg)](https://travis-ci.org/logstash-plugins/logstash-output-graphite)
4
+
5
+ This is a plugin for [Logstash](https://github.com/elastic/logstash).
6
+
7
+ It is fully free and fully open source. The license is Apache 2.0, meaning you are pretty much free to use it however you want in whatever way.
8
+
9
+ ## Documentation
10
+
11
+ Logstash provides infrastructure to automatically generate documentation for this plugin. We use the asciidoc format to write documentation so any comments in the source code will be first converted into asciidoc and then into html. All plugin documentation are placed under one [central location](http://www.elastic.co/guide/en/logstash/current/).
12
+
13
+ - For formatting code or config example, you can use the asciidoc `[source,ruby]` directive
14
+ - For more asciidoc formatting tips, see the excellent reference here https://github.com/elastic/docs#asciidoc-guide
15
+
16
+ ## Need Help?
17
+
18
+ Need help? Try #logstash on freenode IRC or the https://discuss.elastic.co/c/logstash discussion forum.
19
+
20
+ ## Developing
21
+
22
+ ### 1. Plugin Developement and Testing
23
+
24
+ #### Code
25
+ - To get started, you'll need JRuby with the Bundler gem installed.
26
+
27
+ - Create a new plugin or clone and existing from the GitHub [logstash-plugins](https://github.com/logstash-plugins) organization. We also provide [example plugins](https://github.com/logstash-plugins?query=example).
28
+
29
+ - Install dependencies
30
+ ```sh
31
+ bundle install
32
+ ```
33
+
34
+ #### Test
35
+
36
+ - Update your dependencies
37
+
38
+ ```sh
39
+ bundle install
40
+ ```
41
+
42
+ - Run tests
43
+
44
+ ```sh
45
+ bundle exec rspec
46
+ ```
47
+
48
+ ### 2. Running your unpublished Plugin in Logstash
49
+
50
+ #### 2.1 Run in a local Logstash clone
51
+
52
+ - Edit Logstash `Gemfile` and add the local plugin path, for example:
53
+ ```ruby
54
+ gem "logstash-filter-awesome", :path => "/your/local/logstash-filter-awesome"
55
+ ```
56
+ - Install plugin
57
+ ```sh
58
+ # Logstash 2.3 and higher
59
+ bin/logstash-plugin install --no-verify
60
+
61
+ # Prior to Logstash 2.3
62
+ bin/plugin install --no-verify
63
+
64
+ ```
65
+ - Run Logstash with your plugin
66
+ ```sh
67
+ bin/logstash -e 'filter {awesome {}}'
68
+ ```
69
+ At this point any modifications to the plugin code will be applied to this local Logstash setup. After modifying the plugin, simply rerun Logstash.
70
+
71
+ #### 2.2 Run in an installed Logstash
72
+
73
+ You can use the same **2.1** method to run your plugin in an installed Logstash by editing its `Gemfile` and pointing the `:path` to your local plugin development directory or you can build the gem and install it using:
74
+
75
+ - Build your plugin gem
76
+ ```sh
77
+ gem build logstash-filter-awesome.gemspec
78
+ ```
79
+ - Install the plugin from the Logstash home
80
+ ```sh
81
+ # Logstash 2.3 and higher
82
+ bin/logstash-plugin install --no-verify
83
+
84
+ # Prior to Logstash 2.3
85
+ bin/plugin install --no-verify
86
+
87
+ ```
88
+ - Start Logstash and proceed to test the plugin
89
+
90
+ ## Contributing
91
+
92
+ All contributions are welcome: ideas, patches, documentation, bug reports, complaints, and even something you drew up on a napkin.
93
+
94
+ Programming is not a required skill. Whatever you've seen about open source and maintainers or community members saying "send patches or die" - you will not see that here.
95
+
96
+ It is more important to the community that you are able to contribute.
97
+
98
+ For more information about contributing, see the [CONTRIBUTING](https://github.com/elastic/logstash/blob/master/CONTRIBUTING.md) file.
@@ -0,0 +1,172 @@
1
+ :plugin: graphite
2
+ :type: output
3
+
4
+ ///////////////////////////////////////////
5
+ START - GENERATED VARIABLES, DO NOT EDIT!
6
+ ///////////////////////////////////////////
7
+ :version: %VERSION%
8
+ :release_date: %RELEASE_DATE%
9
+ :changelog_url: %CHANGELOG_URL%
10
+ :include_path: ../../../logstash/docs/include
11
+ ///////////////////////////////////////////
12
+ END - GENERATED VARIABLES, DO NOT EDIT!
13
+ ///////////////////////////////////////////
14
+
15
+ [id="plugins-{type}-{plugin}"]
16
+
17
+ === Graphite
18
+
19
+ include::{include_path}/plugin_header.asciidoc[]
20
+
21
+ ==== Description
22
+
23
+ This output allows you to pull metrics from your logs and ship them to
24
+ Graphite. Graphite is an open source tool for storing and graphing metrics.
25
+
26
+ An example use case: Some applications emit aggregated stats in the logs
27
+ every 10 seconds. Using the grok filter and this output, it is possible to
28
+ capture the metric values from the logs and emit them to Graphite.
29
+
30
+ [id="plugins-{type}s-{plugin}-options"]
31
+ ==== Graphite Output Configuration Options
32
+
33
+ This plugin supports the following configuration options plus the <<plugins-{type}s-common-options>> described later.
34
+
35
+ [cols="<,<,<",options="header",]
36
+ |=======================================================================
37
+ |Setting |Input type|Required
38
+ | <<plugins-{type}s-{plugin}-exclude_metrics>> |<<array,array>>|No
39
+ | <<plugins-{type}s-{plugin}-fields_are_metrics>> |<<boolean,boolean>>|No
40
+ | <<plugins-{type}s-{plugin}-host>> |<<string,string>>|No
41
+ | <<plugins-{type}s-{plugin}-include_metrics>> |<<array,array>>|No
42
+ | <<plugins-{type}s-{plugin}-metrics>> |<<hash,hash>>|No
43
+ | <<plugins-{type}s-{plugin}-metrics_format>> |<<string,string>>|No
44
+ | <<plugins-{type}s-{plugin}-nested_object_separator>> |<<string,string>>|No
45
+ | <<plugins-{type}s-{plugin}-port>> |<<number,number>>|No
46
+ | <<plugins-{type}s-{plugin}-reconnect_interval>> |<<number,number>>|No
47
+ | <<plugins-{type}s-{plugin}-resend_on_failure>> |<<boolean,boolean>>|No
48
+ | <<plugins-{type}s-{plugin}-timestamp_field>> |<<string,string>>|No
49
+ |=======================================================================
50
+
51
+ Also see <<plugins-{type}s-common-options>> for a list of options supported by all
52
+ output plugins.
53
+
54
+ &nbsp;
55
+
56
+ [id="plugins-{type}s-{plugin}-exclude_metrics"]
57
+ ===== `exclude_metrics`
58
+
59
+ * Value type is <<array,array>>
60
+ * Default value is `["%{[^}]+}"]`
61
+
62
+ Exclude regex matched metric names, by default exclude unresolved %{field} strings.
63
+
64
+ [id="plugins-{type}s-{plugin}-fields_are_metrics"]
65
+ ===== `fields_are_metrics`
66
+
67
+ * Value type is <<boolean,boolean>>
68
+ * Default value is `false`
69
+
70
+ An array indicating that these event fields should be treated as metrics
71
+ and will be sent verbatim to Graphite. You may use either `fields_are_metrics`
72
+ or `metrics`, but not both.
73
+
74
+ [id="plugins-{type}s-{plugin}-host"]
75
+ ===== `host`
76
+
77
+ * Value type is <<string,string>>
78
+ * Default value is `"localhost"`
79
+
80
+ The hostname or IP address of the Graphite server.
81
+
82
+ [id="plugins-{type}s-{plugin}-include_metrics"]
83
+ ===== `include_metrics`
84
+
85
+ * Value type is <<array,array>>
86
+ * Default value is `[".*"]`
87
+
88
+ Include only regex matched metric names.
89
+
90
+ [id="plugins-{type}s-{plugin}-metrics"]
91
+ ===== `metrics`
92
+
93
+ * Value type is <<hash,hash>>
94
+ * Default value is `{}`
95
+
96
+ The metric(s) to use. This supports dynamic strings like %{host}
97
+ for metric names and also for values. This is a hash field with key
98
+ being the metric name, value being the metric value. Example:
99
+ [source,ruby]
100
+ metrics => { "%{host}/uptime" => "%{uptime_1m}" }
101
+
102
+ The value will be coerced to a floating point value. Values which cannot be
103
+ coerced will be set to zero (0). You may use either `metrics` or `fields_are_metrics`,
104
+ but not both.
105
+
106
+ [id="plugins-{type}s-{plugin}-metrics_format"]
107
+ ===== `metrics_format`
108
+
109
+ * Value type is <<string,string>>
110
+ * Default value is `"*"`
111
+
112
+ Defines the format of the metric string. The placeholder '*' will be
113
+ replaced with the name of the actual metric.
114
+ [source,ruby]
115
+ metrics_format => "foo.bar.*.sum"
116
+
117
+ NOTE: If no metrics_format is defined, the name of the metric will be used as fallback.
118
+
119
+ [id="plugins-{type}s-{plugin}-nested_object_separator"]
120
+ ===== `nested_object_separator`
121
+
122
+ * Value type is <<string,string>>
123
+ * Default value is `"."`
124
+
125
+ When hashes are passed in as values they are broken out into a dotted notation
126
+ For instance if you configure this plugin with
127
+ # [source,ruby]
128
+ metrics => "mymetrics"
129
+
130
+ and "mymetrics" is a nested hash of '{a => 1, b => { c => 2 }}'
131
+ this plugin will generate two metrics: a => 1, and b.c => 2 .
132
+ If you've specified a 'metrics_format' it will respect that,
133
+ but you still may want control over the separator within these nested key names.
134
+ This config setting changes the separator from the '.' default.
135
+
136
+ [id="plugins-{type}s-{plugin}-port"]
137
+ ===== `port`
138
+
139
+ * Value type is <<number,number>>
140
+ * Default value is `2003`
141
+
142
+ The port to connect to on the Graphite server.
143
+
144
+ [id="plugins-{type}s-{plugin}-reconnect_interval"]
145
+ ===== `reconnect_interval`
146
+
147
+ * Value type is <<number,number>>
148
+ * Default value is `2`
149
+
150
+ Interval between reconnect attempts to Carbon.
151
+
152
+ [id="plugins-{type}s-{plugin}-resend_on_failure"]
153
+ ===== `resend_on_failure`
154
+
155
+ * Value type is <<boolean,boolean>>
156
+ * Default value is `false`
157
+
158
+ Should metrics be resent on failure?
159
+
160
+ [id="plugins-{type}s-{plugin}-timestamp_field"]
161
+ ===== `timestamp_field`
162
+
163
+ * Value type is <<string,string>>
164
+ * Default value is `"@timestamp"`
165
+
166
+ Use this field for the timestamp instead of '@timestamp' which is the
167
+ default. Useful when backfilling or just getting more accurate data into
168
+ graphite since you probably have a cache layer infront of Logstash.
169
+
170
+
171
+
172
+ include::{include_path}/{type}.asciidoc[]
@@ -0,0 +1,226 @@
1
+ # encoding: utf-8
2
+ require "logstash/outputs/base"
3
+ require "logstash/namespace"
4
+ require "socket"
5
+
6
+ # This output allows you to pull metrics from your logs and ship them to
7
+ # Graphite. Graphite is an open source tool for storing and graphing metrics.
8
+ #
9
+ # An example use case: Some applications emit aggregated stats in the logs
10
+ # every 10 seconds. Using the grok filter and this output, it is possible to
11
+ # capture the metric values from the logs and emit them to Graphite.
12
+ class LogStash::Outputs::Graphite < LogStash::Outputs::Base
13
+ config_name "graphite"
14
+
15
+ EXCLUDE_ALWAYS = [ "@timestamp", "@version" ]
16
+
17
+ DEFAULT_METRICS_FORMAT = "*"
18
+ METRIC_PLACEHOLDER = "*"
19
+
20
+ # The hostname or IP address of the Graphite server.
21
+ config :host, :validate => :string, :default => "localhost", :deprecated => "This setting is being deprecated, use :hosts instead."
22
+
23
+ # The port to connect to the Graphite server.
24
+ config :port, :validate => :number, :default => 2003, :deprecated => "This setting is being deprecated, use :hosts instead."
25
+
26
+ # The list of known Graphite servers to connect to. One host is selected randomly (there is no precedence). If one host becomes unreachable, another one is selected randomly.
27
+ # All entries in this list can contain a port number. If no port number is given, the default value of 2003 is used.
28
+ config :hosts, :validate => :string, :list => true, :default => ["localhost:2003"]
29
+
30
+ # Interval between reconnect attempts to Graphite server, seconds.
31
+ config :reconnect_interval, :validate => :number, :default => 2
32
+
33
+ # Should metrics be resent on failure?
34
+ config :resend_on_failure, :validate => :boolean, :default => false
35
+
36
+ # Number of attempts to be made resending metrics before abandoning
37
+ config :resend_attempts, :validate => :number, :default => 3
38
+
39
+ # The metric(s) to use. This supports dynamic strings like %{host}
40
+ # for metric names and also for values. This is a hash field with key
41
+ # being the metric name, value being the metric value. Example:
42
+ # [source,ruby]
43
+ # metrics => { "%{host}/uptime" => "%{uptime_1m}" }
44
+ #
45
+ # The value will be coerced to a floating point value. Values which cannot be
46
+ # coerced will be set to zero (0). You may use either `metrics` or `fields_are_metrics`,
47
+ # but not both.
48
+ config :metrics, :validate => :hash, :default => {}
49
+
50
+ # An array indicating that these event fields should be treated as metrics
51
+ # and will be sent verbatim to Graphite. You may use either `fields_are_metrics`
52
+ # or `metrics`, but not both.
53
+ config :fields_are_metrics, :validate => :boolean, :default => false
54
+
55
+ # Include only regex matched metric names.
56
+ config :include_metrics, :validate => :array, :default => [ ".*" ]
57
+
58
+ # Exclude regex matched metric names, by default exclude unresolved %{field} strings.
59
+ config :exclude_metrics, :validate => :array, :default => [ "%\{[^}]+\}" ]
60
+
61
+ # Use this field for the timestamp instead of '@timestamp' which is the
62
+ # default. Useful when backfilling or just getting more accurate data into
63
+ # graphite since you probably have a cache layer infront of Logstash.
64
+ config :timestamp_field, :validate => :string, :default => '@timestamp'
65
+
66
+ # Defines the format of the metric string. The placeholder '*' will be
67
+ # replaced with the name of the actual metric.
68
+ # [source,ruby]
69
+ # metrics_format => "foo.bar.*.sum"
70
+ #
71
+ # NOTE: If no metrics_format is defined, the name of the metric will be used as fallback.
72
+ config :metrics_format, :validate => :string, :default => DEFAULT_METRICS_FORMAT
73
+
74
+ # When hashes are passed in as values they are broken out into a dotted notation
75
+ # For instance if you configure this plugin with
76
+ # # [source,ruby]
77
+ # metrics => "mymetrics"
78
+ #
79
+ # and "mymetrics" is a nested hash of '{a => 1, b => { c => 2 }}'
80
+ # this plugin will generate two metrics: a => 1, and b.c => 2 .
81
+ # If you've specified a 'metrics_format' it will respect that,
82
+ # but you still may want control over the separator within these nested key names.
83
+ # This config setting changes the separator from the '.' default.
84
+ config :nested_object_separator, :validate => :string, :default => "."
85
+
86
+ def register
87
+ @include_metrics.collect!{|regexp| Regexp.new(regexp)}
88
+ @exclude_metrics.collect!{|regexp| Regexp.new(regexp)}
89
+
90
+ if @metrics_format && !@metrics_format.include?(METRIC_PLACEHOLDER)
91
+ @logger.warn("metrics_format does not include placeholder #{METRIC_PLACEHOLDER} .. falling back to default format: #{DEFAULT_METRICS_FORMAT.inspect}")
92
+
93
+ @metrics_format = DEFAULT_METRICS_FORMAT
94
+ end
95
+
96
+ setup_hosts
97
+ end
98
+
99
+ def setup_hosts
100
+ if @hosts && @hosts.size == 1 && @hosts[0] == "localhost:2003"
101
+ @hosts.replace(["%s:%s" % [@host, @port]])
102
+ end
103
+ end
104
+
105
+ def send(message)
106
+ numattempts = 0
107
+ hosts_to_try = @hosts
108
+ host = hosts_to_try.sample
109
+ hosts_to_try.delete(host)
110
+
111
+ begin
112
+ address, _, port = host.rpartition(":")
113
+ TCPSocket.new(address, port).puts(message)
114
+ rescue Errno::ECOPNNREFUSED, Errno::EPIPE, Errno::ECONNRESET, IOError
115
+ if hosts_to_try.size > 0
116
+ host = hosts_to_try.sample
117
+ hosts_to_try.delete(host)
118
+ retry
119
+ elsif @resend_on_failure && numattempts < @resend_attempts
120
+ sleep(@reconnect_interval)
121
+ hosts_to_try = @hosts
122
+ host = hosts_to_try.sample
123
+ hosts_to_try.delete(host)
124
+ numattempts += 1
125
+ retry
126
+ else
127
+ @logger.warn("No more hosts to try, skip sending...")
128
+ end
129
+ end
130
+
131
+ end
132
+
133
+ def construct_metric_name(event, metric)
134
+ if @metrics_format
135
+ sprinted = event.sprintf(@metrics_format)
136
+ return sprinted.gsub(METRIC_PLACEHOLDER, metric)
137
+ end
138
+
139
+ metric
140
+ end
141
+
142
+ def receive(event)
143
+ # Graphite message format: metric value timestamp\n
144
+
145
+ # compact to remove nil messages which produces useless \n
146
+ messages = (
147
+ @fields_are_metrics \
148
+ ? messages_from_event_fields(event, @include_metrics, @exclude_metrics)
149
+ : messages_from_event_metrics(event, @metrics)
150
+ ).compact
151
+
152
+ if messages.empty?
153
+ @logger.debug? && @logger.debug("Message is empty, not sending anything to Graphite", :messages => messages)
154
+ else
155
+ message = messages.join("\n")
156
+ @logger.debug? && @logger.debug("Sending carbon messages", :messages => messages)
157
+
158
+ send(message)
159
+ end
160
+ end
161
+
162
+ private
163
+
164
+ def messages_from_event_fields(event, include_metrics, exclude_metrics)
165
+ @logger.debug? && @logger.debug("got metrics event", :metrics => event.to_hash)
166
+
167
+ timestamp = event_timestamp(event)
168
+ event.to_hash.flat_map do |metric,value|
169
+ next if EXCLUDE_ALWAYS.include?(metric)
170
+ next unless include_metrics.empty? || include_metrics.any? { |regexp| metric.match(regexp) }
171
+ next if exclude_metrics.any? {|regexp| metric.match(regexp)}
172
+
173
+ metrics_lines_for_event(event, metric, value, timestamp)
174
+ end
175
+ end
176
+
177
+ def messages_from_event_metrics(event, metrics)
178
+ timestamp = event_timestamp(event)
179
+ metrics.flat_map do |metric, value|
180
+ @logger.debug? && @logger.debug("processing", :metric => metric, :value => value)
181
+
182
+ metric = event.sprintf(metric)
183
+ next unless @include_metrics.any? {|regexp| metric.match(regexp)}
184
+ next if @exclude_metrics.any? {|regexp| metric.match(regexp)}
185
+
186
+ metrics_lines_for_event(event, metric, value, timestamp)
187
+ end
188
+ end
189
+
190
+ def event_timestamp(event)
191
+ event.get(@timestamp_field).to_i
192
+ end
193
+
194
+ def metrics_lines_for_event(event, metric, value, timestamp)
195
+ if event.get(metric).is_a?(Hash)
196
+ dotify(event.get(metric), metric).map do |k, v|
197
+ metrics_line(event, k, v, timestamp)
198
+ end
199
+ else
200
+ metrics_line(event, event.sprintf(metric), event.sprintf(value).to_f, timestamp)
201
+ end
202
+ end
203
+
204
+ def metrics_line(event, name, value, timestamp)
205
+ "#{construct_metric_name(event, name)} #{value} #{timestamp}"
206
+ end
207
+
208
+ # Take a nested ruby hash of the form {:a => {:b => 2}, c: => 3} and
209
+ # turn it into a hash of the form
210
+ # { "a.b" => 2, "c" => 3}
211
+ def dotify(hash, prefix = nil)
212
+ hash.reduce({}) do |acc, kv|
213
+ k, v = kv
214
+ pk = prefix ? "#{prefix}#{@nested_object_separator}#{k}" : k.to_s
215
+ if v.is_a?(Hash)
216
+ acc.merge!(dotify(v, pk))
217
+ elsif v.is_a?(Array)
218
+ # There's no right answer here, so we do nothing
219
+ @logger.warn("Array values not supported for graphite metrics! Ignoring #{hash} @ #{prefix}")
220
+ else
221
+ acc[pk] = v
222
+ end
223
+ acc
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,30 @@
1
+ Gem::Specification.new do |s|
2
+
3
+ s.name = 'logstash-output-graphite-mavlyutov'
4
+ s.version = '3.2.0'
5
+ s.licenses = ['Apache License (2.0)']
6
+ s.summary = "This output allows you to pull metrics from your logs and ship them to Graphite"
7
+ s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
8
+ s.authors = ["Elastic"]
9
+ s.email = 'info@elastic.co'
10
+ s.homepage = "http://www.elastic.co/guide/en/logstash/current/index.html"
11
+ s.require_paths = ["lib"]
12
+
13
+ # Files
14
+ s.files = Dir["lib/**/*","spec/**/*","*.gemspec","*.md","CONTRIBUTORS","Gemfile","LICENSE","NOTICE.TXT", "vendor/jar-dependencies/**/*.jar", "vendor/jar-dependencies/**/*.rb", "VERSION", "docs/**/*"]
15
+
16
+ # Tests
17
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
+
19
+ # Special flag to let us know this is actually a logstash plugin
20
+ s.metadata = { "logstash_plugin" => "true", "logstash_group" => "output" }
21
+
22
+ # Gem dependencies
23
+ s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
24
+
25
+ s.add_development_dependency 'logstash-devutils'
26
+ s.add_development_dependency 'logstash-input-generator'
27
+ s.add_development_dependency 'logstash-filter-kv'
28
+ s.add_development_dependency 'logstash-filter-ruby'
29
+ end
30
+
@@ -0,0 +1,199 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ describe LogStash::Outputs::Graphite do
6
+
7
+ let(:port) { 4939 }
8
+ let(:server) { subject.socket }
9
+
10
+ before :each do
11
+ subject.register
12
+ subject.receive(event)
13
+ end
14
+
15
+ context "with a default run" do
16
+
17
+ subject { LogStash::Outputs::Graphite.new("host" => "localhost", "port" => port, "metrics" => [ "hurray.%{foo}", "%{bar}" ]) }
18
+ let(:event) { LogStash::Event.new("foo" => "fancy", "bar" => 42) }
19
+
20
+ it "generate one element" do
21
+ expect(server.size).to eq(1)
22
+ end
23
+
24
+ it "include all metrics" do
25
+ line = server.pop
26
+ expect(line).to match(/^hurray.fancy 42.0 \d{10,}\n$/)
27
+ end
28
+ end
29
+
30
+ context "if fields_are_metrics => true" do
31
+ context "when metrics_format => ..." do
32
+ subject { LogStash::Outputs::Graphite.new("host" => "localhost",
33
+ "port" => port,
34
+ "fields_are_metrics" => true,
35
+ "include_metrics" => ["foo"],
36
+ "metrics_format" => "foo.%{@host}.sys.data.*") }
37
+
38
+ let(:event) { LogStash::Event.new("foo" => "123", "@host" => "testhost") }
39
+ let(:expected_metric_prefix) { "foo.#{event.get('@host')}.sys.data" }
40
+
41
+ context "match one key" do
42
+ it "should generate one element" do
43
+ expect(server.size).to eq(1)
44
+ end
45
+
46
+ it "should match the generated key" do
47
+ line = server.pop
48
+ expect(line).to match(/^#{expected_metric_prefix}.foo 123.0 \d{10,}\n$/)
49
+ end
50
+ end
51
+
52
+ context "when matching a nested hash" do
53
+ let(:event) { LogStash::Event.new("foo" => {"a" => 3, "c" => {"d" => 2}}, "@host" => "myhost") }
54
+
55
+ it "should create the proper formatted lines" do
56
+ lines = [server.pop, server.pop].sort # Put key 'a' first
57
+ expect(lines[0]).to match(/^#{expected_metric_prefix}.foo.a 3 \d{10,}\n$/)
58
+ expect(lines[1]).to match(/^#{expected_metric_prefix}.foo.c.d 2 \d{10,}\n$/)
59
+ end
60
+ end
61
+ end
62
+
63
+ context "match all keys" do
64
+
65
+ subject { LogStash::Outputs::Graphite.new("host" => "localhost",
66
+ "port" => port,
67
+ "fields_are_metrics" => true,
68
+ "include_metrics" => [".*"],
69
+ "metrics_format" => "foo.bar.sys.data.*") }
70
+
71
+ let(:event) { LogStash::Event.new("foo" => "123", "bar" => "42") }
72
+
73
+ let(:lines) do
74
+ dict = {}
75
+ while(!server.empty?)
76
+ line = server.pop
77
+ key = line.split(' ')[0]
78
+ dict[key] = line
79
+ end
80
+ dict
81
+ end
82
+
83
+ it "match the generated foo key" do
84
+ expect(lines['foo.bar.sys.data.foo']).to match(/^foo.bar.sys.data.foo 123.0 \d{10,}\n$/)
85
+ end
86
+
87
+ it "match the generated bar key" do
88
+ expect(lines['foo.bar.sys.data.bar']).to match(/^foo.bar.sys.data.bar 42.0 \d{10,}\n$/)
89
+ end
90
+ end
91
+
92
+ context "no match" do
93
+
94
+ subject { LogStash::Outputs::Graphite.new("host" => "localhost",
95
+ "port" => port,
96
+ "fields_are_metrics" => true,
97
+ "include_metrics" => ["notmatchinganything"],
98
+ "metrics_format" => "foo.bar.sys.data.*") }
99
+
100
+ let(:event) { LogStash::Event.new("foo" => "123", "bar" => "42") }
101
+
102
+ it "generate no event" do
103
+ expect(server.empty?).to eq(true)
104
+ end
105
+ end
106
+
107
+ context "match a key with invalid metric_format" do
108
+
109
+ subject { LogStash::Outputs::Graphite.new("host" => "localhost",
110
+ "port" => port,
111
+ "fields_are_metrics" => true,
112
+ "include_metrics" => ["foo"],
113
+ "metrics_format" => "invalidformat") }
114
+
115
+ let(:event) { LogStash::Event.new("foo" => "123") }
116
+
117
+ it "match the foo key" do
118
+ line = server.pop
119
+ expect(line).to match(/^foo 123.0 \d{10,}\n$/)
120
+ end
121
+ end
122
+ end
123
+
124
+ context "fields are metrics = false" do
125
+ context "metrics_format not set" do
126
+ context "match one key with metrics list" do
127
+
128
+ subject { LogStash::Outputs::Graphite.new("host" => "localhost",
129
+ "port" => port,
130
+ "fields_are_metrics" => false,
131
+ "include_metrics" => ["foo"],
132
+ "metrics" => [ "custom.foo", "%{foo}" ]) }
133
+
134
+ let(:event) { LogStash::Event.new("foo" => "123") }
135
+
136
+ it "match the custom.foo key" do
137
+ line = server.pop
138
+ expect(line).to match(/^custom.foo 123.0 \d{10,}\n$/)
139
+ end
140
+
141
+ context "when matching a nested hash" do
142
+ let(:event) { LogStash::Event.new("custom.foo" => {"a" => 3, "c" => {"d" => 2}}) }
143
+
144
+ it "should create the proper formatted lines" do
145
+ lines = [server.pop, server.pop].sort # Put key 'a' first
146
+ expect(lines[0]).to match(/^custom.foo.a 3 \d{10,}\n$/)
147
+ expect(lines[1]).to match(/^custom.foo.c.d 2 \d{10,}\n$/)
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ context "timestamp_field used is timestamp_new" do
155
+
156
+ let(:timestamp_new) { (Time.now + 3).to_i }
157
+
158
+ subject { LogStash::Outputs::Graphite.new("host" => "localhost",
159
+ "port" => port,
160
+ "timestamp_field" => "timestamp_new",
161
+ "metrics" => ["foo", "1"]) }
162
+
163
+ let(:event) { LogStash::Event.new("foo" => "123", "timestamp_new" => timestamp_new) }
164
+
165
+ it "timestamp matches timestamp_new" do
166
+ line = server.pop
167
+ expect(line).to match(/^foo 1.0 #{timestamp_new}\n$/)
168
+ end
169
+ end
170
+
171
+ describe "dotifying a hash" do
172
+ let(:event) { LogStash::Event.new( "metrics" => hash) }
173
+ let(:dotified) { LogStash::Outputs::Graphite.new().send(:dotify, hash) }
174
+
175
+ context "with a complex hash" do
176
+ let(:hash) { {:a => 2, :b => {:c => 3, :d => 4, :e => {:f => 5}}} }
177
+
178
+ it "should dottify correctly" do
179
+ expect(dotified).to eql({"a" => 2, "b.c" => 3, "b.d" => 4, "b.e.f" => 5})
180
+ end
181
+ end
182
+
183
+ context "with a simple hash" do
184
+ let(:hash) { {:a => 2, 5 => 4} }
185
+
186
+ it "should do nothing more than stringify the keys" do
187
+ expect(dotified).to eql("a" => 2, "5" => 4)
188
+ end
189
+ end
190
+
191
+ context "with an array value" do
192
+ let(:hash) { {:a => 2, 5 => 4, :c => [1,2,3]} }
193
+
194
+ it "should ignore array values" do
195
+ expect(dotified).to eql("a" => 2, "5" => 4)
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,11 @@
1
+ require "logstash/devutils/rspec/spec_helper"
2
+ require "logstash/outputs/graphite"
3
+ require_relative "support/server"
4
+
5
+ class LogStash::Outputs::Graphite
6
+ attr_reader :socket
7
+
8
+ def connect
9
+ @socket = Mocks::Server.new
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+ module Mocks
2
+ class Server
3
+
4
+ def initialize
5
+ @queue = Array.new
6
+ end
7
+
8
+ def size
9
+ @queue.length
10
+ end
11
+
12
+ def pop
13
+ @queue.pop
14
+ end
15
+
16
+ def stop
17
+ end
18
+
19
+ def empty?
20
+ @queue.empty?
21
+ end
22
+
23
+ def puts(data)
24
+ data.split("\n").each do |line|
25
+ @queue << "#{line}\n"
26
+ end
27
+ end
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logstash-output-graphite-mavlyutov
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Elastic
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logstash-core-plugin-api
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '1.60'
20
+ - - <=
21
+ - !ruby/object:Gem::Version
22
+ version: '2.99'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '1.60'
30
+ - - <=
31
+ - !ruby/object:Gem::Version
32
+ version: '2.99'
33
+ - !ruby/object:Gem::Dependency
34
+ name: logstash-devutils
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: logstash-input-generator
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: logstash-filter-kv
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: logstash-filter-ruby
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ description: This gem is a Logstash plugin required to be installed on top of the
90
+ Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This
91
+ gem is not a stand-alone program
92
+ email: info@elastic.co
93
+ executables: []
94
+ extensions: []
95
+ extra_rdoc_files: []
96
+ files:
97
+ - lib/logstash/outputs/graphite.rb
98
+ - spec/outputs/graphite_spec.rb
99
+ - spec/spec_helper.rb
100
+ - spec/support/server.rb
101
+ - logstash-output-graphite.gemspec
102
+ - CHANGELOG.md
103
+ - README.md
104
+ - CONTRIBUTORS
105
+ - Gemfile
106
+ - LICENSE
107
+ - NOTICE.TXT
108
+ - docs/index.asciidoc
109
+ homepage: http://www.elastic.co/guide/en/logstash/current/index.html
110
+ licenses:
111
+ - Apache License (2.0)
112
+ metadata:
113
+ logstash_plugin: 'true'
114
+ logstash_group: output
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - '>='
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.0.14.1
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: This output allows you to pull metrics from your logs and ship them to Graphite
135
+ test_files:
136
+ - spec/outputs/graphite_spec.rb
137
+ - spec/spec_helper.rb
138
+ - spec/support/server.rb