logstash-logger 0.26.1 → 1.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/tests.yml +51 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +85 -105
  5. data/Appraisals +6 -18
  6. data/CHANGELOG.md +28 -0
  7. data/Gemfile +0 -1
  8. data/README.md +12 -833
  9. data/docs/buffering.md +70 -0
  10. data/docs/customization.md +86 -0
  11. data/docs/outputs.md +42 -0
  12. data/docs/rails.md +344 -0
  13. data/docs/ssl.md +90 -0
  14. data/docs/troubleshooting.md +84 -0
  15. data/docs/usage.md +148 -0
  16. data/gemfiles/{rails_4.2.gemfile → rails_7.2.gemfile} +1 -2
  17. data/gemfiles/{rails_5.0.gemfile → rails_8.0.gemfile} +1 -2
  18. data/gemfiles/{rails_4.0.gemfile → rails_8.1.gemfile} +1 -2
  19. data/lib/logstash-logger/buffer.rb +0 -1
  20. data/lib/logstash-logger/configuration.rb +1 -2
  21. data/lib/logstash-logger/device/aws_stream.rb +1 -1
  22. data/lib/logstash-logger/device/base.rb +2 -2
  23. data/lib/logstash-logger/device/connectable.rb +3 -11
  24. data/lib/logstash-logger/device/file.rb +21 -4
  25. data/lib/logstash-logger/device/http.rb +33 -0
  26. data/lib/logstash-logger/device/kafka.rb +153 -36
  27. data/lib/logstash-logger/device/redis.rb +8 -1
  28. data/lib/logstash-logger/device/tcp.rb +1 -5
  29. data/lib/logstash-logger/device.rb +24 -2
  30. data/lib/logstash-logger/formatter/base.rb +54 -9
  31. data/lib/logstash-logger/formatter/cee_syslog.rb +1 -1
  32. data/lib/logstash-logger/formatter/json.rb +13 -0
  33. data/lib/logstash-logger/formatter/json_lines.rb +13 -0
  34. data/lib/logstash-logger/formatter.rb +14 -6
  35. data/lib/logstash-logger/logger.rb +6 -19
  36. data/lib/logstash-logger/multi_logger.rb +2 -1
  37. data/lib/logstash-logger/railtie.rb +1 -1
  38. data/lib/logstash-logger/tagged_logging.rb +3 -1
  39. data/lib/logstash-logger/version.rb +1 -1
  40. data/logstash-logger.gemspec +9 -1
  41. data/spec/device/file_spec.rb +65 -0
  42. data/spec/device/http_spec.rb +11 -0
  43. data/spec/device/kafka_spec.rb +337 -14
  44. data/spec/device_spec.rb +13 -0
  45. data/spec/formatter/base_spec.rb +46 -1
  46. data/spec/formatter/cee_syslog_spec.rb +3 -3
  47. data/spec/formatter/json_lines_spec.rb +23 -0
  48. data/spec/formatter/json_spec.rb +49 -0
  49. data/spec/formatter_spec.rb +19 -2
  50. data/spec/logger_spec.rb +5 -5
  51. data/spec/multi_logger_spec.rb +16 -0
  52. data/spec/spec_helper.rb +2 -5
  53. data/spec/tagged_logging_spec.rb +15 -0
  54. metadata +89 -16
  55. data/.travis.yml +0 -26
  56. data/gemfiles/rails_3.2.gemfile +0 -9
  57. data/gemfiles/rails_4.1.gemfile +0 -9
  58. data/gemfiles/rails_5.1.gemfile +0 -9
@@ -1,32 +1,355 @@
1
1
  require 'logstash-logger'
2
2
 
