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.
Files changed (157) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -2
  3. data/.rspec +2 -0
  4. data/.yardopts +6 -0
  5. data/Appraisals +4 -0
  6. data/Gemfile +9 -1
  7. data/LICENSE.md +15 -0
  8. data/README.md +170 -8
  9. data/circle.yml +11 -8
  10. data/lib/timber/config.rb +11 -18
  11. data/lib/timber/context.rb +9 -68
  12. data/lib/timber/contexts/custom.rb +27 -0
  13. data/lib/timber/contexts/http.rb +28 -0
  14. data/lib/timber/contexts/organization.rb +24 -22
  15. data/lib/timber/contexts/user.rb +25 -28
  16. data/lib/timber/contexts.rb +7 -20
  17. data/lib/timber/current_context.rb +26 -41
  18. data/lib/timber/event.rb +13 -0
  19. data/lib/timber/events/controller_call.rb +40 -0
  20. data/lib/timber/events/custom.rb +42 -0
  21. data/lib/timber/events/exception.rb +35 -0
  22. data/lib/timber/events/http_request.rb +50 -0
  23. data/lib/timber/events/http_response.rb +36 -0
  24. data/lib/timber/events/sql_query.rb +26 -0
  25. data/lib/timber/events/template_render.rb +26 -0
  26. data/lib/timber/events.rb +37 -0
  27. data/lib/timber/frameworks/rails.rb +3 -14
  28. data/lib/timber/frameworks.rb +8 -10
  29. data/lib/timber/log_devices/http.rb +72 -13
  30. data/lib/timber/log_devices.rb +8 -4
  31. data/lib/timber/log_entry.rb +59 -0
  32. data/lib/timber/logger.rb +136 -13
  33. data/lib/timber/probe.rb +6 -4
  34. data/lib/timber/probes/action_controller_log_subscriber/log_subscriber.rb +64 -0
  35. data/lib/timber/probes/action_controller_log_subscriber.rb +20 -0
  36. data/lib/timber/probes/action_dispatch_debug_exceptions.rb +59 -35
  37. data/lib/timber/probes/action_view_log_subscriber/log_subscriber.rb +62 -0
  38. data/lib/timber/probes/action_view_log_subscriber.rb +20 -0
  39. data/lib/timber/probes/active_record_log_subscriber/log_subscriber.rb +72 -0
  40. data/lib/timber/probes/active_record_log_subscriber.rb +20 -0
  41. data/lib/timber/probes/rack_http_context.rb +51 -0
  42. data/lib/timber/probes/rails_rack_logger.rb +76 -0
  43. data/lib/timber/probes.rb +13 -16
  44. data/lib/timber/util/active_support_log_subscriber.rb +33 -0
  45. data/lib/timber/util/hash.rb +14 -0
  46. data/lib/timber/util.rb +8 -0
  47. data/lib/timber/version.rb +2 -2
  48. data/lib/timber.rb +6 -11
  49. data/spec/spec_helper.rb +7 -4
  50. data/spec/support/rails.rb +9 -5
  51. data/spec/support/timber.rb +1 -20
  52. data/spec/timber/events_spec.rb +55 -0
  53. data/spec/timber/log_devices/http_spec.rb +62 -0
  54. data/spec/timber/logger_spec.rb +68 -0
  55. data/spec/timber/probes/action_controller_log_subscriber_spec.rb +70 -0
  56. data/spec/timber/probes/action_dispatch_debug_exceptions_spec.rb +24 -18
  57. data/spec/timber/probes/action_view_log_subscriber_spec.rb +61 -0
  58. data/spec/timber/probes/active_record_log_subscriber_spec.rb +49 -0
  59. data/spec/timber/probes/rack_http_context_spec.rb +54 -0
  60. data/spec/timber/probes/rails_rack_logger_spec.rb +46 -0
  61. data/timberio.gemspec +2 -0
  62. metadata +62 -123
  63. data/.codeclimate.yml +0 -34
  64. data/LICENSE +0 -38
  65. data/Rakefile +0 -4
  66. data/TODO +0 -4
  67. data/benchmark/README.md +0 -26
  68. data/benchmark/rails_request.rb +0 -68
  69. data/benchmark/support/rails.rb +0 -69
  70. data/docs/installation/rails_on_heroku.md +0 -31
  71. data/docs/installation/rails_over_http.md +0 -22
  72. data/gemfiles/rails_3.0.X.gemfile +0 -25
  73. data/gemfiles/rails_3.1.X.gemfile +0 -25
  74. data/gemfiles/rails_3.2.X.gemfile +0 -25
  75. data/gemfiles/rails_4.0.X.gemfile +0 -26
  76. data/gemfiles/rails_4.1.X.gemfile +0 -26
  77. data/gemfiles/rails_4.2.X.gemfile +0 -26
  78. data/gemfiles/rails_5.0.X.gemfile +0 -26
  79. data/gemfiles/rails_edge.gemfile +0 -27
  80. data/lib/timber/api_settings.rb +0 -17
  81. data/lib/timber/bootstrap.rb +0 -45
  82. data/lib/timber/context_snapshot.rb +0 -64
  83. data/lib/timber/contexts/dynamic_values.rb +0 -59
  84. data/lib/timber/contexts/exception.rb +0 -40
  85. data/lib/timber/contexts/http_request.rb +0 -22
  86. data/lib/timber/contexts/http_requests/action_controller_specific.rb +0 -48
  87. data/lib/timber/contexts/http_requests/rack/params.rb +0 -26
  88. data/lib/timber/contexts/http_requests/rack.rb +0 -105
  89. data/lib/timber/contexts/http_response.rb +0 -19
  90. data/lib/timber/contexts/http_responses/action_controller.rb +0 -76
  91. data/lib/timber/contexts/logger.rb +0 -33
  92. data/lib/timber/contexts/organizations/action_controller.rb +0 -34
  93. data/lib/timber/contexts/server.rb +0 -21
  94. data/lib/timber/contexts/servers/heroku_specific.rb +0 -48
  95. data/lib/timber/contexts/sql_queries/active_record.rb +0 -30
  96. data/lib/timber/contexts/sql_queries/active_record_specific/binds.rb +0 -37
  97. data/lib/timber/contexts/sql_queries/active_record_specific.rb +0 -59
  98. data/lib/timber/contexts/sql_query.rb +0 -18
  99. data/lib/timber/contexts/template_render.rb +0 -17
  100. data/lib/timber/contexts/template_renders/action_view.rb +0 -29
  101. data/lib/timber/contexts/template_renders/action_view_specific.rb +0 -51
  102. data/lib/timber/contexts/users/action_controller.rb +0 -34
  103. data/lib/timber/current_line_indexes.rb +0 -35
  104. data/lib/timber/internal_logger.rb +0 -35
  105. data/lib/timber/log_device.rb +0 -40
  106. data/lib/timber/log_devices/heroku_logplex/hybrid_formatter.rb +0 -14
  107. data/lib/timber/log_devices/heroku_logplex.rb +0 -14
  108. data/lib/timber/log_devices/http/log_pile.rb +0 -86
  109. data/lib/timber/log_devices/http/log_truck/delivery.rb +0 -116
  110. data/lib/timber/log_devices/http/log_truck.rb +0 -87
  111. data/lib/timber/log_devices/io/formatter.rb +0 -46
  112. data/lib/timber/log_devices/io/hybrid_formatter.rb +0 -41
  113. data/lib/timber/log_devices/io/hybrid_hidden_formatter.rb +0 -36
  114. data/lib/timber/log_devices/io/json_formatter.rb +0 -11
  115. data/lib/timber/log_devices/io/logfmt_formatter.rb +0 -11
  116. data/lib/timber/log_devices/io.rb +0 -41
  117. data/lib/timber/log_line.rb +0 -33
  118. data/lib/timber/macros/compactor.rb +0 -16
  119. data/lib/timber/macros/date_formatter.rb +0 -9
  120. data/lib/timber/macros/deep_merger.rb +0 -11
  121. data/lib/timber/macros/logfmt_encoder.rb +0 -77
  122. data/lib/timber/macros.rb +0 -4
  123. data/lib/timber/patterns/delegated_singleton.rb +0 -21
  124. data/lib/timber/patterns/to_json.rb +0 -22
  125. data/lib/timber/patterns/to_logfmt.rb +0 -9
  126. data/lib/timber/patterns.rb +0 -3
  127. data/lib/timber/probes/action_controller_base.rb +0 -31
  128. data/lib/timber/probes/active_support_log_subscriber/action_controller.rb +0 -15
  129. data/lib/timber/probes/active_support_log_subscriber/action_view.rb +0 -26
  130. data/lib/timber/probes/active_support_log_subscriber/active_record.rb +0 -13
  131. data/lib/timber/probes/active_support_log_subscriber.rb +0 -62
  132. data/lib/timber/probes/heroku.rb +0 -30
  133. data/lib/timber/probes/logger.rb +0 -31
  134. data/lib/timber/probes/rack.rb +0 -36
  135. data/lib/timber/probes/server.rb +0 -18
  136. data/spec/timber/bootstrap_spec.rb +0 -31
  137. data/spec/timber/context_snapshot_spec.rb +0 -10
  138. data/spec/timber/context_spec.rb +0 -4
  139. data/spec/timber/contexts/exception_spec.rb +0 -34
  140. data/spec/timber/contexts/organizations/action_controller_spec.rb +0 -49
  141. data/spec/timber/contexts/users/action_controller_spec.rb +0 -65
  142. data/spec/timber/current_line_indexes_spec.rb +0 -40
  143. data/spec/timber/frameworks/rails_spec.rb +0 -9
  144. data/spec/timber/log_devices/heroku_logplex_spec.rb +0 -45
  145. data/spec/timber/log_devices/http/log_truck/delivery_spec.rb +0 -66
  146. data/spec/timber/log_devices/http/log_truck_spec.rb +0 -65
  147. data/spec/timber/log_devices/io/hybrid_hidden_formatter_spec.rb +0 -28
  148. data/spec/timber/log_line_spec.rb +0 -49
  149. data/spec/timber/macros/compactor_spec.rb +0 -19
  150. data/spec/timber/macros/logfmt_encoder_spec.rb +0 -89
  151. data/spec/timber/patterns/to_json_spec.rb +0 -40
  152. data/spec/timber/probes/action_controller_base_spec.rb +0 -43
  153. data/spec/timber/probes/action_controller_log_subscriber/action_controller_spec.rb +0 -35
  154. data/spec/timber/probes/action_controller_log_subscriber/action_view_spec.rb +0 -44
  155. data/spec/timber/probes/action_controller_log_subscriber/active_record_spec.rb +0 -26
  156. data/spec/timber/probes/logger_spec.rb +0 -20
  157. data/spec/timber/probes/rack_spec.rb +0 -26
