slack_log_device 1.0.1 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a971af4091c07ffdc8b9ee83c180160eadf73352
4
- data.tar.gz: b6595e5dfd101fedb0fece93e7cd45b25406996a
3
+ metadata.gz: 3567b60b8adf372370ebeff154732ea9c0c85a6b
4
+ data.tar.gz: 1eaa0fea7a5ce04b969ef5ee2c5fd806e558d822
5
5
  SHA512:
6
- metadata.gz: 00c9b7b2007d08c6a2f5471a74d454fd12a52789e4d7b3a64c71bbfa182903ae43200d4349185a10afee3676c2a676a6d5e6cf6751d364e9c0c108e47cc03c48
7
- data.tar.gz: 95513c23e9c1dc1cd09f66a8839c4a26400fea2a1ae146f23041c9e47542707a09a5d48b1b3d330b44b84a63872443a380169a7ac957ce1cc4f1f3ac190752ee
6
+ metadata.gz: 721424872c5fbb4f9a997ef2c10b2c35f917255062fe469db09d7cf417295396ff955c23bfa1b7bcd827c5881a060c6e0c8bc157a633a8f8f9a66df9db88d08f
7
+ data.tar.gz: bd94f833858783c0ea6c4b649ea1a9cb7610302cf36e49cf9e42f2ebf4d0783d1c567061b70c0b282cc7ea8a6f5e51d8041d6ac0bc241a0406c3af3fc600bb9b
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  .DS_Store
2
2
  /.bundle/
3
+ /.byebug_history
3
4
  /.ruby-version
4
5
  /Gemfile.lock
5
6
  /pkg/
data/README.mdown CHANGED
@@ -24,6 +24,19 @@ logger.warn('BAM!')
24
24
 
25
25
  Then, the logged message will be writen to webhook's configured channel.
26
26
 
27
+ Note that the messages written are buffered in order to avoid consecutive
28
+ request.
29
+
30
+ ## Options
31
+
32
+ - `auto_flush`: To flush messages directly when a message is written (disabled
33
+ by default).
34
+ - `flush_delay`: The delay in seconds to flush messages (1 by default).
35
+ - `max_buffer_size`: The max buffer size to flush messages (8192 by default).
36
+ - `timeout`: The timeout in seconds to send message to slack (5 by default).
37
+ - `username`: The username to post message as (nil by default).
38
+ - `webhook_url`: The URL of the webhook (mandatory).
39
+
27
40
  ## Executing test suite
28
41
 
