fluent-plugin-irc 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: af5ed88db24d18583875f6882b7b9d4ef794a944
4
- data.tar.gz: 0384195c283d6d18d4a62534b3ea9909134351f5
3
+ metadata.gz: eb8767007a2ccb458b008a2cb617c77cd2bdfdf4
4
+ data.tar.gz: d83c2a017287da67e5fe134a8f5dd2c9e7ba6db7
5
5
  SHA512:
6
- metadata.gz: ec009deb6f1f6b60c253a200803442a109d0f59f8b415528471e984676681250c4d529b70ff91f868211347f24181e5de7c1a463a46331eec8c454160919fac6
7
- data.tar.gz: 4150d2d06239c7aa689c438d78b89f3e0f0430d3e77f8e25d9b9a50c43dba0649386adb1c038094493bd079e3ab4871cc0f26a48de783e48a981d12bfb54f1a1
6
+ metadata.gz: 8fe24e8d588c9d7a2eb13fc4c17beafd1c1b9159dd96121aad1d7bf295d3429509299f340711e327ca298db975c77351a3441e0ddd6ec9f38a9c3151f35d9449
7
+ data.tar.gz: 7cf9bf5284d5398e92ea9b2a30a881e4ac523732bdcd0568fa153dbe38bb38c685ee9cb14c332ef2b7a6a4205045719e7c8c076eef6ef2e9ddabb75e78b83b7e
data/README.md CHANGED
@@ -36,6 +36,7 @@ Fluent plugin to send messages to IRC server
36
36
  |host|IRC server host|localhost|
37
37
  |port|IRC server port number|6667|
38
38
  |channel|channel to send messages (without first '#')||
39
+ |channel_keys|keys used to format channel. %s will be replaced with value specified by channel_keys if this option is used|nil|
39
40
  |nick|nickname registerd of IRC|fluentd|
40
41
  |user|user name registerd of IRC|fluentd|
41
42
  |real|real name registerd of IRC|fluentd|
@@ -44,8 +45,10 @@ Fluent plugin to send messages to IRC server
44
45
  |time_key|key name for time|time|
45
46
  |time_format|time format. This will be formatted with Time#strftime.|%Y/%m/%d %H:%M:%S|
46
47
  |tag_key|key name for tag|tag|
47
- |command|irc command. `priv_msg` or `notice`|priv_msg|
48
- |channel_keys|keys used to format channel. %s will be replaced with value specified by channel_keys if this option is used|nil|
48
+ |command|irc command. `privmsg` or `notice`|privmsg|
49
+ |command_keys|keys used to format command. %s will be replaced with value specified by command_keys if this option is used|nil|
50
+ |send_interval|interval (sec) to send message. defence Excess Flood|2|
51
+ |send_queue_limit|maximum size of send message queue|100|
49
52
 
50
53
  ## Copyright
51
54
 
@@ -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-irc"
6
- s.version = "0.0.6"
6
+ s.version = "0.0.7"
7
7
  s.authors = ["OKUNO Akihiro"]
8
8
  s.email = ["choplin.choplin@gmail.com"]
9
9
  s.homepage = "https://github.com/choplin/fluent-plugin-irc"
@@ -25,17 +25,28 @@ module Fluent
25
25
  config_param :time_key , :string , :default => 'time'
26
26
  config_param :time_format , :string , :default => '%Y/%m/%d %H:%M:%S'
27
27
  config_param :tag_key , :string , :default => 'tag'
28
- config_param :command , :string , :default => 'priv_msg'
28
+ config_param :command , :string , :default => 'privmsg'
29
+ config_param :command_keys, :default => nil do |val|
30
+ val.split(',')
31
+ end
29
32
 
30
- config_param :blocking_timeout, :time, :default => 0.5
33
+ config_param :blocking_timeout, :time, :default => 0.5
34
+ config_param :send_queue_limit, :integer, :default => 100
35
+ config_param :send_interval, :time, :default => 2
31
36
 
32
- COMMAND_LIST = %w[priv_msg notice]
37
+ COMMAND_MAP = {
38
+ 'priv_msg' => :priv_msg,
39
+ 'privmsg' => :priv_msg,
40
+ 'notice' => :notice,
41
+ }
33
42
 
