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,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
|