rackstash 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +17 -9
- data/.travis.yml +26 -0
- data/Gemfile +31 -0
- data/LICENSE.txt +18 -17
- data/README.md +155 -6
- data/Rakefile +7 -7
- data/bin/rackstash +5 -0
- data/lib/rackstash.rb +85 -2
- data/lib/rackstash/buffered_logger.rb +269 -0
- data/lib/rackstash/framework/base.rb +13 -0
- data/lib/rackstash/framework/rack.rb +9 -0
- data/lib/rackstash/framework/rails2.rb +43 -0
- data/lib/rackstash/framework/rails3.rb +46 -0
- data/lib/rackstash/log_middleware.rb +27 -0
- data/lib/rackstash/log_severity.rb +9 -0
- data/lib/rackstash/log_subscriber.rb +108 -0
- data/lib/rackstash/rails_ext/action_controller.rb +101 -0
- data/lib/rackstash/rails_ext/initializer.rb +21 -0
- data/lib/rackstash/railtie.rb +19 -0
- data/lib/rackstash/runner.rb +36 -0
- data/lib/rackstash/version.rb +1 -1
- data/lib/tasks/rackstash.rb +9 -0
- data/rackstash.gemspec +26 -26
- data/test/buffered_logger_test.rb +191 -0
- data/test/log_subscriber_test.rb +89 -0
- data/test/rackstash_test.rb +104 -0
- data/test/runner_test.rb +82 -0
- data/test/test_helper.rb +13 -0
- metadata +105 -27
- data/CODE_OF_CONDUCT.md +0 -49
- data/bin/console +0 -14
- data/bin/setup +0 -8
@@ -0,0 +1,13 @@
|
|
1
|
+
module Rackstash
|
2
|
+
module Framework
|
3
|
+
module Base
|
4
|
+
def setup(config={})
|
5
|
+
Rackstash.request_fields = config.rackstash[:request_fields]
|
6
|
+
Rackstash.fields = config.rackstash[:fields] || HashWithIndifferentAccess.new
|
7
|
+
Rackstash.source = config.rackstash[:source]
|
8
|
+
Rackstash.log_level = config.rackstash[:log_level] || :info
|
9
|
+
Rackstash.tags = config.rackstash[:tags] || []
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rackstash/rails_ext/initializer'
|
2
|
+
require 'initializer'
|
3
|
+
Rails::Initializer.class_eval{ include Rackstash::RailsExt::Initializer }
|
4
|
+
|
5
|
+
module Rackstash
|
6
|
+
module Framework
|
7
|
+
module Rails2
|
8
|
+
# This method MUST be called after Rails::Initializer#initialize_logger
|
9
|
+
# but before Rails::Initializer#initialize_framework_logging
|
10
|
+
# The Rackstash::RailsExt::Initializer module takes care of that.
|
11
|
+
def setup(config={})
|
12
|
+
super
|
13
|
+
|
14
|
+
unless Rails.logger.is_a?(Rackstash::BufferedLogger)
|
15
|
+
rackstash_logger = Rackstash::BufferedLogger.new(Rails.logger)
|
16
|
+
silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", rackstash_logger }
|
17
|
+
Rackstash.logger = rackstash_logger
|
18
|
+
end
|
19
|
+
|
20
|
+
if Rails.configuration.frameworks.include?(:action_controller)
|
21
|
+
require 'rackstash/rails_ext/action_controller'
|
22
|
+
ActionController::Base.class_eval{ include Rackstash::RailsExt::ActionController }
|
23
|
+
|
24
|
+
# Include the logger middleware at the earliest possible moment
|
25
|
+
# to add a log scope which captures all log lines of the request
|
26
|
+
ActionController::Dispatcher.middleware.insert(0, Rackstash::LogMiddleware)
|
27
|
+
end
|
28
|
+
|
29
|
+
if Rails.configuration.frameworks.include?(:active_record)
|
30
|
+
# The ANSI color codes in the ActiveRecord logs don't help much in
|
31
|
+
# plain JSON
|
32
|
+
ActiveRecord::Base.colorize_logging = false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Rails::Configuration.class_eval do
|
40
|
+
def rackstash
|
41
|
+
@rackstash ||= Rails::OrderedOptions.new
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rackstash/log_subscriber'
|
2
|
+
|
3
|
+
module Rackstash
|
4
|
+
module Framework
|
5
|
+
module Rails3
|
6
|
+
def setup(config={})
|
7
|
+
super
|
8
|
+
|
9
|
+
unless Rails.logger.is_a?(Rackstash::BufferedLogger)
|
10
|
+
# This is required by ActiveRecord >= 4
|
11
|
+
if defined?(ActiveRecord::SessionStore::Extension::LoggerSilencer)
|
12
|
+
Rackstash::BufferedLogger.send(:include, ActiveRecord::SessionStore::Extension::LoggerSilencer)
|
13
|
+
end
|
14
|
+
|
15
|
+
Rackstash.logger = Rackstash::BufferedLogger.new(Rails.logger)
|
16
|
+
Rails.logger = Rackstash.logger
|
17
|
+
silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", Rackstash.logger }
|
18
|
+
|
19
|
+
ActiveRecord::Base.logger = Rackstash.logger if defined?(ActiveRecord::Base)
|
20
|
+
ActionController::Base.logger = Rackstash.logger if defined?(ActionController::Base)
|
21
|
+
# New in Rails 4
|
22
|
+
ActionView::Base.logger = Rackstash.logger if defined?(ActionView::Base) && ActionView::Base.respond_to?(:logger=)
|
23
|
+
end
|
24
|
+
# The ANSI color codes in the ActiveRecord logs don't help much in
|
25
|
+
# plain JSON
|
26
|
+
config.colorize_logging = false
|
27
|
+
|
28
|
+
log_subscriber = Rackstash::LogSubscriber.new
|
29
|
+
Rackstash::LogSubscriber.attach_to :action_controller, log_subscriber
|
30
|
+
|
31
|
+
# ActionDispatch captures exceptions too early for us to catch
|
32
|
+
# Thus, we inject our own exceptions_app to be able to catch the
|
33
|
+
# actual backtrace and add it to the fields
|
34
|
+
exceptions_app = config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path)
|
35
|
+
config.exceptions_app = lambda do |env|
|
36
|
+
log_subscriber._extract_exception_backtrace(env)
|
37
|
+
exceptions_app.call(env)
|
38
|
+
end
|
39
|
+
|
40
|
+
ActionController::Base.send :include, Rackstash::Instrumentation
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
require 'rackstash/railtie'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rackstash'
|
2
|
+
|
3
|
+
module Rackstash
|
4
|
+
class LogMiddleware
|
5
|
+
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
Rackstash.with_log_buffer do
|
12
|
+
request = Rack::Request.new(env)
|
13
|
+
fields = {
|
14
|
+
:method => request.request_method,
|
15
|
+
:scheme => request.scheme,
|
16
|
+
:path => (request.fullpath rescue "unknown")
|
17
|
+
}
|
18
|
+
begin
|
19
|
+
status, headers, result = @app.call(env)
|
20
|
+
ensure
|
21
|
+
fields[:status] = status
|
22
|
+
Rackstash.logger.fields.reverse_merge!(fields) if Rackstash.logger.fields
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
require 'active_support/log_subscriber'
|
3
|
+
|
4
|
+
module Rackstash
|
5
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
6
|
+
def process_action(event)
|
7
|
+
return unless Rails.logger.respond_to?(:fields) && Rails.logger.fields
|
8
|
+
payload = event.payload
|
9
|
+
|
10
|
+
data = extract_request(payload)
|
11
|
+
data.merge! extract_exception(payload)
|
12
|
+
data.merge! runtimes(event)
|
13
|
+
data.merge! location(event)
|
14
|
+
|
15
|
+
Rails.logger.fields.reverse_merge!(data)
|
16
|
+
Rails.logger.fields.merge! request_fields(payload)
|
17
|
+
end
|
18
|
+
|
19
|
+
def redirect_to(event)
|
20
|
+
Thread.current[:rackstash_location] = event.payload[:location]
|
21
|
+
end
|
22
|
+
|
23
|
+
def _extract_exception_backtrace(env)
|
24
|
+
return unless env['action_dispatch.exception']
|
25
|
+
|
26
|
+
exception_wrapper = ActionDispatch::ExceptionWrapper.new(env, env['action_dispatch.exception'])
|
27
|
+
data = {
|
28
|
+
:error_backtrace => exception_wrapper.full_trace.join("\n")
|
29
|
+
}
|
30
|
+
Rails.logger.fields.reverse_merge!(data)
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
protected
|
35
|
+
def extract_request(payload)
|
36
|
+
{
|
37
|
+
:controller => payload[:params]['controller'],
|
38
|
+
:action => payload[:params]['action'],
|
39
|
+
:format => extract_format(payload)
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def extract_format(payload)
|
44
|
+
if ::ActionPack::VERSION::MAJOR == 3 && ::ActionPack::VERSION::MINOR == 0
|
45
|
+
payload[:formats].first
|
46
|
+
else
|
47
|
+
payload[:format]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def extract_exception(payload)
|
52
|
+
if payload[:exception]
|
53
|
+
exception, message = payload[:exception]
|
54
|
+
{
|
55
|
+
:error => exception.to_s,
|
56
|
+
:error_message => message
|
57
|
+
}
|
58
|
+
else
|
59
|
+
{}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def runtimes(event)
|
64
|
+
{
|
65
|
+
:duration => event.duration,
|
66
|
+
:view => event.payload[:view_runtime],
|
67
|
+
:db => event.payload[:db_runtime]
|
68
|
+
}.inject({}) do |runtimes, (name, runtime)|
|
69
|
+
runtimes[name] = round(runtime, 2) if runtime
|
70
|
+
runtimes
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
if 0.0.method(:round).arity == 0
|
75
|
+
def round(float, ndigits=0)
|
76
|
+
power = (10**ndigits).to_f
|
77
|
+
(float * power).round / power
|
78
|
+
end
|
79
|
+
else
|
80
|
+
def round(float, ndigits=0)
|
81
|
+
float.to_f.round(ndigits)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def location(event)
|
87
|
+
if location = Thread.current[:rackstash_location]
|
88
|
+
Thread.current[:rackstash_location] = nil
|
89
|
+
{ :location => location }
|
90
|
+
else
|
91
|
+
{}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def request_fields(payload)
|
96
|
+
payload[:rackstash_request_fields] || {}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module Instrumentation
|
101
|
+
extend ActiveSupport::Concern
|
102
|
+
|
103
|
+
def append_info_to_payload(payload)
|
104
|
+
super
|
105
|
+
payload[:rackstash_request_fields] = Rackstash.request_fields(self)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# THIS IS FOR RAILS 2.x COMPATIBILITY ONLY
|
2
|
+
#
|
3
|
+
# This module, when included into ActionController::Base
|
4
|
+
# makes sure that no superfluous log entries are generated during request
|
5
|
+
# handling. Instead, only the configured Lograge output is generated
|
6
|
+
|
7
|
+
module Rackstash
|
8
|
+
module RailsExt
|
9
|
+
module ActionController
|
10
|
+
def self.included(base)
|
11
|
+
base.class_eval do
|
12
|
+
# In Rails 2, the ActionController::Benchmark module we are actually
|
13
|
+
# patching is included before the ActionController::Rescue module.
|
14
|
+
# As we need to preserve the alias chain, we completely replace the call
|
15
|
+
# chain. An unmodified ActionController thus uses the following call
|
16
|
+
# chain for perform_action:
|
17
|
+
#
|
18
|
+
# perform_action_with_flash
|
19
|
+
# perform_action_without_flash == perform_action_with_rescue
|
20
|
+
# perform_action_without_rescue == perform_action_with_benchmark
|
21
|
+
# perform_action_without_benchmark == perform_action_with_filters
|
22
|
+
# perform_action_with_filters == AC::Base#perform_action
|
23
|
+
#
|
24
|
+
# We replace the perform_action_without_rescue method with our own
|
25
|
+
# to keep up the call chain.
|
26
|
+
|
27
|
+
alias_method :perform_action_without_rescue, :perform_action_with_rackstash
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def perform_action_with_rackstash
|
33
|
+
if logger
|
34
|
+
rackstash_fields = {
|
35
|
+
:controller => params["controller"],
|
36
|
+
:action => params["action"],
|
37
|
+
:format => params["format"]
|
38
|
+
}
|
39
|
+
|
40
|
+
ms = [Benchmark.ms { perform_action_without_benchmark }, 0.01].max
|
41
|
+
logging_view = defined?(@view_runtime)
|
42
|
+
logging_active_record = Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
43
|
+
|
44
|
+
log_message = 'Completed in %.0fms' % ms
|
45
|
+
|
46
|
+
if logging_view || logging_active_record
|
47
|
+
log_message << " ("
|
48
|
+
if logging_view
|
49
|
+
log_message << view_runtime
|
50
|
+
rackstash_fields[:view] = (@view_runtime * 100).round / 100.0
|
51
|
+
end
|
52
|
+
|
53
|
+
if logging_active_record
|
54
|
+
db_runtime = active_record_runtime_for_rackstash
|
55
|
+
|
56
|
+
log_message << ", " if logging_view
|
57
|
+
log_message << ("DB: %.0f" % db_runtime) + ")"
|
58
|
+
rackstash_fields[:db] = (db_runtime * 100).round / 100.0
|
59
|
+
else
|
60
|
+
")"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
log_message << " | #{response.status}"
|
64
|
+
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
65
|
+
|
66
|
+
logger.info(log_message)
|
67
|
+
response.headers["X-Runtime"] = "%.0f" % ms
|
68
|
+
|
69
|
+
rackstash_fields[:duration] = (ms * 100).round / 100.0
|
70
|
+
rackstash_fields[:location] = response.location if response.location
|
71
|
+
else
|
72
|
+
perform_action_without_benchmark
|
73
|
+
end
|
74
|
+
rescue Exception => exception
|
75
|
+
rackstash_fields ||= {}
|
76
|
+
rackstash_fields[:error] = exception.class.name
|
77
|
+
rackstash_fields[:error_message] = exception.message
|
78
|
+
rackstash_fields[:error_backtrace] = exception.backtrace.join("\n") if exception.backtrace
|
79
|
+
raise
|
80
|
+
ensure
|
81
|
+
if logger && logger.respond_to?(:fields) && logger.fields
|
82
|
+
rackstash_fields ||= {}
|
83
|
+
logger.fields.reverse_merge!(rackstash_fields)
|
84
|
+
|
85
|
+
request_fields = Rackstash.request_fields(self)
|
86
|
+
logger.fields.merge!(request_fields) if request_fields
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Basically the same as ActionController::Benchmarking#active_record_runtime
|
91
|
+
# but we return a float instead of a pre-formatted string.
|
92
|
+
private
|
93
|
+
def active_record_runtime_for_rackstash
|
94
|
+
db_runtime = ActiveRecord::Base.connection.reset_runtime
|
95
|
+
db_runtime += @db_rt_before_render if @db_rt_before_render
|
96
|
+
db_runtime += @db_rt_after_render if @db_rt_after_render
|
97
|
+
db_runtime
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# THIS IS FOR RAILS 2.x COMPATIBILITY ONLY
|
2
|
+
#
|
3
|
+
# This module, when included into Rails::Initializer
|
4
|
+
# makes sure that our buffered logger gets configured right away.
|
5
|
+
|
6
|
+
module Rackstash
|
7
|
+
module RailsExt
|
8
|
+
module Initializer
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
alias_method_chain :initialize_logger, :rackstash
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize_logger_with_rackstash
|
16
|
+
initialize_logger_without_rackstash
|
17
|
+
Rackstash.setup(configuration) if configuration.rackstash.enabled
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails/railtie'
|
2
|
+
require 'action_view/log_subscriber'
|
3
|
+
require 'action_controller/log_subscriber'
|
4
|
+
|
5
|
+
module Rackstash
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
config.rackstash = ActiveSupport::OrderedOptions.new
|
8
|
+
|
9
|
+
config.rackstash.enabled = false
|
10
|
+
|
11
|
+
initializer :rackstash do |app|
|
12
|
+
if app.config.rackstash.enabled
|
13
|
+
Rackstash.setup(app.config)
|
14
|
+
app.middleware.insert(0, Rackstash::LogMiddleware)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'rackstash/buffered_logger'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module Rackstash
|
6
|
+
class Runner < Thor
|
7
|
+
default_task :capture
|
8
|
+
|
9
|
+
desc "capture", "Capture and buffer STDIN and generate a log entry on STDOUT"
|
10
|
+
method_option :tags, :type => :array, :required => false, :desc => "Tags to set on the Log entry"
|
11
|
+
method_option :fields, :type => :hash, :required => false, :desc => "Additional fields"
|
12
|
+
method_option :source, :type => :string, :required => false, :desc => "The source attribute"
|
13
|
+
def capture
|
14
|
+
logger.with_buffer do
|
15
|
+
$stdin.each_line do |line|
|
16
|
+
logger.info(line)
|
17
|
+
end
|
18
|
+
logger.source = options[:source]
|
19
|
+
|
20
|
+
logger.fields.merge!(options[:fields] || {})
|
21
|
+
logger.tags.push *(options[:tags] || [])
|
22
|
+
end
|
23
|
+
|
24
|
+
puts output.string
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
def output
|
29
|
+
@output ||= StringIO.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def logger
|
33
|
+
@logger ||= Rackstash::BufferedLogger.new(Logger.new(output))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|