1
0
mirror of https://github.com/meineerde/rackstash.git synced 2025-10-17 14:01:01 +00:00

Add basic logger structure with early spikes

The Rackstash::Logger class will server as the public main entry point
for users. It will eventually implement the mostly complete interface of
Ruby's Logger.

The idea of Rackstash is the we will allow to buffer multiple log
messages allong with additional data until a combined log event is
eventually flushed to an underlying log target. This allows to keep
connected log messages and data as a single unit from the start without
having to painstakingly parse and connect these in later systems again.
This commit is contained in:
Holger Just 2017-01-18 23:34:55 +01:00
parent a6f41e4bf1
commit 3081b03db1
17 changed files with 879 additions and 3 deletions

View File

@ -5,5 +5,4 @@
source 'https://rubygems.org'
# Specify your gem's dependencies in rackstash.gemspec
gemspec
gemspec name: 'rackstash'

0
exe/rackstash Normal file → Executable file
View File

View File

@ -6,5 +6,18 @@
require 'rackstash/version'
module Rackstash
# Your code goes here...
SEVERITIES = [
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
FATAL = 4,
UNKNOWN = 5
].freeze
PROGNAME = "rackstash/v#{Rackstash::VERSION}".freeze
EMPTY_STRING = ''.freeze
end
require 'rackstash/logger'

23
lib/rackstash/buffer.rb Normal file
View File

@ -0,0 +1,23 @@
# Copyright 2017 Holger Just
#
# This software may be modified and distributed under the terms
# of the MIT license. See the LICENSE.txt file for details.
module Rackstash
# The Buffer holds all the data of a single log event. It can hold multiple
# messages of multiple calls to the log, additional fields holding structured
# data about the log event, and tags identiying the type of log.
class Buffer
def initialize
@messages = []
end
def add_message(message)
@messages << message
end
def messages
@messages.dup
end
end
end

View File

@ -0,0 +1,19 @@
# Copyright 2017 Holger Just
#
# This software may be modified and distributed under the terms
# of the MIT license. See the LICENSE.txt file for details.
require 'rackstash/buffer'
module Rackstash
# A BufferStack controls one or more Buffers. Each {Buffer} is created,
# referenced by, and accessed via exactly one BufferStack. Each BufferStack
# is used by exactly one BufferedLogger. The responsible {BufferedLogger}
# ensures that each BufferStack is only accessed from a single `Thread`.
class BufferStack
# TODO: this is only a spike for now
def with_buffer
yield Buffer.new
end
end
end

View File

@ -0,0 +1,28 @@
# Copyright 2017 Holger Just
#
# This software may be modified and distributed under the terms
# of the MIT license. See the LICENSE.txt file for details.
require 'logger'
module Rackstash
# The default logging formatter which is responsible for formatting a single
# {Message} for the final emitted log event.
class Formatter < ::Logger::Formatter
# Return the formatted message from the following rules:
# * Strings passed to `msg` are returned with an added newline character at
# the end
# * Exceptions are formatted with their name, message and backtrace,
# separated by newline characters.
# * All other objects will be `inspect`ed with an added newline.
#
# @param _severity [Integer] the log severity, ignored.
# @param _time [Time] the time of the log message, ignored.
# @param _progname [String] the program name, ignored.
# @param msg [String, Exception, #inspect] the log message
# @return [String] the formatted message with a final newline character
def call(_severity, _time, _progname, msg)
"#{msg2str(msg)}\n"
end
end
end

140
lib/rackstash/logger.rb Normal file
View File

