rackstash 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|