@@ -1,58 +1,43 @@
1
- require "set"
1
+ require "singleton"
2
2
 
3
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.
4
+ # Holds the current context in a thread safe memory storage. This context is
5
+ # appended to every log line. Think of context as join data between your log lines,
6
+ # allowing you to relate them and filter them appropriately.
6
7
  class CurrentContext
7
- THREAD_NAMESPACE = :_timber_current_context.freeze
8
- STACK_KEYNAME = :stack.freeze
9
- PRECISION = 8.freeze
8
+ include Singleton
10
9
 
11
- include Patterns::DelegatedSingleton
10
+ THREAD_NAMESPACE = :_timber_current_context.freeze
12
11
 
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
12
+ class << self
13
+ # Convenience method for {#with}.
14
+ #
15
+ # @example Adding a context
16
+ # custom_context = Timber::Contexts::Custom.new(type: :keyspace, data: %{my: "data"})
17
+ # Timber::CurrentContext.with(custom_context) do
18
+ # # ... anything logged here will have the context ...
19
+ # end
20
+ def with(*args, &block)
21
+ instance.with(*args, &block)
18
22
  end
19
- block_given? ? yield : self
20
- ensure
21
- remove(*contexts) if block_given?
22
23
  end
23
24
 
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
25
+ # Adds a context to the current stack.
26
+ def with(data)
27
+ key = data.keyspace
28
+ hash[key] = data
29
+ yield
30
+ ensure
31
+ hash.delete(key)
37
32
  end