3
+ describe LogStashLogger::Device::Kafka::TLSConfiguration do
4
+ context "with TLS" do
5
+ let(:complete_bundle) do
6
+ # NOTE these keys are obviously fake. We don't actually make connections
7
+ {
8
+ ssl_ca_cert: "----BEGIN CERT---- lkajdslkajdsjk ----END CERT---",
9
+ ssl_client_cert: "----BEGIN CERT---- lkajdslkajdsjk ----END CERT---",
10
+ ssl_client_cert_key: "----PRIVATE---- lkajdslkajdsjk ----PRIVATE---",
11
+ }
12
+ end
13
+
14
+ context "when complete params are passed in" do
15
+ let(:instance) { described_class.new(complete_bundle) }
16
+
17
+ it "returns an empty hash when no ssl params are initialized" do
18
+ expect(instance.cert_bundle).to_not be_empty
19
+ expect(instance.valid?).to be_truthy
20
+ end
21
+ end
22
+
23
+ context "when incomplete params are passed in" do
24
+ [:ssl_ca_cert, :ssl_client_cert, :ssl_client_cert_key].each do |param|
25
+ it "fails with an ArgumentError when #{param} no provided" do
26
+ opts = complete_bundle
27
+ opts[param] = nil
28
+ instance = described_class.new(opts)
29
+ expect(instance.cert_bundle).to be_empty
30
+ expect(instance.valid?).to be_falsey
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ context "without TLS" do
37
+ let(:instance) { subject }
38
+
39
+ it "returns an empty hash when no ssl params are initialized" do
40
+ expect(instance.cert_bundle).to be_empty
41
+ expect(instance.valid?).to be_truthy
42
+ end
43
+ end
44
+
45
+ context "#invalid?" do
46
+ it 'is just the opposite of valid?' do
47
+ expect(subject).to receive(:valid?).and_return(false)
48
+ expect(subject.invalid?).to be_truthy
49
+
50
+ expect(subject).to receive(:valid?).and_return(true)
51
+ expect(subject.invalid?).to be_falsey
52
+ end
53
+ end
54
+ end
55
+
3
56
  describe LogStashLogger::Device::Kafka do
4
57
  include_context 'device'
5
58
 
6
- let(:producer) { double("Poseidon::Producer") }
59
+ let(:broker_hosts) { "localhost:9300 localhost:9232" }
60
+ let(:opts) do
61
+ { topic: 'hello-world',
62
+ brokers: broker_hosts,
63
+ }
64
+ end
65
+ let(:instance) { LogStashLogger::Device::Kafka.new(opts) }
66
+ let(:brokers) { %w(localhost:9300 localhost:9232) }
67
+
68
+ describe "initializing" do
69
+ context "uri-derived options" do
70
+ it "normalizes host/port/path into brokers and topic" do
71
+ instance = described_class.new(
72
+ host: 'localhost',
73
+ port: 9092,
74
+ path: '/logstash'
75
+ )
76
+
77
+ expect(instance.brokers).to eq(['localhost:9092'])
78
+ expect(instance.topic).to eq('logstash')
79
+ end
80
+ end
81
+
82
+ context "brokers" do
83
+ context "when array" do
84
+ it "sets the brokers array to @brokers" do
85
+ instance = LogStashLogger::Device::Kafka.new(opts.merge({brokers: brokers}))
86
+
87
+ expect(instance.brokers).to eql(brokers)
88
+ end
89
+
90
+ it 'sets the brokers to an array if a string is passed in' do
91
+ instance = LogStashLogger::Device::Kafka.new(opts.merge({brokers: broker_hosts}))
92
+ expect(instance.brokers).to eql(brokers)
93
+ end
94
+ end
95
+
96
+ context "when missing or empty" do
97
+ it "raises an error if brokers are nil" do
98
+ expect {
99
+ described_class.new(topic: 'hello-world', brokers: nil)
100
+ }.to raise_error(ArgumentError)
101
+ end
102
+
103
+ it "raises an error if brokers are an empty array" do
104
+ expect {
105
+ described_class.new(topic: 'hello-world', brokers: [])
106
+ }.to raise_error(ArgumentError)
107
+ end
108
+
109
+ it "raises an error if brokers are blank" do
110
+ expect {
111
+ described_class.new(topic: 'hello-world', brokers: " ")
112
+ }.to raise_error(ArgumentError)
113
+ end
114
+ end
115
+ end
116
+
117
+ context "topic" do
118
+ it 'sets the topic to the option provided' do
119
+ instance = described_class.new(topic: 'hello-world', brokers: brokers)
120
+ expect(instance.topic).to eql('hello-world')
121
+ end
122
+
123
+ it 'raises an exception if no topic is set' do
124
+ expect {
125
+ described_class.new(topic: nil)
126
+ }.to raise_error(ArgumentError)
127
+ end
128
+
129
+ it 'raises an exception if the topic is blank' do
130
+ expect {
131
+ described_class.new(topic: " ", brokers: brokers)
132
+ }.to raise_error(ArgumentError)
133
+ end
134
+ end
135
+
136
+ context "Client Introspection" do
137
+ # YUCK! ruby-kafka does not presently allow reading certain variables
138
+ module ::Kafka
139
+ class Client
140
+ attr_reader :connection_builder
141
+ end
142
+ end
143
+
144
+ module ::Kafka
145
+ class ConnectionBuilder
146
+ attr_reader :ssl_context, :client_id
147
+ end
148
+ end
149
+
150
+ context "client_id" do
151
+ it 'sets a client_id when connecting if one is passed in to the options' do
152
+ instance = described_class.new(opts.merge(client_id: 'hello-world'))
153
+ expect(instance.client_id).to eql('hello-world')
154
+ expect(instance.connection.connection_builder.client_id).to eql('hello-world')
155
+ end
156
+ end
157
+
158
+ context "cert bundle" do
159
+ let(:complete_bundle) do
160
+ # NOTE these keys are obviously fake. We don't actually make connections
161
+ {
162
+ ssl_ca_cert: "----BEGIN CERT---- lkajdslkajdsjk ----END CERT---",
163
+ ssl_client_cert: "----BEGIN CERT---- lkajdslkajdsjk ----END CERT---",
164
+ ssl_client_cert_key: "----PRIVATE---- lkajdslkajdsjk ----PRIVATE---",
165
+ }
166
+ end
167
+
168
+ context "no certs" do
169
+ it 'creates a connection without an ssl_context' do
170
+ connection = instance.connection
171
+ expect(connection.connection_builder.ssl_context).to be_nil
172
+ end
173
+ end
174
+
175
+ context "partial certs passed in" do
176
+ it 'fails if the complete cert suite is not passed in' do
177
+ [:ssl_ca_cert, :ssl_client_cert, :ssl_client_cert_key].each do |param|
178
+ base_opts = opts
179
+ invalid_opts = complete_bundle.merge(base_opts)
180
+ invalid_opts[param] = nil
181
+ expect {
182
+ LogStashLogger::Device::Kafka.new(invalid_opts).connection
183
+ }.to raise_error( ArgumentError )
184
+ end
185
+ end
186
+ end
7
187
 
