timberio 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +34 -0
- data/.gitignore +14 -0
- data/Appraisals +37 -0
- data/Gemfile +22 -0
- data/LICENSE +38 -0
- data/README.md +22 -0
- data/Rakefile +4 -0
- data/TODO +4 -0
- data/benchmark/README.md +26 -0
- data/benchmark/rails_request.rb +68 -0
- data/benchmark/support/rails.rb +69 -0
- data/circle.yml +27 -0
- data/docs/installation/rails_on_heroku.md +31 -0
- data/docs/installation/rails_over_http.md +22 -0
- data/gemfiles/rails_3.0.X.gemfile +25 -0
- data/gemfiles/rails_3.1.X.gemfile +25 -0
- data/gemfiles/rails_3.2.X.gemfile +25 -0
- data/gemfiles/rails_4.0.X.gemfile +26 -0
- data/gemfiles/rails_4.1.X.gemfile +26 -0
- data/gemfiles/rails_4.2.X.gemfile +26 -0
- data/gemfiles/rails_5.0.X.gemfile +26 -0
- data/gemfiles/rails_edge.gemfile +27 -0
- data/lib/timber/api_settings.rb +17 -0
- data/lib/timber/bootstrap.rb +45 -0
- data/lib/timber/config.rb +25 -0
- data/lib/timber/context.rb +76 -0
- data/lib/timber/context_snapshot.rb +64 -0
- data/lib/timber/contexts/dynamic_values.rb +59 -0
- data/lib/timber/contexts/exception.rb +40 -0
- data/lib/timber/contexts/http_request.rb +22 -0
- data/lib/timber/contexts/http_requests/action_controller_specific.rb +48 -0
- data/lib/timber/contexts/http_requests/rack/params.rb +26 -0
- data/lib/timber/contexts/http_requests/rack.rb +105 -0
- data/lib/timber/contexts/http_response.rb +19 -0
- data/lib/timber/contexts/http_responses/action_controller.rb +76 -0
- data/lib/timber/contexts/logger.rb +33 -0
- data/lib/timber/contexts/organization.rb +33 -0
- data/lib/timber/contexts/organizations/action_controller.rb +34 -0
- data/lib/timber/contexts/server.rb +21 -0
- data/lib/timber/contexts/servers/heroku_specific.rb +48 -0
- data/lib/timber/contexts/sql_queries/active_record.rb +30 -0
- data/lib/timber/contexts/sql_queries/active_record_specific/binds.rb +37 -0
- data/lib/timber/contexts/sql_queries/active_record_specific.rb +59 -0
- data/lib/timber/contexts/sql_query.rb +18 -0
- data/lib/timber/contexts/template_render.rb +17 -0
- data/lib/timber/contexts/template_renders/action_view.rb +29 -0
- data/lib/timber/contexts/template_renders/action_view_specific.rb +51 -0
- data/lib/timber/contexts/user.rb +39 -0
- data/lib/timber/contexts/users/action_controller.rb +34 -0
- data/lib/timber/contexts.rb +23 -0
- data/lib/timber/current_context.rb +58 -0
- data/lib/timber/current_line_indexes.rb +35 -0
- data/lib/timber/frameworks/rails.rb +24 -0
- data/lib/timber/frameworks.rb +21 -0
- data/lib/timber/internal_logger.rb +35 -0
- data/lib/timber/log_device.rb +40 -0
- data/lib/timber/log_devices/heroku_logplex/hybrid_formatter.rb +14 -0
- data/lib/timber/log_devices/heroku_logplex.rb +14 -0
- data/lib/timber/log_devices/http/log_pile.rb +86 -0
- data/lib/timber/log_devices/http/log_truck/delivery.rb +116 -0
- data/lib/timber/log_devices/http/log_truck.rb +87 -0
- data/lib/timber/log_devices/http.rb +28 -0
- data/lib/timber/log_devices/io/formatter.rb +46 -0
- data/lib/timber/log_devices/io/hybrid_formatter.rb +41 -0
- data/lib/timber/log_devices/io/hybrid_hidden_formatter.rb +36 -0
- data/lib/timber/log_devices/io/json_formatter.rb +11 -0
- data/lib/timber/log_devices/io/logfmt_formatter.rb +11 -0
- data/lib/timber/log_devices/io.rb +41 -0
- data/lib/timber/log_devices.rb +4 -0
- data/lib/timber/log_line.rb +33 -0
- data/lib/timber/logger.rb +20 -0
- data/lib/timber/macros/compactor.rb +16 -0
- data/lib/timber/macros/date_formatter.rb +9 -0
- data/lib/timber/macros/deep_merger.rb +11 -0
- data/lib/timber/macros/logfmt_encoder.rb +77 -0
- data/lib/timber/macros.rb +4 -0
- data/lib/timber/patterns/delegated_singleton.rb +21 -0
- data/lib/timber/patterns/to_json.rb +22 -0
- data/lib/timber/patterns/to_logfmt.rb +9 -0
- data/lib/timber/patterns.rb +3 -0
- data/lib/timber/probe.rb +21 -0
- data/lib/timber/probes/action_controller_base.rb +31 -0
- data/lib/timber/probes/action_dispatch_debug_exceptions.rb +57 -0
- data/lib/timber/probes/active_support_log_subscriber/action_controller.rb +15 -0
- data/lib/timber/probes/active_support_log_subscriber/action_view.rb +26 -0
- data/lib/timber/probes/active_support_log_subscriber/active_record.rb +13 -0
- data/lib/timber/probes/active_support_log_subscriber.rb +62 -0
- data/lib/timber/probes/heroku.rb +30 -0
- data/lib/timber/probes/logger.rb +31 -0
- data/lib/timber/probes/rack.rb +36 -0
- data/lib/timber/probes/server.rb +18 -0
- data/lib/timber/probes.rb +24 -0
- data/lib/timber/version.rb +3 -0
- data/lib/timber.rb +27 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/action_controller.rb +4 -0
- data/spec/support/action_view.rb +4 -0
- data/spec/support/active_record.rb +28 -0
- data/spec/support/coveralls.rb +2 -0
- data/spec/support/rails/templates/_partial.html +1 -0
- data/spec/support/rails/templates/template.html +1 -0
- data/spec/support/rails.rb +33 -0
- data/spec/support/simplecov.rb +9 -0
- data/spec/support/socket_hostname.rb +12 -0
- data/spec/support/timber.rb +23 -0
- data/spec/support/timecop.rb +3 -0
- data/spec/support/webmock.rb +2 -0
- data/spec/timber/bootstrap_spec.rb +31 -0
- data/spec/timber/context_snapshot_spec.rb +10 -0
- data/spec/timber/context_spec.rb +4 -0
- data/spec/timber/contexts/exception_spec.rb +34 -0
- data/spec/timber/contexts/organizations/action_controller_spec.rb +49 -0
- data/spec/timber/contexts/users/action_controller_spec.rb +65 -0
- data/spec/timber/current_line_indexes_spec.rb +40 -0
- data/spec/timber/frameworks/rails_spec.rb +9 -0
- data/spec/timber/log_devices/heroku_logplex_spec.rb +45 -0
- data/spec/timber/log_devices/http/log_truck/delivery_spec.rb +66 -0
- data/spec/timber/log_devices/http/log_truck_spec.rb +65 -0
- data/spec/timber/log_devices/io/hybrid_hidden_formatter_spec.rb +28 -0
- data/spec/timber/log_line_spec.rb +49 -0
- data/spec/timber/macros/compactor_spec.rb +19 -0
- data/spec/timber/macros/logfmt_encoder_spec.rb +89 -0
- data/spec/timber/patterns/to_json_spec.rb +40 -0
- data/spec/timber/probes/action_controller_base_spec.rb +43 -0
- data/spec/timber/probes/action_controller_log_subscriber/action_controller_spec.rb +35 -0
- data/spec/timber/probes/action_controller_log_subscriber/action_view_spec.rb +44 -0
- data/spec/timber/probes/action_controller_log_subscriber/active_record_spec.rb +26 -0
- data/spec/timber/probes/action_dispatch_debug_exceptions_spec.rb +45 -0
- data/spec/timber/probes/logger_spec.rb +20 -0
- data/spec/timber/probes/rack_spec.rb +26 -0
- data/timberio.gemspec +20 -0
- metadata +210 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module Timber
|
2
|
+
module Contexts
|
3
|
+
module Organizations
|
4
|
+
class ActionController < Organization
|
5
|
+
DEFAULT_METHOD_NAME = :current_organization.freeze
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_writer :method_name
|
9
|
+
|
10
|
+
def method_name
|
11
|
+
@method_name ||= DEFAULT_METHOD_NAME
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :controller
|
16
|
+
|
17
|
+
def initialize(controller)
|
18
|
+
@controller = controller
|
19
|
+
super()
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def method_name
|
24
|
+
self.class.method_name
|
25
|
+
end
|
26
|
+
|
27
|
+
def organization
|
28
|
+
return @organization if defined?(@organization)
|
29
|
+
@organization = controller.respond_to?(method_name, true) ? controller.send(method_name) : nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
module Timber
|
4
|
+
module Contexts
|
5
|
+
class Server < Context
|
6
|
+
ROOT_KEY = :server.freeze
|
7
|
+
VERSION = 1.freeze
|
8
|
+
|
9
|
+
def hostname
|
10
|
+
@hostname ||= ::Socket.gethostname
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def json_payload
|
15
|
+
@json_payload ||= Macros::DeepMerger.merge({
|
16
|
+
:hostname => hostname
|
17
|
+
}, super).freeze
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Timber
|
2
|
+
module Contexts
|
3
|
+
module Servers
|
4
|
+
# Because this is a sub context we do not extend Server.
|
5
|
+
class HerokuSpecific < Context
|
6
|
+
ROOT_KEY = :heroku.freeze
|
7
|
+
VERSION = 1.freeze
|
8
|
+
DELIMITER = ".".freeze
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def json_shell(&_block)
|
12
|
+
Server.json_shell { super }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :dyno
|
17
|
+
|
18
|
+
def initialize(dyno)
|
19
|
+
# Initialize should be as fast as possible since it is executed inline.
|
20
|
+
# Hence the lazy methods below.
|
21
|
+
@dyno = dyno
|
22
|
+
super()
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_type
|
26
|
+
@process_type ||= parts.first
|
27
|
+
end
|
28
|
+
|
29
|
+
def dyno_id
|
30
|
+
@dyno_id ||= parts.last
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def parts
|
35
|
+
@parts ||= dyno.split(DELIMITER)
|
36
|
+
end
|
37
|
+
|
38
|
+
def json_payload
|
39
|
+
@json_payload ||= Macros::DeepMerger.merge({
|
40
|
+
# order is relevant for logfmt styling
|
41
|
+
:process_type => process_type,
|
42
|
+
:dyno_id => dyno_id
|
43
|
+
}, super).freeze.freeze
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Timber
|
2
|
+
module Contexts
|
3
|
+
module SQLQueries
|
4
|
+
class ActiveRecord < SQLQuery
|
5
|
+
attr_reader :log_subscriber, :event
|
6
|
+
|
7
|
+
def initialize(log_subscriber, event)
|
8
|
+
# Initialize should be as fast as possible since it is executed inline.
|
9
|
+
# Hence the lazy methods below.
|
10
|
+
@log_subscriber = log_subscriber
|
11
|
+
@event = event
|
12
|
+
super()
|
13
|
+
end
|
14
|
+
|
15
|
+
def sql
|
16
|
+
@sql ||= payload[:sql].try(:strip)
|
17
|
+
end
|
18
|
+
|
19
|
+
def time_ms
|
20
|
+
@time_ms ||= event.duration
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def payload
|
25
|
+
event.payload
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Timber
|
2
|
+
module Contexts
|
3
|
+
module SQLQueries
|
4
|
+
class ActiveRecordSpecific < Context
|
5
|
+
class Binds < DynamicValues
|
6
|
+
attr_reader :log_subscriber, :binds
|
7
|
+
|
8
|
+
def initialize(log_subscriber, binds)
|
9
|
+
# Initialize should be as fast as possible since it is executed inline.
|
10
|
+
# Hence the lazy methods below.
|
11
|
+
@log_subscriber = log_subscriber
|
12
|
+
@binds = binds
|
13
|
+
super()
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
# Override values_array so that this is done in the background thread
|
18
|
+
def values_array
|
19
|
+
@values_array ||= binds.collect do |bind|
|
20
|
+
name, value = render_bind(bind)
|
21
|
+
{:name => name, :value => value}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def render_bind(bind)
|
26
|
+
if bind.is_a?(Array)
|
27
|
+
# AR 4.2.X
|
28
|
+
log_subscriber.render_bind(*bind)
|
29
|
+
else
|
30
|
+
log_subscriber.render_bind(bind)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "timber/contexts/sql_queries/active_record_specific/binds"
|
2
|
+
|
3
|
+
module Timber
|
4
|
+
module Contexts
|
5
|
+
module SQLQueries
|
6
|
+
# Because this is a sub context we extend Context
|
7
|
+
class ActiveRecordSpecific < Context
|
8
|
+
ROOT_KEY = :active_record.freeze
|
9
|
+
VERSION = 1.freeze
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def json_shell(&_block)
|
13
|
+
SQLQuery.json_shell { super }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :log_subscriber, :event
|
18
|
+
|
19
|
+
def initialize(log_subscriber, event)
|
20
|
+
# Initialize should be as fast as possible since it is executed inline.
|
21
|
+
# Hence the lazy methods below.
|
22
|
+
@log_subscriber = log_subscriber
|
23
|
+
@event = event
|
24
|
+
super()
|
25
|
+
end
|
26
|
+
|
27
|
+
def binds
|
28
|
+
@binds ||= payload[:binds] && Binds.new(log_subscriber, payload[:binds])
|
29
|
+
end
|
30
|
+
|
31
|
+
def connection_id
|
32
|
+
@connection_id ||= payload[:connection_id].try(:to_s)
|
33
|
+
end
|
34
|
+
|
35
|
+
def statement_name
|
36
|
+
@statement_name ||= payload[:statement_name]
|
37
|
+
end
|
38
|
+
|
39
|
+
def transaction_id
|
40
|
+
@transaction_id ||= event.transaction_id.try(:to_s)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def json_payload
|
45
|
+
@json_payload ||= Macros::DeepMerger.merge({
|
46
|
+
:binds => binds.as_json,
|
47
|
+
:connection_id => connection_id,
|
48
|
+
:statement_name => statement_name,
|
49
|
+
:transaction_id => transaction_id
|
50
|
+
}, super).freeze
|
51
|
+
end
|
52
|
+
|
53
|
+
def payload
|
54
|
+
event.payload
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Timber
|
2
|
+
module Contexts
|
3
|
+
# Base class for SQLQuery sub types.
|
4
|
+
class SQLQuery < Context
|
5
|
+
ROOT_KEY = :sql_query.freeze
|
6
|
+
VERSION = 1.freeze
|
7
|
+
|
8
|
+
private
|
9
|
+
def json_payload
|
10
|
+
@json_payload ||= Macros::DeepMerger.merge({
|
11
|
+
# order is relevant for logfmt styling
|
12
|
+
:sql => sql,
|
13
|
+
:time_ms => time_ms
|
14
|
+
}, super).freeze
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Timber
|
2
|
+
module Contexts
|
3
|
+
class TemplateRender < Context
|
4
|
+
ROOT_KEY = :template_render.freeze
|
5
|
+
VERSION = 1.freeze
|
6
|
+
|
7
|
+
private
|
8
|
+
def json_payload
|
9
|
+
@json_payload ||= Macros::DeepMerger.merge({
|
10
|
+
# order is relevant for logfmt styling
|
11
|
+
:name => name,
|
12
|
+
:time_ms => time_ms
|
13
|
+
}, super).freeze
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Timber
|
2
|
+
module Contexts
|
3
|
+
module TemplateRenders
|
4
|
+
class ActionView < TemplateRender
|
5
|
+
attr_reader :event
|
6
|
+
|
7
|
+
def initialize(event)
|
8
|
+
# Initialize should be as fast as possible since it is executed inline.
|
9
|
+
# Hence the lazy methods below.
|
10
|
+
@event = event
|
11
|
+
super()
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
@name ||= payload[:identifier]
|
16
|
+
end
|
17
|
+
|
18
|
+
def time_ms
|
19
|
+
@time_ms ||= event.duration
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def payload
|
24
|
+
event.payload
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Timber
|
2
|
+
module Contexts
|
3
|
+
module TemplateRenders
|
4
|
+
# Because this is a sub type we extend Context
|
5
|
+
class ActionViewSpecific < Context
|
6
|
+
ROOT_KEY = :action_view.freeze
|
7
|
+
VERSION = 1.freeze
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def json_shell(&_block)
|
11
|
+
TemplateRender.json_shell { super }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :event
|
16
|
+
|
17
|
+
def initialize(event)
|
18
|
+
# Initialize should be as fast as possible since it is executed inline.
|
19
|
+
# Hence the lazy methods below.
|
20
|
+
@event = event
|
21
|
+
super()
|
22
|
+
end
|
23
|
+
|
24
|
+
def cache_hits
|
25
|
+
@cache_hits ||= payload[:cache_hits]
|
26
|
+
end
|
27
|
+
|
28
|
+
def count
|
29
|
+
@count ||= payload[:count]
|
30
|
+
end
|
31
|
+
|
32
|
+
def layout
|
33
|
+
@layout ||= payload[:layout]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def json_payload
|
38
|
+
@json_payload ||= Macros::DeepMerger.merge({
|
39
|
+
:cache_hits => cache_hits,
|
40
|
+
:count => count,
|
41
|
+
:layout => layout
|
42
|
+
}, super).freeze
|
43
|
+
end
|
44
|
+
|
45
|
+
def payload
|
46
|
+
event.payload
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Timber
|
2
|
+
module Contexts
|
3
|
+
class User < Context
|
4
|
+
ROOT_KEY = :user.freeze
|
5
|
+
VERSION = 1.freeze
|
6
|
+
|
7
|
+
attr_reader :user
|
8
|
+
|
9
|
+
def email
|
10
|
+
return @email if defined?(@email)
|
11
|
+
@email = user.respond_to?(:email) ? user.email : nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def id
|
15
|
+
return @id if defined?(@id)
|
16
|
+
@id = user.respond_to?(:id) ? user.id : nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def name
|
20
|
+
return @name if defined?(@name)
|
21
|
+
@name = user.respond_to?(:name) ? user.name : nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid?
|
25
|
+
!user.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def json_payload
|
30
|
+
@json_payload ||= Macros::DeepMerger.merge({
|
31
|
+
# order is relevant for logfmt styling
|
32
|
+
:id => id,
|
33
|
+
:name => name,
|
34
|
+
:email => email
|
35
|
+
}, super).freeze
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Timber
|
2
|
+
module Contexts
|
3
|
+
module Users
|
4
|
+
class ActionController < User
|
5
|
+
DEFAULT_METHOD_NAME = :current_user.freeze
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_writer :method_name
|
9
|
+
|
10
|
+
def method_name
|
11
|
+
@method_name ||= DEFAULT_METHOD_NAME
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :controller
|
16
|
+
|
17
|
+
def initialize(controller)
|
18
|
+
@controller = controller
|
19
|
+
super()
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def method_name
|
24
|
+
self.class.method_name
|
25
|
+
end
|
26
|
+
|
27
|
+
def user
|
28
|
+
return @user if defined?(@user)
|
29
|
+
@user = controller.respond_to?(method_name, true) ? controller.send(method_name) : nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Base, order is relevant
|
2
|
+
require "timber/contexts/dynamic_values"
|
3
|
+
require "timber/contexts/exception"
|
4
|
+
require "timber/contexts/http_request"
|
5
|
+
require "timber/contexts/http_response"
|
6
|
+
require "timber/contexts/logger"
|
7
|
+
require "timber/contexts/organization"
|
8
|
+
require "timber/contexts/server"
|
9
|
+
require "timber/contexts/sql_query"
|
10
|
+
require "timber/contexts/template_render"
|
11
|
+
require "timber/contexts/user"
|
12
|
+
|
13
|
+
# Sub contexts, order is relevant
|
14
|
+
require "timber/contexts/http_requests/rack"
|
15
|
+
require "timber/contexts/http_requests/action_controller_specific"
|
16
|
+
require "timber/contexts/http_responses/action_controller"
|
17
|
+
require "timber/contexts/organizations/action_controller"
|
18
|
+
require "timber/contexts/servers/heroku_specific"
|
19
|
+
require "timber/contexts/sql_queries/active_record"
|
20
|
+
require "timber/contexts/sql_queries/active_record_specific"
|
21
|
+
require "timber/contexts/template_renders/action_view"
|
22
|
+
require "timber/contexts/template_renders/action_view_specific"
|
23
|
+
require "timber/contexts/users/action_controller"
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
module Timber
|
4
|
+
# Holds the current context in the current thread's memory.
|
5
|
+
# This context gets copied as each log line is written.
|
6
|
+
class CurrentContext
|
7
|
+
THREAD_NAMESPACE = :_timber_current_context.freeze
|
8
|
+
STACK_KEYNAME = :stack.freeze
|
9
|
+
PRECISION = 8.freeze
|
10
|
+
|
11
|
+
include Patterns::DelegatedSingleton
|
12
|
+
|
13
|
+
# Adds a context to the current stack.
|
14
|
+
def add(*contexts, &_block)
|
15
|
+
contexts = contexts.compact
|
16
|
+
contexts.each do |context|
|
17
|
+
stack << context
|
18
|
+
end
|
19
|
+
block_given? ? yield : self
|
20
|
+
ensure
|
21
|
+
remove(*contexts) if block_given?
|
22
|
+
end
|
23
|
+
|
24
|
+
# Get a specific context type off the stack
|
25
|
+
def get(type)
|
26
|
+
stack.find { |context| context.is_a?(type) }
|
27
|
+
end
|
28
|
+
|
29
|
+
# Removes the contexts from the current stack.
|
30
|
+
def remove(*contexts)
|
31
|
+
# Ensure we clear the cacke when the stack changes
|
32
|
+
contexts.each do |context|
|
33
|
+
CurrentLineIndexes.context_removed(context)
|
34
|
+
stack.delete(context)
|
35
|
+
end
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
# Used to efficiently clone the context
|
40
|
+
def snapshot
|
41
|
+
# Cloning the array is efficient and will point to the same objects.
|
42
|
+
Timber::ContextSnapshot.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def valid_stack
|
46
|
+
stack.select(&:valid?)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def stack
|
51
|
+
storage[STACK_KEYNAME] ||= []
|
52
|
+
end
|
53
|
+
|
54
|
+
def storage
|
55
|
+
Thread.current[THREAD_NAMESPACE] ||= {}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Timber
|
2
|
+
# Holds the log line counts on a per context basis in memory.
|
3
|
+
class CurrentLineIndexes
|
4
|
+
THREAD_NAMESPACE = :_timber_current_line_counts.freeze
|
5
|
+
|
6
|
+
include Patterns::DelegatedSingleton
|
7
|
+
|
8
|
+
def context_removed(context)
|
9
|
+
indexes.delete(context)
|
10
|
+
end
|
11
|
+
|
12
|
+
def indexes
|
13
|
+
Thread.current[THREAD_NAMESPACE] ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def log_line_added(_log_line)
|
17
|
+
CurrentContext.valid_stack.each do |context|
|
18
|
+
if indexes.key?(context)
|
19
|
+
indexes[context] += 1
|
20
|
+
else
|
21
|
+
indexes[context] = 0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def reset!
|
27
|
+
indexes.clear
|
28
|
+
end
|
29
|
+
|
30
|
+
def snapshot
|
31
|
+
# No need to cache, this is blown out for each log line
|
32
|
+
LineIndexesSnapshot.new(self)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Timber
|
2
|
+
module Frameworks
|
3
|
+
module Rails
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
config.timber = Config.instance
|
6
|
+
config.before_initialize do
|
7
|
+
Bootstrap.bootstrap!(config.app_middleware, ::Rails::Rack::Logger)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.base_logger(logdev)
|
12
|
+
defined?(::ActiveSupport::Logger) ?
|
13
|
+
::ActiveSupport::Logger.new(logdev) :
|
14
|
+
::Logger.new(logdev)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.logger(logdev)
|
18
|
+
defined?(::ActiveSupport::TaggedLogging) ?
|
19
|
+
::ActiveSupport::TaggedLogging.new(base_logger(logdev)) :
|
20
|
+
base_logger(logdev)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "logger"
|
2
|
+
|
3
|
+
# Attempt to require Rails. We can not list it as a gem
|
4
|
+
# dependency because we want to support multiple frameworks.
|
5
|
+
require("rails") rescue LoadError
|
6
|
+
|
7
|
+
if defined?(Rails)
|
8
|
+
require 'timber/frameworks/rails'
|
9
|
+
end
|
10
|
+
|
11
|
+
module Timber
|
12
|
+
module Frameworks
|
13
|
+
def self.logger(logdev)
|
14
|
+
if defined?(Timber::Frameworks::Rails)
|
15
|
+
Rails.logger(logdev)
|
16
|
+
else
|
17
|
+
::Logger.new(logdev)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "logger"
|
2
|
+
|
3
|
+
module Timber
|
4
|
+
# Allows us to prefix all logs with [Timber] without having to
|
5
|
+
# rely on external dependencies. This is slightly different
|
6
|
+
# in that we don't want to create an entirely new logger or modify
|
7
|
+
# the logger they pass us. We only want to prefix logs in the context
|
8
|
+
# of this library.
|
9
|
+
class InternalLogger < ::Logger
|
10
|
+
class Formatter < ::Logger::Formatter
|
11
|
+
TAG = "[Timber]"
|
12
|
+
|
13
|
+
# This method is invoked when a log event occurs
|
14
|
+
def call(_severity, _timestamp, _progname, msg)
|
15
|
+
"#{TAG} #{String === msg ? msg : msg.inspect}\n"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(*args)
|
20
|
+
super
|
21
|
+
@formatter = Formatter.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# This is a convenience method for logging exceptions. Also
|
25
|
+
# allows us to build a notify hook for any exception that happen in
|
26
|
+
# the Timber library. This is extremely important for quality control.
|
27
|
+
def exception(exception)
|
28
|
+
if !exception.is_a?(Exception)
|
29
|
+
raise ArgumentError.new("#exception must take an Exception type")
|
30
|
+
end
|
31
|
+
# TODO: notify us that this exception happened
|
32
|
+
error("#{exception.inspect}: #{exception.backtrace.inspect}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Timber
|
2
|
+
class LogDevice
|
3
|
+
NEWLINE = "\n".freeze
|
4
|
+
SPLIT_LINES = true
|
5
|
+
|
6
|
+
def write(message)
|
7
|
+
return false if ignoring?
|
8
|
+
ignore do
|
9
|
+
messages(message).each do |message_part|
|
10
|
+
log_line = LogLine.new(message_part)
|
11
|
+
write_log_line(log_line)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
true
|
15
|
+
rescue Exception => e
|
16
|
+
Config.logger.exception(e)
|
17
|
+
raise e
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def ignore(&block)
|
22
|
+
@ignoring = true
|
23
|
+
yield
|
24
|
+
ensure
|
25
|
+
@ignoring = false
|
26
|
+
end
|
27
|
+
|
28
|
+
def ignoring?
|
29
|
+
@ignoring == true
|
30
|
+
end
|
31
|
+
|
32
|
+
def messages(message)
|
33
|
+
message.chomp.split(NEWLINE)
|
34
|
+
end
|
35
|
+
|
36
|
+
def write_formatted(formatted_message)
|
37
|
+
raise NotImplementedError.new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Timber
|
2
|
+
module LogDevices
|
3
|
+
class HerokuLogplex < IO
|
4
|
+
module HybridFormatter
|
5
|
+
private
|
6
|
+
def encoded_context(log_line)
|
7
|
+
log_line.context_snapshot.to_logfmt(
|
8
|
+
:except => [Contexts::Servers::HerokuSpecific]
|
9
|
+
)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|