timberio 1.0.0.beta1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -2
- data/.rspec +2 -0
- data/.yardopts +6 -0
- data/Appraisals +4 -0
- data/Gemfile +9 -1
- data/LICENSE.md +15 -0
- data/README.md +170 -8
- data/circle.yml +11 -8
- data/lib/timber/config.rb +11 -18
- data/lib/timber/context.rb +9 -68
- data/lib/timber/contexts/custom.rb +27 -0
- data/lib/timber/contexts/http.rb +28 -0
- data/lib/timber/contexts/organization.rb +24 -22
- data/lib/timber/contexts/user.rb +25 -28
- data/lib/timber/contexts.rb +7 -20
- data/lib/timber/current_context.rb +26 -41
- data/lib/timber/event.rb +13 -0
- data/lib/timber/events/controller_call.rb +40 -0
- data/lib/timber/events/custom.rb +42 -0
- data/lib/timber/events/exception.rb +35 -0
- data/lib/timber/events/http_request.rb +50 -0
- data/lib/timber/events/http_response.rb +36 -0
- data/lib/timber/events/sql_query.rb +26 -0
- data/lib/timber/events/template_render.rb +26 -0
- data/lib/timber/events.rb +37 -0
- data/lib/timber/frameworks/rails.rb +3 -14
- data/lib/timber/frameworks.rb +8 -10
- data/lib/timber/log_devices/http.rb +72 -13
- data/lib/timber/log_devices.rb +8 -4
- data/lib/timber/log_entry.rb +59 -0
- data/lib/timber/logger.rb +136 -13
- data/lib/timber/probe.rb +6 -4
- data/lib/timber/probes/action_controller_log_subscriber/log_subscriber.rb +64 -0
- data/lib/timber/probes/action_controller_log_subscriber.rb +20 -0
- data/lib/timber/probes/action_dispatch_debug_exceptions.rb +59 -35
- data/lib/timber/probes/action_view_log_subscriber/log_subscriber.rb +62 -0
- data/lib/timber/probes/action_view_log_subscriber.rb +20 -0
- data/lib/timber/probes/active_record_log_subscriber/log_subscriber.rb +72 -0
- data/lib/timber/probes/active_record_log_subscriber.rb +20 -0
- data/lib/timber/probes/rack_http_context.rb +51 -0
- data/lib/timber/probes/rails_rack_logger.rb +76 -0
- data/lib/timber/probes.rb +13 -16
- data/lib/timber/util/active_support_log_subscriber.rb +33 -0
- data/lib/timber/util/hash.rb +14 -0
- data/lib/timber/util.rb +8 -0
- data/lib/timber/version.rb +2 -2
- data/lib/timber.rb +6 -11
- data/spec/spec_helper.rb +7 -4
- data/spec/support/rails.rb +9 -5
- data/spec/support/timber.rb +1 -20
- data/spec/timber/events_spec.rb +55 -0
- data/spec/timber/log_devices/http_spec.rb +62 -0
- data/spec/timber/logger_spec.rb +68 -0
- data/spec/timber/probes/action_controller_log_subscriber_spec.rb +70 -0
- data/spec/timber/probes/action_dispatch_debug_exceptions_spec.rb +24 -18
- data/spec/timber/probes/action_view_log_subscriber_spec.rb +61 -0
- data/spec/timber/probes/active_record_log_subscriber_spec.rb +49 -0
- data/spec/timber/probes/rack_http_context_spec.rb +54 -0
- data/spec/timber/probes/rails_rack_logger_spec.rb +46 -0
- data/timberio.gemspec +2 -0
- metadata +62 -123
- data/.codeclimate.yml +0 -34
- data/LICENSE +0 -38
- data/Rakefile +0 -4
- data/TODO +0 -4
- data/benchmark/README.md +0 -26
- data/benchmark/rails_request.rb +0 -68
- data/benchmark/support/rails.rb +0 -69
- data/docs/installation/rails_on_heroku.md +0 -31
- data/docs/installation/rails_over_http.md +0 -22
- data/gemfiles/rails_3.0.X.gemfile +0 -25
- data/gemfiles/rails_3.1.X.gemfile +0 -25
- data/gemfiles/rails_3.2.X.gemfile +0 -25
- data/gemfiles/rails_4.0.X.gemfile +0 -26
- data/gemfiles/rails_4.1.X.gemfile +0 -26
- data/gemfiles/rails_4.2.X.gemfile +0 -26
- data/gemfiles/rails_5.0.X.gemfile +0 -26
- data/gemfiles/rails_edge.gemfile +0 -27
- data/lib/timber/api_settings.rb +0 -17
- data/lib/timber/bootstrap.rb +0 -45
- data/lib/timber/context_snapshot.rb +0 -64
- data/lib/timber/contexts/dynamic_values.rb +0 -59
- data/lib/timber/contexts/exception.rb +0 -40
- data/lib/timber/contexts/http_request.rb +0 -22
- data/lib/timber/contexts/http_requests/action_controller_specific.rb +0 -48
- data/lib/timber/contexts/http_requests/rack/params.rb +0 -26
- data/lib/timber/contexts/http_requests/rack.rb +0 -105
- data/lib/timber/contexts/http_response.rb +0 -19
- data/lib/timber/contexts/http_responses/action_controller.rb +0 -76
- data/lib/timber/contexts/logger.rb +0 -33
- data/lib/timber/contexts/organizations/action_controller.rb +0 -34
- data/lib/timber/contexts/server.rb +0 -21
- data/lib/timber/contexts/servers/heroku_specific.rb +0 -48
- data/lib/timber/contexts/sql_queries/active_record.rb +0 -30
- data/lib/timber/contexts/sql_queries/active_record_specific/binds.rb +0 -37
- data/lib/timber/contexts/sql_queries/active_record_specific.rb +0 -59
- data/lib/timber/contexts/sql_query.rb +0 -18
- data/lib/timber/contexts/template_render.rb +0 -17
- data/lib/timber/contexts/template_renders/action_view.rb +0 -29
- data/lib/timber/contexts/template_renders/action_view_specific.rb +0 -51
- data/lib/timber/contexts/users/action_controller.rb +0 -34
- data/lib/timber/current_line_indexes.rb +0 -35
- data/lib/timber/internal_logger.rb +0 -35
- data/lib/timber/log_device.rb +0 -40
- data/lib/timber/log_devices/heroku_logplex/hybrid_formatter.rb +0 -14
- data/lib/timber/log_devices/heroku_logplex.rb +0 -14
- data/lib/timber/log_devices/http/log_pile.rb +0 -86
- data/lib/timber/log_devices/http/log_truck/delivery.rb +0 -116
- data/lib/timber/log_devices/http/log_truck.rb +0 -87
- data/lib/timber/log_devices/io/formatter.rb +0 -46
- data/lib/timber/log_devices/io/hybrid_formatter.rb +0 -41
- data/lib/timber/log_devices/io/hybrid_hidden_formatter.rb +0 -36
- data/lib/timber/log_devices/io/json_formatter.rb +0 -11
- data/lib/timber/log_devices/io/logfmt_formatter.rb +0 -11
- data/lib/timber/log_devices/io.rb +0 -41
- data/lib/timber/log_line.rb +0 -33
- data/lib/timber/macros/compactor.rb +0 -16
- data/lib/timber/macros/date_formatter.rb +0 -9
- data/lib/timber/macros/deep_merger.rb +0 -11
- data/lib/timber/macros/logfmt_encoder.rb +0 -77
- data/lib/timber/macros.rb +0 -4
- data/lib/timber/patterns/delegated_singleton.rb +0 -21
- data/lib/timber/patterns/to_json.rb +0 -22
- data/lib/timber/patterns/to_logfmt.rb +0 -9
- data/lib/timber/patterns.rb +0 -3
- data/lib/timber/probes/action_controller_base.rb +0 -31
- data/lib/timber/probes/active_support_log_subscriber/action_controller.rb +0 -15
- data/lib/timber/probes/active_support_log_subscriber/action_view.rb +0 -26
- data/lib/timber/probes/active_support_log_subscriber/active_record.rb +0 -13
- data/lib/timber/probes/active_support_log_subscriber.rb +0 -62
- data/lib/timber/probes/heroku.rb +0 -30
- data/lib/timber/probes/logger.rb +0 -31
- data/lib/timber/probes/rack.rb +0 -36
- data/lib/timber/probes/server.rb +0 -18
- data/spec/timber/bootstrap_spec.rb +0 -31
- data/spec/timber/context_snapshot_spec.rb +0 -10
- data/spec/timber/context_spec.rb +0 -4
- data/spec/timber/contexts/exception_spec.rb +0 -34
- data/spec/timber/contexts/organizations/action_controller_spec.rb +0 -49
- data/spec/timber/contexts/users/action_controller_spec.rb +0 -65
- data/spec/timber/current_line_indexes_spec.rb +0 -40
- data/spec/timber/frameworks/rails_spec.rb +0 -9
- data/spec/timber/log_devices/heroku_logplex_spec.rb +0 -45
- data/spec/timber/log_devices/http/log_truck/delivery_spec.rb +0 -66
- data/spec/timber/log_devices/http/log_truck_spec.rb +0 -65
- data/spec/timber/log_devices/io/hybrid_hidden_formatter_spec.rb +0 -28
- data/spec/timber/log_line_spec.rb +0 -49
- data/spec/timber/macros/compactor_spec.rb +0 -19
- data/spec/timber/macros/logfmt_encoder_spec.rb +0 -89
- data/spec/timber/patterns/to_json_spec.rb +0 -40
- data/spec/timber/probes/action_controller_base_spec.rb +0 -43
- data/spec/timber/probes/action_controller_log_subscriber/action_controller_spec.rb +0 -35
- data/spec/timber/probes/action_controller_log_subscriber/action_view_spec.rb +0 -44
- data/spec/timber/probes/action_controller_log_subscriber/active_record_spec.rb +0 -26
- data/spec/timber/probes/logger_spec.rb +0 -20
- data/spec/timber/probes/rack_spec.rb +0 -26
data/lib/timber/logger.rb
CHANGED
@@ -1,20 +1,143 @@
|
|
1
1
|
require "logger"
|
2
2
|
|
3
3
|
module Timber
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
4
|
+
# The Timber Logger behaves exactly like `::Logger`, except that it supports a transparent API
|
5
|
+
# for logging structured messages. It ensures your log messages are communicated properly
|
6
|
+
# with the Timber.io API.
|
7
|
+
#
|
8
|
+
# To adhere to our no code debt / no lock-in promise, the Timber Logger will *never* deviate
|
9
|
+
# from the `::Logger` interface. That is, it will *never* add methods, or alter any
|
10
|
+
# method signatures. This ensures Timber can be removed without consequence.
|
11
|
+
#
|
12
|
+
# @example Basic example (the original ::Logger interface remains untouched):
|
13
|
+
# logger.info "Payment rejected for customer #{customer_id}"
|
14
|
+
#
|
15
|
+
# @example Using a map
|
16
|
+
# # The :message, :type, and :data keys are required
|
17
|
+
# logger.info message: "Payment rejected", type: :payment_rejected, data: {customer_id: customer_id, amount: 100}
|
18
|
+
#
|
19
|
+
# @example Using a Struct (a simple, more structured way, to define events)
|
20
|
+
# PaymentRejectedEvent = Struct.new(:customer_id, :amount, :reason) do
|
21
|
+
# def message; "Payment rejected for #{customer_id}"; end
|
22
|
+
# def type; :payment_rejected; end
|
23
|
+
# end
|
24
|
+
# Logger.info PaymentRejectedEvent.new("abcd1234", 100, "Card expired")
|
25
|
+
#
|
26
|
+
# @example Using typed Event classes
|
27
|
+
# # Event implementation is left to you. Events should be simple classes.
|
28
|
+
# # The only requirement is that it responds to #to_timber_event and return the
|
29
|
+
# # appropriate Timber::Events::* type.
|
30
|
+
# class Event
|
31
|
+
# def to_hash
|
32
|
+
# hash = {}
|
33
|
+
# instance_variables.each { |var| hash[var.to_s.delete("@")] = instance_variable_get(var) }
|
34
|
+
# hash
|
35
|
+
# end
|
36
|
+
# alias to_h to_hash
|
37
|
+
#
|
38
|
+
# def to_timber_event
|
39
|
+
# Timber::Events::Custom.new(type: type, message: message, data: to_hash)
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# def message; raise NotImplementedError.new; end
|
43
|
+
# def type; raise NotImplementedError.new; end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# class PaymentRejectedEvent < Event
|
47
|
+
# attr_accessor :customer_id, :amount
|
48
|
+
# def initialize(customer_id, amount)
|
49
|
+
# @customer_id = customer_id
|
50
|
+
# @amount = amount
|
51
|
+
# end
|
52
|
+
# def message; "Payment rejected for customer #{customer_id}"; end
|
53
|
+
# def type; :payment_rejected_event; end
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# Logger.info PymentRejectedEvent.new("abcd1234", 100)
|
57
|
+
#
|
58
|
+
class Logger < ::Logger
|
59
|
+
# @private
|
60
|
+
class Formatter
|
61
|
+
# Formatters get the formatted level from the logger.
|
62
|
+
SEVERITY_MAP = {
|
63
|
+
"DEBUG" => :debug,
|
64
|
+
"INFO" => :info,
|
65
|
+
"WARN" => :warn,
|
66
|
+
"ERROR" => :error,
|
67
|
+
"FATAL" => :datal,
|
68
|
+
"UNKNOWN" => :unknown
|
69
|
+
}
|
70
|
+
|
71
|
+
private
|
72
|
+
def build_log_entry(severity, time, progname, msg)
|
73
|
+
level = SEVERITY_MAP.fetch(severity)
|
74
|
+
context = CurrentContext.instance.snapshot
|
75
|
+
event = Events.build(msg)
|
76
|
+
if event
|
77
|
+
LogEntry.new(level, time, progname, event.message, context, event)
|
78
|
+
else
|
79
|
+
LogEntry.new(level, time, progname, msg, context, nil)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Structures your log messages into JSON.
|
85
|
+
#
|
86
|
+
# logger = Timber::Logger.new(STDOUT)
|
87
|
+
# logger.formatter = Timber::JSONFormatter.new
|
88
|
+
#
|
89
|
+
# Example message:
|
90
|
+
#
|
91
|
+
# {"level":"info","dt":"2016-09-01T07:00:00.000000-05:00","message":"My log message"}
|
92
|
+
#
|
93
|
+
class JSONFormatter < Formatter
|
94
|
+
def call(severity, time, progname, msg)
|
95
|
+
# use << for concatenation for performance reasons
|
96
|
+
build_log_entry(severity, time, progname, msg).to_json() << "\n"
|
15
97
|
end
|
16
|
-
|
17
|
-
|
98
|
+
end
|
99
|
+
|
100
|
+
# Structures your log messages into Timber's hybrid format, which makes
|
101
|
+
# it easy to read while also appending the appropriate metadata.
|
102
|
+
#
|
103
|
+
# logger = Timber::Logger.new(STDOUT)
|
104
|
+
# logger.formatter = Timber::JSONFormatter.new
|
105
|
+
#
|
106
|
+
# Example message:
|
107
|
+
#
|
108
|
+
# My log message @timber.io {"level":"info","dt":"2016-09-01T07:00:00.000000-05:00"}
|
109
|
+
#
|
110
|
+
class HybridFormatter < Formatter
|
111
|
+
METADATA_CALLOUT = "@timber.io".freeze
|
112
|
+
|
113
|
+
def call(severity, time, progname, msg)
|
114
|
+
log_entry = build_log_entry(severity, time, progname, msg)
|
115
|
+
metadata = log_entry.to_json(:except => [:message])
|
116
|
+
# use << for concatenation for performance reasons
|
117
|
+
puts msg
|
118
|
+
log_entry.message << " " << METADATA_CALLOUT << " " << metadata << "\n"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Creates a new Timber::Logger instances. Accepts the same arguments as `::Logger.new`.
|
123
|
+
# The only difference is that it default the formatter to {HybridFormatter}. Using
|
124
|
+
# a different formatter is easy. For example, if you prefer your logs in JSON.
|
125
|
+
#
|
126
|
+
# @example Changing your formatter
|
127
|
+
# logger = Timber::Logger.new(STDOUT)
|
128
|
+
# logger.formatter = Timber::Logger::JSONFormatter.new
|
129
|
+
def initialize(*args)
|
130
|
+
super(*args)
|
131
|
+
self.formatter = HybridFormatter.new
|
132
|
+
end
|
133
|
+
|
134
|
+
# Backwards compatibility with older ActiveSupport::Logger versions
|
135
|
+
Logger::Severity.constants.each do |severity|
|
136
|
+
class_eval(<<-EOT, __FILE__, __LINE__ + 1)
|
137
|
+
def #{severity.downcase}? # def debug?
|
138
|
+
Logger::#{severity} >= level # DEBUG >= level
|
139
|
+
end # end
|
140
|
+
EOT
|
18
141
|
end
|
19
142
|
end
|
20
143
|
end
|
data/lib/timber/probe.rb
CHANGED
@@ -1,21 +1,23 @@
|
|
1
1
|
module Timber
|
2
|
+
# Base class for `Timber::Probes::*`.
|
3
|
+
# @private
|
2
4
|
class Probe
|
3
5
|
class RequirementNotMetError < StandardError; end
|
4
6
|
|
5
7
|
class << self
|
6
8
|
def insert!(*args)
|
7
9
|
new(*args).insert!
|
8
|
-
Config.logger.debug("Inserted probe #{name}")
|
10
|
+
Config.instance.logger.debug("Inserted probe #{name}")
|
9
11
|
true
|
10
12
|
# RequirementUnsatisfiedError is the only silent failure we support
|
11
13
|
rescue RequirementNotMetError => e
|
12
|
-
Config.logger.debug("Failed inserting probe #{name}: #{e.message}")
|
14
|
+
Config.instance.logger.debug("Failed inserting probe #{name}: #{e.message}")
|
13
15
|
false
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
17
19
|
def insert!
|
18
|
-
raise NotImplementedError.new
|
20
|
+
raise NotImplementedError.new
|
19
21
|
end
|
20
22
|
end
|
21
|
-
end
|
23
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Timber
|
2
|
+
module Probes
|
3
|
+
class ActionControllerLogSubscriber < Probe
|
4
|
+
# The log subscriber that replaces the default `ActionController::LogSubscriber`.
|
5
|
+
# The intent of this subscriber is to, as transparently as possible, properly
|
6
|
+
# track events that are being logged here. This LogSubscriber will never change
|
7
|
+
# default behavior / log messages.
|
8
|
+
class LogSubscriber < ::ActionController::LogSubscriber
|
9
|
+
def start_processing(event)
|
10
|
+
info do
|
11
|
+
payload = event.payload
|
12
|
+
params = payload[:params].except(*INTERNAL_PARAMS)
|
13
|
+
format = extract_format(payload)
|
14
|
+
format = format.to_s.upcase if format.is_a?(Symbol)
|
15
|
+
|
16
|
+
Events::ControllerCall.new(
|
17
|
+
controller: payload[:controller],
|
18
|
+
action: payload[:action],
|
19
|
+
format: format,
|
20
|
+
params: params
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_action(event)
|
26
|
+
info do
|
27
|
+
payload = event.payload
|
28
|
+
additions = ActionController::Base.log_process_action(payload)
|
29
|
+
|
30
|
+
status = payload[:status]
|
31
|
+
if status.nil? && payload[:exception].present?
|
32
|
+
exception_class_name = payload[:exception].first
|
33
|
+
status = extract_status(exception_class_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
Events::HTTPResponse.new(
|
37
|
+
status: status,
|
38
|
+
time_ms: event.duration,
|
39
|
+
additions: additions
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def extract_format(payload)
|
46
|
+
if payload.key?(:format)
|
47
|
+
payload[:format] # rails > 4.X
|
48
|
+
elsif payload.key?(:formats)
|
49
|
+
payload[:formats].first # rails 3.X
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def extract_status(exception_class_name)
|
54
|
+
if defined?(ActionDispatch::ExceptionWrapper)
|
55
|
+
ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
|
56
|
+
else
|
57
|
+
# Rails 3.X
|
58
|
+
Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[exception_class_name]) rescue nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Timber
|
2
|
+
module Probes
|
3
|
+
# Responsible for automatically tracking controller call and http response events
|
4
|
+
# for applications that use `ActionController`.
|
5
|
+
class ActionControllerLogSubscriber < Probe
|
6
|
+
def initialize
|
7
|
+
require "action_controller/log_subscriber"
|
8
|
+
require "timber/probes/action_controller_log_subscriber/log_subscriber"
|
9
|
+
rescue LoadError => e
|
10
|
+
raise RequirementNotMetError.new(e.message)
|
11
|
+
end
|
12
|
+
|
13
|
+
def insert!
|
14
|
+
return true if Util::ActiveSupportLogSubscriber.subscribed?(:action_controller, LogSubscriber)
|
15
|
+
Util::ActiveSupportLogSubscriber.unsubscribe(:action_controller, ::ActionController::LogSubscriber)
|
16
|
+
LogSubscriber.attach_to(:action_controller)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,57 +1,81 @@
|
|
1
1
|
module Timber
|
2
2
|
module Probes
|
3
|
+
# Reponsible for automatically tracking exception events in Rails applications. While
|
4
|
+
# still preserving the default log style.
|
3
5
|
class ActionDispatchDebugExceptions < Probe
|
4
|
-
|
6
|
+
# For Rails >= 3.1
|
7
|
+
# @private
|
8
|
+
module DebugExceptionsInstanceMethods
|
5
9
|
def self.included(klass)
|
6
10
|
klass.class_eval do
|
7
11
|
private
|
8
|
-
|
9
|
-
|
12
|
+
def log_error(request, wrapper)
|
13
|
+
logger = logger(request)
|
14
|
+
puts logger.inspect
|
15
|
+
return unless logger
|
16
|
+
|
17
|
+
exception = wrapper.exception
|
10
18
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
trace = wrapper.application_trace
|
20
|
+
trace = wrapper.framework_trace if trace.empty?
|
21
|
+
|
22
|
+
event = Events::Exception.new(
|
23
|
+
name: exception.class.name,
|
24
|
+
exception_message: exception.message,
|
25
|
+
backtrace: trace
|
26
|
+
)
|
27
|
+
|
28
|
+
logger.fatal event
|
19
29
|
end
|
20
30
|
end
|
21
31
|
end
|
22
32
|
end
|
23
33
|
|
24
|
-
|
34
|
+
# For Rails < 3.1
|
35
|
+
# @private
|
36
|
+
module ShowExceptionsInstanceMethods #:nodoc:
|
37
|
+
def self.included(klass)
|
38
|
+
klass.class_eval do
|
39
|
+
private
|
40
|
+
# We have to monkey patch because ruby < 2.0 does not support prepend.
|
41
|
+
alias_method :_timber_old_log_error, :log_error
|
25
42
|
|
26
|
-
|
27
|
-
|
28
|
-
rescue RequirementNotMetError
|
29
|
-
load_show_exceptions
|
30
|
-
end
|
43
|
+
def log_error(exception)
|
44
|
+
return unless logger
|
31
45
|
|
32
|
-
|
33
|
-
|
34
|
-
|
46
|
+
event = Events::Exception.new(
|
47
|
+
name: exception.class.name,
|
48
|
+
exception_message: exception.message,
|
49
|
+
backtrace: application_trace(exception)
|
50
|
+
)
|
51
|
+
|
52
|
+
logger.fatal event
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
35
56
|
end
|
36
57
|
|
37
|
-
|
38
|
-
|
39
|
-
|
58
|
+
def initialize
|
59
|
+
begin
|
60
|
+
# Rails >= 3.1
|
40
61
|
require "action_dispatch/middleware/debug_exceptions"
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
raise RequirementNotMetError.new(e.message)
|
62
|
+
rescue LoadError
|
63
|
+
# Rails < 3.1
|
64
|
+
require "action_dispatch/middleware/show_exceptions"
|
45
65
|
end
|
66
|
+
rescue LoadError => e
|
67
|
+
raise RequirementNotMetError.new(e.message)
|
68
|
+
end
|
46
69
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
70
|
+
def insert!
|
71
|
+
if defined?(::ActionDispatch::DebugExceptions)
|
72
|
+
return true if ::ActionDispatch::DebugExceptions.include?(DebugExceptionsInstanceMethods)
|
73
|
+
::ActionDispatch::DebugExceptions.send(:include, DebugExceptionsInstanceMethods)
|
74
|
+
else
|
75
|
+
return true if ::ActionDispatch::ShowExceptions.include?(ShowExceptionsInstanceMethods)
|
76
|
+
::ActionDispatch::ShowExceptions.send(:include, ShowExceptionsInstanceMethods)
|
54
77
|
end
|
78
|
+
end
|
55
79
|
end
|
56
80
|
end
|
57
|
-
end
|
81
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Timber
|
2
|
+
module Probes
|
3
|
+
class ActionViewLogSubscriber < Probe
|
4
|
+
# The log subscriber that replaces the default `ActionView::LogSubscriber`.
|
5
|
+
# The intent of this subscriber is to, as transparently as possible, properly
|
6
|
+
# track events that are being logged here. This LogSubscriber will never change
|
7
|
+
# default behavior / log messages.
|
8
|
+
class LogSubscriber < ::ActionView::LogSubscriber
|
9
|
+
def render_template(event)
|
10
|
+
info do
|
11
|
+
full_name = from_rails_root(event.payload[:identifier])
|
12
|
+
message = " Rendered #{full_name}"
|
13
|
+
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
|
14
|
+
message << " (#{event.duration.round(1)}ms)"
|
15
|
+
|
16
|
+
Events::TemplateRender.new(
|
17
|
+
name: full_name,
|
18
|
+
time_ms: event.duration,
|
19
|
+
message: message
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def render_partial(event)
|
25
|
+
info do
|
26
|
+
full_name = from_rails_root(event.payload[:identifier])
|
27
|
+
message = " Rendered #{full_name}"
|
28
|
+
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
|
29
|
+
message << " (#{event.duration.round(1)}ms)"
|
30
|
+
message << " #{cache_message(event.payload)}" if event.payload.key?(:cache_hit)
|
31
|
+
|
32
|
+
Events::TemplateRender.new(
|
33
|
+
name: full_name,
|
34
|
+
time_ms: event.duration,
|
35
|
+
message: message
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def render_collection(event)
|
41
|
+
info do
|
42
|
+
identifier = event.payload[:identifier] || "templates"
|
43
|
+
full_name = from_rails_root(identifier)
|
44
|
+
message = " Rendered collection of #{full_name}" \
|
45
|
+
" #{render_count(event.payload)} (#{event.duration.round(1)}ms)"
|
46
|
+
|
47
|
+
Events::TemplateRender.new(
|
48
|
+
name: full_name,
|
49
|
+
time_ms: event.duration,
|
50
|
+
message: message
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def log_rendering_start(payload)
|
57
|
+
# Rails, you silly. We don't need to template rendering messages :)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Timber
|
2
|
+
module Probes
|
3
|
+
# Reponsible for automatically tracking template rendering events in `ActionView` while
|
4
|
+
# still preserving the default log style.
|
5
|
+
class ActionViewLogSubscriber < Probe
|
6
|
+
def initialize
|
7
|
+
require "action_view/log_subscriber"
|
8
|
+
require "timber/probes/action_view_log_subscriber/log_subscriber"
|
9
|
+
rescue LoadError => e
|
10
|
+
raise RequirementNotMetError.new(e.message)
|
11
|
+
end
|
12
|
+
|
13
|
+
def insert!
|
14
|
+
return true if Util::ActiveSupportLogSubscriber.subscribed?(:action_view, LogSubscriber)
|
15
|
+
Util::ActiveSupportLogSubscriber.unsubscribe(:action_view, ::ActionView::LogSubscriber)
|
16
|
+
LogSubscriber.attach_to(:action_view)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Timber
|
2
|
+
module Probes
|
3
|
+
class ActiveRecordLogSubscriber < Probe
|
4
|
+
# The log subscriber that replaces the default `ActiveRecord::LogSubscriber`.
|
5
|
+
# The intent of this subscriber is to, as transparently as possible, properly
|
6
|
+
# track events that are being logged here. This LogSubscriber will never change
|
7
|
+
# default behavior / log messages.
|
8
|
+
class LogSubscriber < ::ActiveRecord::LogSubscriber #:nodoc:
|
9
|
+
def sql(event)
|
10
|
+
return unless logger.debug?
|
11
|
+
|
12
|
+
self.class.runtime += event.duration
|
13
|
+
|
14
|
+
payload = event.payload
|
15
|
+
|
16
|
+
return if defined?(IGNORE_PAYLOAD_NAMES) && IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
17
|
+
|
18
|
+
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
|
19
|
+
sql = payload[:sql]
|
20
|
+
binds = nil
|
21
|
+
|
22
|
+
unless (payload[:binds] || []).empty?
|
23
|
+
binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect
|
24
|
+
end
|
25
|
+
|
26
|
+
name = colorize_payload_name(name, payload[:name])
|
27
|
+
sql = color(sql, sql_color(sql), true)
|
28
|
+
|
29
|
+
message = " #{name} #{sql}#{binds}"
|
30
|
+
|
31
|
+
event = Events::SQLQuery.new(
|
32
|
+
sql: payload[:sql],
|
33
|
+
time_ms: event.duration,
|
34
|
+
message: message
|
35
|
+
)
|
36
|
+
|
37
|
+
debug event
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def colorize_payload_name(name, payload_name)
|
42
|
+
if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
|
43
|
+
color(name, MAGENTA, true)
|
44
|
+
else
|
45
|
+
color(name, CYAN, true)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def sql_color(sql)
|
50
|
+
case sql
|
51
|
+
when /\A\s*rollback/mi
|
52
|
+
RED
|
53
|
+
when /select .*for update/mi, /\A\s*lock/mi
|
54
|
+
WHITE
|
55
|
+
when /\A\s*select/i
|
56
|
+
BLUE
|
57
|
+
when /\A\s*insert/i
|
58
|
+
GREEN
|
59
|
+
when /\A\s*update/i
|
60
|
+
YELLOW
|
61
|
+
when /\A\s*delete/i
|
62
|
+
RED
|
63
|
+
when /transaction\s*\Z/i
|
64
|
+
CYAN
|
65
|
+
else
|
66
|
+
MAGENTA
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Timber
|
2
|
+
module Probes
|
3
|
+
# Reponsible for automatimcally tracking SQL query events in `ActiveRecord`, while still
|
4
|
+
# preserving the default log style.
|
5
|
+
class ActiveRecordLogSubscriber < Probe
|
6
|
+
def initialize
|
7
|
+
require "active_record/log_subscriber"
|
8
|
+
require "timber/probes/active_record_log_subscriber/log_subscriber"
|
9
|
+
rescue LoadError => e
|
10
|
+
raise RequirementNotMetError.new(e.message)
|
11
|
+
end
|
12
|
+
|
13
|
+
def insert!
|
14
|
+
return true if Util::ActiveSupportLogSubscriber.subscribed?(:active_record, LogSubscriber)
|
15
|
+
Util::ActiveSupportLogSubscriber.unsubscribe(:active_record, ::ActiveRecord::LogSubscriber)
|
16
|
+
LogSubscriber.attach_to(:active_record)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Timber
|
2
|
+
module Probes
|
3
|
+
# Reponsible for automatically adding the HTTP context for applications that use `Rack`.
|
4
|
+
class RackHTTPContext < Probe
|
5
|
+
class Middleware
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
request = ::Rack::Request.new(env)
|
12
|
+
context = Contexts::HTTP.new(
|
13
|
+
method: request.request_method,
|
14
|
+
path: request.path,
|
15
|
+
remote_addr: request.ip,
|
16
|
+
request_id: request_id(env)
|
17
|
+
)
|
18
|
+
CurrentContext.instance.with(context) do
|
19
|
+
@app.call(env)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def request_id(env)
|
25
|
+
env["X-Request-ID"] ||
|
26
|
+
env["X-Request-Id"] ||
|
27
|
+
env["action_dispatch.request_id"]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :middleware, :insert_before
|
32
|
+
|
33
|
+
def initialize(middleware, insert_before)
|
34
|
+
if middleware.nil?
|
35
|
+
raise RequirementNotMetError.new("The middleware class attribute is not set. " +
|
36
|
+
"We need a middleware to insert the probe.")
|
37
|
+
end
|
38
|
+
@middleware = middleware
|
39
|
+
@insert_before = insert_before
|
40
|
+
end
|
41
|
+
|
42
|
+
def insert!
|
43
|
+
var_name = :"@_timber_rack_http_inserted"
|
44
|
+
return true if middleware.instance_variable_defined?(var_name) && middleware.instance_variable_get(var_name) == true
|
45
|
+
# Rails uses a proxy :/, so we need to do this instance variable hack
|
46
|
+
middleware.instance_variable_set(var_name, true)
|
47
|
+
middleware.insert_before insert_before, Middleware
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|