fluent-plugin-yohoushi 0.0.5 → 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 +4 -4
- data/CHANGELOG.md +6 -0
- data/fluent-plugin-yohoushi.gemspec +1 -1
- data/lib/fluent/plugin/out_yohoushi.rb +208 -196
- data/spec/out_yohoushi_spec.rb +4 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65e9f7d306f6d8b262d7af0585fd52b898ed2c3b
|
4
|
+
data.tar.gz: d9caea2616664dd9d2bbffc6abefba2855d9d5ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f1e6c7d991049ad623219a997452a279aa0a105c9ea2353ea5d4549b3df0f92a2f74169e74e4619348cb74966a2c222ee1b692e290b5da501ca191313ef6832
|
7
|
+
data.tar.gz: 8ec234f0bc39e5c4df6a8f5804ccb29759fd256e96966ad3733730f3c870c4063972df958d557fa626c537a9a6bb095616984f0bdb925ebe0bc36decd5282a51
|
data/CHANGELOG.md
CHANGED
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "fluent-plugin-yohoushi"
|
6
|
-
s.version = "0.0
|
6
|
+
s.version = "0.1.0"
|
7
7
|
s.authors = ["Naotoshi Seo"]
|
8
8
|
s.email = ["sonots@gmail.com"]
|
9
9
|
s.homepage = "https://github.com/sonots/fluent-plugin-yohoushi"
|
@@ -1,227 +1,239 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
config_param :base_uri, :string, :default => nil
|
15
|
-
(1..MAPPING_MAX_NUM).each {|i| config_param "mapping#{i}".to_sym, :string, :default => nil }
|
16
|
-
config_param :key_pattern, :string, :default => nil
|
17
|
-
(1..KEY_MAX_NUM).each {|i| config_param "key#{i}".to_sym, :string, :default => nil }
|
18
|
-
config_param :enable_float_number, :bool, :default => false
|
19
|
-
config_param :mode, :default => :gauge do |val|
|
20
|
-
case val.downcase
|
21
|
-
when 'gauge'
|
22
|
-
:gauge
|
23
|
-
when 'count'
|
24
|
-
:count
|
25
|
-
when 'modified'
|
26
|
-
:modified
|
27
|
-
when 'derive'
|
28
|
-
:derive
|
29
|
-
else
|
30
|
-
raise Fluent::ConfigError, "stdout output output_type should be `gauge`, `count`, `modified`, or `derive`"
|
1
|
+
module Fluent
|
2
|
+
class YohoushiOutput < BufferedOutput
|
3
|
+
Plugin.register_output('yohoushi', self)
|
4
|
+
|
5
|
+
MAPPING_MAX_NUM = 20
|
6
|
+
KEY_MAX_NUM = 20
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super
|
10
|
+
require 'socket'
|
11
|
+
require 'multiforecast-client'
|
12
|
+
require 'yohoushi-client'
|
31
13
|
end
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
next unless conf["mapping#{i}"]
|
51
|
-
from, to = conf["mapping#{i}"].split(/ +/, 2)
|
52
|
-
raise Fluent::ConfigError, "mapping#{i} does not contain 2 parameters" unless to
|
53
|
-
@mapping[from] = to
|
54
|
-
end
|
55
|
-
@client = MultiForecast::Client.new('mapping' => @mapping) unless @mapping.empty?
|
56
|
-
end
|
57
|
-
raise Fluent::ConfigError, "Either of `base_uri` or `mapping1` must be specified" unless @client
|
58
|
-
|
59
|
-
if @key_pattern
|
60
|
-
key_pattern, @key_pattern_path = @key_pattern.split(/ +/, 2)
|
61
|
-
raise Fluent::ConfigError, "key_pattern does not contain 2 parameters" unless @key_pattern_path
|
62
|
-
@key_pattern = Regexp.compile(key_pattern)
|
63
|
-
else
|
64
|
-
@keys = {}
|
65
|
-
(1..KEY_MAX_NUM).each do |i|
|
66
|
-
next unless conf["key#{i}"]
|
67
|
-
key, path = conf["key#{i}"].split(/ +/, 2)
|
68
|
-
raise Fluent::ConfigError, "key#{i} does not contain 2 parameters" unless path
|
69
|
-
@keys[key] = path
|
14
|
+
|
15
|
+
config_param :base_uri, :string, :default => nil
|
16
|
+
(1..MAPPING_MAX_NUM).each {|i| config_param "mapping#{i}".to_sym, :string, :default => nil }
|
17
|
+
config_param :key_pattern, :string, :default => nil
|
18
|
+
(1..KEY_MAX_NUM).each {|i| config_param "key#{i}".to_sym, :string, :default => nil }
|
19
|
+
config_param :enable_float_number, :bool, :default => false
|
20
|
+
config_param :mode, :default => :gauge do |val|
|
21
|
+
case val.downcase
|
22
|
+
when 'gauge'
|
23
|
+
:gauge
|
24
|
+
when 'count'
|
25
|
+
:count
|
26
|
+
when 'modified'
|
27
|
+
:modified
|
28
|
+
when 'derive'
|
29
|
+
:derive
|
30
|
+
else
|
31
|
+
raise ConfigError, "stdout output output_type should be `gauge`, `count`, `modified`, or `derive`"
|
70
32
|
end
|
71
33
|
end
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
34
|
+
config_param :enable_ruby, :bool, :default => true # true for lower version compatibility
|
35
|
+
|
36
|
+
# Override default parameters of Bufferedoutput options
|
37
|
+
config_param :buffer_type, :string, :default => 'memory'
|
38
|
+
config_param :flush_interval, :time, :default => 0 # we can not wait 1 minute to create 1 minute graphs (originally, 60)
|
39
|
+
config_param :try_flush_interval, :float, :default => 1 # we would be able to shorten more
|
40
|
+
config_param :retry_limit, :integer, :default => 1 # growthforecast requires a realtime post, so retry only once (originally, 17)
|
41
|
+
config_param :retry_wait, :time, :default => 1.0
|
42
|
+
config_param :max_retry_wait, :time, :default => nil
|
43
|
+
config_param :num_threads, :integer, :default => 1
|
44
|
+
config_param :queued_chunk_flush_interval, :time, :default => 1
|
45
|
+
|
46
|
+
# for test
|
47
|
+
attr_reader :client
|
48
|
+
attr_reader :mapping
|
49
|
+
attr_reader :keys
|
50
|
+
attr_reader :key_pattern
|
51
|
+
attr_reader :key_pattern_path
|
52
|
+
|
53
|
+
def configure(conf)
|
54
|
+
super
|
55
|
+
|
56
|
+
if @base_uri
|
57
|
+
@client = Yohoushi::Client.new(@base_uri)
|
81
58
|
else
|
82
|
-
|
59
|
+
@mapping = {}
|
60
|
+
(1..MAPPING_MAX_NUM).each do |i|
|
61
|
+
next unless conf["mapping#{i}"]
|
62
|
+
from, to = conf["mapping#{i}"].split(/ +/, 2)
|
63
|
+
raise ConfigError, "mapping#{i} does not contain 2 parameters" unless to
|
64
|
+
@mapping[from] = to
|
65
|
+
end
|
66
|
+
@client = MultiForecast::Client.new('mapping' => @mapping) unless @mapping.empty?
|
83
67
|
end
|
68
|
+
raise ConfigError, "Either of `base_uri` or `mapping1` must be specified" unless @client
|
84
69
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
end
|
97
|
-
|
98
|
-
def post(path, number)
|
99
|
-
if @enable_float_number
|
100
|
-
@client.post_graph(path, { 'number' => number.to_f, 'mode' => @mode.to_s })
|
101
|
-
else
|
102
|
-
@client.post_graph(path, { 'number' => number.to_i, 'mode' => @mode.to_s })
|
103
|
-
end
|
104
|
-
rescue => e
|
105
|
-
$log.warn "out_yohoushi: #{e.class} #{e.message} #{e.backtrace.first}"
|
106
|
-
end
|
107
|
-
|
108
|
-
def emit(tag, es, chain)
|
109
|
-
tag_parts = tag.split('.')
|
110
|
-
tag_prefix = tag_prefix(tag_parts)
|
111
|
-
tag_suffix = tag_suffix(tag_parts)
|
112
|
-
placeholders = {
|
113
|
-
'tag' => tag,
|
114
|
-
'tags' => tag_parts, # for lower compatibility
|
115
|
-
'tag_parts' => tag_parts,
|
116
|
-
'tag_prefix' => tag_prefix,
|
117
|
-
'tag_suffix' => tag_suffix,
|
118
|
-
'hostname' => @hostname,
|
119
|
-
}
|
120
|
-
if @key_pattern
|
121
|
-
es.each do |time, record|
|
122
|
-
record.each do |key, value|
|
123
|
-
next unless key =~ @key_pattern
|
124
|
-
placeholders['key'] = key
|
125
|
-
path = expand_placeholder(@key_pattern_path, time, record, placeholders)
|
126
|
-
post(path, value)
|
70
|
+
if @key_pattern
|
71
|
+
key_pattern, @key_pattern_path = @key_pattern.split(/ +/, 2)
|
72
|
+
raise ConfigError, "key_pattern does not contain 2 parameters" unless @key_pattern_path
|
73
|
+
@key_pattern = Regexp.compile(key_pattern)
|
74
|
+
else
|
75
|
+
@keys = {}
|
76
|
+
(1..KEY_MAX_NUM).each do |i|
|
77
|
+
next unless conf["key#{i}"]
|
78
|
+
key, path = conf["key#{i}"].split(/ +/, 2)
|
79
|
+
raise ConfigError, "key#{i} does not contain 2 parameters" unless path
|
80
|
+
@keys[key] = path
|
127
81
|
end
|
128
82
|
end
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
83
|
+
raise ConfigError, "Either of `key_pattern` or `key1` must be specified" if (@key_pattern.nil? and @keys.empty?)
|
84
|
+
|
85
|
+
@placeholder_expander =
|
86
|
+
if @enable_ruby
|
87
|
+
# require utilities which would be used in ruby placeholders
|
88
|
+
require 'pathname'
|
89
|
+
require 'uri'
|
90
|
+
require 'cgi'
|
91
|
+
RubyPlaceholderExpander.new
|
92
|
+
else
|
93
|
+
PlaceholderExpander.new
|
136
94
|
end
|
137
|
-
|
95
|
+
|
96
|
+
@hostname = Socket.gethostname
|
97
|
+
rescue => e
|
98
|
+
raise ConfigError, "#{e.class} #{e.message} #{e.backtrace.first}"
|
138
99
|
end
|
139
100
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
end
|
101
|
+
def start
|
102
|
+
super
|
103
|
+
end
|
144
104
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
end
|
105
|
+
def shutdown
|
106
|
+
super
|
107
|
+
end
|
149
108
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
109
|
+
def post(path, number)
|
110
|
+
if @enable_float_number
|
111
|
+
@client.post_graph(path, { 'number' => number.to_f, 'mode' => @mode.to_s })
|
112
|
+
else
|
113
|
+
@client.post_graph(path, { 'number' => number.to_i, 'mode' => @mode.to_s })
|
114
|
+
end
|
115
|
+
rescue => e
|
116
|
+
$log.warn "out_yohoushi: #{e.class} #{e.message} #{e.backtrace.first}"
|
155
117
|
end
|
156
|
-
tag_prefix
|
157
|
-
end
|
158
118
|
|
159
|
-
|
160
|
-
|
161
|
-
rev_tag_parts = tag_parts.reverse
|
162
|
-
rev_tag_suffix = [rev_tag_parts.first]
|
163
|
-
1.upto(tag_parts.size-1).each do |i|
|
164
|
-
rev_tag_suffix[i] = "#{rev_tag_parts[i]}.#{rev_tag_suffix[i-1]}"
|
119
|
+
def format(tag, time, record)
|
120
|
+
[tag, time, record].to_msgpack
|
165
121
|
end
|
166
|
-
rev_tag_suffix.reverse
|
167
|
-
end
|
168
122
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
123
|
+
def write(chunk)
|
124
|
+
chunk.msgpack_each do |tag, time, record|
|
125
|
+
tag_parts = tag.split('.')
|
126
|
+
tag_prefix = tag_prefix(tag_parts)
|
127
|
+
tag_suffix = tag_suffix(tag_parts)
|
128
|
+
placeholders = {
|
129
|
+
'tag' => tag,
|
130
|
+
'tags' => tag_parts, # for lower compatibility
|
131
|
+
'tag_parts' => tag_parts,
|
132
|
+
'tag_prefix' => tag_prefix,
|
133
|
+
'tag_suffix' => tag_suffix,
|
134
|
+
'hostname' => @hostname,
|
135
|
+
}
|
136
|
+
if @key_pattern
|
137
|
+
record.each do |key, value|
|
138
|
+
next unless key =~ @key_pattern
|
139
|
+
placeholders['key'] = key
|
140
|
+
path = expand_placeholder(@key_pattern_path, time, record, placeholders)
|
141
|
+
post(path, value)
|
142
|
+
end
|
143
|
+
else # keys
|
144
|
+
@keys.each do |key, path|
|
145
|
+
next unless value = record[key]
|
146
|
+
placeholders['key'] = key
|
147
|
+
path = expand_placeholder(path, time, record, placeholders)
|
148
|
+
post(path, value)
|
149
|
+
end
|
185
150
|
end
|
186
151
|
end
|
152
|
+
rescue => e
|
153
|
+
$log.warn "out_yohoushi: #{e.class} #{e.message} #{e.backtrace.first}"
|
154
|
+
end
|
187
155
|
|
188
|
-
|
156
|
+
def expand_placeholder(value, time, record, opts)
|
157
|
+
@placeholder_expander.prepare_placeholders(time, record, opts)
|
158
|
+
@placeholder_expander.expand(value)
|
189
159
|
end
|
190
160
|
|
191
|
-
def
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
161
|
+
def tag_prefix(tag_parts)
|
162
|
+
return [] if tag_parts.empty?
|
163
|
+
tag_prefix = [tag_parts.first]
|
164
|
+
1.upto(tag_parts.size-1).each do |i|
|
165
|
+
tag_prefix[i] = "#{tag_prefix[i-1]}.#{tag_parts[i]}"
|
166
|
+
end
|
167
|
+
tag_prefix
|
196
168
|
end
|
197
|
-
end
|
198
169
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
def prepare_placeholders(time, record, opts)
|
208
|
-
struct = UndefOpenStruct.new(record)
|
209
|
-
struct.time = Time.at(time)
|
210
|
-
opts.each {|key, val| struct.__send__("#{key}=", val) }
|
211
|
-
@placeholders = struct
|
170
|
+
def tag_suffix(tag_parts)
|
171
|
+
return [] if tag_parts.empty?
|
172
|
+
rev_tag_parts = tag_parts.reverse
|
173
|
+
rev_tag_suffix = [rev_tag_parts.first]
|
174
|
+
1.upto(tag_parts.size-1).each do |i|
|
175
|
+
rev_tag_suffix[i] = "#{rev_tag_parts[i]}.#{rev_tag_suffix[i-1]}"
|
176
|
+
end
|
177
|
+
rev_tag_suffix.reverse
|
212
178
|
end
|
213
179
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
180
|
+
class PlaceholderExpander
|
181
|
+
attr_reader :placeholders
|
182
|
+
|
183
|
+
def prepare_placeholders(time, record, opts)
|
184
|
+
placeholders = { '${time}' => Time.at(time).to_s }
|
185
|
+
record.each {|key, value| placeholders.store("${#{key}}", value) }
|
186
|
+
|
187
|
+
opts.each do |key, value|
|
188
|
+
if value.kind_of?(Array) # tag_parts, etc
|
189
|
+
size = value.size
|
190
|
+
value.each_with_index { |v, idx|
|
191
|
+
placeholders.store("${#{key}[#{idx}]}", v)
|
192
|
+
placeholders.store("${#{key}[#{idx-size}]}", v) # support [-1]
|
193
|
+
}
|
194
|
+
else # string, interger, float, and others?
|
195
|
+
placeholders.store("${#{key}}", value)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
@placeholders = placeholders
|
200
|
+
end
|
201
|
+
|
202
|
+
def expand(str)
|
203
|
+
str.gsub(/(\${[a-z_]+(\[-?[0-9]+\])?}|__[A-Z_]+__)/) {
|
204
|
+
$log.warn "record_reformer: unknown placeholder `#{$1}` found" unless @placeholders.include?($1)
|
205
|
+
@placeholders[$1]
|
206
|
+
}
|
207
|
+
end
|
220
208
|
end
|
221
209
|
|
222
|
-
class
|
223
|
-
|
224
|
-
|
210
|
+
class RubyPlaceholderExpander
|
211
|
+
attr_reader :placeholders
|
212
|
+
|
213
|
+
# Get placeholders as a struct
|
214
|
+
#
|
215
|
+
# @param [Time] time the time
|
216
|
+
# @param [Hash] record the record
|
217
|
+
# @param [Hash] opts others
|
218
|
+
def prepare_placeholders(time, record, opts)
|
219
|
+
struct = UndefOpenStruct.new(record)
|
220
|
+
struct.time = Time.at(time)
|
221
|
+
opts.each {|key, value| struct.__send__("#{key}=", value) }
|
222
|
+
@placeholders = struct
|
223
|
+
end
|
224
|
+
|
225
|
+
# Replace placeholders in a string
|
226
|
+
#
|
227
|
+
# @param [String] str the string to be replaced
|
228
|
+
def expand(str)
|
229
|
+
str = str.gsub(/\$\{([^}]+)\}/, '#{\1}') # ${..} => #{..}
|
230
|
+
eval "\"#{str}\"", @placeholders.instance_eval { binding }
|
231
|
+
end
|
232
|
+
|
233
|
+
class UndefOpenStruct < OpenStruct
|
234
|
+
(Object.instance_methods).each do |m|
|
235
|
+
undef_method m unless m.to_s =~ /^__|respond_to_missing\?|object_id|public_methods|instance_eval|method_missing|define_singleton_method|respond_to\?|new_ostruct_member/
|
236
|
+
end
|
225
237
|
end
|
226
238
|
end
|
227
239
|
end
|
data/spec/out_yohoushi_spec.rb
CHANGED
@@ -19,9 +19,8 @@ describe Fluent::YohoushiOutput do
|
|
19
19
|
let(:yohoushi_base_uri) { 'http://localhost:4804' }
|
20
20
|
let(:growthforecast_base_uri) { 'http://localhost:5125' }
|
21
21
|
let(:tag) { 'test' }
|
22
|
-
let(:driver) { Fluent::Test::
|
22
|
+
let(:driver) { Fluent::Test::BufferedOutputTestDriver.new(Fluent::YohoushiOutput, tag).configure(config) }
|
23
23
|
let(:instance) { driver.instance }
|
24
|
-
let(:emit) { driver.run { messages.each {|message| driver.emit(message, time) } } }
|
25
24
|
|
26
25
|
describe 'test configure' do
|
27
26
|
context "empty" do
|
@@ -80,6 +79,7 @@ describe Fluent::YohoushiOutput do
|
|
80
79
|
end
|
81
80
|
|
82
81
|
describe 'test emit' do
|
82
|
+
let(:emit) { d = driver; messages.each {|message| d.emit(message, time) }; d.run; }
|
83
83
|
let(:time) { Time.now.to_i }
|
84
84
|
let(:messages) do
|
85
85
|
[
|
@@ -144,7 +144,7 @@ describe Fluent::YohoushiOutput do
|
|
144
144
|
let(:tag) { 'fluent.error' }
|
145
145
|
let(:time) { Time.now.to_i }
|
146
146
|
let(:record) { { 'foo_count' => "1" } }
|
147
|
-
let(:emit) {
|
147
|
+
let(:emit) { d = driver; d.emit(record, time); d.run; }
|
148
148
|
let(:expected_path) { "/fluent/error/fluent.error/1/foo_count/#{Time.at(time)}" }
|
149
149
|
let(:expected_value) { '1' }
|
150
150
|
before { driver.instance.should_receive(:post).with(expected_path, expected_value) }
|
@@ -173,7 +173,7 @@ describe Fluent::YohoushiOutput do
|
|
173
173
|
let(:tag) { 'fluent.error' }
|
174
174
|
let(:time) { Time.now.to_i }
|
175
175
|
let(:record) { { 'foo_count' => "1" } }
|
176
|
-
let(:emit) {
|
176
|
+
let(:emit) { d = driver; d.emit(record, time); d.run; }
|
177
177
|
let(:expected_path) { "/fluent/error/fluent.error/1/foo_count/#{Time.at(time)}" }
|
178
178
|
let(:expected_value) { '1' }
|
179
179
|
before { driver.instance.should_receive(:post).with(expected_path, expected_value) }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-yohoushi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Naotoshi Seo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fluentd
|