8
- before(:each) do
9
- allow(Poseidon::Producer).to receive(:new) { producer }
188
+ context "complete cert bundle" do
189
+
190
+ it 'correctly passes in the cert bundle to the Kafka Client' do
191
+ certopts = complete_bundle.merge(opts)
192
+
193
+ ssl_context = double("ssl_context")
194
+ expect(::Kafka::SslContext).to receive(:build)
195
+ .with(hash_including(
196
+ ca_cert: certopts[:ssl_ca_cert],
197
+ client_cert: certopts[:ssl_client_cert],
198
+ client_cert_key: certopts[:ssl_client_cert_key],
199
+ ))
200
+ .and_return(ssl_context)
201
+
202
+ LogStashLogger::Device::Kafka.new(certopts).connection
203
+ end
204
+ end
205
+ end
206
+ end
10
207
  end
11
208
 
12
- it "writes to a Kafka topic" do
13
- expect(producer).to receive(:send_messages)
14
- kafka_device.write "foo"
209
+ describe "writing single message to broker" do
210
+ it 'writes the message to the topic' do
211
+ producer = double('producer', produce: true)
212
+ connect_double = double("connection", producer: producer)
213
+ instance = described_class.new(opts)
214
+
215
+ # NOTE: this is stubbing out the ruby-kafka API
216
+ expect(instance).to receive(:connection).and_return(connect_double)
217
+ expect(connect_double).to receive(:producer)
218
+ expect(producer).to receive(:produce).and_return(true)
219
+ expect(producer).to receive(:deliver_messages).and_return(true)
220
+ instance.write_one("hello world")
221
+ end
222
+
223
+ it 'is capabable of writing to a different topic than instantiated' do
224
+ producer = double('producer', produce: lambda {|_message, _topic| "hi" })
225
+ connect_double = double("connection", producer: producer)
226
+ instance = described_class.new(opts)
227
+
228
+ message = 'hello world'
229
+ topic = 'my topic'
230
+ # NOTE: this is stubbing out the ruby-kafka API
231
+ expect(instance).to receive(:connection).and_return(connect_double)
232
+ expect(connect_double).to receive(:producer)
233
+ expect(producer).to receive(:produce).
234
+ with(message, topic: topic).
235
+ and_return(true)
236
+ expect(producer).to receive(:deliver_messages).and_return(true)
237
+
238
+ instance.write_one(message, topic)
239
+ end
15
240
  end