34
43
  # To support log_level option implemented by Fluentd v0.10.43
35
44
  unless method_defined?(:log)
36
45
  define_method("log") { $log }
37
46
  end
38
47
 
48
+ attr_reader :conn # for test
49
+
39
50
  def initialize
40
51
  super
41
52
  require 'irc_parser'
@@ -59,10 +70,19 @@ module Fluent
59
70
  end
60
71
  @channel = '#'+@channel
61
72
 
62
- unless COMMAND_LIST.include?(@command)
63
- raise Fluent::ConfigError, "command must be one of #{COMMAND_LIST.join(', ')}"
73
+ if @command_keys
74
+ begin
75
+ @command % (['1'] * @command_keys.length)
76
+ rescue ArgumentError
77
+ raise Fluent::ConfigError, "string specifier '%s' and command_keys specification mismatch"
78
+ end
79
+ else
80
+ unless @command = COMMAND_MAP[@command]
81
+ raise Fluent::ConfigError, "command must be one of #{COMMAND_MAP.keys.join(', ')}"
82
+ end
64
83
  end
65
84
 
85
+ @send_queue = []
66
86
  end
67
87
 
68
88
  def start
@@ -71,6 +91,8 @@ module Fluent
71
91
  begin
72
92
  @loop = Coolio::Loop.new
73
93
  @conn = create_connection
94
+ @timer = TimerWatcher.new(@send_interval, true, log, &method(:on_timer))
95
+ @loop.attach(@timer)
74
96
  @thread = Thread.new(&method(:run))
75
97
  rescue => e
76
98
  puts e
@@ -102,17 +124,34 @@ module Fluent
102
124
  end
103
125
 
104
126
  es.each do |time,record|
127
+ if @send_queue.size >= @send_queue_limit
128
+ log.warn "out_irc: send queue size exceeded send_queue_limit(#{@send_queue_limit}), discards"
129
+ break
130
+ end
131
+
105
132
  filter_record(tag, time, record)
106
- @conn.send_message(build_message(record), build_channel(record))
133
+
134
+ command = build_command(record)
135
+ channel = build_channel(record)
136
+ message = build_message(record)
137
+
138
+ log.debug { "out_irc: push {command:\"#{command}\", channel:\"#{channel}\", message:\"#{message}\"}" }
139
+ @send_queue.push([command, channel, message])
107
140
  end
108
141
  end
109
142
 
143
+ def on_timer
144
+ return if @send_queue.empty?
145
+ command, channel, message = @send_queue.shift
146
+ log.info { "out_irc: send {command:\"#{command}\", channel:\"#{channel}\", message:\"#{message}\"}" }
147
+ @conn.send_message(command, channel, message)
148
+ end
149
+
110
150
  private
111
151
 
112
152
  def create_connection
113
153
  conn = IRCConnection.connect(@host, @port)
114
- conn.command = @command
115
- conn.channel = '#'+@channel
154
+ conn.log = log
116
155
  conn.nick = @nick
117
156
  conn.user = @user
118
157
  conn.real = @real
@@ -122,35 +161,56 @@ module Fluent
122
161
  end
123
162
 
124
163
  def build_message(record)
125
- values = @out_keys.map do |key|
126
- begin
127
- record.fetch(key).to_s
128
- rescue KeyError
129
- log.warn "out_irc: the specified key '#{key}' not found in record. [#{record}]"
130
- ''
131
- end
132
- end
133
-
164
+ values = fetch_keys(record, @out_keys)
134
165
  @message % values
135
166
  end
136
167
 
137
168
  def build_channel(record)
138
169
  return @channel unless @channel_keys
139
170
 
140
- values = @channel_keys.map do |key|
171
+ values = fetch_keys(record, @channel_keys)
172
+ @channel % values
173
+ end
174
+
175
+ def build_command(record)
176
+ return @command unless @command_keys
177
+
178
+ values = fetch_keys(record, @command_keys)
179
+ unless command = COMMAND_MAP[@command % values]
180
+ log.warn "out_irc: command is not one of #{COMMAND_MAP.keys.join(', ')}, use privmsg"
181
+ end
182
+ command || :priv_msg
183
+ end
184
+
185
+ def fetch_keys(record, keys)
186
+ Array(keys).map do |key|
141
187
  begin
