td-logger 0.3.27 → 0.4.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.
@@ -1,669 +0,0 @@
1
-
2
- require 'spec_helper'
3
-
4
- describe TreasureData::Logger::TreasureDataLogger do
5
- context 'init' do
6
- it 'with apikey' do
7
- td = TreasureData::Logger::TreasureDataLogger.new('db1', :apikey => 'test_1')
8
- expect(td.instance_variable_get(:@client).api.apikey).to eq('test_1')
9
- expect(td.instance_variable_get(:@client).api.instance_variable_get(:@ssl)).to eq(true)
10
- end
11
-
12
- it 'with apikey and use_ssl' do
13
- td = TreasureData::Logger::TreasureDataLogger.new('db1', :apikey => 'test_1', :use_ssl => true)
14
- expect(td.instance_variable_get(:@client).api.apikey).to eq('test_1')
15
- expect(td.instance_variable_get(:@client).api.instance_variable_get(:@ssl)).to eq(true)
16
- end
17
-
18
- it 'with apikey and ssl' do
19
- td = TreasureData::Logger::TreasureDataLogger.new('db1', :apikey => 'test_1', :ssl => true)
20
- expect(td.instance_variable_get(:@client).api.apikey).to eq('test_1')
21
- expect(td.instance_variable_get(:@client).api.instance_variable_get(:@ssl)).to eq(true)
22
- end
23
-
24
- it 'with apikey and HTTP endpoint' do
25
- td = TreasureData::Logger::TreasureDataLogger.new('db1', :apikey => 'test_1', :endpoint => "http://idontexi.st")
26
- expect(td.instance_variable_get(:@client).api.apikey).to eq('test_1')
27
- expect(td.instance_variable_get(:@client).api.instance_variable_get(:@host)).to eq("idontexi.st")
28
- expect(td.instance_variable_get(:@client).api.instance_variable_get(:@port)).to eq(80)
29
- expect(td.instance_variable_get(:@client).api.instance_variable_get(:@ssl)).to eq(false)
30
- end
31
-
32
- it 'with apikey and HTTPS endpoint' do
33
- td = TreasureData::Logger::TreasureDataLogger.new('db1', :apikey => 'test_1', :endpoint => "https://idontexi.st")
34
- expect(td.instance_variable_get(:@client).api.apikey).to eq('test_1')
35
- expect(td.instance_variable_get(:@client).api.instance_variable_get(:@host)).to eq("idontexi.st")
36
- expect(td.instance_variable_get(:@client).api.instance_variable_get(:@port)).to eq(443)
37
- expect(td.instance_variable_get(:@client).api.instance_variable_get(:@ssl)).to eq(true)
38
- end
39
-
40
- it 'db config' do
41
- td = TreasureData::Logger::TreasureDataLogger.new('db1', :apikey => 'test_1')
42
- time = Time.now
43
- expect(td).to receive(:add).with('db1', 'table1', {:foo => :bar, :time => time.to_i})
44
- td.post_with_time('table1', {:foo => :bar}, time)
45
- end
46
-
47
- it 'fluent-logger-td compat' do
48
- td = TreasureData::Logger::TreasureDataLogger.new('db1', :apikey => 'test_2')
49
- time = Time.now
50
- expect(td).to receive(:add).with('overwrite', 'table1', {:foo => :bar, :time => time.to_i})
51
- td.post_with_time('overwrite.table1', {:foo => :bar}, time)
52
- end
53
-
54
- ## TODO this causes real upload
55
- #it 'success' do
56
- # td = TreasureData::Logger::TreasureDataLogger.new('db1', :apikey => 'test_3')
57
- # td.post('valid', {}).should == true
58
- #end
59
- end
60
-
61
- context 'validate' do
62
- it 'validate table name' do
63
- td = TreasureData::Logger::TreasureDataLogger.new('db1', :apikey => 'test_4')
64
- expect {
65
- td.post('invalid-name', {})
66
- }.to raise_error(RuntimeError)
67
- expect {
68
- td.post('', {})
69
- }.to raise_error(RuntimeError)
70
- expect {
71
- td.post('9', {})
72
- }.to raise_error(RuntimeError)
73
- end
74
-
75
- it 'validate database name' do
76
- td = TreasureData::Logger::TreasureDataLogger.new('invalid-db-name', :apikey => 'test_5')
77
- expect {
78
- td.post('table', {})
79
- }.to raise_error(RuntimeError)
80
- end
81
- end
82
-
83
- it "raise error if `apikey` option is missing" do
84
- expect{ described_class.new("dummy-tag", {}) }.to raise_error(ArgumentError)
85
- end
86
-
87
- describe "#logger" do
88
- let(:tag_prefix) { "dummy" }
89
-
90
- subject { described_class.new(tag_prefix, {apikey: "dummy"}.merge(options)).logger }
91
-
92
- context "`debug` option given" do
93
- let(:options) { {debug: true} }
94
-
95
- it { expect(subject.level).to eq ::Logger::DEBUG }
96
- end
97
-
98
- context "not `debug` option given" do
99
- let(:options) { {} }
100
-
101
- it { expect(subject.level).to eq ::Logger::INFO }
102
- end
103
- end
104
-
105
- describe "#post_with_time" do
106
- let(:td) { described_class.new(prefix, {apikey: 'test_1'}.merge(options)) }
107
- let(:tag) { "foo.bar" }
108
- let(:time) { Time.now }
109
- let(:prefix) { "dummy" }
110
- let(:record) { {greeting: "hello", time: 1234567890} }
111
- let(:options) { {} }
112
-
113
- subject { td.post_with_time(tag, record, time) }
114
-
115
- describe "db and table" do
116
- context "no `tag_prefix` option given" do
117
- let(:prefix) { "" }
118
-
119
- it "`db` and `table` determine with tag" do
120
- db, table = tag.split(".")[-2, 2]
121
- allow(td).to receive(:add).with(db, table, record)
122
- subject
123
- end
124
- end
125
-
126
- context "`tag_prefix` option given" do
127
- let(:prefix) { "prefix" }
128
-
129
- it "`db` and `table` determine with tag and tag_prefix" do
130
- db, table = "#{prefix}.#{tag}".split(".")[-2, 2]
131
- allow(td).to receive(:add).with(db, table, record)
132
- subject
133
- end
134
- end
135
- end
136
-
137
- describe "inject time to record" do
138
- context "record has no `:time` key" do
139
- let(:record) { {greeting: "hello"} }
140
-
141
- it "fill-in :time key with `time` argument" do
142
- allow(td).to receive(:add).with(anything, anything, record.merge(time: time.to_i))
143
- subject
144
- end
145
- end
146
-
147
- context "record has `:time` key" do
148
- let(:record) { {greeting: "hello", time: 1234567890 } }
149
-
150
- it do
151
- allow(td).to receive(:add).with(anything, anything, record)
152
- subject
153
- end
154
- end
155
- end
156
- end
157
-
158
- describe "#add" do
159
- let(:td) { described_class.new(prefix, {apikey: 'test_1'}) }
160
- let(:prefix) { "prefix" }
161
- let(:db) { "database" }
162
- let(:table) { "table" }
163
- let(:valid_message) { {"foo" => "FOO", "bar" => "BAR"} }
164
-
165
- before do
166
- # don't try to upload data causing by `at_exit { close }` that registered at TreasureDataLogger#initialize
167
- # this hack causes "zlib(finalizer): Zlib::GzipWriter object must be closed explicitly." warning, but ignoreable for our tests..
168
- allow_any_instance_of(TreasureData::Logger::TreasureDataLogger).to receive(:at_exit).and_return(true)
169
- end
170
-
171
- subject { td.send(:add, db, table, message) } # NOTE: `add` is private method
172
-
173
- describe "message type" do
174
- shared_examples_for "should fail `add` because not a hash" do
175
- it { is_expected.to eq false }
176
- it "logging error" do
177
- expect(td.logger).to receive(:error).with(/TreasureDataLogger: record must be a Hash:/)
178
- subject
179
- end
180
- end
181
-
182
- context "string" do
183
- let(:message) { "string" }
184
- it_behaves_like "should fail `add` because not a hash"
185
- end
186
-
187
- context "array" do
188
- let(:message) { ["hello"] }
189
- it_behaves_like "should fail `add` because not a hash"
190
- end
191
-
192
- context "fixnum" do
193
- let(:message) { 42 }
194
- it_behaves_like "should fail `add` because not a hash"
195
- end
196
-
197
- context "hash" do
198
- let(:message) { {foo: 42} }
199
-
200
- it { is_expected.to eq true }
201
- end
202
- end
203
-
204
- describe "queue is full" do
205
- let(:queue) { td.instance_variable_get(:@queue) }
206
- let(:max) { td.instance_variable_get(:@queue_limit) }
207
- let(:message) { valid_message }
208
-
209
- before do
210
- (max + 1).times { queue << [db, table, {"foo" => "bar"}.to_msgpack] }
211
- end
212
-
213
- it { is_expected.to eq false }
214
- it do
215
- expect(td.logger).to receive(:error).with(/TreasureDataLogger: queue length exceeds limit. can't add new event log:/)
216
- subject
217
- end
218
- end
219
-
220
- describe "if `to_msgpack` failed" do
221
- let(:message) { valid_message }
222
-
223
- before { allow(td).to receive(:to_msgpack) { raise "something error" } }
224
-
225
- it { is_expected.to eq false }
226
- it do
227
- expect(td.logger).to receive(:error).with(/TreasureDataLogger: Can't convert to msgpack:/)
228
- subject
229
- end
230
- end
231
-
232
- describe "buffer size/cardinality check" do
233
- let(:max) { TreasureData::Logger::TreasureDataLogger::MAX_KEY_CARDINALITY }
234
- let(:warn) { TreasureData::Logger::TreasureDataLogger::WARN_KEY_CARDINALITY }
235
- let(:map_key) { [db, table] }
236
- let(:record_keys) { max.times }
237
- let(:message) { record_keys.inject({}){|r, n| r[n] = n; r } }
238
-
239
- context "buffer size == MAX_KEY_CARDINALITY" do
240
- it { is_expected.to eq true }
241
- end
242
-
243
- context "buffer size > MAX_KEY_CARDINALITY" do
244
- let(:record_keys) { (max + 1).times }
245
-
246
- it { is_expected.to eq false }
247
- it do
248
- expect(td.logger).to receive(:error).with(/TreasureDataLogger: kind of keys in a buffer exceeds/)
249
- expect(td.instance_variable_get(:@map)).to receive(:delete).with(map_key)
250
- subject
251
- end
252
- end
253
-
254
- context "before <= WARN && WARN < after" do
255
- let(:record_keys) { (warn + 1).times } # just one key
256
-
257
- it do
258
- expect(td.logger).to receive(:warn).with("TreasureDataLogger: kind of keys in a buffer exceeds #{warn} which is too large. please check the schema design.")
259
- subject
260
- end
261
- end
262
-
263
- context "buffer.size > @chunk_limit" do
264
- let(:chunk_limit) { 0 }
265
-
266
- before { td.instance_variable_set(:@chunk_limit, chunk_limit) }
267
-
268
- it do
269
- expect(td.instance_variable_get(:@queue)).to receive(:<<).with([db, table, anything])
270
- expect(td.instance_variable_get(:@map)).to receive(:delete).with(map_key)
271
- expect(td.instance_variable_get(:@cond)).to receive(:signal)
272
- subject
273
- end
274
- end
275
- end
276
- end
277
-
278
- describe "#to_msgpack" do
279
- let(:td) { described_class.new("prefix", {apikey: 'test_1'}) }
280
-
281
- subject { td.send(:to_msgpack, target) }
282
-
283
- shared_examples_for "original to_msgpack only invoked" do
284
- after { subject }
285
-
286
- it { expect(target).to receive(:to_msgpack) }
287
- it { expect(JSON).to_not receive(:dump) }
288
- it { expect(JSON).to_not receive(:load) }
289
- end
290
-
291
- shared_examples_for "JSON.load/dump then to_msgpack invoke" do
292
- after { subject }
293
-
294
- it { expect(JSON).to receive(:dump).with(target) }
295
- it { expect(JSON).to receive(:load) }
296
- end
297
-
298
- context "have to_msgpack objects" do
299
- # see also for official supported objects: https://github.com/msgpack/msgpack-ruby/blob/master/ext/msgpack/core_ext.c
300
- context "string" do
301
- let(:target) { "foobar" }
302
- it_behaves_like "original to_msgpack only invoked"
303
- end
304
-
305
- context "array" do
306
- let(:target) { [1] }
307
- it_behaves_like "original to_msgpack only invoked"
308
- end
309
-
310
- context "hash" do
311
- let(:target) { {foo: "foo"} }
312
- it_behaves_like "original to_msgpack only invoked"
313
- end
314
-
315
- context "time" do
316
- let(:target) { Time.now }
317
- it_behaves_like "original to_msgpack only invoked"
318
- end
319
- end
320
-
321
- context "have not to_msgpack objects" do
322
- context "date" do
323
- let(:target) { Date.today }
324
- it_behaves_like "JSON.load/dump then to_msgpack invoke"
325
- end
326
-
327
- context "class" do
328
- let(:target) { Class.new }
329
- it_behaves_like "JSON.load/dump then to_msgpack invoke"
330
- end
331
- end
332
- end
333
-
334
- describe "#upload" do
335
- let(:td) { described_class.new(prefix, options) }
336
- let(:prefix) { "prefix" }
337
- let(:options) { {apikey: "apikey"} }
338
- let(:db) { "database" }
339
- let(:table) { "table" }
340
- let(:message) { {foo: "FOO"} }
341
-
342
- subject { td.send(:upload, db, table, message.to_msgpack) } # NOTE: `upload` is private method
343
-
344
- describe "TreasureDataLogger::Client#import success" do
345
- context 'unuse unique_key' do
346
- it do
347
- expect(td.instance_variable_get(:@client)).to receive(:import).with(db, table, "msgpack.gz", anything, anything, nil)
348
- subject
349
- end
350
- end
351
-
352
- context 'use unique_key' do
353
- let(:options) { {apikey: "apike", use_unique_key: true} }
354
-
355
- it do
356
- expect(td.instance_variable_get(:@client)).to receive(:import).with(db, table, "msgpack.gz", anything, anything, kind_of(String))
357
- subject
358
- end
359
- end
360
- end
361
-
362
- describe "TreasureDataLogger::NotFoundError" do
363
- let(:client) { td.instance_variable_get(:@client) }
364
-
365
- before do
366
- # NOTE: raise "not found" client.import at first called, but second and later call not raise
367
- queue = [true]
368
- allow(client).to receive(:import){
369
- raise TreasureData::NotFoundError, "not found" if queue.shift
370
- true
371
- }
372
- end
373
-
374
- context "auto_create_table options disabled" do
375
- let(:options) { {apikey: "apikey", auto_create_table: false} }
376
-
377
- it do
378
- expect{ subject }.to raise_error(TreasureData::NotFoundError)
379
- end
380
- end
381
-
382
- context "auto_create_table options enabled" do
383
- let(:options) { {apikey: "apikey", auto_create_table: true} }
384
-
385
- it "try to create table with exists database" do
386
- expect(client).to receive(:create_log_table)
387
- subject
388
- end
389
-
390
- it "try to create table with no exists databae" do
391
- queue = [true]
392
- allow(client).to receive(:create_log_table){ raise TreasureData::NotFoundError, "not found again" if queue.shift}
393
- expect(client).to receive(:create_database)
394
- subject
395
- end
396
-
397
- it "retry @client.import after create table" do
398
- expect(client).to receive(:create_log_table)
399
- expect(client).to receive(:import).twice # second call is retry
400
- subject
401
- end
402
- end
403
- end
404
- end
405
-
406
- describe "#flush" do
407
- let(:td) { described_class.new("prefix", {apikey: 'test_1'}) }
408
- let(:db) { "database" }
409
- let(:table) { "table" }
410
-
411
- subject { td.flush }
412
-
413
- before { allow(td).to receive(:try_flush) } # do not call try_flush actually, try_flush test is in other place
414
-
415
- it "lock mutex and release" do
416
- expect(td.instance_variable_get(:@mutex)).to receive(:lock)
417
- expect(td.instance_variable_get(:@mutex)).to receive(:unlock)
418
- subject
419
- end
420
-
421
- it "call #try_flush" do
422
- expect(td).to receive(:try_flush).with(no_args)
423
- subject
424
- end
425
-
426
- it "@map to be flushed (enqueue)" do
427
- buffer = TreasureData::Logger::TreasureDataLogger::Buffer.new
428
- td.instance_variable_set(:@map, {[db, table] => buffer})
429
- expect(td.instance_variable_get(:@queue)).to receive(:<<).with([db, table, buffer.flush!])
430
- subject
431
- end
432
-
433
- it "rescue anything error" do
434
- allow(td).to receive(:try_flush){ raise "something error" }
435
- expect(td.instance_variable_get(:@logger)).to receive(:error).with(/Unexpected error at flush:/)
436
- expect(td.instance_variable_get(:@logger)).to receive(:info).at_least(1) # backtrace
437
- expect(td.instance_variable_get(:@mutex)).to_not be_locked
438
- subject
439
- end
440
- end
441
-
442
- describe "#try_flush" do
443
- let(:td) { described_class.new("prefix", {apikey: 'test_1'}) }
444
- let(:db) { "database" }
445
- let(:table) { "table" }
446
-
447
- let(:mutex) { td.instance_variable_get(:@mutex) }
448
- let(:queue) { td.instance_variable_get(:@queue) }
449
- let(:logger) { td.instance_variable_get(:@logger) }
450
-
451
- let(:buffer) { TreasureData::Logger::TreasureDataLogger::Buffer.new }
452
-
453
- before { allow_any_instance_of(TreasureData::Logger::TreasureDataLogger).to receive(:at_exit).and_return(true) }
454
- before { mutex.lock } # try_flush is expected mutex locked that called
455
- after { mutex.unlock }
456
-
457
- subject { td.send(:try_flush) }
458
-
459
- describe "force flush small buffers if queue is empty" do
460
-
461
- before { allow(td).to receive(:upload) } # do not call `upload` actually, that method test is in other place
462
- before { td.instance_variable_set(:@map, {[db, table] => buffer}) } # dummy data append
463
-
464
- context "queue is empty" do
465
- it do
466
- expect(queue).to receive(:<<).with([db, table, anything])
467
- subject
468
- end
469
- end
470
-
471
- context "queue is not empty" do
472
- before { queue << [db, table, buffer.flush!] }
473
-
474
- it do
475
- expect(queue).to_not receive(:<<).with([db, table, anything])
476
- subject
477
- end
478
- end
479
- end
480
-
481
- context "queue and on-memory buffer are empty" do
482
- before { allow(td).to receive(:upload) } # do not call `upload` actually, that method test is in other place
483
-
484
- it "return false" do
485
- is_expected.to eq false
486
- end
487
-
488
- it "don't call #upload" do
489
- expect(td).to_not receive(:upload)
490
- subject
491
- end
492
- end
493
-
494
- context "some data having" do
495
- describe "queue to upload" do
496
- before do
497
- times.times do |n|
498
- buf = TreasureData::Logger::TreasureDataLogger::Buffer.new
499
- buf.append(n)
500
- queue << [db, table, buf.flush!]
501
- end
502
- end
503
-
504
- context "100 queue" do
505
- let(:times) { 100 }
506
-
507
- it "call upload 100 times" do
508
- expect(td).to receive(:upload).exactly(times)
509
- subject
510
- end
511
- end
512
- end
513
-
514
- describe "upload fail" do
515
- before do
516
- 100.times do |n|
517
- buf = TreasureData::Logger::TreasureDataLogger::Buffer.new
518
- buf.append(n)
519
- queue << [db, table, buf.flush!]
520
- end
521
- end
522
-
523
- # NOTE: @retry_limit is hard coded as 12
524
- before do
525
- times = error_times.times.to_a
526
- allow(td).to receive(:upload){
527
- if n = times.shift
528
- raise "something error (#{n})"
529
- else
530
- true
531
- end
532
- }
533
- end
534
-
535
- context "errors 0 times" do
536
- let(:error_times) { 0 }
537
-
538
- it { expect(logger).to_not receive(:info); subject }
539
- it { expect(logger).to_not receive(:error); subject }
540
- end
541
-
542
- context "errors 1 times" do
543
- let(:error_times) { 1 }
544
-
545
- it do
546
- expect(queue).to_not receive(:clear)
547
- subject
548
- end
549
-
550
- it do
551
- expect(logger).to receive(:error).with(/Failed to upload event logs to Treasure Data, retrying:/)
552
- subject
553
- end
554
-
555
- it "not trash" do
556
- expect(logger).to_not receive(:error).with(/Failed to upload event logs to Treasure Data, trashed:/)
557
- subject
558
- end
559
-
560
- it "error_count should reset" do
561
- expect(td.instance_variable_get(:@error_count)).to eq 0
562
- subject
563
- end
564
- end
565
-
566
- context "errors 20 times" do
567
- let(:error_times) { 20 }
568
-
569
- it do
570
- skip "try_flush has bug?"
571
- expect(queue).to receive(:clear)
572
- subject
573
- end
574
-
575
- it do
576
- skip "try_flush has bug?"
577
- expect(logger).to receive(:error).with(/Failed to upload event logs to Treasure Data, retrying:/)
578
- expect(logger).to receive(:error).with(/Failed to upload event logs to Treasure Data, trashed:/)
579
- subject
580
- end
581
-
582
- it do
583
- skip "try_flush has bug?"
584
- expect(queue).to_not receive(:clear)
585
- subject
586
- end
587
-
588
- it "output backtrace as INFO level" do
589
- skip "try_flush has bug?"
590
- expect(logger).to receive(:info).at_least(1) # backtrace
591
- subject
592
- end
593
-
594
- it "error_count should reset" do
595
- skip "try_flush has bug?"
596
- expect(td.instance_variable_get(:@error_count)).to eq 0
597
- subject
598
- end
599
- end
600
- end
601
- end
602
- end
603
-
604
- describe "#close" do
605
- let(:td) { described_class.new("prefix", {apikey: 'test_1'}) }
606
- let(:db) { "database" }
607
- let(:table) { "table" }
608
- let(:queue) { td.instance_variable_get(:@queue) }
609
- let(:logger) { td.instance_variable_get(:@logger) }
610
-
611
- subject { td.close }
612
-
613
- context "queue is empty" do
614
- it do
615
- expect(td).to_not receive(:upload)
616
- subject
617
- end
618
- end
619
-
620
- context "queue is not empty" do
621
- before { queue << [db, table, TreasureData::Logger::TreasureDataLogger::Buffer.new.flush!] }
622
-
623
- context "upload success" do
624
- it do
625
- expect(td).to receive(:upload)
626
- subject
627
- end
628
- end
629
-
630
- context "upload fail" do
631
- it do
632
- allow(td).to receive(:upload){ raise "something error"}
633
- expect(logger).to receive(:error).with(/Failed to upload event logs to Treasure Data, trashed:/)
634
- subject
635
- end
636
- end
637
- end
638
-
639
- context "@map is empty" do
640
- it do
641
- expect(td).to_not receive(:upload)
642
- subject
643
- end
644
- end
645
-
646
- context "@map is not empty" do
647
- before do
648
- buffer = TreasureData::Logger::TreasureDataLogger::Buffer.new
649
- td.instance_variable_set(:@map, {[db, table] => buffer})
650
- end
651
-
652
- context "upload success" do
653
- it do
654
- expect(td).to receive(:upload)
655
- subject
656
- end
657
- end
658
-
659
- context "upload fail" do
660
- it do
661
- allow(td).to receive(:upload){ raise "something error" }
662
- expect(logger).to receive(:error).with(/Failed to upload event logs to Treasure Data, trashed:/)
663
- subject
664
- end
665
- end
666
- end
667
- end
668
- end
669
-