16
241
 
17
- it "defaults the Kafka hosts to ['localhost:9092']" do
18
- expect(kafka_device.hosts).to eq(['localhost:9092'])
242
+ describe "error handling" do
243
+ it "clears the producer and connection when an error occurs" do
244
+ producer = double('producer')
245
+ allow(producer).to receive(:produce).and_raise(StandardError, "boom")
246
+ allow(producer).to receive(:deliver_messages)
247
+
248
+ connection = double("connection", producer: producer, close: true)
249
+ instance = described_class.new(opts)
250
+
251
+ allow(instance).to receive(:connection).and_return(connection)
252
+ expect(connection).to receive(:close)
253
+
254
+ expect {
255
+ instance.write_one("hello world")
256
+ }.to raise_error(StandardError)
257
+
258
+ expect(instance.instance_variable_get(:@producer)).to be_nil
259
+ expect(instance.instance_variable_get(:@io)).to be_nil
260
+ end
19
261
  end
20
262
 
21
- it "defaults the Kafka topic to 'logstash'" do
22
- expect(kafka_device.topic).to eq('logstash')
263
+ describe "buffer group" do
264
+ it "uses the topic so final flush does not pass a boolean topic" do
265
+ instance = described_class.new(opts)
266
+
267
+ expect(instance).to receive(:write_batch).with(['hello world'], 'hello-world')
268
+ instance.write('hello world')
269
+ instance.buffer_flush(final: true)
270
+ end
23
271
  end
24
272
 
25
- it "defaults the Kafka producer to 'logstash-logger'" do
26
- expect(kafka_device.producer).to eq('logstash-logger')
273
+ describe "writing a batch of messages to the broker" do
274
+ it "writes the messages to the topic" do
275
+ producer = double('producer', produce: lambda {|_message, _topic| "hi" })
276
+ connect_double = double("connection", producer: producer)
277
+ instance = described_class.new(opts)
278
+
279
+ messages = ['hello world', 'goodbye world']
280
+ topic = 'my topic'
281
+ # NOTE: this is stubbing out the ruby-kafka API
282
+ expect(instance).to receive(:connection).and_return(connect_double)
283
+ expect(connect_double).to receive(:producer)
284
+ expect(producer).to receive(:produce).
285
+ with(messages.first, topic: topic).
286
+ and_return(true)
287
+ expect(producer).to receive(:produce).
288
+ with(messages.last, topic: topic).
289
+ and_return(true)
290
+ expect(producer).to receive(:deliver_messages).and_return(true)
291
+
292
+ instance.write_batch(messages, topic)
293
+ end
27
294
  end
28
295
 
29
- it "defaults the Kafka backoff to 1" do
30
- expect(kafka_device.backoff).to eq(1)
296
+ describe "connecting/reconnecting" do
297
+ it "sets @io when connecting" do
298
+ instance = described_class.new(opts)
299
+ connection = double("connection")
300
+
301
+ instance.instance_variable_set(:@io, connection)
302
+ expect(instance.connect).to eq(connection)
303
+ expect(instance.io).to eq(connection)
304
+ end
305
+
306
+ it "reconnects without raising" do
307
+ instance = described_class.new(opts)
308
+ connection = double("connection", close: true)
309
+
310
+ allow(instance).to receive(:connection).and_return(connection)
311
+ expect { instance.reconnect }.not_to raise_error
312
+ end
313
+ end
314
+
315
+ describe "producer lifecycle" do
316
+ it "memoizes the producer across writes" do
317
+ producer = double('producer', produce: true, deliver_messages: true)
318
+ connection = double("connection", producer: producer)
319
+ instance = described_class.new(opts)
320
+
321
+ expect(instance).to receive(:connection).and_return(connection)
322
+ expect(connection).to receive(:producer).once.and_return(producer)
323
+
324
+ instance.write_one("hello world")
325
+ instance.write_one("goodbye world")
326
+ end
327
+
328
+ it "shuts down the producer on close" do
329
+ instance = described_class.new(opts)
330
+ producer = double("producer", shutdown: true)
331
+ connection = double("connection", close: true)
332
+
333
+ instance.instance_variable_set(:@producer, producer)
334
+ instance.instance_variable_set(:@io, connection)
335
+
336
+ expect(producer).to receive(:shutdown)
337
+ expect(connection).to receive(:close)
338
+
339
+ instance.close
340
+ end
341
+ end
342
+
343
+ describe "closing connection" do
344
+ it "closes the Kafka connection when present" do
345
+ instance = described_class.new(opts)
346
+ connection = double("connection")
347
+ expect(connection).to receive(:close)
348
+
349
+ instance.instance_variable_set(:@io, connection)
350
+ instance.close
351
+
352
+ expect(instance.instance_variable_get(:@connection)).to be_nil
353
+ end
31
354
  end
