logstash-output-prometheus 0.1.0

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.
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