29
42
  This project is fully tested with [Rspec 3](http://github.com/rspec/rspec).
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.1
1
+ 2.0.0
@@ -5,22 +5,69 @@ require 'logger'
5
5
 
6
6
  class SlackLogDevice
7
7
 
8
- attr_reader :timeout, :username, :webhook_url
8
+ attr_reader :flush_delay, :max_buffer_size, :timeout, :username, :webhook_url
9
9
 
10
10
  def initialize(options = {})
11
- options.assert_valid_keys(:timeout, :username, :webhook_url)
11
+ options.assert_valid_keys(:auto_flush, :flush_delay, :max_buffer_size, :timeout, :username, :webhook_url)
12
+ @buffer = []
13
+ @mutex = Mutex.new
14
+ @flush_thread
15
+ self.auto_flush = options[:auto_flush]
16
+ self.flush_delay = options.key?(:flush_delay) ? options[:flush_delay] : 1
17
+ self.max_buffer_size = options.key?(:max_buffer_size) ? options[:max_buffer_size] : 8192
12
18
  self.timeout = options.key?(:timeout) ? options[:timeout] : 5
13
19
  self.username = options[:username]
14
20
  self.webhook_url = options[:webhook_url]
15
21
  end
16
22
 
23
+ def auto_flush?
24
+ @auto_flush
25
+ end
26
+
27
+ def auto_flush=(value)
28
+ @auto_flush = value.present?
29
+ end
30
+
17
31
  def close
18
32
  # Does nothing, this method must exist to consider the LogDevice as an IO.
19
33
  end
20
34
 
35
+ def flush
36
+ return if @buffer.empty?
37
+ message = ''
38
+ @mutex.synchronize do
39
+ message = @buffer.join("\n")
40
+ @buffer.clear
41
+ end
42
+ data = { 'text' => message.to_s }
43
+ data['username'] = username if username.present?
44
+ begin
45
+ HTTParty.post(webhook_url, body: data.to_json, headers: { 'Content-Type': 'application/json' }, timeout: timeout)
46
+ rescue => e
47
+ STDERR.puts(e)
48
+ end
49
+ nil
50
+ end
51
+
52
+ def flush?
53
+ auto_flush? || flush_delay.zero? || @buffer.join("\n").size > max_buffer_size
54
+ end
55
+
56
+ def flush_delay=(value)
57
+ delay = Integer(value) rescue nil
58
+ raise ArgumentError.new("Invalid flush delay: #{value.inspect}") if delay.nil? || delay < 0
59
+ @flush_delay = delay
60
+ end
61
+
62
+ def max_buffer_size=(value)
63
+ size = Integer(value) rescue nil
64
+ raise ArgumentError.new("Invalid max buffer size: #{value.inspect}") if size.nil? || size < 0
65
+ @max_buffer_size = size
66
+ end
67
+
21
68
  def timeout=(value)
22
69
  timeout = Integer(value) rescue nil
23
- raise ArgumentError.new("Invalid timeout: #{value.inspect}") if timeout.nil?
70
+ raise ArgumentError.new("Invalid timeout: #{value.inspect}") if timeout.nil? || timeout <= 0
24
71
  @timeout = timeout
25
72
  end
26
73
 
@@ -31,16 +78,25 @@ class SlackLogDevice
31
78
  def webhook_url=(value)
32
79
  raise ArgumentError.new('Webhook URL must be specified') if value.blank?
33
80
  uri = URI(value.to_s) rescue nil
34
- raise ArgumentError.new("Invalid Webhook URL: #{value.inspect}") if uri.nil? || !uri.is_a?(URI::HTTP)
81
+ raise ArgumentError.new("Invalid webhook URL: #{value.inspect}") if uri.nil? || !uri.is_a?(URI::HTTP)
35
82
  @webhook_url = uri.to_s
36
83
  end
37
84
 
38
85
  def write(message)
39
86
  message = message.to_s.try(:strip)
40
87
  return if message.blank?
41
- data = { 'text' => message.to_s }
42
- data['username'] = username if username.present?
43
- HTTParty.post(webhook_url, body: data.to_json, headers: { 'Content-Type': 'application/json' }, timeout: timeout)
88
+ @mutex.synchronize do
89
+ @buffer << message
90
+ end
91
+ if flush?
92
+ flush
93
+ else
94
+ @flush_thread.kill if @flush_thread
95
+ @flush_thread = Thread.new do
96
+ sleep(flush_delay)
97
+ flush
98
+ end
99
+ end
44
100
  nil
45
101
  end
46
102
 
@@ -6,6 +6,24 @@ describe SlackLogDevice do
6
6
  let(:logger) { Logger.new(device).tap { |logger| logger.level = Logger::INFO } }
7
7
  let(:options) { { username: 'MyApp', webhook_url: 'https://hooks.slack.com/services/test' } }
8
8
 
9
+ before :each do
10
+ allow(HTTParty).to receive(:post)
11
+ end
12
+
13
+ describe '#auto_flush?' do
14
+
15
+ it 'is false by default' do
16
+ options.delete(:auto_flush)
17
+ expect(device).not_to be_auto_flush
18
+ end
19
+
20
+ it 'can be set to true' do
21
+ options[:auto_flush] = true
22
+ expect(device).to be_auto_flush
23
+ end
24
+
25
+ end
26
+
9
27
  describe '#close' do
10
28
 
11
29
  it 'does nothing' do
@@ -16,12 +34,128 @@ describe SlackLogDevice do
16
34
 
17
35
  end
18
36
 
37
+ describe '#flush' do
38
+
39
+ it 'sends a post to webhook URL with given given message and specified username' do
40
+ device.write('BAM!')
41
+ expect(HTTParty).to receive(:post).with(options[:webhook_url], body: { 'text' => 'BAM!', 'username' => options[:username] }.to_json, headers: { 'Content-Type': 'application/json' }, timeout: 5)
42
+ device.flush
43
+ end
44
+
45
+ it 'does not send username if nil' do
46
+ options.delete(:username)
47
+ device.write('BAM!')
48
+ expect(HTTParty).to receive(:post).with(options[:webhook_url], body: { 'text' => 'BAM!' }.to_json, headers: { 'Content-Type': 'application/json' }, timeout: 5)
49
+ device.flush
50
+ end
51
+
52
+ it 'use specified timeout' do
53
+ options[:timeout] = 12
54
+ device.write('BAM!')
55
+ expect(HTTParty).to receive(:post).with(options[:webhook_url], body: { 'text' => 'BAM!', 'username' => options[:username] }.to_json, headers: { 'Content-Type': 'application/json' }, timeout: 12)
56
+ device.flush
57
+ end
58
+
59
+ it 'flushes all message writen separated by a new line' do
60
+ device.write('BAM!')
61
+ device.write('BIM!')
62
+ expect(HTTParty).to receive(:post).with(options[:webhook_url], body: { 'text' => "BAM!\nBIM!", 'username' => options[:username] }.to_json, headers: { 'Content-Type': 'application/json' }, timeout: 5)
63
+ device.flush
64
+ end
65
+
66
+ it 'returns nil' do
67
+ device.write('BIM!')
68
+ expect(device.flush).to be_nil
69
+ end
70
+
71
+ end
72
+
73
+ describe '#flush?' do
74
+
75
+ it 'is true if max_buffer_size is reached' do
76
+ options[:max_buffer_size] = 10
77
+ device.write('012345678')
78
+ expect {
79
+ device.instance_variable_get(:@buffer).push('a')
80
+ }.to change { device.flush? }.from(false).to(true)
81
+ end
82
+
83
+ it 'is true if auto_flush option is present' do
84
+ expect {
85
+ device.auto_flush = true
86
+ }.to change { device.flush? }.from(false).to(true)
87
+ end
88
+
89
+ it 'is true if flush delay is 0' do
90
+ expect {
91
+ device.flush_delay = 0
92
+ }.to change { device.flush? }.from(false).to(true)
93
+ end
94
+
95
+ end
96
+
97
+ describe '#flush_delay' do
98
+
99
+ it 'is 1 by default' do
100
+ expect(device.flush_delay).to eq(1)
101
+ end
102
+
103
+ it 'can be specified' do
104
+ options[:flush_delay] = 10
105
+ expect(device.flush_delay).to be(10)
106
+ end
107
+
108
+ it 'raise an error if invalid' do
109
+ options[:flush_delay] = 'foo'
110
+ expect {
111
+ device
112
+ }.to raise_error(ArgumentError, 'Invalid flush delay: "foo"')
113
+ end
114
+
115
+ it 'raise an error if negative' do
116
+ options[:flush_delay] = -1
117
+ expect {
118
+ device
119
+ }.to raise_error(ArgumentError, 'Invalid flush delay: -1')
120
+ end
121
+
122
+ it 'can be zero' do
123
+ options[:flush_delay] = 0
124
+ expect(device.flush_delay).to be(0)
125
+ end
126
+
127
+ it 'raise an error if blank' do
128
+ options[:flush_delay] = ' '
129
+ expect {
130
+ device
131
+ }.to raise_error(ArgumentError, 'Invalid flush delay: " "')
132
+ end
133
+
134
+ it 'raise an error if set as nil' do
135
+ expect {
136
+ device.flush_delay = nil
137
+ }.to raise_error(ArgumentError, 'Invalid flush delay: nil')
138
+ end
139
+
140
+ it 'can be specified as string' do
141
+ options[:flush_delay] = '42'
142
+ expect(device.flush_delay).to eq(42)
143
+ end
144
+
145
+ it 'can be set' do
146
+ expect {
147
+ device.flush_delay = 15
148
+ }.to change { device.flush_delay }.from(1).to(15)
149
+ end
150
+
151
+ end
152
+
19
153
  describe '#initialize' do
20
154
 
21
155
  it 'raise an error if an invalid option is given' do
22
156
  expect {
23
157
  SlackLogDevice.new(foo: 'bar')
24
- }.to raise_error(ArgumentError, "Unknown key: :foo. Valid keys are: :timeout, :username, :webhook_url")
158
+ }.to raise_error(ArgumentError, "Unknown key: :foo. Valid keys are: :auto_flush, :flush_delay, :max_buffer_size, :timeout, :username, :webhook_url")
25
159
  end
