Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a tagged method, and some additional functionality around the request fields and tags. #4

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
15 changes: 7 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,23 +102,22 @@ config.rackstash.tags = ['ruby', 'rails2']

# Additional fields which are included into each log event that
# originates from a captured request.
# Can either be a Hash or an object which responds to to_proc which
# subsequently returns a Hash. If it is the latter, the proc will be exceuted
# similar to an after filter in every request of the controller and thus has
# access to the controller state after the request was handled.
config.rackstash.request_fields = lambda do |controller|
# Can either be a Hash or a callable object which responds to #call and which
# subsequently returns a Hash. If it is the latter, the object will called
# with the `request` object after the request is handled.
config.rackstash.request_fields = lambda { |request|
{
:host => request.host,
:source_ip => request.remote_ip,
:user_agent => request.user_agent
}
end
}

# Additional fields that are to be included into every emitted log, both
# buffered and not. You can use this to add global state information to the
# log, e.g. from the current thread or from the current environment.
# Similar to the request_fields, this can be either a static Hash or an
# object which responds to to_proc and returns a Hash there.
# Similar to the request_fields, this can be either a static Hash or a
# callable object which responds to #call and returns a Hash.
#
# Note that the proc is not executed in a controller instance and thus doesn't
# directly have access to the controller state.
Expand Down
53 changes: 38 additions & 15 deletions lib/rackstash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,29 @@ module Rackstash
#
# Currently supported formats are:
# - Hash
# - Any object that responds to to_proc and returns a hash
# - Any object that responds to #call and returns a hash
#
mattr_writer :request_fields
self.request_fields = HashWithIndifferentAccess.new
def self.request_fields(controller)
if @@request_fields.respond_to?(:to_proc)
ret = controller.instance_eval(&@@request_fields)
else
ret = @@request_fields
end
HashWithIndifferentAccess.new(ret)
fields = @@request_fields
fields = fields.call(controller.request) if fields.respond_to?(:call)
HashWithIndifferentAccess.new(fields)
end

# Custom fields that will be merged with every log object, be it a captured
# request or not.
#
# Currently supported formats are:
# - Hash
# - Any object that responds to to_proc and returns a hash
# - Any object that responds to #call and returns a hash
#
mattr_writer :fields
self.fields = HashWithIndifferentAccess.new
def self.fields
if @@fields.respond_to?(:to_proc)
ret = @@fields.to_proc.call
else
ret = @@fields
end
HashWithIndifferentAccess.new(ret)
fields = @@fields
fields = fields.call if fields.respond_to?(:call)
HashWithIndifferentAccess.new(fields)
end

# The source attribute in the generated Logstash output
Expand All @@ -59,10 +53,39 @@ def self.fields
# Additonal tags which are attached to each buffered log event
mattr_reader :tags
def self.tags=(tags)
@@tags = tags.map(&:to_s)
@@tags = tags.map(&:to_s).uniq
end
self.tags = []

# Additional tags to be included when processing a request.
mattr_writer :request_tags
self.request_tags = []
def self.request_tags(controller)
@@request_tags.map do |request_tag|
if request_tag.respond_to?(:call)
request_tag.call(controller.request)
else
request_tag
end
end
end

def self.tagged(*tags, &block)
if block_given?
original_tags = self.tags
begin
with_log_buffer do
self.tags += tags
yield
end
ensure
self.tags = original_tags
end
else
self.tags += tags
end
end

def self.with_log_buffer(&block)
if Rackstash.logger.respond_to?(:with_buffer)
Rackstash.logger.with_buffer(&block)
Expand Down
4 changes: 4 additions & 0 deletions lib/rackstash/buffered_logger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ def tags
buffer && buffer[:tags]
end

def tagged(*tags)
Rackstash.tagged(*tags) { yield }
end

def source=(value)
@source = value
@source_is_customized = true
Expand Down
3 changes: 3 additions & 0 deletions lib/rackstash/framework/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ module Base
def setup(config={})
Rackstash.request_fields = config.rackstash[:request_fields]
Rackstash.fields = config.rackstash[:fields] || HashWithIndifferentAccess.new

Rackstash.source = config.rackstash[:source]
Rackstash.log_level = config.rackstash[:log_level] || :info

