mirror of
https://github.com/meineerde/rackstash.git
synced 2025-12-20 07:11:12 +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
|
end
|
||||||
|
|
||||||
require 'rackstash/logger'
|
require 'rackstash/logger'
|
||||||
|
|
||||||
|
require 'rackstash/adapters/io'
|
||||||
|
|||||||
@ -143,3 +143,4 @@ module Rackstash
|
|||||||
end
|
end
|
||||||
end
|
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