26
160
 
27
161
  it 'raise an error if webhook option is not given' do
@@ -32,6 +166,62 @@ describe SlackLogDevice do
32
166
 
33
167
  end
34
168
 
169
+ describe '#max_buffer_size' do
170
+
171
+ it 'is 8192 by default' do
172
+ expect(device.max_buffer_size).to eq(8192)
173
+ end
174
+
175
+ it 'can be specified' do
176
+ options[:max_buffer_size] = 42
177
+ expect(device.max_buffer_size).to be(42)
178
+ end
179
+
180
+ it 'raise an error if invalid' do
181
+ options[:max_buffer_size] = 'foo'
182
+ expect {
183
+ device
184
+ }.to raise_error(ArgumentError, 'Invalid max buffer size: "foo"')
185
+ end
186
+
187
+ it 'raise an error if negative' do
188
+ options[:max_buffer_size] = -1
189
+ expect {
190
+ device
191
+ }.to raise_error(ArgumentError, 'Invalid max buffer size: -1')
192
+ end
193
+
194
+ it 'can be zero' do
195
+ options[:max_buffer_size] = 0
196
+ expect(device.max_buffer_size).to be_zero
197
+ end
198
+
199
+ it 'raise an error if blank' do
200
+ options[:max_buffer_size] = ' '
201
+ expect {
202
+ device
203
+ }.to raise_error(ArgumentError, 'Invalid max buffer size: " "')
204
+ end
205
+
206
+ it 'raise an error if set as nil' do
207
+ expect {
208
+ device.max_buffer_size = nil
209
+ }.to raise_error(ArgumentError, 'Invalid max buffer size: nil')
210
+ end
211
+
212
+ it 'can be specified as string' do
213
+ options[:max_buffer_size] = '42'
214
+ expect(device.max_buffer_size).to eq(42)
215
+ end
216
+
217
+ it 'can be set' do
218
+ expect {
219
+ device.max_buffer_size = 1024
220
+ }.to change { device.max_buffer_size }.from(8192).to(1024)
221
+ end
222
+
223
+ end
224
+
35
225
  describe '#timeout' do
