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.
- checksums.yaml +4 -4
- data/.gitignore +3 -2
- data/.rspec +2 -0
- data/.yardopts +6 -0
- data/Appraisals +4 -0
- data/Gemfile +9 -1
- data/LICENSE.md +15 -0
- data/README.md +170 -8
- data/circle.yml +11 -8
- data/lib/timber/config.rb +11 -18
- data/lib/timber/context.rb +9 -68
- data/lib/timber/contexts/custom.rb +27 -0
- data/lib/timber/contexts/http.rb +28 -0
- data/lib/timber/contexts/organization.rb +24 -22
- data/lib/timber/contexts/user.rb +25 -28
- data/lib/timber/contexts.rb +7 -20
- data/lib/timber/current_context.rb +26 -41
- data/lib/timber/event.rb +13 -0
- data/lib/timber/events/controller_call.rb +40 -0
- data/lib/timber/events/custom.rb +42 -0
- data/lib/timber/events/exception.rb +35 -0
- data/lib/timber/events/http_request.rb +50 -0
- data/lib/timber/events/http_response.rb +36 -0
- data/lib/timber/events/sql_query.rb +26 -0
- data/lib/timber/events/template_render.rb +26 -0
- data/lib/timber/events.rb +37 -0
- data/lib/timber/frameworks/rails.rb +3 -14
- data/lib/timber/frameworks.rb +8 -10
- data/lib/timber/log_devices/http.rb +72 -13
- data/lib/timber/log_devices.rb +8 -4
- data/lib/timber/log_entry.rb +59 -0
- data/lib/timber/logger.rb +136 -13
- data/lib/timber/probe.rb +6 -4
- data/lib/timber/probes/action_controller_log_subscriber/log_subscriber.rb +64 -0
- data/lib/timber/probes/action_controller_log_subscriber.rb +20 -0
- data/lib/timber/probes/action_dispatch_debug_exceptions.rb +59 -35
- data/lib/timber/probes/action_view_log_subscriber/log_subscriber.rb +62 -0
- data/lib/timber/probes/action_view_log_subscriber.rb +20 -0
- data/lib/timber/probes/active_record_log_subscriber/log_subscriber.rb +72 -0
- data/lib/timber/probes/active_record_log_subscriber.rb +20 -0
- data/lib/timber/probes/rack_http_context.rb +51 -0
- data/lib/timber/probes/rails_rack_logger.rb +76 -0
- data/lib/timber/probes.rb +13 -16
- data/lib/timber/util/active_support_log_subscriber.rb +33 -0
- data/lib/timber/util/hash.rb +14 -0
- data/lib/timber/util.rb +8 -0
- data/lib/timber/version.rb +2 -2
- data/lib/timber.rb +6 -11
- data/spec/spec_helper.rb +7 -4
- data/spec/support/rails.rb +9 -5
- data/spec/support/timber.rb +1 -20
- data/spec/timber/events_spec.rb +55 -0
- data/spec/timber/log_devices/http_spec.rb +62 -0
- data/spec/timber/logger_spec.rb +68 -0
- data/spec/timber/probes/action_controller_log_subscriber_spec.rb +70 -0
- data/spec/timber/probes/action_dispatch_debug_exceptions_spec.rb +24 -18
- data/spec/timber/probes/action_view_log_subscriber_spec.rb +61 -0
- data/spec/timber/probes/active_record_log_subscriber_spec.rb +49 -0
- data/spec/timber/probes/rack_http_context_spec.rb +54 -0
- data/spec/timber/probes/rails_rack_logger_spec.rb +46 -0
- data/timberio.gemspec +2 -0
- metadata +62 -123
- data/.codeclimate.yml +0 -34
- data/LICENSE +0 -38
- data/Rakefile +0 -4
- data/TODO +0 -4
- data/benchmark/README.md +0 -26
- data/benchmark/rails_request.rb +0 -68
- data/benchmark/support/rails.rb +0 -69
- data/docs/installation/rails_on_heroku.md +0 -31
- data/docs/installation/rails_over_http.md +0 -22
- data/gemfiles/rails_3.0.X.gemfile +0 -25
- data/gemfiles/rails_3.1.X.gemfile +0 -25
- data/gemfiles/rails_3.2.X.gemfile +0 -25
- data/gemfiles/rails_4.0.X.gemfile +0 -26
- data/gemfiles/rails_4.1.X.gemfile +0 -26
- data/gemfiles/rails_4.2.X.gemfile +0 -26
- data/gemfiles/rails_5.0.X.gemfile +0 -26
- data/gemfiles/rails_edge.gemfile +0 -27
- data/lib/timber/api_settings.rb +0 -17
- data/lib/timber/bootstrap.rb +0 -45
- data/lib/timber/context_snapshot.rb +0 -64
- data/lib/timber/contexts/dynamic_values.rb +0 -59
- data/lib/timber/contexts/exception.rb +0 -40
- data/lib/timber/contexts/http_request.rb +0 -22
- data/lib/timber/contexts/http_requests/action_controller_specific.rb +0 -48
- data/lib/timber/contexts/http_requests/rack/params.rb +0 -26
- data/lib/timber/contexts/http_requests/rack.rb +0 -105
- data/lib/timber/contexts/http_response.rb +0 -19
- data/lib/timber/contexts/http_responses/action_controller.rb +0 -76
- data/lib/timber/contexts/logger.rb +0 -33
- data/lib/timber/contexts/organizations/action_controller.rb +0 -34
- data/lib/timber/contexts/server.rb +0 -21
- data/lib/timber/contexts/servers/heroku_specific.rb +0 -48
- data/lib/timber/contexts/sql_queries/active_record.rb +0 -30
- data/lib/timber/contexts/sql_queries/active_record_specific/binds.rb +0 -37
- data/lib/timber/contexts/sql_queries/active_record_specific.rb +0 -59
- data/lib/timber/contexts/sql_query.rb +0 -18
- data/lib/timber/contexts/template_render.rb +0 -17
- data/lib/timber/contexts/template_renders/action_view.rb +0 -29
- data/lib/timber/contexts/template_renders/action_view_specific.rb +0 -51
- data/lib/timber/contexts/users/action_controller.rb +0 -34
- data/lib/timber/current_line_indexes.rb +0 -35
- data/lib/timber/internal_logger.rb +0 -35
- data/lib/timber/log_device.rb +0 -40
- data/lib/timber/log_devices/heroku_logplex/hybrid_formatter.rb +0 -14
- data/lib/timber/log_devices/heroku_logplex.rb +0 -14
- data/lib/timber/log_devices/http/log_pile.rb +0 -86
- data/lib/timber/log_devices/http/log_truck/delivery.rb +0 -116
- data/lib/timber/log_devices/http/log_truck.rb +0 -87
- data/lib/timber/log_devices/io/formatter.rb +0 -46
- data/lib/timber/log_devices/io/hybrid_formatter.rb +0 -41
- data/lib/timber/log_devices/io/hybrid_hidden_formatter.rb +0 -36
- data/lib/timber/log_devices/io/json_formatter.rb +0 -11
- data/lib/timber/log_devices/io/logfmt_formatter.rb +0 -11
- data/lib/timber/log_devices/io.rb +0 -41
- data/lib/timber/log_line.rb +0 -33
- data/lib/timber/macros/compactor.rb +0 -16
- data/lib/timber/macros/date_formatter.rb +0 -9
- data/lib/timber/macros/deep_merger.rb +0 -11
- data/lib/timber/macros/logfmt_encoder.rb +0 -77
- data/lib/timber/macros.rb +0 -4
- data/lib/timber/patterns/delegated_singleton.rb +0 -21
- data/lib/timber/patterns/to_json.rb +0 -22
- data/lib/timber/patterns/to_logfmt.rb +0 -9
- data/lib/timber/patterns.rb +0 -3
- data/lib/timber/probes/action_controller_base.rb +0 -31
- data/lib/timber/probes/active_support_log_subscriber/action_controller.rb +0 -15
- data/lib/timber/probes/active_support_log_subscriber/action_view.rb +0 -26
- data/lib/timber/probes/active_support_log_subscriber/active_record.rb +0 -13
- data/lib/timber/probes/active_support_log_subscriber.rb +0 -62
- data/lib/timber/probes/heroku.rb +0 -30
- data/lib/timber/probes/logger.rb +0 -31
- data/lib/timber/probes/rack.rb +0 -36
- data/lib/timber/probes/server.rb +0 -18
- data/spec/timber/bootstrap_spec.rb +0 -31
- data/spec/timber/context_snapshot_spec.rb +0 -10
- data/spec/timber/context_spec.rb +0 -4
- data/spec/timber/contexts/exception_spec.rb +0 -34
- data/spec/timber/contexts/organizations/action_controller_spec.rb +0 -49
- data/spec/timber/contexts/users/action_controller_spec.rb +0 -65
- data/spec/timber/current_line_indexes_spec.rb +0 -40
- data/spec/timber/frameworks/rails_spec.rb +0 -9
- data/spec/timber/log_devices/heroku_logplex_spec.rb +0 -45
- data/spec/timber/log_devices/http/log_truck/delivery_spec.rb +0 -66
- data/spec/timber/log_devices/http/log_truck_spec.rb +0 -65
- data/spec/timber/log_devices/io/hybrid_hidden_formatter_spec.rb +0 -28
- data/spec/timber/log_line_spec.rb +0 -49
- data/spec/timber/macros/compactor_spec.rb +0 -19
- data/spec/timber/macros/logfmt_encoder_spec.rb +0 -89
- data/spec/timber/patterns/to_json_spec.rb +0 -40
- data/spec/timber/probes/action_controller_base_spec.rb +0 -43
- data/spec/timber/probes/action_controller_log_subscriber/action_controller_spec.rb +0 -35
- data/spec/timber/probes/action_controller_log_subscriber/action_view_spec.rb +0 -44
- data/spec/timber/probes/action_controller_log_subscriber/active_record_spec.rb +0 -26
- data/spec/timber/probes/logger_spec.rb +0 -20
- data/spec/timber/probes/rack_spec.rb +0 -26
data/lib/timber/log_device.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
module Timber
|
2
|
-
class LogDevice
|
3
|
-
NEWLINE = "\n".freeze
|
4
|
-
SPLIT_LINES = true
|
5
|
-
|
6
|
-
def write(message)
|
7
|
-
return false if ignoring?
|
8
|
-
ignore do
|
9
|
-
messages(message).each do |message_part|
|
10
|
-
log_line = LogLine.new(message_part)
|
11
|
-
write_log_line(log_line)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
true
|
15
|
-
rescue Exception => e
|
16
|
-
Config.logger.exception(e)
|
17
|
-
raise e
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
def ignore(&block)
|
22
|
-
@ignoring = true
|
23
|
-
yield
|
24
|
-
ensure
|
25
|
-
@ignoring = false
|
26
|
-
end
|
27
|
-
|
28
|
-
def ignoring?
|
29
|
-
@ignoring == true
|
30
|
-
end
|
31
|
-
|
32
|
-
def messages(message)
|
33
|
-
message.chomp.split(NEWLINE)
|
34
|
-
end
|
35
|
-
|
36
|
-
def write_formatted(formatted_message)
|
37
|
-
raise NotImplementedError.new
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
module Timber
|
2
|
-
module LogDevices
|
3
|
-
class HerokuLogplex < IO
|
4
|
-
module HybridFormatter
|
5
|
-
private
|
6
|
-
def encoded_context(log_line)
|
7
|
-
log_line.context_snapshot.to_logfmt(
|
8
|
-
:except => [Contexts::Servers::HerokuSpecific]
|
9
|
-
)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), "heroku_logplex", "hybrid_formatter")
|
2
|
-
|
3
|
-
module Timber
|
4
|
-
module LogDevices
|
5
|
-
class HerokuLogplex < IO
|
6
|
-
def initialize(_options = {})
|
7
|
-
super(STDOUT)
|
8
|
-
if formatter.is_a?(IO::HybridFormatter)
|
9
|
-
formatter.extend HybridFormatter
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,86 +0,0 @@
|
|
1
|
-
require "thread"
|
2
|
-
|
3
|
-
module Timber
|
4
|
-
module LogDevices
|
5
|
-
class HTTP < LogDevice
|
6
|
-
# This is a thread safe queue for transporting logs to the Timber API.
|
7
|
-
# TODO: Have these log lines persist to a file where
|
8
|
-
# a daemon can pick them up.
|
9
|
-
class LogPile
|
10
|
-
class << self
|
11
|
-
def each(&block)
|
12
|
-
instances.values.each(&block)
|
13
|
-
end
|
14
|
-
|
15
|
-
def get(application_key)
|
16
|
-
instances[application_key] ||= new(application_key)
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
def instances
|
21
|
-
@instances ||= {}
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
attr_reader :application_key
|
26
|
-
|
27
|
-
def initialize(application_key)
|
28
|
-
@application_key = application_key
|
29
|
-
@mutex = Mutex.new
|
30
|
-
end
|
31
|
-
|
32
|
-
def drop(log_line)
|
33
|
-
mutex.synchronize do
|
34
|
-
log_lines << log_line
|
35
|
-
end
|
36
|
-
rescue LogLine::InvalidMessageError => e
|
37
|
-
# Ignore the error and log it.
|
38
|
-
Config.logger.error(e)
|
39
|
-
rescue Exception => e
|
40
|
-
# Fail safe to ensure the Timber gem never fails the app.
|
41
|
-
Config.logger.exception(e)
|
42
|
-
end
|
43
|
-
|
44
|
-
def empty(&_block)
|
45
|
-
if log_lines.any?
|
46
|
-
copy = log_lines_copy
|
47
|
-
yield(copy) if block_given?
|
48
|
-
remove(copy)
|
49
|
-
self
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def size
|
54
|
-
log_lines.size
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
def mutex
|
59
|
-
@mutex
|
60
|
-
end
|
61
|
-
|
62
|
-
def remove(log_lines_copy)
|
63
|
-
mutex.synchronize do
|
64
|
-
# Delete items by object_id since we are working
|
65
|
-
# with the same object. Do not use equality here.
|
66
|
-
log_lines_copy.each do |l1|
|
67
|
-
log_lines.delete_if { |l2| l2.object_id == l1.object_id }
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def log_lines_copy
|
73
|
-
mutex.synchronize do
|
74
|
-
# Copy the array structure so we aren't dealing with
|
75
|
-
# a changing array, but do not copy the items.
|
76
|
-
log_lines.clone
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def log_lines
|
81
|
-
@log_lines ||= []
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
@@ -1,116 +0,0 @@
|
|
1
|
-
require "base64"
|
2
|
-
require "net/http"
|
3
|
-
require "net/https"
|
4
|
-
require "uri"
|
5
|
-
|
6
|
-
module Timber
|
7
|
-
module LogDevices
|
8
|
-
class HTTP < LogDevice
|
9
|
-
class LogTruck
|
10
|
-
class Delivery
|
11
|
-
class DeliveryError < StandardError; end
|
12
|
-
|
13
|
-
API_URI = URI.parse("https://timber-odin.herokuapp.com/agent_log_frames")
|
14
|
-
CONTENT_TYPE = 'application/json'.freeze
|
15
|
-
READ_TIMEOUT_SECONDS = 35.freeze
|
16
|
-
RETRY_BACKOFF_SECONDS = 1.freeze
|
17
|
-
RETRY_COUNT = 4.freeze
|
18
|
-
USER_AGENT = "Timber Ruby Gem/#{Timber::VERSION}".freeze
|
19
|
-
|
20
|
-
HTTPS = Net::HTTP.new(API_URI.host, API_URI.port).tap do |https|
|
21
|
-
https.use_ssl = true
|
22
|
-
https.read_timeout = READ_TIMEOUT_SECONDS
|
23
|
-
end
|
24
|
-
|
25
|
-
attr_reader :application_key, :log_lines
|
26
|
-
|
27
|
-
def initialize(application_key, log_lines)
|
28
|
-
@application_key = application_key
|
29
|
-
@log_lines = log_lines
|
30
|
-
end
|
31
|
-
|
32
|
-
def deliver!(retry_count = 0)
|
33
|
-
Config.logger.debug("Attempting delivery of: #{body_json}")
|
34
|
-
request!
|
35
|
-
# Catch them all because of all the unknown exceptions that can happen during
|
36
|
-
# a http request.
|
37
|
-
rescue Exception => e
|
38
|
-
# Ensure that we are always returning a consistent error.
|
39
|
-
# This ensures we handle it appropriately and don't kill the
|
40
|
-
# thread above.
|
41
|
-
Config.logger.warn("Failed delivery: #{e.message}")
|
42
|
-
|
43
|
-
retry_count += 1
|
44
|
-
if retry_count <= RETRY_COUNT
|
45
|
-
backoff_seconds = RETRY_BACKOFF_SECONDS ** retry_count
|
46
|
-
Config.logger.warn("Backing off #{backoff_seconds} seconds")
|
47
|
-
sleep backoff_seconds
|
48
|
-
Config.logger.warn("Retrying, attempt #{retry_count}")
|
49
|
-
deliver!(retry_count)
|
50
|
-
else
|
51
|
-
Config.logger.warn("Retry attempts exceeded, dropping logs")
|
52
|
-
raise DeliveryError.new(e.message)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
private
|
57
|
-
def https
|
58
|
-
@https ||= HTTPS
|
59
|
-
end
|
60
|
-
|
61
|
-
def request!
|
62
|
-
https.request(new_request).tap do |res|
|
63
|
-
code = res.code.to_i
|
64
|
-
if code < 200 || code >= 300
|
65
|
-
raise DeliveryError.new("Bad response from Timber API - #{res.code}: #{res.body}")
|
66
|
-
end
|
67
|
-
Config.logger.debug("Success! #{code}: #{res.body}")
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def new_request
|
72
|
-
Net::HTTP::Post.new(API_URI.request_uri).tap do |req|
|
73
|
-
req['Authorization'] = authorization_payload
|
74
|
-
req['Body-Checksum'] = body_checksum # the API checks for duplicate requests
|
75
|
-
req['Content-Type'] = CONTENT_TYPE
|
76
|
-
req['Log-Line-Count'] = log_lines.size # additional check to ensure the correct # of log lines were sent
|
77
|
-
req['User-Agent'] = USER_AGENT
|
78
|
-
req.body = body_json
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# Used by the API to check for duplicate requests.
|
83
|
-
def body_checksum
|
84
|
-
@body_checksum ||= Digest::MD5.hexdigest(body_json)
|
85
|
-
end
|
86
|
-
|
87
|
-
def body_json
|
88
|
-
return @body_json if defined?(@body_json)
|
89
|
-
# Build the json as a string since it is more efficient.
|
90
|
-
# We are also working with string upstream for the same reason.
|
91
|
-
@body_json ||= <<-JSON
|
92
|
-
{"agent_log_frame": {"log_lines": #{log_lines_json}}}
|
93
|
-
JSON
|
94
|
-
@body_json.strip!
|
95
|
-
@body_json
|
96
|
-
end
|
97
|
-
|
98
|
-
def log_lines_json
|
99
|
-
return @log_lines_json if defined?(@log_lines_json)
|
100
|
-
@log_lines_json = "["
|
101
|
-
last_index = log_lines.size - 1
|
102
|
-
log_lines.each_with_index do |log_line, index|
|
103
|
-
@log_lines_json += log_line.to_json
|
104
|
-
@log_lines_json += ", " if index != last_index
|
105
|
-
end
|
106
|
-
@log_lines_json += "]"
|
107
|
-
end
|
108
|
-
|
109
|
-
def authorization_payload
|
110
|
-
@authorization_payload ||= "Basic #{Base64.strict_encode64(application_key).chomp}"
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
@@ -1,87 +0,0 @@
|
|
1
|
-
require "uri"
|
2
|
-
require "net/http"
|
3
|
-
require "net/https"
|
4
|
-
require File.join(File.dirname(__FILE__), "log_truck", "delivery")
|
5
|
-
|
6
|
-
module Timber
|
7
|
-
module LogDevices
|
8
|
-
class HTTP < LogDevice
|
9
|
-
# Temporary class for alpha / beta purposes.
|
10
|
-
# Log lines will be written to a file where a daemon
|
11
|
-
# will pick them up. Most of this code will be moved
|
12
|
-
# to that daemon.
|
13
|
-
class LogTruck
|
14
|
-
THROTTLE_SECONDS = 3.freeze
|
15
|
-
|
16
|
-
class NoPayloadError < ArgumentError; end
|
17
|
-
|
18
|
-
class << self
|
19
|
-
def start!(options = {}, &_block)
|
20
|
-
return if @thread && @thread.alive?
|
21
|
-
|
22
|
-
# Old school options to support ruby 1.9 :(
|
23
|
-
options[:throttle_seconds] = THROTTLE_SECONDS if !options.key?(:throttle_seconds)
|
24
|
-
Config.logger.debug("Starting log truck with a #{options[:throttle_seconds]} second throttle")
|
25
|
-
|
26
|
-
# A new thread for looping and monitoring. We need to
|
27
|
-
# use a thread so that we can share memory.
|
28
|
-
@thread = Thread.new do
|
29
|
-
# ensure we always deliver upon exiting
|
30
|
-
at_exit { deliver }
|
31
|
-
|
32
|
-
# Keep looking for logs
|
33
|
-
loop do
|
34
|
-
deliver
|
35
|
-
|
36
|
-
# Yield a block, primarily for testing purposes
|
37
|
-
yield(Thread.current) if block_given?
|
38
|
-
|
39
|
-
# Throttle to reduce checking the pile
|
40
|
-
sleep options[:throttle_seconds]
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
rescue Exception => e
|
45
|
-
# failsafe to ensure we don't kill the app
|
46
|
-
Config.logger.exception(e)
|
47
|
-
end
|
48
|
-
|
49
|
-
# Deliver, return LogTruck object, otherwise
|
50
|
-
# raise an error.
|
51
|
-
def deliver
|
52
|
-
log_truck = nil
|
53
|
-
LogPile.each do |log_pile|
|
54
|
-
log_pile.empty do |log_lines|
|
55
|
-
# LogPile only empties if no exception is raised
|
56
|
-
begin
|
57
|
-
# This will retry a number of times. If we can't get it during the retries
|
58
|
-
# we drop the logs. Note, this strategy will improve when we write to a file
|
59
|
-
# and use an actual agent.
|
60
|
-
log_truck = new(log_pile.application_key, log_lines).tap(&:deliver!)
|
61
|
-
rescue Delivery::DeliveryError => e
|
62
|
-
Config.logger.exception(e)
|
63
|
-
# TODO: How do we handle server timeouts? The request could have still been processed.
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
log_truck
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
attr_reader :application_key, :log_lines
|
72
|
-
|
73
|
-
def initialize(application_key, log_lines)
|
74
|
-
if log_lines.empty?
|
75
|
-
raise NoPayloadError.new("a truck must contain a payload (at least one log line)")
|
76
|
-
end
|
77
|
-
@application_key = application_key
|
78
|
-
@log_lines = log_lines
|
79
|
-
end
|
80
|
-
|
81
|
-
def deliver!
|
82
|
-
Delivery.new(application_key, log_lines).deliver!
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
module Timber
|
2
|
-
module LogDevices
|
3
|
-
class IO < LogDevice
|
4
|
-
class Formatter
|
5
|
-
# Do not change this, the API matches on it. Otherwise nothing
|
6
|
-
# get parsed.
|
7
|
-
CALLOUT = "@timber.io "
|
8
|
-
CALLOUT_END = "@original "
|
9
|
-
|
10
|
-
# Embed in a String to clear all previous ANSI sequences.
|
11
|
-
CLEAR = "\e[0m"
|
12
|
-
BOLD = "\e[1m"
|
13
|
-
|
14
|
-
# Colors
|
15
|
-
BLACK = "\e[30m"
|
16
|
-
DARK_GRAY = "\e[1;30m"
|
17
|
-
RED = "\e[31m"
|
18
|
-
GREEN = "\e[32m"
|
19
|
-
YELLOW = "\e[33m"
|
20
|
-
BLUE = "\e[34m"
|
21
|
-
MAGENTA = "\e[35m"
|
22
|
-
CYAN = "\e[36m"
|
23
|
-
WHITE = "\e[37m"
|
24
|
-
|
25
|
-
def initialize(options = {})
|
26
|
-
@ansi_format = options.key?(:ansi_format) ? options[:ansi_format] == true : true
|
27
|
-
end
|
28
|
-
|
29
|
-
def ansi_format?
|
30
|
-
@ansi_format == true
|
31
|
-
end
|
32
|
-
|
33
|
-
def format(_log_line)
|
34
|
-
raise NotImplementedError.new("#format is not implemented")
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
def ansi_format(*args)
|
39
|
-
text = args.pop
|
40
|
-
return text unless ansi_format?
|
41
|
-
"#{args.join}#{text}#{CLEAR}"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
module Timber
|
2
|
-
module LogDevices
|
3
|
-
class IO < LogDevice
|
4
|
-
class HybridFormatter < Formatter
|
5
|
-
def initialize(options = {})
|
6
|
-
super
|
7
|
-
@date_prefix = options.key?(:date_prefix) ? options[:date_prefix] : false
|
8
|
-
end
|
9
|
-
|
10
|
-
def date_prefix?
|
11
|
-
@date_prefix == true
|
12
|
-
end
|
13
|
-
|
14
|
-
def format(log_line)
|
15
|
-
"#{log_line.message}#{context_message(log_line)}"
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
def base_message(log_line)
|
20
|
-
text = ""
|
21
|
-
if date_prefix?
|
22
|
-
text << "#{log_line.formatted_dt} "
|
23
|
-
end
|
24
|
-
text << log_line.message
|
25
|
-
text
|
26
|
-
end
|
27
|
-
|
28
|
-
def context_message(log_line)
|
29
|
-
# The callout must be before the formatting, otherwise we leave
|
30
|
-
# the message ending with a color formatting and not a reset.
|
31
|
-
# Anything before the callout modifies the original message.
|
32
|
-
CALLOUT + ansi_format(DARK_GRAY, encoded_context(log_line))
|
33
|
-
end
|
34
|
-
|
35
|
-
def encoded_context(log_line)
|
36
|
-
log_line.context_snapshot.to_logfmt
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,36 +0,0 @@
|
|
1
|
-
module Timber
|
2
|
-
module LogDevices
|
3
|
-
class IO < LogDevice
|
4
|
-
class HybridHiddenFormatter < HybridFormatter
|
5
|
-
CLEAR_SEQUENCE = "\e8\e[K".freeze
|
6
|
-
CLEAR_STEP_SIZE = 20.freeze
|
7
|
-
SAVE_CURSOR_POSITION = "\e7".freeze
|
8
|
-
|
9
|
-
def format(log_line)
|
10
|
-
"#{SAVE_CURSOR_POSITION}#{context_message(log_line)}#{base_message(log_line)}"
|
11
|
-
end
|
12
|
-
|
13
|
-
private
|
14
|
-
def context_message(log_line)
|
15
|
-
text = encoded_context(log_line)
|
16
|
-
position = CLEAR_STEP_SIZE
|
17
|
-
sequence_size = CLEAR_SEQUENCE.size
|
18
|
-
step_size = sequence_size + CLEAR_STEP_SIZE
|
19
|
-
while position < text.length
|
20
|
-
# ensure we don't insert before a \
|
21
|
-
while text[position - 1] == "\\"
|
22
|
-
position += 1
|
23
|
-
end
|
24
|
-
text.insert(position, CLEAR_SEQUENCE)
|
25
|
-
position += step_size
|
26
|
-
end
|
27
|
-
ansi_format(DARK_GRAY, "#{CALLOUT}#{CLEAR_SEQUENCE}#{text} #{CALLOUT_END}#{CLEAR_SEQUENCE}")
|
28
|
-
end
|
29
|
-
|
30
|
-
def encoded_context(log_line)
|
31
|
-
log_line.context_snapshot.to_logfmt
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), "io", "formatter")
|
2
|
-
require File.join(File.dirname(__FILE__), "io", "hybrid_formatter")
|
3
|
-
require File.join(File.dirname(__FILE__), "io", "hybrid_hidden_formatter")
|
4
|
-
require File.join(File.dirname(__FILE__), "io", "json_formatter")
|
5
|
-
require File.join(File.dirname(__FILE__), "io", "logfmt_formatter")
|
6
|
-
|
7
|
-
module Timber
|
8
|
-
module LogDevices
|
9
|
-
# The purpose of a Timber log device is to take the raw log message and enrich it
|
10
|
-
# with the current context.
|
11
|
-
#
|
12
|
-
# The IO log device works with any IO object. That is, any object that
|
13
|
-
# response to #write(message).
|
14
|
-
class IO < LogDevice
|
15
|
-
attr_reader :formatter
|
16
|
-
|
17
|
-
# Instantiates a new Timber IO log device.
|
18
|
-
#
|
19
|
-
# @param io [IO] any object the responds to #write(message)
|
20
|
-
def initialize(io = STDOUT, options = {})
|
21
|
-
io.sync = true if io.respond_to?(:sync=) # ensures logs are written immediately instead of being buffered by ruby
|
22
|
-
@formatter = options[:formatter] || HybridHiddenFormatter.new
|
23
|
-
@io = io
|
24
|
-
end
|
25
|
-
|
26
|
-
def close(*_args)
|
27
|
-
io.close
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
def write_log_line(log_line)
|
32
|
-
formatted_message = formatter.format(log_line)
|
33
|
-
io.write(formatted_message + "\n")
|
34
|
-
end
|
35
|
-
|
36
|
-
def io
|
37
|
-
@io
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
data/lib/timber/log_line.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
module Timber
|
2
|
-
class LogLine
|
3
|
-
include Patterns::ToJSON
|
4
|
-
include Patterns::ToLogfmt
|
5
|
-
|
6
|
-
# Raised when there is an issue with the message being passed.
|
7
|
-
# Note: this is handled in Logger
|
8
|
-
class InvalidMessageError < ArgumentError; end
|
9
|
-
|
10
|
-
attr_reader :context_snapshot, :dt, :line_indexes, :message
|
11
|
-
|
12
|
-
def initialize(message)
|
13
|
-
@dt = Time.now.utc # Capture the time as soon as possible
|
14
|
-
message = message.to_s
|
15
|
-
if message.bytesize > APISettings::MESSAGE_BYTE_SIZE_MAX
|
16
|
-
Config.logger.warn("Log line message is too long, truncating")
|
17
|
-
message = message.byteslice(0, APISettings::MESSAGE_BYTE_SIZE_MAX)
|
18
|
-
end
|
19
|
-
@message = message
|
20
|
-
CurrentLineIndexes.log_line_added(self) # Bump the indexes
|
21
|
-
@context_snapshot = CurrentContext.snapshot
|
22
|
-
end
|
23
|
-
|
24
|
-
def formatted_dt
|
25
|
-
@formatted_dt ||= Macros::DateFormatter.format(dt)
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
def json_payload
|
30
|
-
@json_payload ||= {:dt => formatted_dt, :message => message}.merge(context_snapshot.as_json)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
module Timber
|
2
|
-
module Macros
|
3
|
-
module Compactor
|
4
|
-
def self.compact(hash)
|
5
|
-
new_hash = {}
|
6
|
-
hash.each do |k, v|
|
7
|
-
deep_v = v.is_a?(Hash) ? compact(v) : v
|
8
|
-
if !deep_v.nil? && deep_v != [] && deep_v != {}
|
9
|
-
new_hash[k] = deep_v
|
10
|
-
end
|
11
|
-
end
|
12
|
-
new_hash
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|