38
33
 
39
- # Used to efficiently clone the context
40
34
  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?)
35
+ hash.clone
47
36
  end
48
37
 
49
38
  private
50
- def stack
51
- storage[STACK_KEYNAME] ||= []
52
- end
53
-
54
- def storage
39
+ def hash
55
40
  Thread.current[THREAD_NAMESPACE] ||= {}
56
41
  end
57
42
  end
58
- end
43
+ end
@@ -0,0 +1,13 @@
1
+ module Timber
2
+ # Base class for `Timber::Events::*`
3
+ # @private
4
+ class Event
5
+ def as_json(options = {})
6
+ raise NotImplementedError.new
7
+ end
8
+
9
+ def to_json(options = {})
10
+ Util::Hash.compact(as_json).to_json(options)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,40 @@
1
+ module Timber
2
+ module Events
3
+ # The controller call event tracks controller invocations. For example, this line in Rails:
4
+ #
5
+ # Processing by PagesController#home as HTML
6
+ #
7
+ # @note This event should be installed automatically through probes,
8
+ # such as the {Probes::ActionControllerLogSubscriber} probe.
9
+ class ControllerCall < Timber::Event
10
+ attr_reader :controller, :action, :params, :format
11
+
12
+ def initialize(attributes)
13
+ @controller = attributes[:controller] || raise(ArgumentError.new(":controller is required"))
14
+ @action = attributes[:action] || raise(ArgumentError.new(":action is required"))
15
+ @params = attributes[:params]
16
+ @format = attributes[:format]
17
+ end
18
+
19
+ def to_hash
20
+ {controller: controller, action: action}
21
+ end
22
+ alias to_h to_hash
23
+
24
+ def as_json(_options = {})
25
+ {:controller_call => to_hash}
26
+ end
27
+
28
+ def message
29
+ message = "Processing by #{controller}##{action}"
30
+ if !message.nil?
31
+ message << " as #{format}"
32
+ end
33
+ if !params.nil? && params.length > 0
34
+ message << "\n Parameters: #{params.inspect}"
35
+ end
36
+ message
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,42 @@
1
+ module Timber
2
+ module Events
3
+ # Allows for custom events that aren't covered elsewhere.
4
+ #
5
+ # Custom events can be used to encode information about events that are central
6
+ # to your line of business like receiving credit card payments, saving a draft of a post,
7
+ # or changing a user's password.
8
+ #
9
+ # For examples of logging custom events see {Logger}.
10
+ class Custom < Timber::Event
11
+ attr_reader :type, :message, :data
12
+
13
+ # Instantiates a new custom event that can be logged. See {Logger} for examples
14
+ # on logging custom events.
15
+ #
16
+ # @param [Hash] attributes the options to create a custom event with.
17
+ # @option attributes [Symbol] :type *required* The custom event type. This should be in
18
+ # snake case. Example: `:my_custom_event`.
19
+ # @option attributes [String] :message *required* The message to be logged.
20
+ # @option attributes [Hash] :data A hash of JSON encodable data to be stored with the
21
+ # log line.
22
+ def initialize(attributes)
23
+ @type = attributes[:type] || raise(ArgumentError.new(":type is required"))
24
+ @message = attributes[:message] || raise(ArgumentError.new(":message is required"))
25
+ @data = attributes[:data]
26
+ end
27
+
28
+ def to_hash
29
+ {type => data}
30
+ end
31
+ alias to_h to_hash
32
+
33
+ def as_json(_options = {})
34
+ {:custom => to_hash}
35
+ end
36
+
37
+ def to_json(options = {})
38
+ as_json().to_json(options)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,35 @@
1
+ module Timber
2
+ module Events
3
+ # The exception event is used to track exceptions.
4
+ #
5
+ # @note This event should be installed automatically through probes,
6
+ # such as the {Probes::ActionDispatchDebugExceptions} probe.
7
+ class Exception < Timber::Event
8
+ attr_reader :name, :exception_message, :backtrace
9
+
10
+ def initialize(attributes)
11
+ @name = attributes[:name] || raise(ArgumentError.new(":name is required"))
12
+ @exception_message = attributes[:exception_message] || raise(ArgumentError.new(":exception_message is required"))
13
+ @backtrace = attributes[:backtrace]
14
+ end
15
+
16
+ def to_hash
17
+ {name: name, message: exception_message, backtrace: backtrace}
18
+ end
19
+ alias to_h to_hash
20
+
21
+ def as_json(_options = {})
22
+ {:exception => to_hash}
23
+ end
24
+
25
+ def message
26
+ message = "#{name} (#{exception_message}):"
27
+ if backtrace.is_a?(Array) && backtrace.length > 0
28
+ message << "\n\n"
29
+ message << backtrace.join("\n")
30
+ end
31
+ message
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,50 @@
1
+ module Timber
2
+ module Events
3
+ # The HTTP request event tracks incoming HTTP requests.
4
+ #
5
+ # @note This event should be installed automatically through probes,
6
+ # such as the {Probes::ActionControllerLogSubscriber} probe.
7
+ class HTTPRequest < Timber::Event
8
+ attr_reader :host, :method, :path, :port, :query_params, :content_type,
9
+ :remote_addr, :referrer, :request_id, :user_agent
10
+
11
+ def initialize(attributes)
12
+ @host = attributes[:host] || raise(ArgumentError.new(":host is required"))
13
+ @method = attributes[:method] || raise(ArgumentError.new(":method is required"))
14
+ @path = attributes[:path] || raise(ArgumentError.new(":path is required"))
15
+ @port = attributes[:port]
16
+ @query_params = attributes[:query_params]
17
+ @content_type = attributes[:content_type]
18
+ @remote_addr = attributes[:remote_addr]
19
+ @referrer = attributes[:referrer]
20
+ @request_id = attributes[:request_id]
21
+ @user_agent = attributes[:user_agent]
22
+ end
23
+
24
+ def to_hash
25
+ {host: host, method: method, path: path, port: port, query_params: query_params,
26
+ headers: {content_type: content_type, remote_addr: remote_addr, referrer: referrer,
27
+ request_id: request_id, user_agent: user_agent}}
28
+ end
29
+ alias to_h to_hash
30
+
31
+ def as_json(_options = {})
32
+ hash = to_hash
33
+ hash[:headers] = Util::Hash.compact(hash[:headers])
34
+ hash = Util::Hash.compact(hash)
35
+ {:http_request => hash}
36
+ end
37
+
38
+ def message
39
+ 'Started %s "%s" for %s' % [
40
+ method,
41
+ path,
42
+ remote_addr]
43
+ end
44
+
45
+ def status_description
46
+ Rack::Utils::HTTP_STATUS_CODES[status]
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,36 @@
1
+ module Timber
2
+ module Events
3
+ # The HTTP response event tracks outgoing HTTP request responses.
4
+ #
5
+ # @note This event should be installed automatically through probes,
6
+ # such as the {Probes::ActionControllerLogSubscriber} probe.
7
+ class HTTPResponse < Timber::Event
8
+ attr_reader :status, :time_ms, :additions
9
+
10
+ def initialize(attributes)
11
+ @status = attributes[:status] || raise(ArgumentError.new(":status is required"))
12
+ @time_ms = attributes[:time_ms] || raise(ArgumentError.new(":time_ms is required"))
13
+ @additions = attributes[:additions]
14
+ end
15
+
16
+ def to_hash
17
+ {status: status, time_ms: time_ms}
18
+ end
19
+ alias to_h to_hash
20
+
21
+ def as_json(_options = {})
22
+ {:http_response => to_hash}
23
+ end
24
+
25
+ def message
26
+ message = "Completed #{status} #{status_description} in #{time_ms}ms"
27
+ message << " (#{additions.join(" | ".freeze)})" unless additions.empty?
28
+ message
29
+ end
30
+
31
+ def status_description
32
+ Rack::Utils::HTTP_STATUS_CODES[status]
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ module Timber
2
+ module Events
3
+ # The SQL query event tracks sql queries to your database.
4
+ #
5
+ # @note This event should be installed automatically through probes,
6
+ # such as the {Probes::ActiveRecordLogSubscriber} probe.
7
+ class SQLQuery < Timber::Event
8
+ attr_reader :sql, :time_ms, :message
9
+
10
+ def initialize(attributes)
11
+ @sql = attributes[:sql] || raise(ArgumentError.new(":sql is required"))
12
+ @time_ms = attributes[:time_ms] || raise(ArgumentError.new(":time_ms is required"))
13
+ @message = attributes[:message] || raise(ArgumentError.new(":message is required"))
14
+ end
15
+
16
+ def to_hash
17
+ {sql: sql, time_ms: time_ms}
18
+ end
19
+ alias to_h to_hash
20
+
21
+ def as_json(_options = {})
22
+ {:sql_query => to_hash}
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module Timber
2
+ module Events
3
+ # The template render event track template renderings and their performance.
4
+ #
5
+ # @note This event should be installed automatically through probes,
6
+ # such as the {Probes::ActionViewLogSubscriber} probe.
7
+ class TemplateRender < Timber::Event
8
+ attr_reader :message, :name, :time_ms
9
+
10
+ def initialize(attributes)
11
+ @message = attributes[:message] || raise(ArgumentError.new(":message is required"))
12
+ @name = attributes[:name] || raise(ArgumentError.new(":name is required"))
13
+ @time_ms = attributes[:time_ms] || raise(ArgumentError.new(":time_ms is required"))
14
+ end
15
+
16
+ def to_hash
17
+ {name: name, time_ms: time_ms}
18
+ end
19
+ alias to_h to_hash
20
+
21
+ def as_json(_options = {})
22
+ {:template_render => to_hash}
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ require "timber/events/controller_call"
2
+ require "timber/events/custom"
3
+ require "timber/events/exception"
4
+ require "timber/events/http_request"
5
+ require "timber/events/http_response"
6
+ require "timber/events/sql_query"
7
+ require "timber/events/template_render"
8
+
9
+ module Timber
10
+ module Events
11
+ # Protocol for casting objects into a `Timber::Event`.
12
+ #
13
+ # @example Casting a hash
14
+ # Timber::Events.build({type: :custom_event, message: "My log message", data: {my: "data"}})
15
+ def self.build(obj)
16
+ if obj.is_a?(::Timber::Event)
17
+ obj
18
+ elsif obj.respond_to?(:to_timber_event)
19
+ obj.to_timber_event
20
+ elsif obj.is_a?(Hash) && obj.key?(:message) && obj.key?(:type) && obj.key?(:data)
21
+ Events::Custom.new(
22
+ type: obj[:type],
23
+ message: obj[:message],
24
+ data: obj[:data]
25
+ )
26
+ elsif obj.is_a?(Struct) && obj.respond_to?(:message) && obj.respond_to?(:type)
27
+ Events::Custom.new(
28
+ type: obj.type,
29
+ message: obj.message,
30
+ data: obj.respond_to?(:hash) ? obj.hash : obj.to_h # ruby 1.9.3 does not have to_h
31
+ )
32
+ else
33
+ nil
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,24 +1,13 @@
1
1
  module Timber
