timber 2.1.0.rc2 → 2.1.0.rc3
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 +4 -4
- data/README.md +22 -3
- data/lib/timber/cli/installer.rb +8 -0
- data/lib/timber/cli/installers.rb +34 -4
- data/lib/timber/cli/installers/other.rb +28 -21
- data/lib/timber/cli/installers/rails.rb +16 -9
- data/lib/timber/cli/os_helper.rb +21 -8
- data/lib/timber/config.rb +31 -7
- data/lib/timber/contexts/release.rb +3 -0
- data/lib/timber/current_context.rb +35 -26
- data/lib/timber/integrations/rack/exception_event.rb +6 -0
- data/lib/timber/integrations/rack/http_context.rb +3 -0
- data/lib/timber/integrations/rack/http_events.rb +5 -0
- data/lib/timber/integrations/rack/session_context.rb +34 -5
- data/lib/timber/integrations/rack/user_context.rb +7 -16
- data/lib/timber/log_devices/http.rb +14 -26
- data/lib/timber/logger.rb +7 -0
- data/lib/timber/version.rb +1 -1
- data/spec/support/timber.rb +5 -1
- data/spec/timber/cli/installers/rails_spec.rb +1 -1
- data/spec/timber/cli/installers/root_spec.rb +1 -1
- data/spec/timber/integrations/rack/session_context_spec.rb +62 -0
- data/spec/timber/logger_spec.rb +34 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f1b00e64f1666ef47d33c347cea42bcc4d7b262
|
4
|
+
data.tar.gz: cbde1d3d51a40c4e08eb3357f8d1fa72beae5d19
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 374705c1b1a8f0e0091d92a4ee0186246af3126a8c4e5e5358591deac6a1f2174052347350d42d1c8d1e79548a9a159438e39bdcbc95a2753182d5b52f18b921
|
7
|
+
data.tar.gz: baa0ba4b26cc7157710213635f12750da54ee3bde50e7b1fdac74a06da5ba2b1314c66db5aa258cfd8a170d4299b4f866520ba9a63a31a6ee39f200f95c9331e
|
data/README.md
CHANGED
@@ -329,7 +329,8 @@ logger.formatter = Timber::Logger::JSONFormatter.new
|
|
329
329
|
Your options are:
|
330
330
|
|
331
331
|
1. [`Timber::Logger::AugmentedFormatter`](http://www.rubydoc.info/github/timberio/timber-ruby/Timber/Logger/AugmentedFormatter) -
|
332
|
-
(default) A human readable format that _appends_ metadata to the original log line.
|
332
|
+
(default) A human readable format that _appends_ metadata to the original log line. The Timber
|
333
|
+
service can parse this data appropriately.
|
333
334
|
Ex: `My log message @metadata {"level":"info","dt":"2017-01-01T01:02:23.234321Z"}`
|
334
335
|
|
335
336
|
2. [`Timber::Logger::JSONFormatter`](http://www.rubydoc.info/github/timberio/timber-ruby/Timber/Logger/JSONFormatter) -
|
@@ -397,7 +398,7 @@ All variables are optional, but at least one must be present.
|
|
397
398
|
|
398
399
|
## Jibber-Jabber
|
399
400
|
|
400
|
-
<details><summary><strong>Which events and
|
401
|
+
<details><summary><strong>Which events and contexts does Timber capture for me?</strong></summary><p>
|
401
402
|
|
402
403
|
Out of the box you get everything in the
|
403
404
|
[`Timber::Events`](http://www.rubydoc.info/github/timberio/timber-ruby/Timber/Events) namespace.
|
@@ -408,13 +409,31 @@ namespace. Context is structured data representing the current environment when
|
|
408
409
|
was written. It is included in every log line. Think of it like join data for your logs. It's
|
409
410
|
how Timber is able to accomplished tailing users (`context.user.id:1`).
|
410
411
|
|
411
|
-
Lastly, you can checkout
|
412
|
+
Lastly, you can checkout how we capture these events in
|
412
413
|
[`Timber::Integrations`](lib/timber/integrations).
|
413
414
|
|
414
415
|
---
|
415
416
|
|
416
417
|
</p></details>
|
417
418
|
|
419
|
+
<details><summary><strong>Won't this increase the size of my log data?</strong></summary><p>
|
420
|
+
|
421
|
+
Yes. In terms of size, it's no different than adding tags to your logs or any other useful
|
422
|
+
data. A few things to point out though:
|
423
|
+
|
424
|
+
1. Timber generally _reduces_ the amount of logs your app generates by providing options to
|
425
|
+
consolidate request / response logs, template logs, and even silence logs that are not
|
426
|
+
of value to you. (see [configuration](#configuration) for examples).
|
427
|
+
2. Your log provider should be compressing your data and charging you accordingly. Log data
|
428
|
+
is notoriously repetitive, and the context Timber generates is repetitive as well.
|
429
|
+
Because of compression we've seen somes apps only incur a 10% increase in data size.
|
430
|
+
|
431
|
+
Finally, log what is useful to you. Quality over quantity certainly applies to logging.
|
432
|
+
|
433
|
+
---
|
434
|
+
|
435
|
+
</p></details>
|
436
|
+
|
418
437
|
<details><summary><strong>What about my current log statements?</strong></summary><p>
|
419
438
|
|
420
439
|
They'll continue to work as expected. Timber adheres to the default `::Logger` interface.
|
data/lib/timber/cli/installer.rb
CHANGED
@@ -15,6 +15,14 @@ module Timber
|
|
15
15
|
end
|
16
16
|
|
17
17
|
private
|
18
|
+
def get_delivery_strategy(app)
|
19
|
+
if app.heroku?
|
20
|
+
:stdout
|
21
|
+
else
|
22
|
+
:http
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
18
26
|
# Determines the API key storage prference (environment variable or inline)
|
19
27
|
def get_api_key_storage_preference
|
20
28
|
io.puts ""
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require "timber/cli/api"
|
2
4
|
require "timber/cli/installers/root"
|
3
5
|
require "timber/cli/io/messages"
|
@@ -14,13 +16,41 @@ module Timber
|
|
14
16
|
io.puts ""
|
15
17
|
|
16
18
|
if !api_key
|
17
|
-
|
19
|
+
app_url = IO::Messages::APP_URL
|
20
|
+
|
21
|
+
io.puts "Hey there! Welcome to Timber. In order to proceed, you'll need an API key."
|
22
|
+
io.puts "You can grab one by registering at #{IO::ANSI.colorize(app_url, :blue)}."
|
23
|
+
io.puts ""
|
24
|
+
io.puts "It takes less than a minute, with one click Google and Github registration."
|
25
|
+
io.puts ""
|
18
26
|
|
19
|
-
|
20
|
-
|
21
|
-
|
27
|
+
if OSHelper.can_open?
|
28
|
+
case io.ask_yes_no("Open #{app_url}?")
|
29
|
+
when :yes
|
30
|
+
puts ""
|
31
|
+
io.task("Opening #{app_url}") do
|
32
|
+
OSHelper.open(app_url)
|
33
|
+
end
|
34
|
+
when :no
|
35
|
+
io.puts ""
|
36
|
+
io.puts "No problem, navigate to the following URL:"
|
37
|
+
io.puts ""
|
38
|
+
io.puts " #{IO::ANSI.colorize(app_url, :blue)}"
|
39
|
+
end
|
40
|
+
else
|
41
|
+
io.puts ""
|
42
|
+
io.puts "Please navigate to:"
|
43
|
+
io.puts ""
|
44
|
+
io.puts " #{IO::ANSI.colorize(app_url, :blue)}"
|
22
45
|
end
|
23
46
|
|
47
|
+
io.puts ""
|
48
|
+
io.puts "Once you obtain your API key, you can run the installer like"
|
49
|
+
io.puts ""
|
50
|
+
io.puts " #{IO::ANSI.colorize("bundle exec timber my-api-key", :blue)}"
|
51
|
+
io.puts ""
|
52
|
+
io.puts "See you soon! 🎈"
|
53
|
+
io.puts ""
|
24
54
|
else
|
25
55
|
api = API.new(api_key)
|
26
56
|
api.event(:started)
|
@@ -6,40 +6,47 @@ module Timber
|
|
6
6
|
module Installers
|
7
7
|
class Other < Installer
|
8
8
|
def run(app)
|
9
|
-
puts ""
|
10
|
-
puts IO::Messages.separator
|
11
|
-
puts ""
|
12
|
-
|
13
9
|
if app.heroku?
|
14
10
|
install_stdout
|
15
11
|
else
|
16
12
|
api_key_storage_preference = get_api_key_storage_preference
|
17
|
-
api_key_code = get_api_key_code(
|
18
|
-
|
13
|
+
api_key_code = get_api_key_code(api_key_storage_preference)
|
19
14
|
install_http(api_key_code)
|
20
15
|
end
|
16
|
+
|
17
|
+
io.puts ""
|
18
|
+
io.puts IO::Messages.separator
|
19
|
+
io.puts ""
|
20
|
+
io.puts "We're going to send a few test messages to ensure communication is working."
|
21
|
+
io.puts ""
|
22
|
+
io.ask_to_proceed
|
23
|
+
io.puts ""
|
21
24
|
end
|
22
25
|
|
23
26
|
private
|
24
27
|
def install_stdout
|
25
|
-
puts ""
|
26
|
-
puts IO::Messages.separator
|
27
|
-
puts ""
|
28
|
-
puts "To integrate Timber,
|
29
|
-
puts "logger
|
30
|
-
puts ""
|
31
|
-
puts colorize(" LOGGER = Timber::Logger.new(STDOUT)", :blue)
|
28
|
+
io.puts ""
|
29
|
+
io.puts IO::Messages.separator
|
30
|
+
io.puts ""
|
31
|
+
io.puts "To integrate Timber, simply use the Timber::Logger. Just set your"
|
32
|
+
io.puts "global logger to something like this:"
|
33
|
+
io.puts ""
|
34
|
+
io.puts IO::ANSI.colorize(" LOGGER = Timber::Logger.new(STDOUT)", :blue)
|
35
|
+
io.puts ""
|
36
|
+
io.ask_to_proceed
|
32
37
|
end
|
33
38
|
|
34
39
|
def install_http(api_key_code)
|
35
|
-
puts ""
|
36
|
-
puts IO::Messages.separator
|
37
|
-
puts ""
|
38
|
-
puts "To integrate Timber,
|
39
|
-
puts "logger
|
40
|
-
puts ""
|
41
|
-
puts colorize(" log_device = Timber::LogDevices::HTTP.new(#{api_key_code})", :blue)
|
42
|
-
puts colorize(" LOGGER = Timber::Logger.new(log_device)", :blue)
|
40
|
+
io.puts ""
|
41
|
+
io.puts IO::Messages.separator
|
42
|
+
io.puts ""
|
43
|
+
io.puts "To integrate Timber, simply use the Timber::Logger. Just set your"
|
44
|
+
io.puts "global logger to something like this:"
|
45
|
+
io.puts ""
|
46
|
+
io.puts IO::ANSI.colorize(" log_device = Timber::LogDevices::HTTP.new(#{api_key_code})", :blue)
|
47
|
+
io.puts IO::ANSI.colorize(" LOGGER = Timber::Logger.new(log_device)", :blue)
|
48
|
+
io.puts ""
|
49
|
+
io.ask_to_proceed
|
43
50
|
end
|
44
51
|
end
|
45
52
|
end
|
@@ -17,7 +17,13 @@ module Timber
|
|
17
17
|
# Ask all of the questions up front. This allows us to to apply the
|
18
18
|
# changes as a neat task list when done.
|
19
19
|
development_preference = get_development_preference
|
20
|
-
|
20
|
+
|
21
|
+
api_key_storage_preference = if get_delivery_strategy(app) == :http
|
22
|
+
get_api_key_storage_preference
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
21
27
|
should_logrageify = logrageify?
|
22
28
|
|
23
29
|
io.puts ""
|
@@ -135,11 +141,11 @@ module Timber
|
|
135
141
|
case development_preference
|
136
142
|
when :send
|
137
143
|
extra_comment = <<-NOTE
|
138
|
-
# Note: When you are done testing, simply instantiate the logger like this:
|
139
|
-
#
|
140
|
-
# logger = Timber::Logger.new(STDOUT)
|
141
|
-
#
|
142
|
-
# Be sure to remove the "log_device =" and "logger =" lines below.
|
144
|
+
# Note: When you are done testing, simply instantiate the logger like this:
|
145
|
+
#
|
146
|
+
# logger = Timber::Logger.new(STDOUT)
|
147
|
+
#
|
148
|
+
# Be sure to remove the "log_device =" and "logger =" lines below.
|
143
149
|
NOTE
|
144
150
|
extra_comment = extra_comment.rstrip
|
145
151
|
install_http(environment_file_path, :inline, extra_comment: extra_comment)
|
@@ -166,10 +172,11 @@ NOTE
|
|
166
172
|
return true
|
167
173
|
end
|
168
174
|
|
169
|
-
|
170
|
-
|
171
|
-
else
|
175
|
+
case get_delivery_strategy(app)
|
176
|
+
when :http
|
172
177
|
install_http(environment_file_path, api_key_storage_preference)
|
178
|
+
when :stdout
|
179
|
+
install_stdout(environment_file_path)
|
173
180
|
end
|
174
181
|
end
|
175
182
|
|
data/lib/timber/cli/os_helper.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Timber
|
2
2
|
class CLI
|
3
3
|
module OSHelper
|
4
|
-
def self.
|
4
|
+
def self.can_copy_to_clipboard?
|
5
5
|
`which pbcopy` != ""
|
6
6
|
rescue Exception
|
7
7
|
false
|
@@ -34,20 +34,33 @@ module Timber
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
def self.can_open?
|
38
|
+
begin
|
39
|
+
`which #{open_command}` != ""
|
40
|
+
rescue Exception
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
37
45
|
# Attemps to open a URL in the user's default browser across
|
38
46
|
# the popular operating systems.
|
39
47
|
def self.open(link)
|
40
|
-
|
41
|
-
system "start #{link}"
|
42
|
-
elsif RbConfig::CONFIG['host_os'] =~ /darwin/
|
43
|
-
system "open #{link}"
|
44
|
-
elsif RbConfig::CONFIG['host_os'] =~ /linux|bsd/
|
45
|
-
system "xdg-open #{link}"
|
46
|
-
end
|
48
|
+
`#{open_command} #{link}`
|
47
49
|
true
|
48
50
|
rescue Exception
|
49
51
|
false
|
50
52
|
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def self.open_command
|
56
|
+
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
57
|
+
"start"
|
58
|
+
elsif RbConfig::CONFIG['host_os'] =~ /darwin/
|
59
|
+
"open"
|
60
|
+
elsif RbConfig::CONFIG['host_os'] =~ /linux|bsd/
|
61
|
+
"xdg-open"
|
62
|
+
end
|
63
|
+
end
|
51
64
|
end
|
52
65
|
end
|
53
66
|
end
|
data/lib/timber/config.rb
CHANGED
@@ -25,8 +25,10 @@ module Timber
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
DEVELOPMENT_NAME = "development".freeze
|
28
29
|
PRODUCTION_NAME = "production".freeze
|
29
30
|
STAGING_NAME = "staging".freeze
|
31
|
+
TEST_NAME = "test".freeze
|
30
32
|
|
31
33
|
include Singleton
|
32
34
|
|
@@ -37,10 +39,22 @@ module Timber
|
|
37
39
|
@http_body_limit = 2000
|
38
40
|
end
|
39
41
|
|
42
|
+
# Convenience method for logging debug statements to the debug logger
|
43
|
+
# set in this class.
|
44
|
+
# @private
|
45
|
+
def debug(&block)
|
46
|
+
debug_logger = Config.instance.debug_logger
|
47
|
+
if debug_logger
|
48
|
+
message = yield
|
49
|
+
debug_logger.debug(message)
|
50
|
+
end
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
40
54
|
# This is useful for debugging. This Sets a debug_logger to view internal Timber library
|
41
55
|
# log messages. The default is `nil`. Meaning log to nothing.
|
42
56
|
#
|
43
|
-
# See {#debug_to_file} and {#debug_to_stdout} for convenience methods that handle creating
|
57
|
+
# See {#debug_to_file!} and {#debug_to_stdout!} for convenience methods that handle creating
|
44
58
|
# and setting the logger.
|
45
59
|
#
|
46
60
|
# @example Rails
|
@@ -59,10 +73,10 @@ module Timber
|
|
59
73
|
# A convenience method for writing internal Timber debug messages to a file.
|
60
74
|
#
|
61
75
|
# @example Rails
|
62
|
-
# config.timber.debug_to_file("#{Rails.root}/log/timber.log")
|
76
|
+
# config.timber.debug_to_file!("#{Rails.root}/log/timber.log")
|
63
77
|
# @example Everything else
|
64
|
-
# Timber::Config.instance.debug_to_file("log/timber.log")
|
65
|
-
def debug_to_file(file_path)
|
78
|
+
# Timber::Config.instance.debug_to_file!("log/timber.log")
|
79
|
+
def debug_to_file!(file_path)
|
66
80
|
unless File.exist? File.dirname path
|
67
81
|
FileUtils.mkdir_p File.dirname path
|
68
82
|
end
|
@@ -77,10 +91,10 @@ module Timber
|
|
77
91
|
# A convenience method for writing internal Timber debug messages to STDOUT.
|
78
92
|
#
|
79
93
|
# @example Rails
|
80
|
-
# config.timber.debug_to_stdout
|
94
|
+
# config.timber.debug_to_stdout!
|
81
95
|
# @example Everything else
|
82
|
-
# Timber::Config.instance.debug_to_stdout
|
83
|
-
def debug_to_stdout
|
96
|
+
# Timber::Config.instance.debug_to_stdout!
|
97
|
+
def debug_to_stdout!
|
84
98
|
stdout_logger = ::Logger.new(STDOUT)
|
85
99
|
stdout_logger.formatter = SimpleLogFormatter.new
|
86
100
|
self.debug_logger = stdout_logger
|
@@ -203,6 +217,16 @@ module Timber
|
|
203
217
|
end
|
204
218
|
end
|
205
219
|
|
220
|
+
# @private
|
221
|
+
def development?
|
222
|
+
environment == DEVELOPMENT_NAME
|
223
|
+
end
|
224
|
+
|
225
|
+
# @private
|
226
|
+
def test?
|
227
|
+
environment == TEST_NAME
|
228
|
+
end
|
229
|
+
|
206
230
|
# @private
|
207
231
|
def production?
|
208
232
|
environment == PRODUCTION_NAME
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require "timber/config"
|
1
2
|
require "timber/context"
|
2
3
|
require "timber/util"
|
3
4
|
|
@@ -22,8 +23,10 @@ module Timber
|
|
22
23
|
version = ENV['RELEASE_VERSION'] || ENV['HEROKU_RELEASE_VERSION']
|
23
24
|
|
24
25
|
if commit_hash || created_at || version
|
26
|
+
Timber::Config.instance.debug { "Release env vars detected, adding to context" }
|
25
27
|
new(commit_hash: commit_hash, created_at: created_at, version: version)
|
26
28
|
else
|
29
|
+
Timber::Config.instance.debug { "Release env vars _not_ detected" }
|
27
30
|
nil
|
28
31
|
end
|
29
32
|
end
|
@@ -1,5 +1,4 @@
|
|
1
|
-
require "
|
2
|
-
|
1
|
+
require "timber/config"
|
3
2
|
require "timber/contexts/release"
|
4
3
|
|
5
4
|
module Timber
|
@@ -10,11 +9,15 @@ module Timber
|
|
10
9
|
# @note Because context is appended to every log line, it is recommended that you limit this
|
11
10
|
# to only neccessary data needed to relate your log lines.
|
12
11
|
class CurrentContext
|
13
|
-
include Singleton
|
14
|
-
|
15
12
|
THREAD_NAMESPACE = :_timber_current_context.freeze
|
16
13
|
|
17
14
|
class << self
|
15
|
+
# Impelements the Singleton pattern in a thread specific way. Each thread receives
|
16
|
+
# its own context.
|
17
|
+
def instance
|
18
|
+
Thread.current[THREAD_NAMESPACE] ||= new
|
19
|
+
end
|
20
|
+
|
18
21
|
# Convenience method for {CurrentContext#with}. See {CurrentContext#with} for more info.
|
19
22
|
def with(*args, &block)
|
20
23
|
instance.with(*args, &block)
|
@@ -41,17 +44,6 @@ module Timber
|
|
41
44
|
end
|
42
45
|
end
|
43
46
|
|
44
|
-
# Instantiates a new object, automatically adding context based on env variables (if present).
|
45
|
-
# For example, the {Contexts::ReleaseContext} is automatically added if the proper environment
|
46
|
-
# variables are present. Please see that class for more details.
|
47
|
-
def initialize
|
48
|
-
super
|
49
|
-
release_context = Contexts::Release.from_env
|
50
|
-
if release_context
|
51
|
-
add(release_context)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
47
|
# Adds a context and then removes it when the block is finished executing.
|
56
48
|
#
|
57
49
|
# @note Because context is included with every log line, it is recommended that you limit this
|
@@ -89,16 +81,7 @@ module Timber
|
|
89
81
|
# to only neccessary data.
|
90
82
|
def add(*objects)
|
91
83
|
objects.each do |object|
|
92
|
-
|
93
|
-
key = context.keyspace
|
94
|
-
json = context.as_json # Convert to json now so that we aren't doing it for every line
|
95
|
-
if key == :custom
|
96
|
-
# Custom contexts are merged into the space
|
97
|
-
hash[key] ||= {}
|
98
|
-
hash[key].merge!(json)
|
99
|
-
else
|
100
|
-
hash[key] = json
|
101
|
-
end
|
84
|
+
add_to(hash, object)
|
102
85
|
end
|
103
86
|
expire_cache!
|
104
87
|
self
|
@@ -151,7 +134,33 @@ module Timber
|
|
151
134
|
private
|
152
135
|
# The internal hash that is maintained. Use {#with} and {#add} for hash maintenance.
|
153
136
|
def hash
|
154
|
-
|
137
|
+
@hash ||= build_initial_hash
|
138
|
+
end
|
139
|
+
|
140
|
+
# Builds the initial hash. This is extract into a method to support a threaded
|
141
|
+
# environment. Each thread holds it's own context and also needs to instantiate
|
142
|
+
# it's hash properly.
|
143
|
+
def build_initial_hash
|
144
|
+
new_hash = {}
|
145
|
+
release_context = Contexts::Release.from_env
|
146
|
+
if release_context
|
147
|
+
add_to(new_hash, release_context)
|
148
|
+
end
|
149
|
+
new_hash
|
150
|
+
end
|
151
|
+
|
152
|
+
def add_to(hash, object)
|
153
|
+
context = Contexts.build(object) # Normalizes objects into a Timber::Context descendant.
|
154
|
+
key = context.keyspace
|
155
|
+
json = context.as_json # Convert to json now so that we aren't doing it for every line
|
156
|
+
if key == :custom
|
157
|
+
# Custom contexts are merged into the space
|
158
|
+
hash[key] ||= {}
|
159
|
+
hash[key].merge!(json)
|
160
|
+
else
|
161
|
+
hash[key] = json
|
162
|
+
end
|
163
|
+
hash
|
155
164
|
end
|
156
165
|
|
157
166
|
# Hook to clear any caching implement in this class
|
@@ -1,9 +1,15 @@
|
|
1
1
|
begin
|
2
|
+
# Rails 3.2 requires you to require all of Rails before requiring
|
3
|
+
# the exception wrapper.
|
4
|
+
require "rails"
|
2
5
|
require "action_dispatch/middleware/exception_wrapper"
|
3
6
|
rescue Exception
|
4
7
|
end
|
5
8
|
|
9
|
+
require "timber/config"
|
10
|
+
require "timber/events/exception"
|
6
11
|
require "timber/integrations/rack/middleware"
|
12
|
+
require "timber/util"
|
7
13
|
|
8
14
|
module Timber
|
9
15
|
module Integrations
|
@@ -1,5 +1,10 @@
|
|
1
1
|
require "set"
|
2
2
|
|
3
|
+
require "timber/config"
|
4
|
+
require "timber/contexts/http"
|
5
|
+
require "timber/current_context"
|
6
|
+
require "timber/events/http_server_request"
|
7
|
+
require "timber/events/http_server_response"
|
3
8
|
require "timber/integrations/rack/middleware"
|
4
9
|
|
5
10
|
module Timber
|
@@ -1,3 +1,10 @@
|
|
1
|
+
begin
|
2
|
+
require "rails"
|
3
|
+
rescue Exception
|
4
|
+
end
|
5
|
+
|
6
|
+
require "timber/config"
|
7
|
+
require "timber/contexts/session"
|
1
8
|
require "timber/integrations/rack/middleware"
|
2
9
|
|
3
10
|
module Timber
|
@@ -9,7 +16,7 @@ module Timber
|
|
9
16
|
def call(env)
|
10
17
|
id = get_session_id(env)
|
11
18
|
if id
|
12
|
-
context = Contexts::Session.new(id:
|
19
|
+
context = Contexts::Session.new(id: id)
|
13
20
|
CurrentContext.with(context) do
|
14
21
|
@app.call(env)
|
15
22
|
end
|
@@ -20,15 +27,37 @@ module Timber
|
|
20
27
|
|
21
28
|
private
|
22
29
|
def get_session_id(env)
|
23
|
-
if
|
24
|
-
|
25
|
-
|
26
|
-
|
30
|
+
if defined?(::Rails)
|
31
|
+
session_key = ::Rails.application.config.session_options[:key]
|
32
|
+
request = ::ActionDispatch::Request.new(env)
|
33
|
+
Timber::Config.instance.debug { "Rails detected, extracting session_id from cookie" }
|
34
|
+
extract_from_cookie(request, session_key)
|
35
|
+
|
36
|
+
elsif session = env['rack.session']
|
37
|
+
if session.respond_to?(:id)
|
38
|
+
Timber::Config.instance.debug { "Rack env session detected, using id attribute" }
|
39
|
+
session.id
|
40
|
+
elsif session.respond_to?(:[])
|
41
|
+
Timber::Config.instance.debug { "Rack env session detected, using the session_id key" }
|
42
|
+
session["session_id"]
|
43
|
+
else
|
44
|
+
Timber::Config.instance.debug { "Rack env session detected but could not extract id" }
|
27
45
|
nil
|
28
46
|
end
|
29
47
|
else
|
48
|
+
Timber::Config.instance.debug { "No session data could be detected, skipping" }
|
49
|
+
|
30
50
|
nil
|
31
51
|
end
|
52
|
+
rescue Exception => e
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def extract_from_cookie(request, session_key)
|
57
|
+
data = request
|
58
|
+
.cookie_jar
|
59
|
+
.signed_or_encrypted[session_key] || {}
|
60
|
+
data["session_id"]
|
32
61
|
end
|
33
62
|
end
|
34
63
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "timber/config"
|
2
|
+
require "timber/contexts/user"
|
1
3
|
require "timber/integrations/rack/middleware"
|
2
4
|
|
3
5
|
module Timber
|
@@ -79,20 +81,20 @@ module Timber
|
|
79
81
|
# not return a user, in which case the user data might be in the omniauth
|
80
82
|
# data.
|
81
83
|
if self.class.custom_user_hash.is_a?(Proc)
|
82
|
-
debug { "Obtaining user context from the custom user hash" }
|
84
|
+
Timber::Config.instance.debug { "Obtaining user context from the custom user hash" }
|
83
85
|
self.class.custom_user_hash.call(env)
|
84
86
|
elsif (auth_hash = env['omniauth.auth'])
|
85
|
-
debug { "Obtaining user context from the omniauth auth hash" }
|
87
|
+
Timber::Config.instance.debug { "Obtaining user context from the omniauth auth hash" }
|
86
88
|
get_omniauth_user_hash(auth_hash)
|
87
89
|
elsif env[:clearance] && env[:clearance].signed_in?
|
88
|
-
debug { "Obtaining user context from the clearance user" }
|
90
|
+
Timber::Config.instance.debug { "Obtaining user context from the clearance user" }
|
89
91
|
user = env[:clearance].current_user
|
90
92
|
get_user_object_hash(user)
|
91
93
|
elsif env['warden'] && (user = env['warden'].user)
|
92
|
-
debug { "Obtaining user context from the warden user" }
|
94
|
+
Timber::Config.instance.debug { "Obtaining user context from the warden user" }
|
93
95
|
get_user_object_hash(user)
|
94
96
|
else
|
95
|
-
debug { "Could not locate any user data" }
|
97
|
+
Timber::Config.instance.debug { "Could not locate any user data" }
|
96
98
|
nil
|
97
99
|
end
|
98
100
|
end
|
@@ -140,17 +142,6 @@ module Timber
|
|
140
142
|
nil
|
141
143
|
end
|
142
144
|
end
|
143
|
-
|
144
|
-
def debug_logger
|
145
|
-
Timber::Config.instance.debug_logger
|
146
|
-
end
|
147
|
-
|
148
|
-
def debug(&block)
|
149
|
-
if debug_logger
|
150
|
-
message = yield
|
151
|
-
debug_logger.debug(message)
|
152
|
-
end
|
153
|
-
end
|
154
145
|
end
|
155
146
|
end
|
156
147
|
end
|
@@ -94,7 +94,7 @@ module Timber
|
|
94
94
|
ensure_flush_threads_are_started
|
95
95
|
|
96
96
|
if @msg_queue.full?
|
97
|
-
debug { "Flushing HTTP buffer via write" }
|
97
|
+
Timber::Config.instance.debug { "Flushing HTTP buffer via write" }
|
98
98
|
flush_async
|
99
99
|
end
|
100
100
|
true
|
@@ -122,18 +122,6 @@ module Timber
|
|
122
122
|
end
|
123
123
|
|
124
124
|
private
|
125
|
-
def debug_logger
|
126
|
-
Timber::Config.instance.debug_logger
|
127
|
-
end
|
128
|
-
|
129
|
-
# Convenience method for writing debug messages.
|
130
|
-
def debug(&block)
|
131
|
-
if debug_logger
|
132
|
-
message = yield
|
133
|
-
debug_logger.debug(message)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
125
|
# This is a convenience method to ensure the flush thread are
|
138
126
|
# started. This is called lazily from {#write} so that we
|
139
127
|
# only start the threads as needed, but it also ensures
|
@@ -172,7 +160,7 @@ module Timber
|
|
172
160
|
@last_async_flush = Time.now
|
173
161
|
req = build_request
|
174
162
|
if !req.nil?
|
175
|
-
debug { "New request placed on queue" }
|
163
|
+
Timber::Config.instance.debug { "New request placed on queue" }
|
176
164
|
@request_queue.enq(req)
|
177
165
|
end
|
178
166
|
end
|
@@ -183,10 +171,10 @@ module Timber
|
|
183
171
|
# Wait 20 seconds
|
184
172
|
40.times do |i|
|
185
173
|
if @request_queue.size == 0 && @requests_in_flight == 0
|
186
|
-
debug { "Request queue is empty and no requests are in flight, finish waiting" }
|
174
|
+
Timber::Config.instance.debug { "Request queue is empty and no requests are in flight, finish waiting" }
|
187
175
|
return true
|
188
176
|
end
|
189
|
-
debug do
|
177
|
+
Timber::Config.instance.debug do
|
190
178
|
"Request size #{@request_queue.size}, reqs in-flight #{@requests_in_flight}, " \
|
191
179
|
"continue waiting (iteration #{i + 1})"
|
192
180
|
end
|
@@ -204,13 +192,13 @@ module Timber
|
|
204
192
|
loop do
|
205
193
|
begin
|
206
194
|
if intervaled_flush_ready?
|
207
|
-
debug { "Flushing HTTP buffer via the interval" }
|
195
|
+
Timber::Config.instance.debug { "Flushing HTTP buffer via the interval" }
|
208
196
|
flush_async
|
209
197
|
end
|
210
198
|
|
211
199
|
sleep(0.5)
|
212
200
|
rescue Exception => e
|
213
|
-
debug { "Intervaled HTTP flush failed: #{e.inspect}\n\n#{e.backtrace}" }
|
201
|
+
Timber::Config.instance.debug { "Intervaled HTTP flush failed: #{e.inspect}\n\n#{e.backtrace}" }
|
214
202
|
end
|
215
203
|
end
|
216
204
|
end
|
@@ -225,7 +213,7 @@ module Timber
|
|
225
213
|
# Builds an `Net::HTTP` object to deliver requests over.
|
226
214
|
def build_http
|
227
215
|
http = Net::HTTP.new(@timber_url.host, @timber_url.port)
|
228
|
-
http.set_debug_output(debug_logger) if debug_logger
|
216
|
+
http.set_debug_output(Config.instance.debug_logger) if Config.instance.debug_logger
|
229
217
|
http.use_ssl = true if @timber_url.scheme == 'https'
|
230
218
|
http.read_timeout = 30
|
231
219
|
http.ssl_timeout = 10
|
@@ -239,15 +227,15 @@ module Timber
|
|
239
227
|
http = build_http
|
240
228
|
|
241
229
|
begin
|
242
|
-
debug { "Starting HTTP connection" }
|
230
|
+
Timber::Config.instance.debug { "Starting HTTP connection" }
|
243
231
|
|
244
232
|
http.start do |conn|
|
245
233
|
deliver_requests(conn)
|
246
234
|
end
|
247
235
|
rescue => e
|
248
|
-
debug { "#request_outlet error: #{e.message}" }
|
236
|
+
Timber::Config.instance.debug { "#request_outlet error: #{e.message}" }
|
249
237
|
ensure
|
250
|
-
debug { "Finishing HTTP connection" }
|
238
|
+
Timber::Config.instance.debug { "Finishing HTTP connection" }
|
251
239
|
http.finish if http.started?
|
252
240
|
end
|
253
241
|
end
|
@@ -261,7 +249,7 @@ module Timber
|
|
261
249
|
num_reqs = 0
|
262
250
|
|
263
251
|
while num_reqs < @requests_per_conn
|
264
|
-
debug { "Waiting on next request, threads waiting: #{@request_queue.num_waiting}" }
|
252
|
+
Timber::Config.instance.debug { "Waiting on next request, threads waiting: #{@request_queue.num_waiting}" }
|
265
253
|
|
266
254
|
# Blocks waiting for a request.
|
267
255
|
req = @request_queue.deq
|
@@ -270,7 +258,7 @@ module Timber
|
|
270
258
|
begin
|
271
259
|
resp = conn.request(req)
|
272
260
|
rescue => e
|
273
|
-
debug { "#deliver_request error: #{e.message}" }
|
261
|
+
Timber::Config.instance.debug { "#deliver_request error: #{e.message}" }
|
274
262
|
|
275
263
|
@successive_error_count += 1
|
276
264
|
|
@@ -278,7 +266,7 @@ module Timber
|
|
278
266
|
calculated_backoff = @successive_error_count * 2
|
279
267
|
backoff = calculated_backoff > 30 ? 30 : calculated_backoff
|
280
268
|
|
281
|
-
debug { "Backing off #{backoff} seconds, error ##{@successive_error_count}" }
|
269
|
+
Timber::Config.instance.debug { "Backing off #{backoff} seconds, error ##{@successive_error_count}" }
|
282
270
|
|
283
271
|
sleep backoff
|
284
272
|
|
@@ -291,7 +279,7 @@ module Timber
|
|
291
279
|
|
292
280
|
@successive_error_count = 0
|
293
281
|
num_reqs += 1
|
294
|
-
debug { "Request successful: #{resp.code}" }
|
282
|
+
Timber::Config.instance.debug { "Request successful: #{resp.code}" }
|
295
283
|
end
|
296
284
|
end
|
297
285
|
|
data/lib/timber/logger.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "logger"
|
2
2
|
require "msgpack"
|
3
3
|
|
4
|
+
require "timber/config"
|
4
5
|
require "timber/current_context"
|
5
6
|
require "timber/event"
|
6
7
|
require "timber/log_devices/http"
|
@@ -195,8 +196,12 @@ module Timber
|
|
195
196
|
args.first.sync = true
|
196
197
|
end
|
197
198
|
|
199
|
+
# Set the default formatter. The formatter cannot be set during
|
200
|
+
# initialization, and can be changed with #formatter=.
|
198
201
|
if args.size == 1 and args.first.is_a?(LogDevices::HTTP)
|
199
202
|
self.formatter = PassThroughFormatter.new
|
203
|
+
elsif Config.instance.development? || Config.instance.test?
|
204
|
+
self.formatter = MessageOnlyFormatter.new
|
200
205
|
else
|
201
206
|
self.formatter = AugmentedFormatter.new
|
202
207
|
end
|
@@ -205,6 +210,8 @@ module Timber
|
|
205
210
|
|
206
211
|
after_initialize if respond_to?(:after_initialize)
|
207
212
|
|
213
|
+
Timber::Config.instance.debug { "Timber::Logger instantiated, level: #{level}, formatter: #{formatter.class}" }
|
214
|
+
|
208
215
|
@initialized = true
|
209
216
|
end
|
210
217
|
|
data/lib/timber/version.rb
CHANGED
data/spec/support/timber.rb
CHANGED
@@ -102,7 +102,7 @@ describe Timber::CLI::Installers::Rails, :rails_23 => true do
|
|
102
102
|
and_return("\nend")
|
103
103
|
|
104
104
|
logger_code = defined?(ActiveSupport::TaggedLogging) ? "ActiveSupport::TaggedLogging.new(logger)" : "logger"
|
105
|
-
new_contents = "\n\n # Install the Timber.io logger, send logs over HTTP.\n
|
105
|
+
new_contents = "\n\n # Install the Timber.io logger, send logs over HTTP.\n # Note: When you are done testing, simply instantiate the logger like this:\n #\n # logger = Timber::Logger.new(STDOUT)\n #\n # Be sure to remove the \"log_device =\" and \"logger =\" lines below.\n log_device = Timber::LogDevices::HTTP.new('abcd1234')\n logger = Timber::Logger.new(log_device)\n logger.level = config.log_level\n config.logger = #{logger_code}\n\nend"
|
106
106
|
|
107
107
|
expect(Timber::CLI::FileHelper).to receive(:write).
|
108
108
|
with(env_file_path, new_contents).
|
@@ -55,7 +55,7 @@ describe Timber::CLI::Installers::Root, :rails_23 => true do
|
|
55
55
|
|
56
56
|
expect(installer.send(:install_platform, app)).to eq(true)
|
57
57
|
|
58
|
-
copy_to_clipboard_message = Timber::CLI::OSHelper.
|
58
|
+
copy_to_clipboard_message = Timber::CLI::OSHelper.can_copy_to_clipboard? ? "\n \e[32m(✓ copied to clipboard)\e[0m" : ""
|
59
59
|
expected_output = "\n--------------------------------------------------------------------------------\n\nFirst, let's setup your Heroku drain. Run this command in a separate window:\n\n \e[34mheroku drains:add http://drain.heroku.com\e[0m#{copy_to_clipboard_message}\n\nReady to proceed? (y/n) "
|
60
60
|
expect(output.string).to eq(expected_output)
|
61
61
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
if defined?(::Rack)
|
4
|
+
describe Timber::Integrations::Rack::SessionContext do
|
5
|
+
let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
|
6
|
+
let(:io) { StringIO.new }
|
7
|
+
let(:logger) do
|
8
|
+
logger = Timber::Logger.new(io)
|
9
|
+
logger.level = ::Logger::INFO
|
10
|
+
logger
|
11
|
+
end
|
12
|
+
|
13
|
+
around(:each) do |example|
|
14
|
+
class RackHttpController < ActionController::Base
|
15
|
+
layout nil
|
16
|
+
|
17
|
+
def index
|
18
|
+
Thread.current[:_timber_context_snapshot] = Timber::CurrentContext.instance.snapshot
|
19
|
+
render json: {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_for_action(action_name)
|
23
|
+
action_name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
::RailsApp.routes.draw do
|
28
|
+
get '/rack_http' => 'rack_http#index'
|
29
|
+
end
|
30
|
+
|
31
|
+
with_rails_logger(logger) do
|
32
|
+
Timecop.freeze(time) { example.run }
|
33
|
+
end
|
34
|
+
|
35
|
+
Object.send(:remove_const, :RackHttpController)
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#process" do
|
39
|
+
it "should set the context" do
|
40
|
+
allow(Benchmark).to receive(:ms).and_return(1).and_yield
|
41
|
+
|
42
|
+
expect_any_instance_of(described_class).to receive(:extract_from_cookie).exactly(1).times.and_return("1234")
|
43
|
+
|
44
|
+
dispatch_rails_request("/rack_http")
|
45
|
+
session_context = Thread.current[:_timber_context_snapshot][:session]
|
46
|
+
|
47
|
+
expect(session_context).to eq({:id => "1234"})
|
48
|
+
|
49
|
+
lines = clean_lines(io.string.split("\n"))
|
50
|
+
expect(lines.length).to eq(3)
|
51
|
+
lines.each do |line|
|
52
|
+
expect(line).to include("\"session\":{\"id\":\"1234\"}")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Remove blank lines since Rails does this to space out requests in the logs
|
58
|
+
def clean_lines(lines)
|
59
|
+
lines.select { |line| !line.start_with?(" @metadat") }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/spec/timber/logger_spec.rb
CHANGED
@@ -1,6 +1,27 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Timber::Logger, :rails_23 => true do
|
4
|
+
describe "#initialize" do
|
5
|
+
it "shoud select the augmented formatter" do
|
6
|
+
logger = described_class.new(nil)
|
7
|
+
expect(logger.formatter).to be_kind_of(Timber::Logger::AugmentedFormatter)
|
8
|
+
end
|
9
|
+
|
10
|
+
context "development environment" do
|
11
|
+
around(:each) do |example|
|
12
|
+
old_env = Timber::Config.instance.environment
|
13
|
+
Timber::Config.instance.environment = "development"
|
14
|
+
example.run
|
15
|
+
Timber::Config.instance.environment = old_env
|
16
|
+
end
|
17
|
+
|
18
|
+
it "shoud select the augmented formatter" do
|
19
|
+
logger = described_class.new(nil)
|
20
|
+
expect(logger.formatter).to be_kind_of(Timber::Logger::MessageOnlyFormatter)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
4
25
|
describe "#add" do
|
5
26
|
let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
|
6
27
|
let(:io) { StringIO.new }
|
@@ -124,6 +145,19 @@ describe Timber::Logger, :rails_23 => true do
|
|
124
145
|
end
|
125
146
|
end
|
126
147
|
|
148
|
+
describe "#silence" do
|
149
|
+
let(:io) { StringIO.new }
|
150
|
+
let(:logger) { Timber::Logger.new(io) }
|
151
|
+
|
152
|
+
it "should silence the logs" do
|
153
|
+
logger.silence do
|
154
|
+
logger.info("test")
|
155
|
+
end
|
156
|
+
|
157
|
+
expect(io.string).to eq("")
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
127
161
|
describe "#with_context" do
|
128
162
|
let(:io) { StringIO.new }
|
129
163
|
let(:logger) { Timber::Logger.new(io) }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timber
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.0.
|
4
|
+
version: 2.1.0.rc3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Timber Technologies, Inc.
|
@@ -267,6 +267,7 @@ files:
|
|
267
267
|
- spec/timber/integrations/active_record/log_subscriber_spec.rb
|
268
268
|
- spec/timber/integrations/rack/http_context_spec.rb
|
269
269
|
- spec/timber/integrations/rack/http_events_spec.rb
|
270
|
+
- spec/timber/integrations/rack/session_context_spec.rb
|
270
271
|
- spec/timber/integrations/rails/rack_logger_spec.rb
|
271
272
|
- spec/timber/log_devices/http_spec.rb
|
272
273
|
- spec/timber/log_entry_spec.rb
|
@@ -331,6 +332,7 @@ test_files:
|
|
331
332
|
- spec/timber/integrations/active_record/log_subscriber_spec.rb
|
332
333
|
- spec/timber/integrations/rack/http_context_spec.rb
|
333
334
|
- spec/timber/integrations/rack/http_events_spec.rb
|
335
|
+
- spec/timber/integrations/rack/session_context_spec.rb
|
334
336
|
- spec/timber/integrations/rails/rack_logger_spec.rb
|
335
337
|
- spec/timber/log_devices/http_spec.rb
|
336
338
|
- spec/timber/log_entry_spec.rb
|