fluent-plugin-prometheus 1.7.0 → 1.8.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.
@@ -24,14 +24,14 @@ module Fluent::Plugin
24
24
  def configure(conf)
25
25
  super
26
26
  hostname = Socket.gethostname
27
- expander = Fluent::Plugin::Prometheus.placeholder_expander(log)
28
- placeholders = expander.prepare_placeholders({'hostname' => hostname, 'worker_id' => fluentd_worker_id})
27
+ expander_builder = Fluent::Plugin::Prometheus.placeholder_expander(log)
28
+ expander = expander_builder.build({ 'hostname' => hostname, 'worker_id' => fluentd_worker_id })
29
29
  @base_labels = parse_labels_elements(conf)
30
30
  @base_labels.each do |key, value|
31
31
  unless value.is_a?(String)
32
32
  raise Fluent::ConfigError, "record accessor syntax is not available in prometheus_monitor"
33
33
  end
34
- @base_labels[key] = expander.expand(value, placeholders)
34
+ @base_labels[key] = expander.expand(value)
35
35
  end
36
36
 
37
37
  if defined?(Fluent::Plugin) && defined?(Fluent::Plugin::MonitorAgentInput)
@@ -43,14 +43,14 @@ module Fluent::Plugin
43
43
  def configure(conf)
44
44
  super
45
45
  hostname = Socket.gethostname
46
- expander = Fluent::Plugin::Prometheus.placeholder_expander(log)
47
- placeholders = expander.prepare_placeholders({'hostname' => hostname, 'worker_id' => fluentd_worker_id})
46
+ expander_builder = Fluent::Plugin::Prometheus.placeholder_expander(log)
47
+ expander = expander_builder.build({ 'hostname' => hostname, 'worker_id' => fluentd_worker_id })
48
48
  @base_labels = parse_labels_elements(conf)
49
49
  @base_labels.each do |key, value|
50
50
  unless value.is_a?(String)
51
51
  raise Fluent::ConfigError, "record accessor syntax is not available in prometheus_output_monitor"
52
52
  end
53
- @base_labels[key] = expander.expand(value, placeholders)
53
+ @base_labels[key] = expander.expand(value)
54
54
  end
55
55
 
56
56
  if defined?(Fluent::Plugin) && defined?(Fluent::Plugin::MonitorAgentInput)
@@ -28,14 +28,14 @@ module Fluent::Plugin
28
28
  def configure(conf)
29
29
  super
30
30
  hostname = Socket.gethostname
31
- expander = Fluent::Plugin::Prometheus.placeholder_expander(log)
32
- placeholders = expander.prepare_placeholders({'hostname' => hostname, 'worker_id' => fluentd_worker_id})
31
+ expander_builder = Fluent::Plugin::Prometheus.placeholder_expander(log)
32
+ expander = expander_builder.build({ 'hostname' => hostname, 'worker_id' => fluentd_worker_id })
33
33
  @base_labels = parse_labels_elements(conf)
34
34
  @base_labels.each do |key, value|
35
35
  unless value.is_a?(String)
36
36
  raise Fluent::ConfigError, "record accessor syntax is not available in prometheus_tail_monitor"
37
37
  end
38
- @base_labels[key] = expander.expand(value, placeholders)
38
+ @base_labels[key] = expander.expand(value)
39
39
  end
40
40
 
41
41
  if defined?(Fluent::Plugin) && defined?(Fluent::Plugin::MonitorAgentInput)
@@ -1,6 +1,6 @@
1
1
  require 'prometheus/client'
2
2
  require 'prometheus/client/formats/text'
3
- require 'fluent/plugin/filter_record_transformer'
3
+ require 'fluent/plugin/prometheus/placeholder_expander'
4
4
 
5
5
  module Fluent
6
6
  module Plugin
@@ -82,14 +82,13 @@ module Fluent
82
82
  end
83
83
 
84
84
  def self.placeholder_expander(log)
85
- # Use internal class in order to expand placeholder
86
- Fluent::Plugin::RecordTransformerFilter::PlaceholderExpander.new(log: log)
85
+ Fluent::Plugin::Prometheus::ExpandBuilder.new(log: log)
87
86
  end
88
87
 
89
88
  def configure(conf)
90
89
  super
91
90
  @placeholder_values = {}
92
- @placeholder_expander = Fluent::Plugin::Prometheus.placeholder_expander(log)
91
+ @placeholder_expander_builder = Fluent::Plugin::Prometheus.placeholder_expander(log)
93
92
  @hostname = Socket.gethostname
