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.
@@ -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,9 @@
1
+ module Rackstash
2
+ module Framework
3
+ module Rack
4
+ def setup(config={})
5
+ super
6
+ end
7
+ end
8
+ end
9
+ 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,9 @@
1
+ module Rackstash
2
+ module LogSeverity
3
+ Severities = [:DEBUG, :INFO, :WARN, :ERROR, :FATAL, :UNKNOWN]
4
+
5
+ Severities.each_with_index do |s,i|
6
+ const_set(s, i)
7
+ end
8
+ end
9
+ 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