32
355
  end
data/spec/device_spec.rb CHANGED
@@ -38,6 +38,19 @@ describe LogStashLogger::Device do
38
38
  it { is_expected.to eq({type: 'udp', host: 'localhost', port: 5228, path: ''}) }
39
39
  end
40
40
 
41
+ context "when uri includes buffer_max_items" do
42
+ let(:uri_config) { {uri: "udp://localhost:5228?buffer_max_items=75"} }
43
+ it do
44
+ expect(parse_uri_config).to eq({
45
+ type: 'udp',
46
+ host: 'localhost',
47
+ port: 5228,
48
+ path: '',
49
+ buffer_max_items: 75,
50
+ })
51
+ end
52
+ end
53
+
41
54
  context "when uri is invalid" do
42
55
  let(:uri_config) { invalid_uri_config }
43
56
  specify { expect { parse_uri_config }.to raise_error(URI::InvalidURIError) }
@@ -23,6 +23,51 @@ describe LogStashLogger::Formatter::Base do
23
23
  expect(subject.call(severity, time, progname, message)).to be_nil
24
24
  end
25
25
  end
26
+
27
+ context "when exception is raised" do
28
+ before do
29
+ allow(subject).to receive(:error_logger).and_return(Logger.new('/dev/null'))
30
+ allow(subject).to receive(:format_event).and_throw
31
+ end
32
+
33
+ it "logs an exception" do
34
+ expect(subject).to receive(:log_error)
35
+ subject.call(severity, time, progname, message)
36
+ end
37
+
38
+ it "returns a failed to format message" do
39
+ expect(subject.call(severity, time, progname, message)).to eq(LogStashLogger::Formatter::Base::FAILED_TO_FORMAT_MSG)
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '#force_utf8_encoding' do
45
+ let(:message) { "foo x\xc3x".force_encoding('ASCII-8BIT').freeze }
46
+ let(:event) { LogStash::Event.new("message" => message) }
47
+
48
+ it 'returns the same event' do
49
+ expect(subject.send(:force_utf8_encoding, event)).to eq(event)
50
+ end
51
+
52
+ it 'forces the event message to UTF-8 encoding' do
53
+ subject.send(:force_utf8_encoding, event)
54
+ updated_event_data = event.instance_variable_get(:@data)
55
+ expect(updated_event_data['message'].encoding.name).to eq('UTF-8')
56
+ end
57
+
58
+ context 'frozen message string' do
59
+ let(:message) { "foo x\xc3x".dup.force_encoding('ASCII-8BIT').freeze }
60
+
61
+ it 'returns the same event' do
62
+ expect(subject.send(:force_utf8_encoding, event)).to eq(event)
63
+ end
64
+
65
+ it 'forces the event message to UTF-8 encoding' do
66
+ subject.send(:force_utf8_encoding, event)
67
+ updated_event_data = event.instance_variable_get(:@data)
68
+ expect(updated_event_data['message'].encoding.name).to eq('UTF-8')
69
+ end
70
+ end
26
71
  end
27
72
 
28
73
  describe "#build_event" do
@@ -81,7 +126,7 @@ describe LogStashLogger::Formatter::Base do
81
126
  end
82
127
 
83
128
  it "adds host" do
84
- expect(event['host']).to eq(hostname)
129
+ expect(event['host']['hostname']).to eq(hostname)
85
130
  end
86
131
  end
87
132
 
@@ -22,21 +22,21 @@ describe LogStashLogger::Formatter::CeeSyslog do
22
22
  end
23
23
 
24
24
  describe "#build_facility" do
25
- let(:host) { Socket.gethostname }
25
+ let(:host) { { 'hostname' => Socket.gethostname } }
26
26
 
27
27
  before do
28
28
  formatted_message
29
29
  end
30
30
 
31
31
  it "includes hostname and progname" do
32
- expect(subject.send(:build_facility, host)).to match(/\A#{host}\s#{progname}\z/)
32
+ expect(subject.send(:build_facility, host)).to match(/\A#{host['hostname']}\s#{progname}\z/)
33
33
  end
34
34
 
35
35
  context "without progname" do
36
36
  let(:progname) { nil }
37
37
 
38
38
  it "only includes hostname" do
39
- expect(subject.send(:build_facility, host)).to match(/\A#{host}\z/)
39
+ expect(subject.send(:build_facility, host)).to match(/\A#{host['hostname']}\z/)
40
40
  end
41
41
  end
42
42
  end
@@ -11,4 +11,27 @@ describe LogStashLogger::Formatter::JsonLines do
11
11
  it "terminates with a line break" do
12
12
  expect(formatted_message[-1]).to eq("\n")
13
13
  end
14
+
15
+ context 'encoding error is raised' do
16
+ let(:message) { "foo x\xc3x".force_encoding('ASCII-8BIT') }
17
+
18
+ before do
19
+ allow(subject).to receive(:error_logger).and_return(Logger.new('/dev/null'))
20
+ end
21
+
22
+ it 'logs the error' do
23
+ expect(subject).to receive(:log_error)
24
+ formatted_message
25
+ end
26
+
27
+ it 'forces the message encoding to utf8' do
28
+ expect(subject).to receive(:force_utf8_encoding)
29
+ formatted_message
30
+ end
31
+
32
+ it "outputs in JSON format with message encoding updated to utf8" do
33
+ json_message = JSON.parse(formatted_message)
34
+ expect(json_message["message"]).to eq(message.force_encoding(Encoding::UTF_8).scrub)
35
+ end
36
+ end
14
37
  end
@@ -7,4 +7,53 @@ describe LogStashLogger::Formatter::Json do
7
7
  json_message = JSON.parse(formatted_message)
8
8
  expect(json_message["message"]).to eq(message)
9
9
  end
10
+
11
+ context 'encoding error is raised' do
12
+ let(:message) { "foo x\xc3x".force_encoding('ASCII-8BIT') }
13
+
14
+ before do
15
+ allow(subject).to receive(:error_logger).and_return(Logger.new('/dev/null'))
16
+ end
17
+
18
+ it 'logs the error' do
19
+ expect(subject).to receive(:log_error)
20
+ formatted_message
21
+ end
22
+
23
+ it 'forces the message encoding to utf8' do
24
+ expect(subject).to receive(:force_utf8_encoding)
25
+ formatted_message
26
+ end
27
+
28
+ it "outputs in JSON format with message encoding updated to utf8" do
29
+ json_message = JSON.parse(formatted_message)
30
+ expect(json_message["message"]).to eq(message.force_encoding(Encoding::UTF_8).scrub)
31
+ end
32
+ end
33
+
34
+ context "timestamp ordering" do
35
+ let(:message) { { message: 'test', foo: 'bar' } }
36
+
37
+ it "places @timestamp first in JSON output" do
38
+ timestamp_index = formatted_message.index('"@timestamp"')
39
+ message_index = formatted_message.index('"message"')
40
+
41
+ expect(timestamp_index).not_to be_nil
42
+ expect(message_index).not_to be_nil
43
+ expect(timestamp_index).to be < message_index
44
+ end
45
+ end
46
+
47
+ context "key ordering" do
48
+ let(:message) { { foo: 'bar', baz: 'qux' } }
49
+
50
+ it "preserves custom field order in JSON output" do
51
+ foo_index = formatted_message.index('"foo"')
52
+ baz_index = formatted_message.index('"baz"')
53
+
54
+ expect(foo_index).not_to be_nil
55
+ expect(baz_index).not_to be_nil
56
+ expect(foo_index).to be < baz_index
57
+ end
58
+ end
10
59
  end
@@ -35,6 +35,23 @@ describe LogStashLogger::Formatter do
35
35
  end
36
36
  end
37
37
 
38
+ context "formatter class that accepts customize_event keyword arg in initializer" do
39
+ let(:formatter) do
40
+ Class.new do
41
+ def initialize(customize_event: nil)
42
+ end
43
+
44
+ def call
45
+ end
46
+ end
47
+ end
48
+
49
+ it "returns a new instance of the class" do
50
+ expect(formatter).to receive(:new).with(hash_including(:customize_event)).and_call_original
51
+ expect(subject).to be_a formatter
52
+ end
53
+ end
54
+
38
55
  context "formatter instance" do
39
56
  let(:formatter) { ::Logger::Formatter.new }
40
57
 
@@ -49,7 +66,7 @@ describe LogStashLogger::Formatter do
49
66
 
50
67
  context "formatter proc" do
51
68
  let(:formatter) do
52
- proc { |severity, time, progname, msg| msg }
69
+ proc { |_severity, _time, _progname, msg| msg }
53
70
  end
54
71
 
55
72
  it "returns the same formatter proc" do
@@ -63,7 +80,7 @@ describe LogStashLogger::Formatter do
63
80
 
64
81
  context "formatter lambda" do
65
82
  let(:formatter) do
66
- ->(severity, time, progname, msg) { msg }
83
+ ->(_severity, _time, _progname, msg) { msg }
67
84
  end
68
85
 
69
86
  it "returns the same formatter lambda" do
data/spec/logger_spec.rb CHANGED
@@ -58,14 +58,14 @@ describe LogStashLogger do
58
58
 
59
59
  expect(listener_event['severity']).to eql('INFO')
60
60
  expect(listener_event['message']).to eq(message)
61
- expect(listener_event['host']).to eq(hostname)
61
+ expect(listener_event['host']['hostname']).to eq(hostname)
62
62
  end
63
63
 
64
64
  it 'takes a logstash-formatted json string as input and writes out a logstash event' do
65
65
  logger.info(logstash_event.to_json)
66
66
 
67
67
  expect(listener_event['message']).to eq(logstash_event['message'])
68
- expect(listener_event['host']).to eq(hostname)
68
+ expect(listener_event['host']['hostname']).to eq(hostname)
69
69
  end
70
70
 
71
71
  it 'takes a LogStash::Event as input and writes it out intact' do
@@ -74,7 +74,7 @@ describe LogStashLogger do
74
74
  expect(listener_event['message']).to eq(logstash_event['message'])
75
75
  expect(listener_event['severity']).to eq(logstash_event['severity'])
76
76
  expect(listener_event['@timestamp'].iso8601).to eq(logstash_event.timestamp.iso8601)
77
- expect(listener_event['host']).to eq(hostname)
77
+ expect(listener_event['host']['hostname']).to eq(hostname)
78
78
  end
79
79
 
80
80
  it 'takes a data hash as input and writes out a logstash event' do
@@ -89,7 +89,7 @@ describe LogStashLogger do
89
89
  expect(listener_event['message']).to eq(data["message"])
90
90
  expect(listener_event['severity']).to eq(data['severity'])
91
91
  expect(listener_event['foo']).to eq(data['foo'])
92
- expect(listener_event['host']).to eq(hostname)
92
+ expect(listener_event['host']['hostname']).to eq(hostname)
93
93
  expect(listener_event['@timestamp']).to_not be_nil
94
94
  end
95
95
 
@@ -99,7 +99,7 @@ describe LogStashLogger do
99
99
  logger.info(message)
100
100
 
101
101
  expect(listener_event['message']).to eq(message.inspect)
102
- expect(listener_event['host']).to eq(hostname)
102
+ expect(listener_event['host']['hostname']).to eq(hostname)
103
103
  expect(listener_event['severity']).to eq('INFO')
104
104
  end
105
105
 
@@ -56,4 +56,20 @@ describe LogStashLogger::MultiLogger do
56
56
  logger.info 'test'
57
57
  end
58
58
  end
59
+
60
+ it "can call .reset on each logger" do
61
+ subject.loggers.each do |logger|
62
+ expect(logger).to receive(:reset).and_call_original
63
+ end
64
+
65
+ subject.reset
66
+ end
67
+
68
+ it "delegates #log to loggers" do
69
+ subject.loggers.each do |logger|
70
+ expect(logger).to receive(:add).with(::Logger::DEBUG, "test", nil)
71
+ end
72
+
73
+ subject.log(::Logger::DEBUG, "test")
74
+ end
59
75
  end