From 26597680af6f21a13733e9137bc000f621964de6 Mon Sep 17 00:00:00 2001 From: Holger Just Date: Tue, 5 Dec 2017 18:55:04 +0100 Subject: [PATCH] Set rack.errors in the Rack::Middlware This ensures that other middlewares whi want to log to the default rack.errors stream will log to a sensible location instead of the usual default of raw STDOUT or STDERR. --- lib/rackstash/rack/errors.rb | 66 ++++++++++++++++++++++++++ lib/rackstash/rack/middleware.rb | 2 + spec/rackstash/rack/errors_spec.rb | 56 ++++++++++++++++++++++ spec/rackstash/rack/middleware_spec.rb | 13 +++++ 4 files changed, 137 insertions(+) create mode 100644 lib/rackstash/rack/errors.rb create mode 100644 spec/rackstash/rack/errors_spec.rb diff --git a/lib/rackstash/rack/errors.rb b/lib/rackstash/rack/errors.rb new file mode 100644 index 0000000..f6b27d4 --- /dev/null +++ b/lib/rackstash/rack/errors.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true +# Copyright 2016 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 + module Rack + # Provide an error stream to Rack applications which logs to a + # {Rackstash::Logger} instance. + # + # According to the [Rack SPEC](http://www.rubydoc.info/github/rack/rack/file/SPEC#The_Error_Stream), + # the error stream in `env['rack.errors']` must coform to the following + # interface: + # + # > The error stream must respond to `puts`, `write` and `flush`. + # > + # > * `puts` must be called with a single argument that responds to `to_s`. + # > * `write` must be called with a single argument that is a `String`. + # > * `flush` must be called without arguments and must be called in order + # > to make the error appear for sure. + # > * `close` must never be called on the error stream. + class Errors + # @return [Rackstash::Logger] + attr_reader :logger + + # @param logger [Rackstash::Logger] a {Logger} instance to write error + # logs to + def initialize(logger) + @logger = logger + end + + # Log a formatted error message to the current buffer of the `logger`. We + # will format the given message and log it with an `UNKNOWN` severity to + # the current buffer. Usually, the logger's formatter adds a trailing + # newline to the message. + # + # @param msg [#to_s] a message to write to the error stream + # @return [String] the given `msg` as a String + def puts(msg) + msg = msg.to_s + @logger.unknown(msg) + msg + end + + # Log a raw and unformatted error message to the current buffer of the + # `logger`. It will be logged as an unformatted {Message} without any + # aded newline characters. + # + # @param msg [String] a raw message to write to the error stream + # @return [String] the given `msg` + def write(msg) + @logger << msg + msg + end + + # This method does nothing. It is only provided to satisfy the + # requirements of the error stream interface. The {Logger} (resp. its + # adapters) are responsible to flush their buffers on their own as + # suitable or required. + def flush + # no-op + end + end + end +end diff --git a/lib/rackstash/rack/middleware.rb b/lib/rackstash/rack/middleware.rb index 93c6f72..d240091 100644 --- a/lib/rackstash/rack/middleware.rb +++ b/lib/rackstash/rack/middleware.rb @@ -7,6 +7,7 @@ require 'rack' require 'rackstash/helpers/time' +require 'rackstash/rack/errors' module Rackstash module Rack @@ -199,6 +200,7 @@ module Rackstash began_at = clock_time env['rackstash.logger'.freeze] = @logger env['rack.logger'.freeze] = @logger + env['rack.errors'.freeze] = Rackstash::Rack::Errors.new(@logger) @logger.push_buffer(buffering: @buffering, allow_silent: true) begin diff --git a/spec/rackstash/rack/errors_spec.rb b/spec/rackstash/rack/errors_spec.rb new file mode 100644 index 0000000..7d84e19 --- /dev/null +++ b/spec/rackstash/rack/errors_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true +# +# 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/rack/errors' + +describe Rackstash::Rack::Errors do + let(:logger) { instance_double(Rackstash::Logger) } + let(:errors) { described_class.new(logger) } + + describe '#initialize' do + it 'takes a logger' do + errors = described_class.new(logger) + expect(errors.logger).to equal logger + end + end + + describe '#puts' do + it 'logs a formatted message' do + expect(logger).to receive(:unknown).with('an error') + errors.puts('an error') + end + + it 'returns the stringified message' do + allow(logger).to receive(:unknown) + + expect(errors.puts('error')).to eql 'error' + expect(errors.puts(123)).to eql '123' + end + end + + describe '#write' do + it 'logs an unformatted message' do + expect(logger).to receive(:<<).with('an error') + errors.write('an error') + end + + it 'returns the raw message' do + allow(logger).to receive(:<<) + + expect(errors.write('error')).to eql 'error' + expect(errors.write(123)).to eql 123 + end + end + + describe '#flush' do + it 'does nothing' do + errors.flush + end + end +end diff --git a/spec/rackstash/rack/middleware_spec.rb b/spec/rackstash/rack/middleware_spec.rb index e434738..d91fc8c 100644 --- a/spec/rackstash/rack/middleware_spec.rb +++ b/spec/rackstash/rack/middleware_spec.rb @@ -92,6 +92,19 @@ describe Rackstash::Rack::Middleware do expect(called).to be true end + it 'sets rack.errors environment variable' do + called = false + app = lambda do |env| + called = true + expect(env['rack.errors']).to be_instance_of Rackstash::Rack::Errors + expect(env['rack.errors'].logger).to equal logger + [200, { 'Content-Type' => 'text/plain' }, ['Hello, World!']] + end + + ::Rack::MockRequest.new(described_class.new(app, logger)).get('/') + expect(called).to be true + end + it 'logs basic request data' do get('/demo')