94
93
  end
95
94
 
@@ -101,10 +100,10 @@ module Fluent
101
100
  }
102
101
 
103
102
  placeholders = record.merge(@placeholder_values[tag])
104
- placeholders = @placeholder_expander.prepare_placeholders(placeholders)
103
+ expander = @placeholder_expander_builder.build(placeholders)
105
104
  metrics.each do |metric|
106
105
  begin
107
- metric.instrument(record, @placeholder_expander, placeholders)
106
+ metric.instrument(record, expander)
108
107
  rescue => e
109
108
  log.warn "prometheus: failed to instrument a metric.", error_class: e.class, error: e, tag: tag, name: metric.name
110
109
  router.emit_error_event(tag, time, record, e)
@@ -121,10 +120,10 @@ module Fluent
121
120
 
122
121
  es.each do |time, record|
123
122
  placeholders = record.merge(placeholder_values)
124
- placeholders = @placeholder_expander.prepare_placeholders(placeholders)
123
+ expander = @placeholder_expander_builder.build(placeholders)
125
124
  metrics.each do |metric|
126
125
  begin
127
- metric.instrument(record, @placeholder_expander, placeholders)
126
+ metric.instrument(record, expander)
128
127
  rescue => e
129
128
  log.warn "prometheus: failed to instrument a metric.", error_class: e.class, error: e, tag: tag, name: metric.name
130
129
  router.emit_error_event(tag, time, record, e)
@@ -154,11 +153,11 @@ module Fluent
154
153
  @base_labels = labels.merge(@base_labels)
155
154
  end
156
155
 
157
- def labels(record, expander, placeholders)
156
+ def labels(record, expander)
158
157
  label = {}
159
158
  @base_labels.each do |k, v|
160
159
  if v.is_a?(String)
161
- label[k] = expander.expand(v, placeholders)
160
+ label[k] = expander.expand(v)
162
161
  else
163
162
  label[k] = v.call(record)
164
163
  end
@@ -195,14 +194,14 @@ module Fluent
195
194
  end
196
195
  end
197
196
 
198
- def instrument(record, expander, placeholders)
197
+ def instrument(record, expander)
199
198
  if @key.is_a?(String)
200
199
  value = record[@key]
201
200
  else
202
201
  value = @key.call(record)
203
202
  end
204
203
  if value
205
- @gauge.set(labels(record, expander, placeholders), value)
204
+ @gauge.set(labels(record, expander), value)
206
205
  end
207
206
  end
208
207
  end
@@ -217,7 +216,7 @@ module Fluent
217
216
  end
218
217
  end
219
218
 
220
- def instrument(record, expander, placeholders)
219
+ def instrument(record, expander)
221
220
  # use record value of the key if key is specified, otherwise just increment
222
221
  if @key.nil?
223
222
  value = 1
@@ -230,7 +229,7 @@ module Fluent
230
229
  # ignore if record value is nil
231
230
  return if value.nil?
232
231
 
233
- @counter.increment(labels(record, expander, placeholders), value)
232
+ @counter.increment(labels(record, expander), value)
234
233
  end
235
234
  end
236
235
 
@@ -248,14 +247,14 @@ module Fluent
248
247
  end
249
248
  end
250
249
 
251
- def instrument(record, expander, placeholders)
250
+ def instrument(record, expander)
252
251
  if @key.is_a?(String)
253
252
  value = record[@key]
254
253
  else
255
254
  value = @key.call(record)
256
255
  end
257
256
  if value
258
- @summary.observe(labels(record, expander, placeholders), value)
257
+ @summary.observe(labels(record, expander), value)
259
258
  end
260
259
  end
261
260
  end
@@ -281,14 +280,14 @@ module Fluent
281
280
  end
282
281
  end
283
282
 
284
- def instrument(record, expander, placeholders)
283
+ def instrument(record, expander)
285
284
  if @key.is_a?(String)
286
285
  value = record[@key]
287
286
  else
288
287
  value = @key.call(record)
289
288
  end
290
289
  if value
291
- @histogram.observe(labels(record, expander, placeholders), value)
290
+ @histogram.observe(labels(record, expander), value)
292
291
  end
293
292
  end
294
293
  end
