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,17 @@
1
+ module Timber
2
+ # These settings are established by the Timber API. Changing these will result
3
+ # in unsuccessful log delivery.
4
+ module APISettings
5
+ DATE_FORMAT = :iso8601.freeze # must be a method name
6
+ DATE_FORMAT_PRECISION = 6 # millisecond digits
7
+ MESSAGE_BYTE_SIZE_MAX = 1_000_000.freeze # 1mb
8
+
9
+ # List of types that the Timber API accepts
10
+ BOOLEAN_TYPE = "integer".freeze
11
+ DATE_TYPE = "date".freeze
12
+ FLOAT_TYPE = "float".freeze
13
+ INTEGER_TYPE = "integer".freeze
14
+ NIL_TYPE = "nil".freeze
15
+ STRING_TYPE = "string".freeze
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ module Timber
2
+ # Intermediary class between frameworks and Timber. Defines
3
+ # requirements and normalized the setup process.
4
+ class Bootstrap
5
+ def self.bootstrap!(*args)
6
+ new(*args).bootstrap!
7
+ rescue Exception => e
8
+ # Failsafe to ensure Timber never takes down the app
9
+ Config.logger.exception(e)
10
+ end
11
+
12
+ attr_reader :middleware, :insert_before
13
+
14
+ def initialize(middleware, insert_before)
15
+ @middleware = middleware
16
+ @insert_before = insert_before
17
+ end
18
+
19
+ def bootstrap!
20
+ return false unless enabled?
21
+ Probes.insert!(middleware, insert_before)
22
+ log_started
23
+ true
24
+ end
25
+
26
+ private
27
+ def enabled?
28
+ if !Config.enabled?
29
+ Config.logger.warn("Skipping bootstrap, Timber::Config.enabled is not true")
30
+ false
31
+ else
32
+ true
33
+ end
34
+ end
35
+
36
+ def log_started
37
+ Config.logger.info(" _,-,")
38
+ Config.logger.info("T_ | Timber enabled")
39
+ Config.logger.info("||`-'")
40
+ Config.logger.info("||")
41
+ Config.logger.info("||")
42
+ Config.logger.info("~~")
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ module Timber
2
+ class Config
3
+ include Patterns::DelegatedSingleton
4
+
5
+ attr_writer :application_key, :enabled, :logger
6
+
7
+ def application_key
8
+ @application_key ||= ENV['TIMBER_KEY']
9
+ end
10
+
11
+ def enabled
12
+ return @enabled if defined?(@enabled)
13
+ @enabled = true
14
+ end
15
+
16
+ def enabled?
17
+ enabled == true
18
+ end
19
+
20
+ # Internal logger for the Timber library, only for debugging purposes.
21
+ def logger
22
+ @logger ||= InternalLogger.new(nil)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,76 @@
1
+ require "securerandom"
2
+
3
+ module Timber
4
+ class Context
5
+ include Patterns::ToJSON
6
+ include Patterns::ToLogfmt
7
+
8
+ PATH_DELIMITER = ".".freeze
9
+ SECURE_RANDOM_LENGTH = 16.freeze
10
+
11
+ class << self
12
+ def json_shell(&_block)
13
+ {_root_key => yield}
14
+ end
15
+
16
+ def _path
17
+ @path ||= Macros::LogfmtEncoder.encode(json_shell { 1 }).split("=").first
18
+ end
19
+
20
+ def _root_key
21
+ @_root_key ||= const_get(:ROOT_KEY)
22
+ end
23
+
24
+ def _version
25
+ @_version ||= const_get(:VERSION)
26
+ end
27
+ end
28
+
29
+ def _dt
30
+ @_dt ||= Time.now.utc
31
+ end
32
+
33
+ def _path
34
+ self.class._path
35
+ end
36
+
37
+ def _version
38
+ self.class._version
39
+ end
40
+
41
+ def _root_key
42
+ self.class._root_key
43
+ end
44
+
45
+ def as_json(*args)
46
+ @as_json ||= json_shell { super }
47
+ end
48
+
49
+ def json_shell(&block)
50
+ self.class.json_shell(&block)
51
+ end
52
+
53
+ def inspect(*_args)
54
+ "#<#{self.class.name}:#{object_id} ...>"
55
+ end
56
+
57
+ # Some contexts hold mutable object that change as the context block
58
+ # executes. This method checks the state of that object to ensure
59
+ # that the context is valid and ready to be copied for each log line.
60
+ def valid?
61
+ true
62
+ end
63
+
64
+ private
65
+ def json_payload
66
+ @json_payload ||= {
67
+ :_dt => Macros::DateFormatter.format(_dt),
68
+ :_version => _version
69
+ }
70
+ end
71
+
72
+ def generate_secure_random
73
+ SecureRandom.urlsafe_base64(SECURE_RANDOM_LENGTH)
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,64 @@
1
+ module Timber
2
+ class ContextSnapshot
3
+ include Timber::Patterns::ToJSON
4
+
5
+ CONTEXT_VERSION = 1.freeze
6
+
7
+ attr_reader :indexes, :stack
8
+
9
+ def initialize
10
+ # Cloning arrays and hashes is extremely fast. This
11
+ # should not be a concern for hindering performance as we are
12
+ # only cloning the structures, not the content.
13
+ @stack = CurrentContext.valid_stack.clone.freeze
14
+ @indexes = CurrentLineIndexes.indexes.clone.freeze
15
+ end
16
+
17
+ def to_logfmt(options = {})
18
+ @to_logfmt ||= {}
19
+ @to_logfmt[options] ||= begin
20
+ items = stack.collect do |context|
21
+ next if options[:except].is_a?(Array) && options[:except].include?(context.class)
22
+ # Delegate to #to_logfmt on the context object for caching.
23
+ # Add the index on the fly, as a string, since it's more performant.
24
+ Macros::LogfmtEncoder.join(context.to_logfmt, "#{context._path}._index=#{index(context)}")
25
+ end.compact
26
+ items << Macros::LogfmtEncoder.encode(:_version => CONTEXT_VERSION, :_hierarchy => hierarchy)
27
+ Macros::LogfmtEncoder.join(*items)
28
+ end
29
+ end
30
+
31
+ def size
32
+ stack.size
33
+ end
34
+
35
+ private
36
+ def context_hash
37
+ @context_hash ||= begin
38
+ hash = stack.inject({}) do |acc, context|
39
+ acc = Macros::DeepMerger.merge(acc, context.as_json)
40
+ Macros::DeepMerger.merge(acc, index_hash(context))
41
+ end
42
+ hash[:_version] = CONTEXT_VERSION
43
+ hash[:_hierarchy] = hierarchy
44
+ hash
45
+ end
46
+ end
47
+
48
+ def hierarchy
49
+ @hierarchy ||= stack.collect(&:_path).uniq
50
+ end
51
+
52
+ def index(context)
53
+ indexes[context] || raise("couldn't find index for #{context}")
54
+ end
55
+
56
+ def index_hash(context)
57
+ context.json_shell { {:_index => index(context)}}
58
+ end
59
+
60
+ def json_payload
61
+ @json_payload ||= {:context => context_hash}
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,59 @@
1
+ require "bigdecimal"
2
+ require "date"
3
+
4
+ module Timber
5
+ module Contexts
6
+ class DynamicValues
7
+ class UnrecognizedObjectTypeError < StandardError; end
8
+
9
+ include Patterns::ToJSON
10
+
11
+ BOOLEAN_TYPES = [FalseClass, TrueClass].freeze
12
+ DATE_TYPES = [Date, Time].freeze
13
+ FLOAT_TYPES = [BigDecimal, Float].freeze
14
+ INTEGER_TYPES = [Fixnum].freeze
15
+ NIL_TYPES = [NilClass].freeze
16
+ STRING_TYPES = [String].freeze
17
+
18
+ def initialize(values_array = nil)
19
+ @values_array = values_array
20
+ end
21
+
22
+ private
23
+ def json_payload
24
+ @json_payload ||= values_array.collect do |value|
25
+ to_item(value)
26
+ end
27
+ end
28
+
29
+ def to_item(value)
30
+ {
31
+ :name => value[:name],
32
+ :type => type(value[:value]),
33
+ :value => value[:value]
34
+ }
35
+ end
36
+
37
+ def type(value)
38
+ # Using is_a? because it checks the entire hierarchy, unlike a case statement.
39
+ if BOOLEAN_TYPES.any? { |type| value.is_a?(type) }
40
+ APISettings::BOOLEAN_TYPE
41
+ elsif DATE_TYPES.any? { |type| value.is_a?(type) }
42
+ APISettings::DATE_TYPE
43
+ elsif FLOAT_TYPES.any? { |type| value.is_a?(type) }
44
+ APISettings::FLOAT_TYPE
45
+ elsif INTEGER_TYPES.any? { |type| value.is_a?(type) }
46
+ APISettings::INTEGER_TYPE
47
+ elsif NIL_TYPES.any? { |type| value.is_a?(type) }
48
+ APISettings::NIL_TYPE
49
+ else
50
+ APISettings::STRING_TYPE
51
+ end
52
+ end
53
+
54
+ def values_array
55
+ @values_array
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,40 @@
1
+ module Timber
2
+ module Contexts
3
+ class Exception < Context
4
+ ROOT_KEY = :exception.freeze
5
+ VERSION = 1.freeze
6
+
7
+ attr_reader :exception
8
+
9
+ def initialize(exception)
10
+ # Initialize should be as fast as possible since it is executed inline.
11
+ # Hence the lazy methods below.
12
+ @exception = exception
13
+ super()
14
+ end
15
+
16
+ def backtrace
17
+ # only the first 5 lines to save on space
18
+ @backtrace ||= exception.backtrace[0..4]
19
+ end
20
+
21
+ def name
22
+ @name ||= exception.class.name
23
+ end
24
+
25
+ def message
26
+ @message ||= exception.message
27
+ end
28
+
29
+ private
30
+ def json_payload
31
+ @json_payload ||= Macros::DeepMerger.merge({
32
+ # order is relevant for logfmt styling
33
+ :name => name,
34
+ :message => message,
35
+ :backtrace => backtrace
36
+ }, super).freeze
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,22 @@
1
+ module Timber
2
+ module Contexts
3
+ class HTTPRequest < Context
4
+ ROOT_KEY = :http_request.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
+ :method => method,
12
+ :scheme => scheme,
13
+ :host => host,
14
+ :port => port,
15
+ :path => path,
16
+ :query_params => query_params.as_json,
17
+ :headers => headers.as_json
18
+ }, super).freeze
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,48 @@
1
+ module Timber
2
+ module Contexts
3
+ module HTTPRequests
4
+ # Extend Context since we are a sub context and not an actual HTTPRequest
5
+ class ActionControllerSpecific < Context
6
+ ROOT_KEY = :action_controller.freeze
7
+ VERSION = 1.freeze
8
+
9
+ class << self
10
+ def json_shell(&_block)
11
+ Rack.json_shell { super }
12
+ end
13
+ end
14
+
15
+ attr_reader :controller_obj
16
+
17
+ def initialize(controller_obj)
18
+ # Initialize should be as fast as possible since it is executed inline.
19
+ # Hence the lazy methods below.
20
+ @controller_obj = controller_obj
21
+ super()
22
+ end
23
+
24
+ def action
25
+ @action ||= controller_obj.action_name
26
+ end
27
+
28
+ def controller
29
+ @controller ||= controller_obj.class.name
30
+ end
31
+
32
+ def format
33
+ @format ||= controller_obj.request.format.try(:ref)
34
+ end
35
+
36
+ private
37
+ def json_payload
38
+ @json_payload ||= Macros::DeepMerger.merge({
39
+ # order is relevant for logfmt styling
40
+ :controller => controller,
41
+ :action => action,
42
+ :format => format
43
+ }, super).freeze
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,26 @@
1
+ module Timber
2
+ module Contexts
3
+ module HTTPRequests
4
+ class Rack < HTTPRequest
5
+ class Params < DynamicValues
6
+ attr_reader :params
7
+
8
+ def initialize(params)
9
+ # Initialize should be as fast as possible since it is executed inline.
10
+ # Hence the lazy methods below.
11
+ @params = params
12
+ super()
13
+ end
14
+
15
+ private
16
+ # Override values_array so that this is done in the background thread
17
+ def values_array
18
+ @values_array ||= params.collect do |key, value|
19
+ {:name => key, :value => value}
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,105 @@
1
+ require "timber/contexts/http_requests/rack/params"
2
+
3
+ module Timber
4
+ module Contexts
5
+ module HTTPRequests
6
+ class Rack < HTTPRequest
7
+ class Headers
8
+ include Patterns::ToJSON
9
+
10
+ attr_reader :env, :request
11
+
12
+ def initialize(env, request)
13
+ @env = env
14
+ @request = request
15
+ end
16
+
17
+ def content_type
18
+ @content_type ||= request.content_type
19
+ end
20
+
21
+ def referrer
22
+ @referrer ||= request.referrer
23
+ end
24
+
25
+ def remote_addr
26
+ @remote_addr ||= request.ip
27
+ end
28
+
29
+ def request_id
30
+ return @request_id if defined?(@request_id)
31
+ found = env.find do |k,v|
32
+ # Needs to support:
33
+ # action_dispatch.request_id
34
+ # HTTP_X_REQUEST_ID
35
+ # Request-ID
36
+ # Request-Id
37
+ # X-Request-ID
38
+ # X-Request-Id
39
+ # etc
40
+ (k.downcase.include?("request_id") || k.downcase.include?("request-id")) && !v.nil?
41
+ end
42
+ @request_id = found && found.last
43
+ end
44
+
45
+ def user_agent
46
+ @user_agent ||= request.user_agent
47
+ end
48
+
49
+ private
50
+ def json_payload
51
+ @json_payload ||= {
52
+ :content_type => content_type,
53
+ :referrer => referrer,
54
+ :remote_addr => remote_addr,
55
+ :request_id => request_id,
56
+ :user_agent => user_agent
57
+ }
58
+ end
59
+ end
60
+
61
+ attr_reader :env
62
+
63
+ def initialize(env)
64
+ # Initialize should be as fast as possible since it is executed inline.
65
+ # Hence the lazy methods below.
66
+ @env = env
67
+ super()
68
+ end
69
+
70
+ def headers
71
+ @headers ||= Headers.new(env, request)
72
+ end
73
+
74
+ def host
75
+ @host ||= request.host
76
+ end
77
+
78
+ def method
79
+ @method ||= request.request_method.upcase
80
+ end
81
+
82
+ def path
83
+ @path ||= request.path
84
+ end
85
+
86
+ def port
87
+ @port ||= request.port
88
+ end
89
+
90
+ def query_params
91
+ @query_params ||= request.params && Params.new(request.params)
92
+ end
93
+
94
+ def scheme
95
+ @scheme ||= request.scheme
96
+ end
97
+
98
+ private
99
+ def request
100
+ @request ||= ::Rack::Request.new(env)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,19 @@
1
+ module Timber
2
+ module Contexts
3
+ # Generica HTTP response shared across all platforms.
4
+ class HTTPResponse < Context
5
+ ROOT_KEY = :http_response.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
+ :status => status,
13
+ :headers => headers.as_json,
14
+ :time_ms => time_ms
15
+ }, super).freeze
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,76 @@
1
+ module Timber
2
+ module Contexts
3
+ module HTTPResponses
4
+ class ActionController < HTTPResponse
5
+ class Headers
6
+ include Patterns::ToJSON
7
+
8
+ attr_reader :response
9
+
10
+ def initialize(response)
11
+ @response = response
12
+ end
13
+
14
+ def content_length
15
+ @content_length ||= response.content_length
16
+ end
17
+
18
+ def cache_control
19
+ @cache_control ||= response.headers['Cache-Control']
20
+ end
21
+
22
+ def content_disposition
23
+ @content_disposition ||= response.headers['Cache-Disposition']
24
+ end
25
+
26
+ def content_type
27
+ @content_type ||= response.headers['Content-Type']
28
+ end
29
+
30
+ def location
31
+ @location ||= response.headers['Location']
32
+ end
33
+
34
+ private
35
+ def json_payload
36
+ @json_payload ||= {
37
+ :content_length => content_length,
38
+ :cache_control => cache_control,
39
+ :content_disposition => content_disposition,
40
+ :content_type => content_type,
41
+ :location => location
42
+ }
43
+ end
44
+ end
45
+
46
+ attr_reader :controller
47
+ attr_accessor :event
48
+
49
+ def initialize(controller)
50
+ @controller = controller
51
+ end
52
+
53
+ def headers
54
+ @headers ||= Headers.new(response)
55
+ end
56
+
57
+ def status
58
+ @status ||= response.status
59
+ end
60
+
61
+ def time_ms
62
+ @time_ms ||= event.duration
63
+ end
64
+
65
+ def valid?
66
+ !response.nil? && !event.nil?
67
+ end
68
+
69
+ private
70
+ def response
71
+ controller.response
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,33 @@
1
+ module Timber
2
+ module Contexts
3
+ class Logger < Context
4
+ ROOT_KEY = :logger.freeze
5
+ VERSION = 1.freeze
6
+ LEVEL_MAP = {
7
+ 0 => "debug",
8
+ 1 => "info",
9
+ 2 => "warn",
10
+ 3 => "error",
11
+ 4 => "fatal"
12
+ }
13
+ UNKNOWN_LEVEL = "unknown"
14
+
15
+ attr_reader :level, :progname
16
+
17
+ def initialize(level, progname)
18
+ @level = LEVEL_MAP[level] || UNKNOWN_LEVEL
19
+ @progname = progname
20
+ super()
21
+ end
22
+
23
+ private
24
+ def json_payload
25
+ @json_payload ||= Macros::DeepMerger.merge({
26
+ # order is relevant for logfmt styling
27
+ :level => level,
28
+ :progname => progname
29
+ }, super).freeze
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ module Timber
2
+ module Contexts
3
+ class Organization < Context
4
+ ROOT_KEY = :organization.freeze
5
+ VERSION = 1.freeze
6
+
7
+ attr_reader :organization
8
+
9
+ def id
10
+ return @id if defined?(@id)
11
+ @id = organization.respond_to?(:id) ? organization.id : nil
12
+ end
13
+
14
+ def name
15
+ return @name if defined?(@name)
16
+ @name = organization.respond_to?(:name) ? organization.name : nil
17
+ end
18
+
19
+ def valid?
20
+ !organization.nil?
21
+ end
22
+
23
+ private
24
+ def json_payload
25
+ @json_payload ||= Macros::DeepMerger.merge({
26
+ # order is relevant for logfmt styling
27
+ :id => id,
28
+ :name => name
29
+ }, super).freeze
30
+ end
31
+ end
32
+ end
33
+ end