2
2
  module Frameworks
3
3
  module Rails
4
+ # Installs Timber into your Rails app automatically.
4
5
  class Railtie < ::Rails::Railtie
5
6
  config.timber = Config.instance
6
7
  config.before_initialize do
7
- Bootstrap.bootstrap!(config.app_middleware, ::Rails::Rack::Logger)
8
+ Probes.insert!(config.app_middleware, ::Rails::Rack::Logger)
8
9
  end
9
10
  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
11
  end
23
12
  end
24
- end
13
+ end
@@ -2,20 +2,18 @@ require "logger"
2
2
 
3
3
  # Attempt to require Rails. We can not list it as a gem
4
4
  # dependency because we want to support multiple frameworks.
5
- require("rails") rescue LoadError
5
+ begin
6
+ require("rails")
7
+ rescue LoadError
8
+ end
6
9
 
7
- if defined?(Rails)
10
+ if defined?(::Rails) && defined?(::Rails::Railtie)
8
11
  require 'timber/frameworks/rails'
9
12
  end
10
13
 
11
14
  module Timber
15
+ # Namespace for installing Timber into frameworks
16
+ # @private
12
17
  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
18
  end
21
- end
19
+ end
@@ -1,27 +1,86 @@
1
- require File.join(File.dirname(__FILE__), "http", "log_pile")
2
- require File.join(File.dirname(__FILE__), "http", "log_truck")
1
+ require "monitor"
2
+ require "msgpack"
3
3
 
