timberio 1.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|