mirror of
https://github.com/meineerde/rackstash.git
synced 2025-10-17 14:01:01 +00:00
Add IO adapter
This adapter allows to write newline-separated log lines to an existing IO object, e.g. STDOUT. It does not allow to reopen the IO device.
This commit is contained in:
parent
b3f9a63253
commit
00786283f0
@ -54,3 +54,5 @@ module Rackstash
|
||||
end
|
||||
|
||||
require 'rackstash/logger'
|
||||
|
||||
require 'rackstash/adapters/io'
|
||||
|
||||
@ -143,3 +143,4 @@ module Rackstash
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
77
lib/rackstash/adapters/io.rb
Normal file
77
lib/rackstash/adapters/io.rb
Normal file
@ -0,0 +1,77 @@
|
||||
# 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 'thread'
|
||||
|
||||
require 'rackstash/adapters/adapter'
|
||||
|
||||
module Rackstash
|
||||
module Adapters
|
||||
# This adapter allows to write logs to an existing `IO` object, e.g.,
|
||||
# `STDOUT`, an open file, a `StringIO` object, ...
|
||||
#
|
||||
# When writing a [12factor](https://12factor.net/logs) app, you can use this
|
||||
# adapter to write formatted logs to `STDOUT` of the process to be captured
|
||||
# by the environment and eventually sent to a log collector.
|
||||
#
|
||||
# Concurrent writes to this adapter will be serialized to ensure there are
|
||||
# no overlapping writes. You still have to ensure that there are no other
|
||||
# writes to the IO object from outside this adapter to ensure there that
|
||||
# is no overlapping data visible on the IO object.
|
||||
#
|
||||
# Note that with some deployment models involving pre-forked application
|
||||
# servers, e.g., Unicorn or Puma servers with multiple worker processes, the
|
||||
# combined `STDOUT` stream of multiple processes can cause interleaved data
|
||||
# when writing large log lines (typically > 4 KB). If you are using such a
|
||||
# deployment model and expect large log lines, you should consider using a
|
||||
# different adapter to ensure consistent logs.
|
||||
#
|
||||
# Suitable adapters include:
|
||||
#
|
||||
# * {Rackstash::Adapters::File} - When writing to a file, we ensure with
|
||||
# explicit file locks that all data is written consistently.
|
||||
# * {Rackstash::Adapters::TCP} - With a single TCP connection per adapter
|
||||
# instance, the receiver can handle the log lines separately.
|
||||
class IO < Adapter
|
||||
register_for ->(o) { o.respond_to?(:write) && o.respond_to?(:close) }
|
||||
|
||||
# @param io [#write, #close] an IO object. It must at least respond to
|
||||
# `write` and `close`.
|
||||
def initialize(io)
|
||||
unless io.respond_to?(:write) && io.respond_to?(:close)
|
||||
raise TypeError, "#{io.inspect} does not look like an IO object"
|
||||
end
|
||||
|
||||
@io = io
|
||||
@mutex = Mutex.new
|
||||
end
|
||||
|
||||
# Write a single log line with a trailing newline character to the IO
|
||||
# object.
|
||||
#
|
||||
# @param log [#to_s] the encoded log event
|
||||
# @return [nil]
|
||||
def write_single(log)
|
||||
@mutex.synchronize do
|
||||
@io.write normalize_line(log)
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Close the IO object.
|
||||
#
|
||||
# After closing, no further writes are possible. Further attempts to
|
||||
# {#write} will result in an exception being thrown.
|
||||
#
|
||||
# @return [nil]
|
||||
def close
|
||||
@mutex.synchronize do
|
||||
@io.close
|
||||
end
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
67
spec/rackstash/adapters/io_spec.rb
Normal file
67
spec/rackstash/adapters/io_spec.rb
Normal file
@ -0,0 +1,67 @@
|
||||
# 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 'stringio'
|
||||
require 'tempfile'
|
||||
|
||||
require 'rackstash/adapters/io'
|
||||
|
||||
describe Rackstash::Adapters::IO do
|
||||
let(:io) { StringIO.new }
|
||||
let(:adapter) { Rackstash::Adapters::IO.new(io) }
|
||||
|
||||
describe '#initialize' do
|
||||
it 'accepts an IO object' do
|
||||
expect { Rackstash::Adapters::IO.new($stdout) }.not_to raise_error
|
||||
expect { Rackstash::Adapters::IO.new(StringIO.new) }.not_to raise_error
|
||||
expect { Rackstash::Adapters::IO.new(Tempfile.new('foo')) }.not_to raise_error
|
||||
end
|
||||
|
||||
it 'rejects non-IO objects' do
|
||||
expect { Rackstash::Adapters::IO.new(nil) }.to raise_error TypeError
|
||||
expect { Rackstash::Adapters::IO.new('hello') }.to raise_error TypeError
|
||||
expect { Rackstash::Adapters::IO.new(Object.new) }.to raise_error TypeError
|
||||
end
|
||||
end
|
||||
|
||||
describe '.default_encoder' do
|
||||
it 'returns a JSON encoder' do
|
||||
expect(adapter.default_encoder).to be_instance_of Rackstash::Encoders::JSON
|
||||
end
|
||||
end
|
||||
|
||||
describe '#close' do
|
||||
it 'closes the IO object' do
|
||||
expect(io).to receive(:close).and_call_original
|
||||
adapter.close
|
||||
expect { adapter.write('hello') }.to raise_error IOError
|
||||
end
|
||||
end
|
||||
|
||||
describe '#reopen' do
|
||||
it 'does nothing' do
|
||||
expect(io).not_to receive(:close)
|
||||
adapter.reopen
|
||||
end
|
||||
end
|
||||
|
||||
describe '#write_single' do
|
||||
it 'writes the log line to the IO object' do
|
||||
adapter.write('a log line')
|
||||
expect(io.tap(&:rewind).read).to eql "a log line\n"
|
||||
end
|
||||
|
||||
it 'always writes a string' do
|
||||
adapter.write([123, 'hello'])
|
||||
expect(io.tap(&:rewind).read).to eql "[123, \"hello\"]\n"
|
||||
end
|
||||
|
||||
it 'appends a trailing newline if necessary' do
|
||||
adapter.write("a full line.\n")
|
||||
expect(io.tap(&:rewind).read).to eql "a full line.\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
x
Reference in New Issue
Block a user