1
0
mirror of https://github.com/meineerde/rackstash.git synced 2025-10-17 14:01:01 +00:00
rackstash/spec/rackstash/buffer_spec.rb
Holger Just 7fe284309b Remove scope parameter from Logger#tag and Buffer#tag
This reduces the change of a Hash argument being interpreted as keyword
argument inadvertently and clarifies the interface. If a resolver scope
is required, you can also call

    delayed_tag = ->{ |request| request.host }
    logger.tags.merge!(delayed_tag, scope: request)
2019-10-07 19:58:20 +02:00

553 lines
14 KiB
Ruby

# frozen_string_literal: true
#
# Copyright 2017 - 2018 Holger Just
#
# This software may be modified and distributed under the terms
# of the MIT license. See the LICENSE.txt file for details.
require 'spec_helper'
require 'rackstash/buffer'
RSpec.describe Rackstash::Buffer do
let(:buffer_options) { {} }
let(:flows) {
instance_double(Rackstash::Flows).tap do |flows|
allow(flows).to receive(:flush)
allow(flows).to receive(:auto_flush)
end
}
let(:buffer) { described_class.new(flows, **buffer_options) }
describe '#allow_silent?' do
it 'defaults to true' do
expect(buffer.allow_silent?).to be true
end
end
describe '#add_exception' do
it 'adds the exception fields' do
begin
raise 'My Error'
rescue RuntimeError => e
buffer.add_exception(e)
end
expect(buffer.fields['error']).to eql 'RuntimeError'
expect(buffer.fields['error_message']).to eql 'My Error'
expect(buffer.fields['error_trace']).to match %r{\A#{__FILE__}:#{__LINE__ - 7}:in}
end
it 'does not require a backtrace' do
buffer.add_exception(StandardError.new('Error'))
expect(buffer.fields['error']).to eql 'StandardError'
expect(buffer.fields['error_message']).to eql 'Error'
expect(buffer.fields['error_trace']).to eql ''
end
context 'with force: true' do
it 'overwrites exceptions' do
begin
raise 'Error'
rescue RuntimeError => first
buffer.add_exception(first, force: true)
end
begin
raise TypeError, 'Another Error'
rescue TypeError => second
buffer.add_exception(second, force: true)
end
expect(buffer.fields['error']).to eql 'TypeError'
expect(buffer.fields['error_message']).to eql 'Another Error'
expect(buffer.fields['error_trace']).to match %r{\A#{__FILE__}:#{__LINE__ - 7}:in}
end
end
context 'with force: false' do
it 'does not overwrite exceptions' do
buffer.fields['error'] = 'Something is wrong'
begin
raise TypeError, 'Error'
rescue TypeError => second
buffer.add_exception(second, force: false)
end
expect(buffer.fields['error']).to eql 'Something is wrong'
expect(buffer.fields['error_message']).to be_nil
expect(buffer.fields['error_trace']).to be_nil
end
end
end
describe '#add_fields' do
it 'deep-merges fields' do
buffer.add_fields(foo: :bar, number: 123)
expect(buffer.fields['foo']).to eql 'bar'
expect(buffer.fields['number']).to eql 123
end
it 'overwrites fields' do
buffer.fields['foo'] = 'initial'
buffer.add_fields(foo: 'overwritten')
expect(buffer.fields['foo']).to eql 'overwritten'
end
it 'raises ArgumentError when trying to set a forbidden key' do
expect { buffer.add_fields(message: 'oh no!') }.to raise_error ArgumentError
end
it 'sets the timestamp' do
expect(buffer).to receive(:timestamp)
buffer.add_fields(key: 'value')
end
context 'when allow_silent?' do
before do
buffer_options[:allow_silent] = true
end
it 'sets pending? to true' do
buffer.add_fields(key: 'value')
expect(buffer.pending?).to be true
end
it 'calls auto_flush' do
expect(flows).to receive(:auto_flush)
buffer.add_fields(key: 'value')
end
end
context 'when not allow_silent?' do
before do
buffer_options[:allow_silent] = false
end
it 'does not set pending? to true' do
buffer.add_fields(key: 'value')
expect(buffer.pending?).to be false
end
it 'calls auto_flush' do
expect(flows).to receive(:auto_flush)
buffer.add_fields(key: 'value')
end
end
context 'with buffering: true' do
before do
buffer_options[:buffering] = true
end
it 'does not flush the buffer' do
expect(flows).not_to receive(:flush)
# We always auto_flush buffers to send the newly added fields to
# interested flows
expect(flows).to receive(:auto_flush)
buffer.add_fields(key: 'value')
end
it 'does not clear the buffer' do
expect(buffer).not_to receive(:clear)
buffer.add_fields(key: 'value')
expect(buffer.fields['key']).to eql 'value'
expect(buffer.pending?).to be true
end
end
context 'with buffering: false' do
before do
buffer_options[:buffering] = false
end
it 'flushes the buffer' do
expect(flows).to receive(:flush)
expect(flows).to receive(:auto_flush)
buffer.add_fields(key: 'value')
end
it 'clears the buffer' do
buffer.add_fields(key: 'value')
expect(buffer.fields).to be_empty
expect(buffer.pending?).to be false
end
end
it 'returns the given value' do
fields = { key: 'value' }
expect(buffer.add_fields(fields)).to equal fields
end
end
describe '#add_message' do
it 'adds a message to the buffer' do
msg = double(message: 'Hello World', time: Time.now)
expect(buffer.add_message(msg)).to equal msg
expect(buffer.messages).to eql [msg]
end
it 'sets the timestamp' do
time = Time.parse('2016-10-17 13:37:00 +03:00')
msg = double(message: 'Hello World', time: time)
buffer.add_message msg
expect(buffer.timestamp).to eql time.getutc
end
context 'with buffering: true' do
before do
buffer_options[:buffering] = true
end
it 'does not flush the buffer' do
expect(flows).not_to receive(:flush)
buffer.add_message double(message: 'Hello World!', time: Time.now)
end
it 'retains messages' do
buffer.add_message double(message: 'Hello World!', time: Time.now)
expect(buffer.messages.count).to eql 1
end
end
context 'with buffering: false' do
before do
buffer_options[:buffering] = false
end
it 'flushes the buffer' do
expect(flows).to receive(:flush)
buffer.add_message double(message: 'Hello World!', time: Time.now)
end
it 'clears messages' do
buffer.add_message double(message: 'Hello World!', time: Time.now)
expect(buffer.messages.count).to eql 0
expect(buffer.pending?).to be false
end
end
end
describe '#buffering' do
it 'defaults to true' do
expect(buffer.buffering?).to eql true
end
it 'can be set to true' do
expect(described_class.new(flows, buffering: true).buffering?).to be true
expect(described_class.new(flows, buffering: 'whatever').buffering?).to be true
end
it 'can be set to false' do
expect(described_class.new(flows, buffering: false).buffering?).to be false
expect(described_class.new(flows, buffering: nil).buffering?).to be false
end
end
describe '#clear' do
it 'removes all fields and tags' do
buffer.fields['foo'] = 'bar'
buffer.tag 'super_important'
buffer.clear
expect(buffer.tags).to be_empty
expect(buffer.fields).to be_empty
end
it 'clears the message buffer' do
buffer.add_message double(message: 'Hello World!', time: Time.now)
buffer.clear
expect(buffer.messages).to eql []
end
it 'removes the pending flag' do
buffer.add_message double(message: 'raw', time: Time.now)
expect(buffer.pending?).to be true
buffer.clear
expect(buffer.pending?).to be false
end
it 'resets the timestamp' do
buffer.timestamp(Time.parse('2016-10-17 15:37:00 +02:00'))
expect(buffer.timestamp).to eql Time.utc(2016, 10, 17, 13, 37, 0)
buffer.clear
expect(Time).to receive(:now).and_call_original
expect(buffer.timestamp).not_to eql Time.utc(2016, 10, 17, 13, 37, 0)
end
end
describe '#fields' do
it 'returns a Rackstash::Fields::Hash' do
expect(buffer.fields).to be_a Rackstash::Fields::Hash
# Always returns the same fields object
expect(buffer.fields).to equal buffer.fields
end
it 'forbids setting reserved fields' do
expect { buffer.fields['message'] = 'test' } .to raise_error ArgumentError
expect { buffer.fields['tags'] = 'test' } .to raise_error ArgumentError
expect { buffer.fields['@version'] = 'test' } .to raise_error ArgumentError
expect { buffer.fields['@timestamp'] = 'test' } .to raise_error ArgumentError
end
end
describe '#flush' do
before do
# Create a buffering Buffer to prevent #add_message from flushing the
# Buffer on its own.
buffer_options[:buffering] = true
end
context 'when pending?' do
let(:time) { Time.parse('2016-10-17 15:37:00 +02:00') }
let(:message) { double(message: 'Hello World!', time: time) }
let(:event) {
{
'message' => [message],
'tags' => [],
'@timestamp' => time
}
}
before do
buffer.add_message(message)
# We might call Buffer#flush during the following tests
allow(flows).to receive(:flush).with(event).once
end
it 'flushes the buffer to the flows' do
expect(flows).to receive(:flush).with(event).once
buffer.flush
end
it 'does not clear the buffer' do
expect(buffer).not_to receive(:clear)
buffer.flush
expect(buffer.messages.count).to eql 1
end
it 'returns the buffer' do
expect(buffer.flush).to equal buffer
end
end
context 'when not pending?' do
it 'does not flushes the buffer to the flows' do
expect(flows).not_to receive(:flush)
buffer.flush
end
it 'does not clear the buffer' do
expect(buffer).not_to receive(:clear)
buffer.flush
end
it 'returns nil' do
expect(buffer.flush).to be nil
end
end
end
describe '#messages' do
it 'returns an array of messages' do
msg = double(message: 'Hello World', time: Time.now)
buffer.add_message(msg)
expect(buffer.messages).to eql [msg]
end
it 'returns a new array each time' do
expect(buffer.messages).not_to equal buffer.messages
expect(buffer.messages).to eql []
buffer.messages << 'invalid'
expect(buffer.messages).to eql []
end
end
describe '#pending?' do
it 'sets pending when adding a message' do
buffer.add_message double(message: 'some message', time: Time.now)
expect(buffer.pending?).to be true
end
context 'with allow_silent: true' do
before do
buffer_options[:allow_silent] = true
expect(buffer.allow_silent?).to be true
end
it 'defaults to false' do
expect(buffer.pending?).to be false
end
it 'is true if there are any fields' do
buffer.fields['alice'] = 'bob'
expect(buffer.pending?).to be true
end
it 'is true if there are any tags' do
buffer.tags << 'alice'
expect(buffer.pending?).to be true
end
it 'is true if the timestamp was set' do
buffer.timestamp
expect(buffer.pending?).to be true
end
end
context 'with allow_silent: false' do
before do
buffer_options[:allow_silent] = false
expect(buffer.allow_silent?).to be false
end
it 'defaults to false' do
expect(buffer.pending?).to be false
end
it 'ignores fields' do
buffer.fields['alice'] = 'bob'
expect(buffer.pending?).to be false
end
it 'ignores tags' do
buffer.tags << 'alice'
expect(buffer.pending?).to be false
end
it 'ignores the timestamp' do
buffer.timestamp
expect(buffer.pending?).to be false
end
end
end
describe '#tag' do
it 'adds tags' do
buffer.tag # don't fail with empty argument list
buffer.tag 'tag1', 'tag2'
expect(buffer.tags).to contain_exactly('tag1', 'tag2')
end
it 'adds tags only once' do
buffer.tag 'hello'
buffer.tag :hello
expect(buffer.tags).to contain_exactly('hello')
end
it 'stringifys tags and expands procs' do
buffer.tag 123, {}, :symbol, -> { :proc }
expect(buffer.tags).to contain_exactly('123', '{}', 'symbol', 'proc')
end
it 'does not set blank tags' do
buffer.tag 'tag', nil, [], ''
expect(buffer.tags).to contain_exactly('tag')
end
it 'sets the timestamp' do
expect(buffer).to receive(:timestamp)
buffer.tag('hello')
end
describe 'when passing procs' do
let(:struct) {
Struct.new(:value) do
def to_s
value
end
end
}
let(:object) {
struct.new('Hello')
}
it 'expands single-value proc objects' do
buffer.tag(-> { object })
expect(buffer.tags).to contain_exactly('Hello')
end
it 'expands multi-value proc objects' do
buffer.tag(-> { [[object, 'foobar'], 123] })
expect(buffer.tags).to contain_exactly('Hello', 'foobar', '123')
end
end
end
describe '#timestamp' do
it 'initializes @timestamp to Time.now.utc' do
now = Time.parse('2016-10-17 13:37:00 +03:00')
now_utc = Time.utc(2016, 10, 17, 10, 37, 0).freeze
expect(Time).to receive(:now).once.and_return(now)
expect(now).to receive(:utc).once.and_return(now_utc)
expect(buffer.timestamp).to equal now_utc
expect(buffer.timestamp).to equal now_utc
end
it 'initializes @timestamp with the passed time' do
now = Time.parse('2016-10-17 13:37:00 +03:00')
now_utc = Time.utc(2016, 10, 17, 10, 37, 0).freeze
expect(Time).not_to receive(:now)
expect(buffer.timestamp(now)).to eql now_utc
expect(buffer.timestamp).to eql now_utc
end
it 'does not overwrites an already set timestamp' do
first = Time.parse('2016-10-17 10:10:10 +03:00')
second = Time.parse('2016-10-17 20:20:20 +03:00')
buffer.timestamp(first)
expect(buffer.timestamp).to eql first.getutc
buffer.timestamp
expect(buffer.timestamp).to eql first.getutc
buffer.timestamp(second)
expect(buffer.timestamp).to eql first.getutc
end
end
describe '#event' do
it 'creates an event hash' do
message = double(message: 'Hello World', time: Time.now)
allow(message)
buffer.add_message(message)
buffer.fields[:foo] = 'bar'
buffer.tags << 'some_tag'
expect(buffer.event).to match(
'foo' => 'bar',
'message' => [message],
'tags' => ['some_tag'],
'@timestamp' => instance_of(Time)
)
end
end
end