Rackstash.tags = config.rackstash[:tags] || []
Rackstash.request_tags = config.rackstash[:request_tags] || []
end
end
end
Expand Down
12 changes: 8 additions & 4 deletions lib/rackstash/framework/rails3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@ def setup(config={})
# ActionDispatch captures exceptions too early for us to catch
# Thus, we inject our own exceptions_app to be able to catch the
# actual backtrace and add it to the fields
exceptions_app = config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path)
config.exceptions_app = lambda do |env|
log_subscriber._extract_exception_backtrace(env)
exceptions_app.call(env)
if config.respond_to?(:exceptions_app)
exceptions_app = config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path)
config.exceptions_app = lambda do |env|
log_subscriber._extract_exception_backtrace(env)
exceptions_app.call(env)
end
else
# TODO: figure out how to get exceptions in rails 3.0-3.1
end

ActionController::Base.send :include, Rackstash::Instrumentation
Expand Down
7 changes: 7 additions & 0 deletions lib/rackstash/log_subscriber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ def process_action(event)

Rails.logger.fields.reverse_merge!(data)
Rails.logger.fields.merge! request_fields(payload)

Rails.logger.tags.push *request_tags(payload)
end

def redirect_to(event)
Expand Down Expand Up @@ -98,6 +100,10 @@ def location(event)
end
end

def request_tags(payload)
payload[:rackstash_request_tags] || []
end

def request_fields(payload)
payload[:rackstash_request_fields] || {}
end
Expand All @@ -108,6 +114,7 @@ module Instrumentation

def append_info_to_payload(payload)
super
payload[:rackstash_request_tags] = Rackstash.request_tags(self)
payload[:rackstash_request_fields] = Rackstash.request_fields(self)
end
end
Expand Down
3 changes: 3 additions & 0 deletions lib/rackstash/rails_ext/action_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ def perform_action_with_rackstash

request_fields = Rackstash.request_fields(self)
logger.fields.merge!(request_fields) if request_fields

request_tags = Rackstash.request_tags(self)
logger.tags.push *request_tags
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/rackstash/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Rackstash
VERSION = "0.1.0"
VERSION = "0.3.1.basecamp"
end
8 changes: 8 additions & 0 deletions test/buffered_logger_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ def json
json["@message"].must_equal " [INFO] Hello"
end

it "can set additional tags for the duration of a block" do
subject.tagged("foo", "bar") { subject.info "Testing" }
json["@tags"].must_equal ["foo", "bar"]

subject.info "Testing"
json["@tags"].must_equal []
end

it "can set additional fields" do
subject.with_buffer do
subject.fields[:foo] = :bar
Expand Down
18 changes: 7 additions & 11 deletions test/rackstash_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ def json
end

let(:controller) do
controller = Class.new(Object){attr_accessor :status}.new
controller.status = "running"
controller
Struct.new(:request).new(Struct.new(:status).new(200))
end

it "won't be included in unbuffered mode" do
Expand All @@ -72,31 +70,29 @@ def json
end

it "can be defined as a proc" do
Rackstash.request_fields = proc do |controller|
Rackstash.request_fields = proc do |request|
{
:foo => :bar,
:status => @status,
:instance_status => controller.status
:status => request.status
}
end

Rackstash.request_fields(controller).must_be_instance_of HashWithIndifferentAccess
Rackstash.request_fields(controller).must_equal({"foo" => :bar, "status" => "running", "instance_status" => "running"})
Rackstash.request_fields(controller).must_equal({"foo" => :bar, "status" => 200})

# TODO: fake a real request and ensure that the field gets set in the log output
end

it "can be defined as a lambda" do
Rackstash.request_fields = lambda do |controller|
Rackstash.request_fields = lambda do |request|
{
:foo => :bar,
:status => @status,
:instance_status => controller.status
:status => request.status
}
end

Rackstash.request_fields(controller).must_be_instance_of HashWithIndifferentAccess
Rackstash.request_fields(controller).must_equal({"foo" => :bar, "status" => "running", "instance_status" => "running"})
Rackstash.request_fields(controller).must_equal({"foo" => :bar, "status" => 200})

# TODO: fake a real request and ensure that the field gets set in the log output
end
Expand Down