4
4
  module Timber
5
5
  module LogDevices
6
- class HTTP < LogDevice
7
- SPLIT_LINES = false
6
+ # A log device that buffers and sends logs to the Timber API over HTTP in intervals. The buffer
7
+ # uses MessagePack::Buffer, which is fast, efficient with memory, and reduces
8
+ # the payload size sent to Timber.
9
+ class HTTP
10
+ class DeliveryError < StandardError; end
8
11
 
9
- attr_reader :application_key
12
+ API_URI = URI.parse("https://api.timber.io/http_frames")
13
+ CONTENT_TYPE = "application/json".freeze
14
+ CONNECTION_HEADER = "keep-alive".freeze
15
+ USER_AGENT = "Timber Ruby Gem/#{Timber::VERSION}".freeze
10
16
 
11
- def initialize(application_key = nil)
12
- @application_key = application_key || Config.application_key
13
- if @application_key.nil?
14
- raise ArgumentError.new("A Timber application_key is required")
17
+ HTTPS = Net::HTTP.new(API_URI.host, API_URI.port).tap do |https|
18
+ https.use_ssl = true
19
+ https.read_timeout = 30
20
+ https.ssl_timeout = 10
21
+ if https.respond_to?(:keep_alive_timeout=)
22
+ https.keep_alive_timeout = 60
15
23
  end
