timber 2.1.0.rc2 → 2.1.0.rc3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|