36
226
 
37
227
  it 'is 5 by default' do
@@ -50,7 +240,21 @@ describe SlackLogDevice do
50
240
  }.to raise_error(ArgumentError, 'Invalid timeout: "foo"')
51
241
  end
52
242
 
53
- it 'is default value if blank' do
243
+ it 'raise an error if negative' do
244
+ options[:timeout] = -1
245
+ expect {
246
+ device
247
+ }.to raise_error(ArgumentError, 'Invalid timeout: -1')
248
+ end
249
+
250
+ it 'raise an error if zero' do
251
+ options[:timeout] = 0
252
+ expect {
253
+ device
254
+ }.to raise_error(ArgumentError, 'Invalid timeout: 0')
255
+ end
256
+
257
+ it 'raise an error if blank' do
54
258
  options[:timeout] = ' '
55
259
  expect {
56
260
  device
@@ -115,7 +319,7 @@ describe SlackLogDevice do
115
319
  options[:webhook_url] = 'foo'
116
320
  expect {
117
321
  device
118
- }.to raise_error(ArgumentError, 'Invalid Webhook URL: "foo"')
322
+ }.to raise_error(ArgumentError, 'Invalid webhook URL: "foo"')
119
323
  end
120
324
 
121
325
  it 'can be an HTTP URL' do