16
- LogTruck.start!
24
+ https.open_timeout = 10
17
25
  end
18
26
 
19
- def close(*args)
27
+ DEFAULT_DELIVERY_FREQUENCY = 2.freeze
28
+
29
+ # Instantiates a new HTTP log device.
30
+ #
31
+ # @param api_key [String] The API key provided to you after you add your application to
32
+ # [Timber](https://timber.io).
33
+ # @param [Hash] options the options to create a HTTP log device with.
34
+ # @option attributes [Symbol] :frequency_seconds (2) How often the client should
35
+ # attempt to deliver logs to the Timber API. The HTTP client buffers logs between calls.
36
+ def initialize(api_key, options = {})
37
+ @api_key = api_key
38
+ @buffer = []
39
+ @monitor = Monitor.new
40
+ @delivery_thread = Thread.new do
41
+ at_exit { deliver }
42
+ loop do
43
+ sleep options[:frequency_seconds] || DEFAULT_DELIVERY_FREQUENCY
44
+ deliver
45
+ end
46
+ end
47
+ end
48
+
49
+ def write(msg)
50
+ @monitor.synchronize {
51
+ @buffer << msg
52
+ }
53
+ end
54
+
55
+ def close
56
+ @delivery_thread.kill
20
57
  end
21
58
 
