logstash-output-prometheus 0.1.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
+ SHA256:
3
+ metadata.gz: 667c219d828f53a1a0a6e3949297b5a7ff9f2561db62332f7d286c4abde345fd
4
+ data.tar.gz: 6043bc410be20a1dcc2722134d2cccb15d4d2715488e881098e52741bae63057
5
+ SHA512:
6
+ metadata.gz: a842d58037e05203313574bd5c9180c4a4255918a9f4321c0618728b8d38c34154d16d093db7c9bbf2475fd45cc49b2b44881a5df0adb4c38eaeb78aaa1bc4d3
7
+ data.tar.gz: ad7f9ddc0b44a250223994af94cc285ce5d8ec191707c9f29b08b908fc3b7d4da57a84ab422fcdc92053a24a1b03dd60ff4ef58e6b6a9178e72faaf35bae715a
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ## 0.1.0
2
+ - Plugin created with the logstash plugin generator
data/CONTRIBUTORS ADDED
@@ -0,0 +1,10 @@
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
+ * Spencer Malone - smalone@rsglab.com
6
+
7
+ Note: If you've sent us patches, bug reports, or otherwise contributed to
8
+ Logstash, and you aren't on the list above and want to be, please let us know
9
+ and we'll make sure you're here. Contributions from folks like you are what make
10
+ open source awesome.
data/DEVELOPER.md ADDED
@@ -0,0 +1,2 @@
1
+ # logstash-output-prometheus
2
+ Example output plugin. This should help bootstrap your effort to write your own output plugin!
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ gem 'logstash-core'
5
+ gem 'logstash-core-plugin-api'
6
+
7
+ gem 'prometheus-client', "0.10.0.pre.alpha.2"
8
+ gem "rack", ">= 1.6.11"
9
+
10
+ gem 'logstash-devutils', ">= 1.3.1", :group => [:development]
data/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ Licensed under the Apache License, Version 2.0 (the "License");
2
+ you may not use this file except in compliance with the License.
3
+ You may obtain a copy of the License at
4
+
5
+ http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # Logstash Plugin
2
+
3
+ This is a plugin for [Logstash](https://github.com/elastic/logstash).
4
+
5
+ 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.
6
+
7
+ This plugin allows you to expose metrics from logstash to a prometheus exporter, hosted by your logstash instance.
8
+
9
+
10
+ ## Building
11
+
12
+ ### Requirements
13
+ - JRuby
14
+ - JDK
15
+ - Git
16
+ - bundler
17
+
18
+ ### Build steps
19
+
20
+ `./script/build`
21
+
22
+ ## Contributing
23
+
24
+ All contributions are welcome: ideas, patches, documentation, bug reports, complaints, and even something you drew up on a napkin.
25
+
26
+ 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.
27
+
28
+ It is more important to the community that you are able to contribute.
29
+
30
+ For more information about contributing, see the [CONTRIBUTING](https://github.com/elastic/logstash/blob/master/CONTRIBUTING.md) file.
31
+
32
+ ## Examples
33
+
34
+ ```
35
+ logstash -e 'input { stdin { } }
36
+ filter {
37
+ mutate {
38
+ add_field => {
39
+ "timer" => "%{[message]}"
40
+ }
41
+ }
42
+ mutate {
43
+ convert => { "timer" => "float" }
44
+ }
45
+ }
46
+ output {
47
+ prometheus {
48
+ timer => {
49
+ histogramtest => {
50
+ description => "This is my histogram"
51
+ value => "%{[timer]}"
52
+ type => "histogram"
53
+ buckets => [0.1, 1, 5, 10]
54
+ labels => {
55
+ mylabel => "testlabel"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ prometheus {
61
+ port => 9641 # You can run multiple ports if you want different outputs scraped by different prom instances.
62
+ timer => {
63
+ summarytest => {
64
+ description => "This is my summary"
65
+ value => "%{[timer]}"
66
+ type => "summary"
67
+ }
68
+ }
69
+ }
70
+ }'
71
+ ```
72
+
73
+ ```
74
+ logstash -e 'input { stdin { } }
75
+ output {
76
+ prometheus {
77
+ increment => {
78
+ mycounter => {
79
+ description => "This is my test counter"
80
+ labels => {
81
+ value => "%{[message]}"
82
+ }
83
+ type => "counter"
84
+ }
85
+ }
86
+ }
87
+
88
+ prometheus {
89
+ increment => {
90
+ totaleventscustom => {
91
+ description => "This is my second test counter"
92
+ }
93
+ }
94
+ }
95
+ }'
96
+ ```
97
+
98
+ ## Things to keep in mind
99
+
100
+ As outlined in
@@ -0,0 +1,140 @@
1
+ # encoding: utf-8
2
+ require "logstash/outputs/base"
3
+ require 'rack'
4
+ require 'prometheus/middleware/exporter'
5
+ require 'prometheus/middleware/collector'
6
+ require 'prometheus/client'
7
+
8
+ # An prometheus output that does nothing.
9
+ class LogStash::Outputs::Prometheus < LogStash::Outputs::Base
10
+ config_name "prometheus"
11
+ concurrency :shared
12
+
13
+ config :port, :validate => :number, :default => 9640
14
+
15
+ config :increment, :validate => :hash, :default => {}
16
+ # Decrement is only available for gauges
17
+ config :decrement, :validate => :hash, :default => {}
18
+ # Decrement is only available for gauges
19
+ config :set, :validate => :hash, :default => {}
20
+
21
+ config :timer, :validate => :hash, :default => {}
22
+
23
+ public
24
+ def register
25
+ $prom_servers ||= {}
26
+ $metrics ||= {}
27
+
28
+ if $prom_servers[@port].nil?
29
+ $prom_servers[@port] = Prometheus::Client::Registry.new
30
+ prom_server = $prom_servers[@port]
31
+
32
+ app =
33
+ Rack::Builder.new(@port) do
34
+ use ::Rack::Deflater
35
+ use ::Prometheus::Middleware::Exporter, registry: prom_server
36
+
37
+ run ->(_) { [200, {'Content-Type' => 'text/html'}, ['Please access /metrics to see exposed metrics for this Logstash instance.']] }
38
+ end.to_app
39
+
40
+ @thread = Thread.new do
41
+ Rack::Handler::WEBrick.run(app, Port: @port, BindAddress: "0.0.0.0", Host: "0.0.0.0")
42
+ end
43
+ end
44
+
45
+ prom_server = $prom_servers[@port]
46
+
47
+ @increment.each do |metric_name, val|
48
+ val = setup_registry_labels(val)
49
+ if $metrics[port.to_s + metric_name].nil?
50
+ if val['type'] == "gauge"
51
+ metric = prom_server.gauge(metric_name.to_sym, docstring: val['description'], labels: val['labels'].keys)
52
+ else
53
+ metric = prom_server.counter(metric_name.to_sym, docstring: val['description'], labels: val['labels'].keys)
54
+ end
55
+ $metrics[port.to_s + metric_name] = metric
56
+ end
57
+ end
58
+
59
+ @decrement.each do |metric_name, val|
60
+ val = setup_registry_labels(val)
61
+ if $metrics[port.to_s + metric_name].nil?
62
+ metric = prom_server.gauge(metric_name.to_sym, docstring: val['description'], labels: val['labels'].keys)
63
+
64
+ $metrics[port.to_s + metric_name] = metric
65
+ end
66
+ end
67
+
68
+ @set.each do |metric_name, val|
69
+ val = setup_registry_labels(val)
70
+ if $metrics[port.to_s + metric_name].nil?
71
+ metric = prom_server.gauge(metric_name.to_sym, docstring: val['description'], labels: val['labels'].keys)
72
+
73
+ $metrics[port.to_s + metric_name] = metric
74
+ end
75
+ end
76
+
77
+ @timer.each do |metric_name, val|
78
+ val = setup_registry_labels(val)
79
+
80
+ if val['type'] == "histogram"
81
+ metric = prom_server.histogram(metric_name.to_sym, docstring: val['description'], labels: val['labels'].keys, buckets: val['buckets'])
82
+ else
83
+ metric = prom_server.summary(metric_name.to_sym, labels: val['labels'].keys, docstring: val['description'])
84
+ end
85
+
86
+ $metrics[port.to_s + metric_name] = metric
87
+ end
88
+ end # def register
89
+
90
+ def kill_thread()
91
+ @thread.kill
92
+ $prom_servers[@port] = nil
93
+ end
94
+
95
+ protected
96
+ def setup_registry_labels(val)
97
+ if val['labels'].nil?
98
+ val['labels'] = {}
99
+ end
100
+
101
+ val['labels'].keys.each do |key|
102
+ val['labels'][(key.to_sym rescue key) || key] = val['labels'].delete(key)
103
+ end
104
+
105
+ return val
106
+ end
107
+
108
+ public
109
+ def receive(event)
110
+ @increment.each do |metric_name, val|
111
+ labels = setup_event_labels(val, event)
112
+ $metrics[port.to_s + metric_name].increment(labels: labels)
113
+ end
114
+
115
+ @decrement.each do |metric_name, val|
116
+ labels = setup_event_labels(val, event)
117
+ $metrics[port.to_s + metric_name].decrement(labels: labels)
118
+ end
119
+
120
+ @set.each do |metric_name, val|
121
+ labels = setup_event_labels(val, event)
122
+ $metrics[port.to_s + metric_name].set(event.sprintf(val['value']).to_f,labels: labels)
123
+ end
124
+
125
+ @timer.each do |metric_name, val|
126
+ labels = setup_event_labels(val, event)
127
+ $metrics[port.to_s + metric_name].observe(event.sprintf(val['value']).to_f,labels: labels)
128
+ end
129
+ end # def event
130
+
131
+ protected
132
+ def setup_event_labels(val, event)
133
+ labels = {}
134
+ val['labels'].each do |label, lval|
135
+ labels[label] = event.sprintf(lval)
136
+ end
137
+
138
+ return labels
139
+ end
140
+ end # class LogStash::Outputs::Prometheus
@@ -0,0 +1,26 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'logstash-output-prometheus'
3
+ s.version = '0.1.0'
4
+ s.licenses = ['Apache-2.0']
5
+ s.summary = 'Output logstash data to a prometheus exporter'
6
+ # s.homepage = 'Nada'
7
+ s.authors = ['Spencer Malone']
8
+ s.email = 'spencer@mailchimp.com'
9
+ s.require_paths = ['lib']
10
+
11
+ # Files
12
+ s.files = Dir['lib/**/*','spec/**/*','vendor/**/*','*.gemspec','*.md','CONTRIBUTORS','Gemfile','LICENSE','NOTICE.TXT']
13
+ # Tests
14
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
15
+
16
+ # Special flag to let us know this is actually a logstash plugin
17
+ s.metadata = { "logstash_plugin" => "true", "logstash_group" => "output" }
18
+
19
+ # Gem dependencies
20
+ s.add_runtime_dependency "logstash-core-plugin-api", "~> 2.0"
21
+ s.add_runtime_dependency "logstash-codec-plain"
22
+ s.add_runtime_dependency "prometheus-client", "0.10.0.pre.alpha.2"
23
+ s.add_runtime_dependency "rack", ">= 1.6.11"
24
+
25
+ s.add_development_dependency "logstash-devutils", "~> 1.3", ">= 1.3.1"
26
+ end
@@ -0,0 +1,239 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/outputs/prometheus"
4
+ require "logstash/codecs/plain"
5
+ require "logstash/event"
6
+ require 'net/http'
7
+
8
+ describe LogStash::Outputs::Prometheus do
9
+ let(:port) { rand(2000..10000) }
10
+ let(:output) { LogStash::Outputs::Prometheus.new(properties) }
11
+
12
+ before do
13
+ output.register
14
+ end
15
+
16
+ after do
17
+ output.kill_thread
18
+ end
19
+
20
+ let(:event) do
21
+ LogStash::Event.new(
22
+ properties
23
+ )
24
+ end
25
+
26
+ shared_examples "it should expose data" do |*values|
27
+ it "should expose data" do
28
+ output.receive(event)
29
+
30
+ url = URI.parse("http://localhost:#{port}/metrics")
31
+ req = Net::HTTP::Get.new(url.to_s)
32
+
33
+ attempts = 0
34
+
35
+ begin
36
+ res = Net::HTTP.start(url.host, url.port) {|http|
37
+ http.request(req)
38
+ }
39
+ rescue
40
+ attempts++
41
+ sleep(0.1)
42
+ if attempts < 10
43
+ retry
44
+ end
45
+ end
46
+
47
+ values.each do |value|
48
+ expect(res.body).to include(value)
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "counter behavior" do
54
+ let(:properties) {
55
+ {
56
+ "port" => port,
57
+ "increment" => {
58
+ "basic_counter" => {
59
+ "description" => "Test",
60
+ "labels" => {
61
+ "mylabel" => "hi"
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ include_examples "it should expose data", 'basic_counter{mylabel="hi"} 1', "# TYPE basic_counter counter", "# HELP basic_counter Test"
69
+ end
70
+
71
+ describe "gauge behavior" do
72
+ describe "increment" do
73
+ let(:properties) {
74
+ {
75
+ "port" => port,
76
+ "increment" => {
77
+ "basic_gauge" => {
78
+ "description" => "Test1",
79
+ "type" => "gauge"
80
+ }
81
+ }
82
+ }
83
+ }
84
+ include_examples "it should expose data", "basic_gauge 1.0", "# TYPE basic_gauge gauge", "# HELP basic_gauge Test1"
85
+ end
86
+
87
+ describe "decrement" do
88
+ let(:properties) {
89
+ {
90
+ "port" => port,
91
+ "decrement" => {
92
+ "basic_gauge" => {
93
+ "description" => "Testone",
94
+ "type" => "gauge"
95
+ }
96
+ }
97
+ }
98
+ }
99
+ include_examples "it should expose data", "basic_gauge -1.0", "# TYPE basic_gauge gauge", "# HELP basic_gauge Testone"
100
+ end
101
+
102
+ describe "set" do
103
+ let(:properties) {
104
+ {
105
+ "port" => port,
106
+ "set" => {
107
+ "basic_gauge" => {
108
+ "description" => "Testone",
109
+ "type" => "gauge",
110
+ "value" => "123"
111
+ }
112
+ }
113
+ }
114
+ }
115
+ include_examples "it should expose data", "basic_gauge 123.0", "# TYPE basic_gauge gauge", "# HELP basic_gauge Testone"
116
+ end
117
+ end
118
+
119
+ describe "summary behavior" do
120
+ let(:properties) {
121
+ {
122
+ "port" => port,
123
+ "timer" => {
124
+ "huh" => {
125
+ "description" => "noway",
126
+ "type" => "summary",
127
+ "value" => "11"
128
+ }
129
+ }
130
+ }
131
+ }
132
+ include_examples "it should expose data", "huh_sum 11.0", "huh_count 1.0", "# TYPE huh summary", "# HELP huh noway"
133
+ end
134
+
135
+ describe "histogram behavior" do
136
+ describe "description" do
137
+ let(:properties) {
138
+ {
139
+ "port" => port,
140
+ "timer" => {
141
+ "history" => {
142
+ "description" => "abe",
143
+ "type" => "histogram",
144
+ "buckets" => [1, 5, 10],
145
+ "value" => "0"
146
+ }
147
+ }
148
+ }
149
+ }
150
+ include_examples "it should expose data", "# TYPE history histogram", "# HELP history abe"
151
+ end
152
+
153
+ describe "sum and count" do
154
+ let(:properties) {
155
+ {
156
+ "port" => port,
157
+ "timer" => {
158
+ "history" => {
159
+ "description" => "abe",
160
+ "type" => "histogram",
161
+ "buckets" => [1, 5, 10],
162
+ "value" => "111"
163
+ }
164
+ }
165
+ }
166
+ }
167
+ include_examples "it should expose data", "history_sum 111.0", "history_count 1.0"
168
+ end
169
+
170
+ describe "minimum histogram" do
171
+ let(:properties) {
172
+ {
173
+ "port" => port,
174
+ "timer" => {
175
+ "history" => {
176
+ "description" => "abe",
177
+ "type" => "histogram",
178
+ "buckets" => [1, 5, 10],
179
+ "value" => "0"
180
+ }
181
+ }
182
+ }
183
+ }
184
+ include_examples "it should expose data", 'history_bucket{le="1"} 1.0', 'history_bucket{le="5"} 1.0', 'history_bucket{le="10"} 1.0', 'history_bucket{le="+Inf"} 1.0'
185
+ end
186
+
187
+ describe "middle histogram" do
188
+ let(:properties) {
189
+ {
190
+ "port" => port,
191
+ "timer" => {
192
+ "history" => {
193
+ "description" => "abe",
194
+ "type" => "histogram",
195
+ "buckets" => [1, 5, 10],
196
+ "value" => "5"
197
+ }
198
+ }
199
+ }
200
+ }
201
+ include_examples "it should expose data", 'history_bucket{le="1"} 0.0', 'history_bucket{le="5"} 0.0', 'history_bucket{le="10"} 1.0', 'history_bucket{le="+Inf"} 1.0'
202
+ end
203
+
204
+ describe "max histogram" do
205
+ let(:properties) {
206
+ {
207
+ "port" => port,
208
+ "timer" => {
209
+ "history" => {
210
+ "description" => "abe",
211
+ "type" => "histogram",
212
+ "buckets" => [1, 5, 10],
213
+ "value" => "9"
214
+ }
215
+ }
216
+ }
217
+ }
218
+ include_examples "it should expose data", 'history_bucket{le="1"} 0.0', 'history_bucket{le="5"} 0.0', 'history_bucket{le="10"} 1.0', 'history_bucket{le="+Inf"} 1.0'
219
+ end
220
+
221
+ describe "beyond max histogram" do
222
+ let(:properties) {
223
+ {
224
+ "port" => port,
225
+ "timer" => {
226
+ "history" => {
227
+ "description" => "abe",
228
+ "type" => "histogram",
229
+ "buckets" => [1, 5, 10],
230
+ "value" => "100"
231
+ }
232
+ }
233
+ }
234
+ }
235
+ include_examples "it should expose data", 'history_bucket{le="1"} 0.0', 'history_bucket{le="5"} 0.0', 'history_bucket{le="10"} 0.0', 'history_bucket{le="+Inf"} 1.0'
236
+ end
237
+
238
+ end
239
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logstash-output-prometheus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Spencer Malone
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-09-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ name: logstash-core-plugin-api
20
+ prerelease: false
21
+ type: :runtime
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ name: logstash-codec-plain
34
+ prerelease: false
35
+ type: :runtime
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - '='
45
+ - !ruby/object:Gem::Version
46
+ version: 0.10.0.pre.alpha.2
47
+ name: prometheus-client
48
+ prerelease: false
49
+ type: :runtime
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.10.0.pre.alpha.2
55
+ - !ruby/object:Gem::Dependency
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.6.11
61
+ name: rack
62
+ prerelease: false
63
+ type: :runtime
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 1.6.11
69
+ - !ruby/object:Gem::Dependency
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.3'
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 1.3.1
78
+ name: logstash-devutils
79
+ prerelease: false
80
+ type: :development
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - "~>"
84
+ - !ruby/object:Gem::Version
85
+ version: '1.3'
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 1.3.1
89
+ description:
90
+ email: spencer@mailchimp.com
91
+ executables: []
92
+ extensions: []
93
+ extra_rdoc_files: []
94
+ files:
95
+ - CHANGELOG.md
96
+ - CONTRIBUTORS
97
+ - DEVELOPER.md
98
+ - Gemfile
99
+ - LICENSE
100
+ - README.md
101
+ - lib/logstash/outputs/prometheus.rb
102
+ - logstash-output-prometheus.gemspec
103
+ - spec/outputs/prometheus_spec.rb
104
+ homepage:
105
+ licenses:
106
+ - Apache-2.0
107
+ metadata:
108
+ logstash_plugin: 'true'
109
+ logstash_group: output
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.7.6
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Output logstash data to a prometheus exporter
130
+ test_files:
131
+ - spec/outputs/prometheus_spec.rb