@@ -132,7 +336,7 @@ describe SlackLogDevice do
132
336
  options[:webhook_url] = 'ftp://google.com'
133
337
  expect {
134
338
  device
135
- }.to raise_error(ArgumentError, 'Invalid Webhook URL: "ftp://google.com"')
339
+ }.to raise_error(ArgumentError, 'Invalid webhook URL: "ftp://google.com"')
136
340
  end
137
341
 
138
342
  it 'can be set' do
@@ -145,35 +349,18 @@ describe SlackLogDevice do
145
349
 
146
350
  describe '#write' do
147
351
 
148
- it 'sends a post to webhook URL with given given message and specified username' do
149
- expect(HTTParty).to receive(:post).with(options[:webhook_url], body: { 'text' => 'BAM!', 'username' => options[:username] }.to_json, headers: { 'Content-Type': 'application/json' }, timeout: 5)
150
- device.write('BAM!')
151
- end
152
-
153
352
  it 'returns nil' do
154
- allow(HTTParty).to receive(:post)
155
353
  expect(device.write('BAM!')).to be_nil
156
354
  end
157
355
 
158
- it 'does not send username if nil' do
159
- options.delete(:username)
160
- expect(HTTParty).to receive(:post).with(options[:webhook_url], body: { 'text' => 'BAM!' }.to_json, headers: { 'Content-Type': 'application/json' }, timeout: 5)
161
- device.write('BAM!')
162
- end
163
-
164
356
  it 'does nothing if log level is lower than specified one' do
165
357
  expect(HTTParty).not_to receive(:post)
166
358
  logger.debug('BIM!')
167
359
  end
168
360
 
169
- it 'send HTTP request if log level is equal to specified one' do
170
- expect(HTTParty).to receive(:post)
171
- logger.info('BIM!')
172
- end
173
-
174
361
  it 'strips message' do
175
- expect(HTTParty).to receive(:post).with(options[:webhook_url], body: { 'text' => 'BAM !', 'username' => options[:username] }.to_json, headers: { 'Content-Type': 'application/json' }, timeout: 5)
176
362
  device.write(" BAM !\n")
363
+ expect(device.instance_variable_get(:@buffer)).to eq(['BAM !'])
177
364
  end
178
365
 
179
366
  it 'does nothing if message is blank' do
@@ -184,16 +371,29 @@ describe SlackLogDevice do
184
371
  it 'does nothing if message is nil' do
185
372
  expect(HTTParty).not_to receive(:post)
186
373
  expect(device.write(nil)).to be_nil
374
+ expect(device.instance_variable_get(:@buffer)).to eq([])
375
+ end
376
+
377
+ it 'send HTTP request if log level is equal to specified one' do
378
+ expect(HTTParty).to receive(:post)
379
+ logger.info('BIM!')
380
+ device.flush
187
381
  end
188
382
 
189
383
  it 'send HTTP request if log level is higher to specified one' do
190
384
  expect(HTTParty).to receive(:post)
191
385
  logger.warn('BIM!')
386
+ device.flush
192
387
  end
193
388
 
194
- it 'use specified timeout' do
195
- options[:timeout] = 12
196
- expect(HTTParty).to receive(:post).with(options[:webhook_url], body: { 'text' => 'BAM!', 'username' => options[:username] }.to_json, headers: { 'Content-Type': 'application/json' }, timeout: 12)
389
+ it 'flush if auto flush is true' do
390
+ options[:auto_flush] = true
391
+ expect(HTTParty).to receive(:post)
392
+ device.write('BAM!')
393
+ end
394
+
395
+ it 'does not post HTTP message if auto flush is false' do
396
+ expect(HTTParty).not_to receive(:post)
197
397
  device.write('BAM!')
198
398
  end
199
399
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slack_log_device
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexis Toulotte