@ -0,0 +1,140 @@
# Copyright 2017 Holger Just
#
# This software may be modified and distributed under the terms
# of the MIT license. See the LICENSE.txt file for details.
require 'forwardable'
require 'rackstash/buffer_stack'
require 'rackstash/formatter'
require 'rackstash/message'
require 'rackstash/sink'
module Rackstash
# The Logger is the main entry point for Rackstash. It provides an interface
# very similar to the Logger class in Ruby's Stamdard Library but extends it
# with facilities for structured logging.
class Logger
extend Forwardable
# Logging formatter, a `Proc`-like object which take four arguments and
# returns the formatted message. The arguments are:
#
# * `severity` - The Severity of the log message.
# * `time` - A Time instance representing when the message was logged.
# * `progname` - The progname configured passed to the logger method.
# * `msg` - The `Object` the user passed to the log message; not necessarily
# a String.
#
# The formatter should return a String. When no formatter is set, an
# instance of {Formatter} is used.
#
# @return [#call] the log formatter for each individual buffered line
attr_accessor :formatter
# @return [Integer] a numeric log level, normally you'd use one of the
# `SEVERITIES` constants, i.e. an integer between 0 and 5.
attr_reader :level
# @return [String] the logger's progname, used as the default for log
# messages if none is passed to {#add} and passed to the {#formatter}.
# By default we use {PROGNAME}.
attr_accessor :progname
# @return [Sink] the log {Sink} which flushes a {Buffer} to one or more
# external log targets like a file, a socket, ...
attr_reader :sink
def initialize(targets)
@sink = Sink.new(targets)
@level = DEBUG
@progname = PROGNAME
@formatter = Formatter.new
end
# Set the base log level as either one of the {SEVERITIES} or a
# String/Symbol describing the level. When logging a message, it will only
# be added if its log level is at or above the base level defined here
#
# @param severity [Integer, String, Symbol] one of the {SEVERITIES} or its
# name
def level=(severity)
if severity.is_a?(Integer)
@level = severity
else
case severity.to_s.downcase
when 'debug'.freeze
@level = DEBUG
when 'info'.freeze
@level = INFO
when 'warn'.freeze
@level = WARN
when 'error'.freeze
@level = ERROR
when 'fatal'.freeze
@level = FATAL
when 'unknown'.freeze
@level = UNKNOWN
else
raise ArgumentError, "invalid log level: #{severity}"
end
end
end
# Log a message if the given severity is high enough. This is the generic
# logging method. Users will be more inclined to use {#debug}, {#info},
# {#warn}, {#error}, or {#fatal}.
#
# The message will be added to the current log buffer. If we are currently
# buffering (i.e. if we are inside a {#with_buffer} block), the message is
# merely added but not flushed to the underlying logger. Else, the message
# along with any previously defined fields and tags will be flushed to the
# base logger immediately.
#
# @param severity [Integer] The log severity. One of {DEBUG}, {INFO},
# {WARN}, {ERROR}, {FATAL}, or {UNKNOWN}.
# @param msg [#to_s, Exception, nil] The log message. A `String` or
# `Exception`. If unset, we try to use the return value of the optional
# block.
# @param progname [String, nil] The program name. Can be omitted. It's
# treated as a message if no `msg` and `block` are given.
# @yield If `message` is `nil`, we yield to the block to get a message
# string.
# @return [String] The resolved unformatted message string
def add(severity, msg = nil, progname = nil)
severity = severity ? Integer(severity) : UNKNOWN
return if @level > severity
progname ||= @progname
if msg.nil?
if block_given?
msg = yield
else
msg = progname
progname = @progname
end
end
now = Time.now.utc.freeze
buffer_stack.with_buffer do |buffer|
buffer.add_message Message.new(
msg,
time: now,
progname: progname,
severity: severity,
formatter: formatter
)
end
msg
end
alias_method :log, :add
private
def buffer_stack
@buffer_stack ||= Rackstash::BufferStack.new
end
end
end

89
lib/rackstash/message.rb Normal file
View File