142
188
  record.fetch(key).to_s
143
189
  rescue KeyError
144
- $log.warn "out_irc: the specified key '#{key}' not found in record. [#{record}]"
190
+ log.warn "out_irc: the specified key '#{key}' not found in record. [#{record}]"
145
191
  ''
146
192
  end
147
193
  end
194
+ end
195
+
196
+ class TimerWatcher < Coolio::TimerWatcher
197
+ def initialize(interval, repeat, log, &callback)
198
+ @callback = callback
199
+ @log = log
200
+ super(interval, repeat)
201
+ end
148
202
 
149
- @channel % values
203
+ def on_timer
204
+ @callback.call
205
+ rescue
206
+ @log.error $!.to_s
207
+ @log.error_backtrace
208
+ end
150
209
  end
151
210
 
152
211
  class IRCConnection < Cool.io::TCPSocket
153
- attr_accessor :command, :channel, :nick, :user, :real, :password
212
+ attr_reader :joined # for test
213
+ attr_accessor :log, :nick, :user, :real, :password
154
214
 
155
215
  def initialize(*args)
156
216
  super
@@ -179,13 +239,23 @@ module Fluent
179
239
  data.each_line do |line|
180
240
  begin
181
241
  msg = IRCParser.parse(line)
242
+ log.debug { "out_irc: on_read :#{msg.class.to_sym}" }
182
243
  case msg.class.to_sym
244
+ when :rpl_welcome
245
+ log.info { "out_irc: welcome \"#{msg.nick}\" to \"#{msg.prefix}\"" }
183
246
  when :ping
184
247
  IRCParser.message(:pong) do |m|
185
248
  m.target = msg.target
186
249
  m.body = msg.body
187
250
  write m
188
251
  end
252
+ when :join
253
+ log.info { "out_irc: joined to #{msg.channels.join(', ')}" }
254
+ msg.channels.each {|channel| @joined[channel] = true }
255
+ when :err_nick_name_in_use
256
+ log.warn "out_irc: nickname \"#{msg.error_nick}\" is already in use. use \"#{@nick}_\" instead."
257
+ @nick = "#{@nick}_"
258
+ on_connect
189
259
  when :error
190
260
  log.warn "out_irc: an error occured. \"#{msg.error_message}\""
191
261
  end
@@ -204,16 +274,17 @@ module Fluent
204
274
  m.channels = channel
205
275
  write m
206
276
  end
207
- @joined[channel] = true
277
+ log.debug { "out_irc: join to #{channel}" }
208
278
  end
209
279
 
210
- def send_message(msg, channel)
280
+ def send_message(command, channel, message)
211
281
  join(channel) unless joined?(channel)
212
- IRCParser.message(@command) do |m|
282
+ IRCParser.message(command) do |m|
213
283
  m.target = channel
214
- m.body = msg
284
+ m.body = message
215
285
  write m
216
286
  end
287
+ channel # return channel for test
217
288
  end
218
289
  end
219
290
  end
@@ -20,9 +20,11 @@ class IRCOutputTest < Test::Unit::TestCase
20
20
  def config(
21
21
  port: PORT,
22
22
  channel: CHANNEL,
23
- channel_keys: ""
23
+ channel_keys: "",
24
+ command: COMMAND.to_s,
25
+ command_keys: nil
24
26
  )
