timberio 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +34 -0
  3. data/.gitignore +14 -0
  4. data/Appraisals +37 -0
  5. data/Gemfile +22 -0
  6. data/LICENSE +38 -0
  7. data/README.md +22 -0
  8. data/Rakefile +4 -0
  9. data/TODO +4 -0
  10. data/benchmark/README.md +26 -0
  11. data/benchmark/rails_request.rb +68 -0
  12. data/benchmark/support/rails.rb +69 -0
  13. data/circle.yml +27 -0
  14. data/docs/installation/rails_on_heroku.md +31 -0
  15. data/docs/installation/rails_over_http.md +22 -0
  16. data/gemfiles/rails_3.0.X.gemfile +25 -0
  17. data/gemfiles/rails_3.1.X.gemfile +25 -0
  18. data/gemfiles/rails_3.2.X.gemfile +25 -0
  19. data/gemfiles/rails_4.0.X.gemfile +26 -0
  20. data/gemfiles/rails_4.1.X.gemfile +26 -0
  21. data/gemfiles/rails_4.2.X.gemfile +26 -0
  22. data/gemfiles/rails_5.0.X.gemfile +26 -0
  23. data/gemfiles/rails_edge.gemfile +27 -0
  24. data/lib/timber/api_settings.rb +17 -0
  25. data/lib/timber/bootstrap.rb +45 -0
  26. data/lib/timber/config.rb +25 -0
  27. data/lib/timber/context.rb +76 -0
  28. data/lib/timber/context_snapshot.rb +64 -0
  29. data/lib/timber/contexts/dynamic_values.rb +59 -0
  30. data/lib/timber/contexts/exception.rb +40 -0
  31. data/lib/timber/contexts/http_request.rb +22 -0
  32. data/lib/timber/contexts/http_requests/action_controller_specific.rb +48 -0
  33. data/lib/timber/contexts/http_requests/rack/params.rb +26 -0
  34. data/lib/timber/contexts/http_requests/rack.rb +105 -0
  35. data/lib/timber/contexts/http_response.rb +19 -0
  36. data/lib/timber/contexts/http_responses/action_controller.rb +76 -0
  37. data/lib/timber/contexts/logger.rb +33 -0
  38. data/lib/timber/contexts/organization.rb +33 -0
  39. data/lib/timber/contexts/organizations/action_controller.rb +34 -0
  40. data/lib/timber/contexts/server.rb +21 -0
  41. data/lib/timber/contexts/servers/heroku_specific.rb +48 -0
  42. data/lib/timber/contexts/sql_queries/active_record.rb +30 -0
  43. data/lib/timber/contexts/sql_queries/active_record_specific/binds.rb +37 -0
  44. data/lib/timber/contexts/sql_queries/active_record_specific.rb +59 -0
  45. data/lib/timber/contexts/sql_query.rb +18 -0
  46. data/lib/timber/contexts/template_render.rb +17 -0
  47. data/lib/timber/contexts/template_renders/action_view.rb +29 -0
  48. data/lib/timber/contexts/template_renders/action_view_specific.rb +51 -0
  49. data/lib/timber/contexts/user.rb +39 -0
  50. data/lib/timber/contexts/users/action_controller.rb +34 -0
  51. data/lib/timber/contexts.rb +23 -0
  52. data/lib/timber/current_context.rb +58 -0
  53. data/lib/timber/current_line_indexes.rb +35 -0
  54. data/lib/timber/frameworks/rails.rb +24 -0
  55. data/lib/timber/frameworks.rb +21 -0
  56. data/lib/timber/internal_logger.rb +35 -0
  57. data/lib/timber/log_device.rb +40 -0
  58. data/lib/timber/log_devices/heroku_logplex/hybrid_formatter.rb +14 -0
  59. data/lib/timber/log_devices/heroku_logplex.rb +14 -0
  60. data/lib/timber/log_devices/http/log_pile.rb +86 -0
  61. data/lib/timber/log_devices/http/log_truck/delivery.rb +116 -0
  62. data/lib/timber/log_devices/http/log_truck.rb +87 -0
  63. data/lib/timber/log_devices/http.rb +28 -0
  64. data/lib/timber/log_devices/io/formatter.rb +46 -0
  65. data/lib/timber/log_devices/io/hybrid_formatter.rb +41 -0
  66. data/lib/timber/log_devices/io/hybrid_hidden_formatter.rb +36 -0
  67. data/lib/timber/log_devices/io/json_formatter.rb +11 -0
  68. data/lib/timber/log_devices/io/logfmt_formatter.rb +11 -0
  69. data/lib/timber/log_devices/io.rb +41 -0
  70. data/lib/timber/log_devices.rb +4 -0
  71. data/lib/timber/log_line.rb +33 -0
  72. data/lib/timber/logger.rb +20 -0
  73. data/lib/timber/macros/compactor.rb +16 -0
  74. data/lib/timber/macros/date_formatter.rb +9 -0
  75. data/lib/timber/macros/deep_merger.rb +11 -0
  76. data/lib/timber/macros/logfmt_encoder.rb +77 -0
  77. data/lib/timber/macros.rb +4 -0
  78. data/lib/timber/patterns/delegated_singleton.rb +21 -0
  79. data/lib/timber/patterns/to_json.rb +22 -0
  80. data/lib/timber/patterns/to_logfmt.rb +9 -0
  81. data/lib/timber/patterns.rb +3 -0
  82. data/lib/timber/probe.rb +21 -0
  83. data/lib/timber/probes/action_controller_base.rb +31 -0
  84. data/lib/timber/probes/action_dispatch_debug_exceptions.rb +57 -0
  85. data/lib/timber/probes/active_support_log_subscriber/action_controller.rb +15 -0
  86. data/lib/timber/probes/active_support_log_subscriber/action_view.rb +26 -0
  87. data/lib/timber/probes/active_support_log_subscriber/active_record.rb +13 -0
  88. data/lib/timber/probes/active_support_log_subscriber.rb +62 -0
  89. data/lib/timber/probes/heroku.rb +30 -0
  90. data/lib/timber/probes/logger.rb +31 -0
  91. data/lib/timber/probes/rack.rb +36 -0
  92. data/lib/timber/probes/server.rb +18 -0
  93. data/lib/timber/probes.rb +24 -0
  94. data/lib/timber/version.rb +3 -0
  95. data/lib/timber.rb +27 -0
  96. data/spec/spec_helper.rb +27 -0
  97. data/spec/support/action_controller.rb +4 -0
  98. data/spec/support/action_view.rb +4 -0
  99. data/spec/support/active_record.rb +28 -0
  100. data/spec/support/coveralls.rb +2 -0
  101. data/spec/support/rails/templates/_partial.html +1 -0
  102. data/spec/support/rails/templates/template.html +1 -0
  103. data/spec/support/rails.rb +33 -0
  104. data/spec/support/simplecov.rb +9 -0
  105. data/spec/support/socket_hostname.rb +12 -0
  106. data/spec/support/timber.rb +23 -0
  107. data/spec/support/timecop.rb +3 -0
  108. data/spec/support/webmock.rb +2 -0
  109. data/spec/timber/bootstrap_spec.rb +31 -0
  110. data/spec/timber/context_snapshot_spec.rb +10 -0
  111. data/spec/timber/context_spec.rb +4 -0
  112. data/spec/timber/contexts/exception_spec.rb +34 -0
  113. data/spec/timber/contexts/organizations/action_controller_spec.rb +49 -0
  114. data/spec/timber/contexts/users/action_controller_spec.rb +65 -0
  115. data/spec/timber/current_line_indexes_spec.rb +40 -0
  116. data/spec/timber/frameworks/rails_spec.rb +9 -0
  117. data/spec/timber/log_devices/heroku_logplex_spec.rb +45 -0
  118. data/spec/timber/log_devices/http/log_truck/delivery_spec.rb +66 -0
  119. data/spec/timber/log_devices/http/log_truck_spec.rb +65 -0
  120. data/spec/timber/log_devices/io/hybrid_hidden_formatter_spec.rb +28 -0
  121. data/spec/timber/log_line_spec.rb +49 -0
  122. data/spec/timber/macros/compactor_spec.rb +19 -0
  123. data/spec/timber/macros/logfmt_encoder_spec.rb +89 -0
  124. data/spec/timber/patterns/to_json_spec.rb +40 -0
  125. data/spec/timber/probes/action_controller_base_spec.rb +43 -0
  126. data/spec/timber/probes/action_controller_log_subscriber/action_controller_spec.rb +35 -0
  127. data/spec/timber/probes/action_controller_log_subscriber/action_view_spec.rb +44 -0
  128. data/spec/timber/probes/action_controller_log_subscriber/active_record_spec.rb +26 -0
  129. data/spec/timber/probes/action_dispatch_debug_exceptions_spec.rb +45 -0
  130. data/spec/timber/probes/logger_spec.rb +20 -0
  131. data/spec/timber/probes/rack_spec.rb +26 -0
  132. data/timberio.gemspec +20 -0
  133. 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