@@ -0,0 +1,132 @@
1
+ module Fluent
2
+ module Plugin
3
+ module Prometheus
4
+ class ExpandBuilder
5
+ def self.build(placeholder, log:)
6
+ new(log: log).build(placeholder)
7
+ end
8
+
9
+ def initialize(log:)
10
+ @log = log
11
+ end
12
+
13
+ def build(placeholder_values)
14
+ placeholders = {}
15
+ placeholder_values.each do |key, value|
16
+ case value
17
+ when Array
18
+ size = value.size
19
+ value.each_with_index do |v, i|
20
+ placeholders["${#{key}[#{i}]}"] = v
21
+ placeholders["${#{key}[#{i - size}]}"] = v
22
+ end
23
+ when Hash
24
+ value.each do |k, v|
25
+ placeholders[%(${#{key}["#{k}"]})] = v
26
+ end
27
+ else
28
+ if key == 'tag'
29
+ placeholders.merge!(build_tag(value))
30
+ else
31
+ placeholders["${#{key}}"] = value
32
+ end
33
+ end
34
+ end
35
+
36
+ Fluent::Plugin::Prometheus::ExpandBuilder::PlaceholderExpander.new(@log, placeholders)
37
+ end
38
+
39
+ private
40
+
41
+ def build_tag(tag)
42
+ tags = tag.split('.')
43
+
44
+ placeholders = { '${tag}' => tag }
45
+
46
+ size = tags.size
47
+
48
+ tags.each_with_index do |v, i|
49
+ placeholders["${tag_parts[#{i}]}"] = v
50
+ placeholders["${tag_parts[#{i - size}]}"] = v
51
+ end
52
+
53
+ tag_prefix(tags).each_with_index do |v, i|
54
+ placeholders["${tag_prefix[#{i}]}"] = v
55
+ end
56
+
57
+ tag_suffix(tags).each_with_index do |v, i|
58
+ placeholders["${tag_suffix[#{i}]}"] = v
59
+ end
60
+
61
+ placeholders
62
+ end
63
+
64
+ def tag_prefix(tags)
65
+ tags = tags.dup
66
+ return [] if tags.empty?
67
+
68
+ ret = [tags.shift]
69
+ tags.each.with_index(1) do |tag, i|
70
+ ret[i] = "#{ret[i-1]}.#{tag}"
71
+ end
72
+ ret
73
+ end
74
+
75
+ def tag_suffix(tags)
76
+ return [] if tags.empty?
77
+
78
+ tags = tags.dup.reverse
79
+ ret = [tags.shift]
80
+ tags.each.with_index(1) do |tag, i|
81
+ ret[i] = "#{tag}.#{ret[i-1]}"
82
+ end
83
+ ret
84
+ end
85
+
86
+ class PlaceholderExpander
87
+ PLACEHOLDER_REGEX = /(\${[^\[}]+(\[[^\]]+\])?})/.freeze
88
+
89
+ attr_reader :placeholder
90
+
91
+ def initialize(log, placeholder)
92
+ @placeholder = placeholder
93
+ @log = log
94
+ @expander_cache = {}
95
+ end
96
+
97
+ def merge_placeholder(placeholder)
98
+ @placeholder.merge!(placeholder)
99
+ end
100
+
101
+ def expand(str, dynamic_placeholders: nil)
102
+ expander = if dynamic_placeholders
103
+ if @expander_cache[dynamic_placeholders]
104
+ @expander_cache[dynamic_placeholders]
105
+ else
106
+ e = ExpandBuilder.build(dynamic_placeholders, log: @log)
107
+ e.merge_placeholder(@placeholder)
108
+ @expander_cache[dynamic_placeholders] = e
109
+ e
110
+ end
111
+ else
112
+ self
113
+ end
114
+
115
+ expander.expand!(str)
116
+ end
117
+
118
+ protected
119
+
120
+ def expand!(str)
121
+ str.gsub(PLACEHOLDER_REGEX) { |value|
122
+ @placeholder.fetch(value) do
123
+ @log.warn("unknown placeholder `#{value}` found")
124
+ value # return as it is
125
+ end
126
+ }
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -6,7 +6,11 @@ require_relative 'shared'
6
6
  describe Fluent::Plugin::PrometheusFilter do
7
7
  let(:tag) { 'prometheus.test' }
8
8
  let(:driver) { Fluent::Test::Driver::Filter.new(Fluent::Plugin::PrometheusFilter).configure(config) }
9
- let(:registry) { ::Prometheus::Client.registry }
9
+ let(:registry) { ::Prometheus::Client::Registry.new }
10
+
11
+ before do
12
+ allow(Prometheus::Client).to receive(:registry).and_return(registry)
13
+ end
10
14
 
11
15
  describe '#configure' do
12
16
  it_behaves_like 'output configuration'
@@ -14,22 +18,28 @@ describe Fluent::Plugin::PrometheusFilter do
14
18
 
15
19
  describe '#run' do
16
20
  let(:message) { {"foo" => 100, "bar" => 100, "baz" => 100, "qux" => 10} }
17
- let(:es) {
18
- driver.run(default_tag: tag) { driver.feed(event_time, message) }
19
- driver.filtered_records
20
- }
21
21
 
22
22
  context 'simple config' do
23
- include_context 'simple_config'
23
+ let(:config) {
24
+ BASE_CONFIG + %(
25
+ <metric>
26
+ name simple
27
+ type counter
28
+ desc Something foo.
29
+ key foo
30
+ </metric>
31
+ )
32
+ }
24
33
 
25
34
  it 'adds a new counter metric' do
26
- expect(registry.metrics.map(&:name)).not_to include(name)
27
- es
28
- expect(registry.metrics.map(&:name)).to include(name)
35
+ expect(registry.metrics.map(&:name)).not_to eq([:simple])
36
+ driver.run(default_tag: tag) { driver.feed(event_time, message) }
37
+ expect(registry.metrics.map(&:name)).to eq([:simple])
29
38
  end
30
39
 
31
40
  it 'should keep original message' do
32
- expect(es.first).to eq(message)
41
+ driver.run(default_tag: tag) { driver.feed(event_time, message) }
42
+ expect(driver.filtered_records.first).to eq(message)
33
43
  end
34
44
  end
35
45
 
@@ -23,7 +23,6 @@ describe Fluent::Plugin::PrometheusMonitorInput do
23
23
  ]
24
24
 
25
25
  let(:config) { MONITOR_CONFIG }
26
- let(:port) { 24231 }
27
26
  let(:driver) { Fluent::Test::Driver::Input.new(Fluent::Plugin::PrometheusMonitorInput).configure(config) }
28
27
 
29
28
  describe '#configure' do
@@ -0,0 +1,225 @@
1
+ require 'spec_helper'
2
+ require 'fluent/plugin/in_prometheus'
3
+ require 'fluent/test/driver/input'
4
+
5
+ require 'net/http'
6
+
7
+ describe Fluent::Plugin::PrometheusInput do
8
+ CONFIG = %[
9
+ @type prometheus
10
+ ]
11
+
12
+ LOCAL_CONFIG = %[
13
+ @type prometheus
14
+ bind 127.0.0.1
15
+ ]
16
+
17
+ let(:config) { CONFIG }
18
+ let(:port) { 24231 }
19
+ let(:driver) { Fluent::Test::Driver::Input.new(Fluent::Plugin::PrometheusInput).configure(config) }
20
+
21
+ describe '#configure' do
22
+ describe 'bind' do
23
+ let(:config) { CONFIG + %[
24
+ bind 127.0.0.1
25
+ ] }
26
+ it 'should be configurable' do
27
+ expect(driver.instance.bind).to eq('127.0.0.1')
28
+ end
29
+ end
30
+
31
+ describe 'port' do
32
+ let(:config) { CONFIG + %[
33
+ port 8888
34
+ ] }
35
+ it 'should be configurable' do
36
+ expect(driver.instance.port).to eq(8888)
37
+ end
38
+ end
39
+
40
+ describe 'metrics_path' do
41
+ let(:config) { CONFIG + %[
42
+ metrics_path /_test
43
+ ] }
44
+ it 'should be configurable' do
45
+ expect(driver.instance.metrics_path).to eq('/_test')
46
+ end
47
+ end
48
+ end
49
+
50
+ describe '#start' do
51
+ context 'with transport section' do
52
+ let(:config) do
53
+ %[
54
+ @type prometheus
55
+ bind 127.0.0.1
56
+ <transport tls>
57
+ insecure true
58
+ </transport>
59
+ ]
60
+ end
61
+
62
+ it 'returns 200' do
63
+ driver.run(timeout: 1) do
64
+ Net::HTTP.start('127.0.0.1', port, verify_mode: OpenSSL::SSL::VERIFY_NONE, use_ssl: true) do |http|
65
+ req = Net::HTTP::Get.new('/metrics')
66
+ res = http.request(req)
67
+ expect(res.code).to eq('200')
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ context 'old parameters are given' do
74
+ context 'when extra_conf is used' do
75
+ let(:config) do
76
+ %[
77
+ @type prometheus
78
+ bind 127.0.0.1
79
+ <ssl>
80
+ enable true
81
+ extra_conf { "SSLCertName": [["CN", "nobody"], ["DC", "example"]] }
82
+ </ssl>
83
+ ]
84
+ end
85
+
86
+ it 'uses webrick' do
87
+ expect(driver.instance).to receive(:start_webrick).once
88
+ driver.run(timeout: 1)
89
+ end
90
+
91
+ it 'returns 200' do
92
+ driver.run(timeout: 1) do
93
+ Net::HTTP.start('127.0.0.1', port, verify_mode: OpenSSL::SSL::VERIFY_NONE, use_ssl: true) do |http|
94
+ req = Net::HTTP::Get.new('/metrics')
95
+ res = http.request(req)
96
+ expect(res.code).to eq('200')
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ context 'cert_path and private_key_path combination' do
103
+ let(:config) do
104
+ %[
105
+ @type prometheus
106
+ bind 127.0.0.1
107
+ <ssl>
108
+ enable true
109
+ certificate_path path
110
+ private_key_path path1
111
+ </ssl>
112
+ ]
113
+ end
114
+
115
+ it 'converts them into new transport section' do
116
+ expect(driver.instance).to receive(:http_server_create_http_server).with(
117
+ :in_prometheus_server,
118
+ addr: anything,
119
+ logger: anything,
120
+ port: anything,
121
+ proto: :tls,
122
+ tls_opts: { 'cert_path' => 'path', 'private_key_path' => 'path1' }
123
+ ).once
124
+
125
+ driver.run(timeout: 1)
126
+ end
127
+ end
128
+
129
+ context 'insecure and ca_path' do
130
+ let(:config) do
131
+ %[
132
+ @type prometheus
133
+ bind 127.0.0.1
134
+ <ssl>
135
+ enable true
136
+ ca_path path
137
+ </ssl>
138
+ ]
139
+ end
140
+
141
+ it 'converts them into new transport section' do
142
+ expect(driver.instance).to receive(:http_server_create_http_server).with(
143
+ :in_prometheus_server,
144
+ addr: anything,
145
+ logger: anything,
146
+ port: anything,
147
+ proto: :tls,
148
+ tls_opts: { 'ca_path' => 'path', 'insecure' => true }
149
+ ).once
150
+
151
+ driver.run(timeout: 1)
152
+ end
153
+ end
154
+
155
+ context 'when only private_key_path is geven' do
156
+ let(:config) do
157
+ %[
158
+ @type prometheus
159
+ bind 127.0.0.1
160
+ <ssl>
161
+ enable true
162
+ private_key_path path
163
+ </ssl>
164
+ ]
165
+ end
166
+
167
+ it 'raises ConfigError' do
168
+ expect { driver.run(timeout: 1) }.to raise_error(Fluent::ConfigError, 'both certificate_path and private_key_path must be defined')
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ describe '#run' do
175
+ context '/metrics' do
176
+ let(:config) { LOCAL_CONFIG }
177
+ it 'returns 200' do
178
+ driver.run(timeout: 1) do
179
+ Net::HTTP.start("127.0.0.1", port) do |http|
180
+ req = Net::HTTP::Get.new("/metrics")
181
+ res = http.request(req)
182
+ expect(res.code).to eq('200')
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+ context '/foo' do
189
+ let(:config) { LOCAL_CONFIG }
190
+ it 'does not return 200' do
191
+ driver.run(timeout: 1) do
192
+ Net::HTTP.start("127.0.0.1", port) do |http|
193
+ req = Net::HTTP::Get.new("/foo")
194
+ res = http.request(req)
195
+ expect(res.code).not_to eq('200')
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ describe '#run_multi_workers' do
203
+ context '/metrics' do
204
+ Fluent::SystemConfig.overwrite_system_config('workers' => 4) do
205
+ let(:config) { CONFIG + %[
206
+ port #{port - 2}
207
+ ] }
208
+
209
+ it 'should configure port using sequential number' do
210
+ driver = Fluent::Test::Driver::Input.new(Fluent::Plugin::PrometheusInput)
211
+ driver.instance.instance_eval{ @_fluentd_worker_id = 2 }
212
+ driver.configure(config)
213
+ expect(driver.instance.port).to eq(port)
214
+ driver.run(timeout: 1) do
215
+ Net::HTTP.start("127.0.0.1", port) do |http|
216
+ req = Net::HTTP::Get.new("/metrics")
217
+ res = http.request(req)
218
+ expect(res.code).to eq('200')
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end