25
- %[
27
+ config = %[
26
28
  type irc
27
29
  host localhost
28
30
  port #{port}
@@ -31,13 +33,17 @@ class IRCOutputTest < Test::Unit::TestCase
31
33
  nick #{NICK}
32
34
  user #{USER}
33
35
  real #{REAL}
34
- command #{COMMAND.to_s}
36
+ command #{command}
35
37
  message #{MESSAGE}
36
38
  out_keys tag,time,msg
37
39
  time_key time
38
40
  time_format #{TIME_FORMAT}
39
41
  tag_key tag
42
+ send_queue_limit 10
43
+ send_interval 0.5
40
44
  ]
45
+ config += %[command_keys #{command_keys}] if command_keys
46
+ config
41
47
  end
42
48
 
43
49
 
@@ -54,12 +60,14 @@ class IRCOutputTest < Test::Unit::TestCase
54
60
  assert_equal NICK, d.instance.nick
55
61
  assert_equal USER, d.instance.user
56
62
  assert_equal REAL, d.instance.real
57
- assert_equal COMMAND.to_s, d.instance.command
63
+ assert_equal COMMAND, d.instance.command
58
64
  assert_equal MESSAGE, d.instance.message
59
65
  assert_equal ["tag","time","msg"], d.instance.out_keys
60
66
  assert_equal "time", d.instance.time_key
61
67
  assert_equal TIME_FORMAT, d.instance.time_format
62
68
  assert_equal "tag", d.instance.tag_key
69
+ assert_equal 10, d.instance.send_queue_limit
70
+ assert_equal 0.5, d.instance.send_interval
63
71
  end
64
72
 
65
73
  def test_configure_channel_keys
@@ -68,6 +76,22 @@ class IRCOutputTest < Test::Unit::TestCase
68
76
  assert_equal ["channel"], d.instance.channel_keys
69
77
  end
70
78
 
79
+ def test_configure_command_keys
80
+ d = create_driver(config(command:"%s", command_keys:"command"))
81
+ assert_equal "%s", d.instance.command
82
+ assert_equal ["command"], d.instance.command_keys
83
+ end
84
+
85
+ def test_configure_command
86
+ assert_raise Fluent::ConfigError do
87
+ create_driver(config(command: 'foo'))
88
+ end
89
+
90
+ assert_nothing_raised { create_driver(config(command: 'priv_msg')) }
91
+ assert_nothing_raised { create_driver(config(command: 'privmsg')) }
92
+ assert_nothing_raised { create_driver(config(command: 'notice')) }
93
+ end
94
+
71
95
  def test_emit
72
96
  msg = "test"
73
97
  msgs = [{"msg" => msg}]
@@ -75,22 +99,22 @@ class IRCOutputTest < Test::Unit::TestCase
75
99
 
76
100
  emit_test(msgs) do |socket|
77
101
  m = IRCParser.parse(socket.gets)
78
- assert_equal m.class.to_sym, :nick
79
- assert_equal m.nick, NICK
102
+ assert_equal :nick, m.class.to_sym
103
+ assert_equal NICK, m.nick
80
104
 
81
105
  m = IRCParser.parse(socket.gets)
82
- assert_equal m.class.to_sym, :user
83
- assert_equal m.user, USER
84
- assert_equal m.postfix, REAL
106
+ assert_equal :user, m.class.to_sym
107
+ assert_equal USER, m.user
108
+ assert_equal REAL, m.postfix
85
109
 
86
110
  m = IRCParser.parse(socket.gets)
87
- assert_equal m.class.to_sym, :join
88
- assert_equal m.channels, ["##{CHANNEL}"]
111
+ assert_equal :join, m.class.to_sym, :join
112
+ assert_equal ["##{CHANNEL}"], m.channels
89
113
 
90
114
  m = IRCParser.parse(socket.gets)
91
- assert_equal m.class.to_sym, COMMAND
92
- assert_equal m.target, "##{CHANNEL}"
93
- assert_equal m.body, body
115
+ assert_equal COMMAND, m.class.to_sym
116
+ assert_equal "##{CHANNEL}", m.target
117
+ assert_equal body, m.body
94
118
 
95
119
  assert_nil socket.gets # expects EOF
96
120
  end
@@ -113,29 +137,91 @@ class IRCOutputTest < Test::Unit::TestCase
113
137
  socket.gets # ignore USER
114
138
 
115
139
  m = IRCParser.parse(socket.gets)
116
- assert_equal m.class.to_sym, :join
117
- assert_equal m.channels, ["#chan1"]
140
+ assert_equal :join, m.class.to_sym
141
+ assert_equal ["#chan1"], m.channels
118
142
 
119
143
  m = IRCParser.parse(socket.gets)
120
- assert_equal m.class.to_sym, COMMAND
121
- assert_equal m.target, "#chan1"
144
+ assert_equal COMMAND, m.class.to_sym
145
+ assert_equal "#chan1", m.target
122
146
 
123
147
  m = IRCParser.parse(socket.gets)
124
- assert_equal m.class.to_sym, :join
125
- assert_equal m.channels, ["#chan2"]
148
+ assert_equal :join, m.class.to_sym
149
+ assert_equal ["#chan2"], m.channels
126
150
 
127
151
  m = IRCParser.parse(socket.gets)
128
- assert_equal m.class.to_sym, COMMAND
129
- assert_equal m.target, "#chan2"
152
+ assert_equal COMMAND, m.class.to_sym
153
+ assert_equal "#chan2", m.target
130
154
 
131
155
  m = IRCParser.parse(socket.gets)
132
- assert_equal m.class.to_sym, COMMAND
133
- assert_equal m.target, "#chan1"
156
+ assert_equal COMMAND, m.class.to_sym
157
+ assert_equal "#chan1", m.target
134
158
 
135
159
  assert_nil socket.gets # expects EOF
136
160
  end
137
161
  end
138
162
 
163
+ def test_dynamic_command
164
+ msgs = [
165
+ {"msg" => "test", "command" => "privmsg"},
166
+ {"msg" => "test", "command" => "priv_msg"},
167
+ {"msg" => "test", "command" => "notice"},
168
+ {"msg" => "test", "command" => "something_wrong"},
169
+ ]
170
+
171
+ extra_config = {
172
+ command: "%s",
173
+ command_keys: "command",
174
+ }
175
+
176
+ emit_test(msgs, extra_config: extra_config) do |socket|
177
+ socket.gets # ignore NICK
178
+ socket.gets # ignore USER
179
+
180
+ m = IRCParser.parse(socket.gets)
181
+ assert_equal :join, m.class.to_sym
182
+
183
+ m = IRCParser.parse(socket.gets)
184
+ assert_equal :priv_msg, m.class.to_sym
185
+
186
+ m = IRCParser.parse(socket.gets)
187
+ assert_equal :priv_msg, m.class.to_sym
188
+
189
+ m = IRCParser.parse(socket.gets)
190
+ assert_equal :notice, m.class.to_sym
191
+
192
+ m = IRCParser.parse(socket.gets)
193
+ assert_equal :priv_msg, m.class.to_sym # replaced by default priv_msg
194
+
195
+ assert_nil socket.gets # expects EOF
196
+ end
197
+ end
198
+
199
+ def test_fallback_err_nick_name_in_use
200
+ msgs = [
201
+ {"msg" => "test", "command" => "privmsg"},
202
+ ]
203
+
204
+ emit_test(msgs) do |socket, d|
205
+ socket.gets # ignore NICK
206
+ socket.gets # ignore USER
207
+ socket.gets # ignore join
208
+ socket.gets # ignore priv_msg
209
+
210
+ # imitate to receive :err_nick_name_in_use
211
+ conn = d.instance.instance_variable_get(:@conn)
212
+ IRCParser.message(:err_nick_name_in_use) do |m|
213
+ conn.on_read(m.to_s)
214
+ end
215
+
216
+ sleep 1
217
+
218
+ # test to use `#{NICK}_` instead
219
+ m = IRCParser.parse(socket.gets)
220
+ assert_equal :nick, m.class.to_sym
221
+ assert_equal "#{NICK}_", m.nick
222
+ end
223
+ end
224
+
139
225
  private
140
226
 
141
227
  def emit_test(msgs, extra_config: {}, &block)
@@ -145,13 +231,15 @@ class IRCOutputTest < Test::Unit::TestCase
145
231
 
146
232
  thread = Thread.new do
147
233
  s = serv.accept
148
- block.call(s)
234
+ block.call(s, d)
149
235
  s.close
150
236
  end
151
237
 
152
238
  d.run do
153
239
  msgs.each do |m|
154
240
  d.emit(m, Fluent::Engine.now)
241
+ channel = d.instance.on_timer
242
+ d.instance.conn.joined[channel] = true # pseudo join
155
243
  end
156
244
  # How to remove sleep?
157
245
  # It is necessary to ensure that no data remains in Cool.io write buffer before detach.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-irc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - OKUNO Akihiro
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-18 00:00:00.000000000 Z
11
+ date: 2015-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd