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.
- checksums.yaml +4 -4
- data/.github/workflows/tests.yml +51 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +85 -105
- data/Appraisals +6 -18
- data/CHANGELOG.md +28 -0
- data/Gemfile +0 -1
- data/README.md +12 -833
- data/docs/buffering.md +70 -0
- data/docs/customization.md +86 -0
- data/docs/outputs.md +42 -0
- data/docs/rails.md +344 -0
- data/docs/ssl.md +90 -0
- data/docs/troubleshooting.md +84 -0
- data/docs/usage.md +148 -0
- data/gemfiles/{rails_4.2.gemfile → rails_7.2.gemfile} +1 -2
- data/gemfiles/{rails_5.0.gemfile → rails_8.0.gemfile} +1 -2
- data/gemfiles/{rails_4.0.gemfile → rails_8.1.gemfile} +1 -2
- data/lib/logstash-logger/buffer.rb +0 -1
- data/lib/logstash-logger/configuration.rb +1 -2
- data/lib/logstash-logger/device/aws_stream.rb +1 -1
- data/lib/logstash-logger/device/base.rb +2 -2
- data/lib/logstash-logger/device/connectable.rb +3 -11
- data/lib/logstash-logger/device/file.rb +21 -4
- data/lib/logstash-logger/device/http.rb +33 -0
- data/lib/logstash-logger/device/kafka.rb +153 -36
- data/lib/logstash-logger/device/redis.rb +8 -1
- data/lib/logstash-logger/device/tcp.rb +1 -5
- data/lib/logstash-logger/device.rb +24 -2
- data/lib/logstash-logger/formatter/base.rb +54 -9
- data/lib/logstash-logger/formatter/cee_syslog.rb +1 -1
- data/lib/logstash-logger/formatter/json.rb +13 -0
- data/lib/logstash-logger/formatter/json_lines.rb +13 -0
- data/lib/logstash-logger/formatter.rb +14 -6
- data/lib/logstash-logger/logger.rb +6 -19
- data/lib/logstash-logger/multi_logger.rb +2 -1
- data/lib/logstash-logger/railtie.rb +1 -1
- data/lib/logstash-logger/tagged_logging.rb +3 -1
- data/lib/logstash-logger/version.rb +1 -1
- data/logstash-logger.gemspec +9 -1
- data/spec/device/file_spec.rb +65 -0
- data/spec/device/http_spec.rb +11 -0
- data/spec/device/kafka_spec.rb +337 -14
- data/spec/device_spec.rb +13 -0
- data/spec/formatter/base_spec.rb +46 -1
- data/spec/formatter/cee_syslog_spec.rb +3 -3
- data/spec/formatter/json_lines_spec.rb +23 -0
- data/spec/formatter/json_spec.rb +49 -0
- data/spec/formatter_spec.rb +19 -2
- data/spec/logger_spec.rb +5 -5
- data/spec/multi_logger_spec.rb +16 -0
- data/spec/spec_helper.rb +2 -5
- data/spec/tagged_logging_spec.rb +15 -0
- metadata +89 -16
- data/.travis.yml +0 -26
- data/gemfiles/rails_3.2.gemfile +0 -9
- data/gemfiles/rails_4.1.gemfile +0 -9
- data/gemfiles/rails_5.1.gemfile +0 -9
data/spec/device/kafka_spec.rb
CHANGED
|
@@ -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(:
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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) }
|
data/spec/formatter/base_spec.rb
CHANGED
|
@@ -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
|
data/spec/formatter/json_spec.rb
CHANGED
|
@@ -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
|
data/spec/formatter_spec.rb
CHANGED
|
@@ -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 { |
|
|
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
|
-
->(
|
|
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
|
|
data/spec/multi_logger_spec.rb
CHANGED
|
@@ -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
|