22
59
  private
23
- def write_log_line(log_line)
24
- LogPile.get(application_key).drop(log_line)
60
+ def deliver
61
+ body = @buffer.read
62
+
63
+ request = Net::HTTP::Post.new(API_URI.request_uri).tap do |req|
64
+ req['Authorization'] = authorization_payload
65
+ req['Connection'] = CONNECTION_HEADER
66
+ req['Content-Type'] = CONTENT_TYPE
67
+ req['User-Agent'] = USER_AGENT
68
+ req.body = body
69
+ end
70
+
71
+ HTTPS.request(request).tap do |res|
72
+ code = res.code.to_i
73
+ if code < 200 || code >= 300
74
+ raise DeliveryError.new("Bad response from Timber API - #{res.code}: #{res.body}")
75
+ end
76
+ Config.instance.logger.debug("Success! #{code}: #{res.body}")
77
+ end
78
+
79
+ @buffer.clear
80
+ end
81
+
82
+ def authorization_payload
83
+ @authorization_payload ||= "Basic #{Base64.strict_encode64(@api_key).chomp}"
25
84
  end
26
85
  end
27
86
  end
@@ -1,4 +1,8 @@
1
- # Order is relevant
2
- require File.join(File.dirname(__FILE__), "log_devices", "io")
3
- require File.join(File.dirname(__FILE__), "log_devices", "heroku_logplex")
4
- require File.join(File.dirname(__FILE__), "log_devices", "http")
1
+ require "timber/log_devices/http"
2
+
3
+ module Timber
4
+ # Namespace for all log devices.
5
+ # @private
6
+ module LogDevices
7
+ end
8
+ end
@@ -0,0 +1,59 @@
1
+ module Timber
2
+ # Represents a new log entry into the log. This is an intermediary class between
3
+ # `Logger` and the log device that you set it up with.
4
+ class LogEntry #:nodoc:
5
+ DT_PRECISION = 6.freeze
6
+
7
+ attr_reader :level, :time, :progname, :message, :context, :event
8
+
9
+ # Creates a log entry suitable to be sent to the Timber API.
10
+ # @param severity [Integer] the log level / severity
11
+ # @param time [Time] the exact time the log message was written
12
+ # @param progname [String] the progname scope for the log message
13
+ # @param message [#to_json] structured data representing the log line event, this can
14
+ # be anything that responds to #to_json
15
+ # @return [LogEntry] the resulting LogEntry object
16
+ def initialize(level, time, progname, message, context, event)
17
+ @level = level
18
+ @time = time.utc
19
+ @progname = progname
20
+ @message = message
21
+ @context = context
22
+ @event = event
23
+ end
24
+
25
+ def as_json(options = {})
26
+ options ||= {}
27
+ hash = {level: level, dt: formatted_dt, message: message}
28
+
29
+ if !event.nil?
30
+ hash[:event] = event
31
+ end
32
+
33
+ if !context.nil? && context.length > 0
34
+ hash[:context] = context
35
+ end
36
+
37
+ if options[:only]
38
+ hash.select do |key, _value|
39
+ options[:only].include?(key)
40
+ end
41
+ elsif options[:except]
42
+ hash.select do |key, _value|
43
+ !options[:except].include?(key)
44
+ end
45
+ else
46
+ hash
47
+ end
48
+ end
49
+
50
+ def to_json(options = {})
51
+ as_json(options).to_json
52
+ end
53
+
54
+ private
55
+ def formatted_dt
56
+ @formatted_dt ||= time.iso8601(DT_PRECISION)
57
+ end
58
+ end
59
+ end