rackstash 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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