@ -0,0 +1,89 @@
# Copyright 2017 Holger Just
#
# This software may be modified and distributed under the terms
# of the MIT license. See the LICENSE.txt file for details.
module Rackstash
# This class and all its data are immutable after initialization
class Message
RAW_FORMATTER = ->(_severity, _timestamp, _progname, msg) { msg }
SEVERITY_LABEL = [
'DEBUG'.freeze,
'INFO'.freeze,
'WARN'.freeze,
'ERROR'.freeze,
'FATAL'.freeze,
'ANY'.freeze
].freeze
attr_reader :message
attr_reader :severity
attr_reader :progname
attr_reader :time
attr_reader :formatter
def initialize(
msg,
severity: UNKNOWN,
time: Time.now.utc.freeze,
progname: PROGNAME,
formatter: RAW_FORMATTER
)
@message = cleanup_message(msg)
@severity = Integer(severity)
@severity = 0 if @severity < 0
@time = dup_freeze(time)
@progname = dup_freeze(progname)
@formatter = formatter
end
def severity_label
SEVERITY_LABEL[@severity] || SEVERITY_LABEL.last
end
def frozen?
true
end
def to_s
@to_s ||= @formatter.call(severity_label, @time, @progname, @message)
end
alias_method :to_str, :to_s
alias_method :as_json, :to_s
private
# Sanitize a single mesage to be added to the buffer, can be a single or
# multi line string
#
# @param msg [#to_s] a message to be added to the buffer
# @return [String] the sanitized frozen message
def cleanup_message(msg)
msg = msg.inspect unless msg.is_a?(String)
msg = utf8_encode(msg)
# remove useless ANSI color codes
msg.gsub!(/\e\[[0-9;]*m/, EMPTY_STRING)
msg.freeze
end
def utf8_encode(str)
str.to_s.encode(
Encoding::UTF_8,
invalid: :replace,
undef: :replace,
universal_newline: true
)
end
def dup_freeze(obj)
obj.frozen? ? obj : obj.dup.freeze
end
end
end

20
lib/rackstash/sink.rb Normal file
View File

@ -0,0 +1,20 @@
# Copyright 2017 Holger Just
#
# This software may be modified and distributed under the terms
# of the MIT license. See the LICENSE.txt file for details.
module Rackstash
class Sink
attr_reader :targets
def initialize(targets)
@targets = targets.respond_to?(:to_ary) ? targets.to_ary : [targets]
end
def flush(buffer)
@targets.each do |target|
target.flush(buffer)
end
end
end
end

View File

@ -0,0 +1,38 @@
# Copyright 2017 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'
describe Rackstash::Buffer do
let(:buffer) { Rackstash::Buffer.new }
describe '#add_message' do
it 'adds a message to the buffer' do
msg = double(message: 'Hello World')
buffer.add_message msg
expect(buffer.messages).to eql [msg]
end
end
describe 'messages' do
it 'returns an array of messages' do
msg = double('Rackstash::Message')
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
end

View File

@ -0,0 +1,20 @@
# Copyright 2017 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_stack'
describe Rackstash::BufferStack do
let(:stack) { Rackstash::BufferStack.new }
describe '#with_buffer' do
it 'initializes a buffer' do
stack.with_buffer do |buffer|
expect(buffer).to be_a Rackstash::Buffer
end
end
end
end

View File

@ -0,0 +1,48 @@
# Copyright 2017 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 'time'
require 'rackstash/formatter'
describe Rackstash::Formatter do
let(:formatter) { Rackstash::Formatter.new }
it 'formats plain strings' do
expect(formatter.call('ERROR', Time.now, 'progname', 'Hello')).to eql "Hello\n"
end
it 'formats stringifiable objects' do
expect(formatter.call('ERROR', Time.now, 'progname', 123)).to eql "123\n"
end
it 'formats Hashes' do
expect(formatter.call('ERROR', Time.now, 'progname', { k: 'v' })).to eql "{:k=>\"v\"}\n"
end
it 'formats exceptions' do
exception = nil
begin
raise StandardError, 'An Error'
rescue => e
exception = e
end
checker = Regexp.new <<-EOF.gsub(/^\s+/, '').rstrip, Regexp::MULTILINE
\\AAn Error \\(StandardError\\)
#{Regexp.escape __FILE__}:#{__LINE__ - 7}:in `block .*`
EOF
expect(formatter.call('ERROR', Time.now, 'progname', exception)).to match checker
end
it 'inspects unknown objects' do
object = Object.new
inspected = object.inspect
expect(object).to receive(:inspect).once.and_call_original
expect(formatter.call('ERROR', Time.now, 'progname', object)).to eql "#{inspected}\n"
end
end

View File

@ -0,0 +1,219 @@
# Copyright 2017 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/logger'
describe Rackstash::Logger do
let(:targets) { double('targets') }
let(:logger) { Rackstash::Logger.new(targets) }
describe '#formatter' do
it 'defaults to a Rackstash::Formatter' do
expect(logger.formatter).to be_a Rackstash::Formatter
end
it 'allows to set a custom formatter' do
formatter = ->(_severity, _time, _progname, msg) { msg.reverse }
logger.formatter = formatter
expect(logger.formatter).to equal formatter
end
end
describe '#level' do
it 'defaults to DEBUG' do
expect(logger.level).to eql 0
end
it 'can be set as an integer' do
logger.level = 3
expect(logger.level).to eql 3
logger.level = 42
expect(logger.level).to eql 42
logger.level = -25
expect(logger.level).to eql -25
end
it 'can be set as a symbol' do
%i[debug info warn error fatal unknown].each_with_index do |level, i|
logger.level = level
expect(logger.level).to eql i
end
%i[DeBuG InFo WaRn ErRoR FaTaL UnKnOwN].each_with_index do |level, i|
logger.level = level
expect(logger.level).to eql i
end
end
it 'can be set as a string' do
%w[debug info warn error fatal unknown].each_with_index do |level, i|
logger.level = level
expect(logger.level).to eql i
end
%w[DeBuG InFo WaRn ErRoR FaTaL UnKnOwN].each_with_index do |level, i|
logger.level = level
expect(logger.level).to eql i
end
end
it 'rejects invalid values' do
expect { logger.level = 'invalid' }.to raise_error(ArgumentError)
expect { logger.level = Object.new }.to raise_error(ArgumentError)
expect { logger.level = nil }.to raise_error(ArgumentError)
expect { logger.level = false }.to raise_error(ArgumentError)
expect { logger.level = true }.to raise_error(ArgumentError)
end
end
describe '#progname' do
it 'defaults to PROGNAME' do
expect(logger.progname).to match %r{\Arackstash/v\d+(\..+)*\z}
end
it 'can be set to a custom value' do
logger.progname = 'my app'
expect(logger.progname).to eql 'my app'
end
end
describe '#sink' do
it 'returns the created sink' do
expect(logger.sink).to be_a Rackstash::Sink
end
end
describe '#add' do
let(:messages) { [] }
let(:buffer) {
double('Rackstash::Buffer').tap do |buffer|
expect(buffer).to receive(:add_message) { |message| messages << message }
.at_least(:once)
end
}
let(:buffer_stack) {
double('Rackstash::BufferStack').tap do |buffer_stack|
expect(buffer_stack).to receive(:with_buffer)
.at_least(:once)
.and_yield(buffer)
end
}
before(:each) do
class_double('Rackstash::Message').as_stubbed_const.tap do |klass|
expect(klass).to receive(:new) { |msg, **kwargs| {message: msg, **kwargs} }
.at_least(:once)
end
expect(logger).to receive(:buffer_stack)
.at_least(:once)
.and_return(buffer_stack)
end
it 'sets the current time as UTC to the message' do
logger.add(nil, 'msg')
expect(messages.last[:time]).to be_a(Time).and be_frozen.and be_utc
end
it 'sets the provided a severity' do
logger.log(Rackstash::DEBUG, 'Debug message')
expect(messages.last).to include message: 'Debug message', severity: 0
logger.log(Rackstash::INFO, 'Info message')
expect(messages.last).to include message: 'Info message', severity: 1
logger.log(Rackstash::WARN, 'Warn message')
expect(messages.last).to include message: 'Warn message', severity: 2
logger.log(Rackstash::ERROR, 'Error message')
expect(messages.last).to include message: 'Error message', severity: 3
logger.log(Rackstash::FATAL, 'Fatal message')
expect(messages.last).to include message: 'Fatal message', severity: 4
logger.log(Rackstash::UNKNOWN, 'Unknown message')
expect(messages.last).to include message: 'Unknown message', severity: 5
# Positive severities are passed along
logger.log(42, 'The answer')
expect(messages.last).to include message: 'The answer', severity: 42
# nil is changed to UNKNOWN
logger.log(nil, 'Missing')
expect(messages.last).to include message: 'Missing', severity: 5
# Non-number arguments result in an error
expect { logger.log(:debug, 'Missing') }.to raise_error(TypeError)
expect { logger.log('debug', 'Missing') }.to raise_error(ArgumentError)
end
it 'defaults to severity to UNKNOWN' do
logger.add(nil, 'test')
expect(messages.last).to include severity: 5
end
it 'calls the block if message is nil' do
temp = 0
expect do
logger.log(nil, nil, 'TestApp') do
temp = 1 + 1
end
end.to_not raise_error
expect(temp).to eql 2
end
it 'ignores the block if the message is not nil' do
temp = 0
expect do
logger.log(nil, 'not nil', 'TestApp') do
temp = 1 + 1
end
end.to_not raise_error
expect(temp).to eql 0
end
it 'follows Ruby\'s logger logic to find the message' do
# If there is a message, it will be logged
logger.add(0, 'Hello', nil)
expect(messages.last).to include message: 'Hello', severity: 0, progname: Rackstash::PROGNAME
logger.add(4, 'Hello', 'prog')
expect(messages.last).to include message: 'Hello', severity: 4, progname: 'prog'
logger.add(5, 'Hello', 'prog') { 'block' }
expect(messages.last).to include message: 'Hello', severity: 5, progname: 'prog'
logger.add(nil, 'Hello', nil)
expect(messages.last).to include message: 'Hello', severity: 5, progname: Rackstash::PROGNAME
# If there is no message, we use the block
logger.add(1, nil, 'prog') { 'Hello' }
expect(messages.last).to include message: 'Hello', severity: 1, progname: 'prog'
logger.add(1, nil, nil) { 'Hello' }
expect(messages.last).to include message: 'Hello', severity: 1, progname: Rackstash::PROGNAME
# If there is no block either, we use the progname and pass the default
# progname to the message
logger.add(2, nil, 'prog')
expect(messages.last).to include message: 'prog', severity: 2, progname: Rackstash::PROGNAME
# ... which defaults to `Rackstash::BufferedLogger::PROGNAME`
logger.add(3, nil, nil)
expect(messages.last).to include message: Rackstash::PROGNAME, severity: 3, progname: Rackstash::PROGNAME
# If we resolve the message to a blank string, we still add it
logger.add(1, '', nil) { 'Hello' }
expect(messages.last).to include message: '', severity: 1, progname: Rackstash::PROGNAME
# Same with nil which is later inspect'ed by the formatter
logger.add(0, nil, 'prog') { nil }
expect(messages.last).to include message: nil, severity: 0, progname: 'prog'
end
end
end

View File

@ -0,0 +1,159 @@
# Copyright 2017 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 'digest'
require 'rackstash/message'
describe Rackstash::Message do
describe '#message' do
it 'dups the message string' do
str = 'a message'
message = Rackstash::Message.new(str)
expect(message.message).to eql str
expect(message.message).not_to equal str
expect(message.message).to be_frozen
end
it 'cleans the message' do
messages = [
["First\r\nSecond", "First\nSecond"],
["First\r\nSecond\n\r", "First\nSecond\n\n"],
["Foo\r\n\rBar", "Foo\n\nBar"],
["\r \tWord\n\nPhrase\n", "\n \tWord\n\nPhrase\n"],
["\e[31mRED TEXT\e[0m", 'RED TEXT']
]
messages.each do |msg, clean|
message = Rackstash::Message.new(msg)
expect(message.message).to eql clean
end
end
it 'encodes the message as UTF-8' do
utf8_str = 'Dönerstraße'
latin_str = utf8_str.encode(Encoding::ISO8859_9)
expect(latin_str.encoding).to eql Encoding::ISO8859_9
message = Rackstash::Message.new(latin_str)
expect(message.message).to eql utf8_str
expect(message.message.encoding).to eql Encoding::UTF_8
end
it 'does not raise an error on incompatible encodings' do
binary = Digest::SHA256.digest('string')
message = Rackstash::Message.new(binary)
expect(message.message).to include '<27>'
end
end
describe '#severity' do
it 'defaults to UNKNOWN' do
expect(Rackstash::Message.new('').severity).to eql 5
end
it 'accepts any non-negative integer' do
expect(Rackstash::Message.new('', severity: 0).severity).to eql 0
expect(Rackstash::Message.new('', severity: 1).severity).to eql 1
expect(Rackstash::Message.new('', severity: 23).severity).to eql 23
expect(Rackstash::Message.new('', severity: '3').severity).to eql 3
end
it 'uses 0 for negative severities' do
expect(Rackstash::Message.new('', severity: -1).severity).to eql 0
expect(Rackstash::Message.new('', severity: -42).severity).to eql 0
end
it 'does not accept non-integers' do
expect { Rackstash::Message.new('', severity: nil) }.to raise_error TypeError
expect { Rackstash::Message.new('', severity: [42]) }.to raise_error TypeError
expect { Rackstash::Message.new('', severity: 'foo') }.to raise_error ArgumentError
end
end
describe '#severity_label' do
it 'formats the given severity as a string' do
%w[DEBUG INFO WARN ERROR FATAL ANY].each_with_index do |label, severity|
expect(Rackstash::Message.new('', severity: severity).severity_label).to eql label
end
end
it 'returns ANY for unknown severities' do
expect(Rackstash::Message.new('', severity: 42).severity_label).to eql 'ANY'
end
end
describe 'progname' do
it 'dups the progname' do
progname = 'a message'
message = Rackstash::Message.new('', progname: progname)
expect(message.progname).to eql progname
expect(message.progname).not_to equal progname
expect(message.progname).to be_frozen
end
it 'defaults to PROGNAME' do
expect(Rackstash::Message.new('').progname).to match %r{\Arackstash/v\d+(\..+)*\z}
end
end
describe 'time' do
it 'dups the time' do
time = Time.now
message = Rackstash::Message.new('', time: time)
expect(message.time).to eql time
expect(message.time).not_to equal time
expect(message.time).to be_frozen
# User-supplied time is not enforced to be in UTC
expect(message.time).to_not be_utc
end
it 'defaults to Time.now.utc' do
expect(Rackstash::Message.new('').time).to be_within(1).of(Time.now)
expect(Rackstash::Message.new('').time).to be_frozen
expect(Rackstash::Message.new('').time).to be_utc
end
end
describe 'formatter' do
it 'defaults to RAW_FORMATTER' do
expect(Rackstash::Message.new('').formatter).to equal Rackstash::Message::RAW_FORMATTER
message = Rackstash::Message.new('Beep boop')
expect(message.to_s).to equal message.message
end
end
describe '#to_s' do
it 'formats the message' do
severity = 0
time = Time.now
progname = 'ProgramName'
message = 'Hello World'
formatter = double('formatter')
expect(formatter).to receive(:call).once
.with('DEBUG', time, progname, message)
.and_return('Formatted Message')
message = Rackstash::Message.new(
message,
severity: severity,
time: time,
progname: progname,
formatter: formatter
)
expect(message.to_s).to eql 'Formatted Message'
# Same result, but no additional call to the formatter
expect(message.to_s).to eql 'Formatted Message'
end
end
end

View File

@ -0,0 +1,37 @@
# Copyright 2017 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/sink'
describe Rackstash::Sink do
let(:targets) { [] }
let(:sink) { Rackstash::Sink.new(targets) }
describe '#initialize' do
it 'accepts an array with targets' do
expect(targets).to receive(:to_ary).once.and_call_original
expect(sink.targets).to equal targets
end
it 'wraps a single target into an array' do
target = Object.new
expect(Rackstash::Sink.new(target).targets).to eql [target]
end
end
describe '#flush' do
it 'flushes the buffer to all targets' do
buffer = double('buffer')
target = double('target')
targets << target
expect(target).to receive(:flush).with(buffer)
sink.flush(buffer)
end
end
end

View File

@ -6,4 +6,18 @@
require 'spec_helper'
describe Rackstash do
it 'defines PROGRAME with the correct version' do
expect(Rackstash::PROGNAME).to match %r{\Arackstash/v\d+(\..+)*\z}
end
it 'defines SEVERITIES constants' do
expect(Rackstash::SEVERITIES).to eql (0..5).to_a
expect(Rackstash::DEBUG).to eql 0
expect(Rackstash::INFO).to eql 1
expect(Rackstash::WARN).to eql 2
expect(Rackstash::ERROR).to eql 3
expect(Rackstash::FATAL).to eql 4
expect(Rackstash::UNKNOWN).to eql 5
end
end

View File

@ -5,3 +5,13 @@
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
require 'rackstash'
RSpec.configure do |config|
config.mock_with :rspec do |mocks|
# This option should be set when all dependencies are being loaded
# before a spec run, as is the case in a typical spec helper. It will
# cause any verifying double instantiation for a class that does not
# exist to raise, protecting against incorrectly spelt names.
mocks.verify_doubled_constant_names = true
end
end