timber 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +41 -7
- data/lib/timber/log_entry.rb +7 -5
- data/lib/timber/logger.rb +15 -6
- data/lib/timber/version.rb +1 -1
- data/spec/timber/log_devices/http_spec.rb +4 -4
- data/spec/timber/log_entry_spec.rb +1 -1
- data/spec/timber/logger_spec.rb +21 -6
- data/spec/timber/probes/action_controller_log_subscriber_spec.rb +2 -2
- data/spec/timber/probes/action_dispatch_debug_exceptions_spec.rb +1 -1
- data/spec/timber/probes/action_view_log_subscriber_spec.rb +1 -1
- data/spec/timber/probes/active_record_log_subscriber_spec.rb +2 -2
- data/spec/timber/probes/rails_rack_logger_spec.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8edaf795dfbf8e705fb737cdb7f43ce3821ef2f
|
4
|
+
data.tar.gz: e2d21bde5b38fe63b346070cf0e5ac4f86e158ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 162e70c46240ca58184950aa7165ff146f2f7b7e78c7e012a694b3aa97b968112870327a8bba978e503ab8a418b86989e8fdecacdf61c6dcefb4204d239322b3
|
7
|
+
data.tar.gz: fdbbff594c9caf2a773db228648f8384c5e345b120d90878ea25289dab529955ce762af955fd0a73de9d00c5fabc22085ef6733ed2a41d3cef0643ae60d14c41
|
data/README.md
CHANGED
@@ -55,6 +55,7 @@ blog post.
|
|
55
55
|
5. **Long term retention.** Timber is designed on modern big-data principles. As a result, we can
|
56
56
|
offer 6+ months of retention at prices cheaper than alternatives offering <1 month.
|
57
57
|
This allows you to unlock your logs for purposes beyond debugging.
|
58
|
+
|
58
59
|
---
|
59
60
|
|
60
61
|
</p></details>
|
@@ -63,8 +64,9 @@ blog post.
|
|
63
64
|
|
64
65
|
1. Captures and structures your framework and 3rd party logs. (see next question)
|
65
66
|
2. Adds useful context to every log line. (see next question)
|
66
|
-
3.
|
67
|
-
4.
|
67
|
+
3. Allows you to easily add tags and timings to log. (see [Usage](#usage))
|
68
|
+
4. Provides a framework for logging custom structured events. (see [Usage](#usage))
|
69
|
+
5. Offers transport strategies to [send your logs](#send-your-logs) to the Timber service.
|
68
70
|
|
69
71
|
---
|
70
72
|
|
@@ -122,7 +124,7 @@ logger.info("My log message")
|
|
122
124
|
# My log message @metadata {"level": "info", "context": {...}}
|
123
125
|
```
|
124
126
|
|
125
|
-
Timber will never deviate from the public `::Logger` interface in *any* way.
|
127
|
+
Timber will *never* deviate from the public `::Logger` interface in *any* way.
|
126
128
|
|
127
129
|
---
|
128
130
|
|
@@ -130,7 +132,7 @@ Timber will never deviate from the public `::Logger` interface in *any* way.
|
|
130
132
|
|
131
133
|
<details><summary><strong>Tagging logs</strong></summary><p>
|
132
134
|
|
133
|
-
Need a quick
|
135
|
+
Need a quick way to identify logs? Use tags!:
|
134
136
|
|
135
137
|
```ruby
|
136
138
|
logger.info(message: "My log message", tag: "tag")
|
@@ -156,10 +158,41 @@ end
|
|
156
158
|
# My log message @metadata {"level": "info", "tags": ["tag"], "context": {...}}
|
157
159
|
```
|
158
160
|
|
161
|
+
* In the Timber console use the query: `tags:tag`.
|
162
|
+
|
163
|
+
---
|
164
|
+
|
159
165
|
</p></details>
|
160
166
|
|
167
|
+
<details><summary><strong>Timings, Durations, & Metrics</strong></summary><p>
|
168
|
+
|
169
|
+
Timings allow you to easily capture one-off timings in your code; a simple
|
170
|
+
way to benchmark code execution:
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
start = Time.now
|
174
|
+
# ...my code to time...
|
175
|
+
time_ms = (Time.now - start) * 1000
|
176
|
+
logger.info(message: "Task complete", tag: "my_task", time_ms: time_ms)
|
177
|
+
|
178
|
+
# My log message @metadata {"level": "info", tags: ["my_task"], "time_ms": 54.2132, "context": {...}}
|
179
|
+
```
|
180
|
+
|
181
|
+
* In the Timber console use the query: `tags:my_task time_ms>500`
|
182
|
+
* The Timber console will also display this value inline with your logs. No need to include it
|
183
|
+
in the log message, but you certainly can if you'd prefer.
|
184
|
+
|
185
|
+
---
|
186
|
+
|
187
|
+
</p></details>
|
188
|
+
|
189
|
+
|
161
190
|
<details><summary><strong>Custom events</strong></summary><p>
|
162
191
|
|
192
|
+
Custom events can be used to structure information about events that are central
|
193
|
+
to your line of business like receiving credit card payments, saving a draft of a post,
|
194
|
+
or changing a user's password. You have 2 options to do this:
|
195
|
+
|
163
196
|
1. Log a structured Hash (simplest)
|
164
197
|
|
165
198
|
```ruby
|
@@ -168,7 +201,8 @@ end
|
|
168
201
|
# Payment rejected @metadata {"level": "warn", "event": {"payment_rejected": {"customer_id": "abcd1234", "amount": 100, "reason": "Card expired"}}, "context": {...}}
|
169
202
|
```
|
170
203
|
|
171
|
-
* The hash can *only* have
|
204
|
+
* The hash can *only* have 2 keys: `:message` and "event type" key; `:payment_rejected` in this example.
|
205
|
+
* Timber will keyspace your event data by the event type key passed.
|
172
206
|
|
173
207
|
2. Log a Struct (recommended)
|
174
208
|
|
@@ -185,7 +219,7 @@ end
|
|
185
219
|
# Payment rejected @metadata {"level": "warn", "event": {"payment_rejected": {"customer_id": "abcd1234", "amount": 100, "reason": "Card expired"}}, "context": {...}}
|
186
220
|
```
|
187
221
|
|
188
|
-
*
|
222
|
+
* In the Timber console use queries like: `payment_rejected.customer_id:xiaus1934` or `payment_rejected.amount>100`
|
189
223
|
* For more advanced examples see [`Timber::Logger`](lib/timber.logger.rb).
|
190
224
|
* Also, notice there is no mention of Timber in the above code. Just plain old logging.
|
191
225
|
|
@@ -261,7 +295,7 @@ gem 'timber'
|
|
261
295
|
|
262
296
|
## Setup
|
263
297
|
|
264
|
-
<details><summary><strong>Rails
|
298
|
+
<details><summary><strong>Rails (all versions, including edge)</strong></summary><p>
|
265
299
|
|
266
300
|
*Replace* any existing `config.logger=` calls in `config/environments/production.rb` with:
|
267
301
|
|
data/lib/timber/log_entry.rb
CHANGED
@@ -3,9 +3,9 @@ module Timber
|
|
3
3
|
# `Logger` and the log device that you set it up with.
|
4
4
|
class LogEntry #:nodoc:
|
5
5
|
DT_PRECISION = 6.freeze
|
6
|
-
SCHEMA = "https://raw.githubusercontent.com/timberio/log-event-json-schema/1.2.
|
6
|
+
SCHEMA = "https://raw.githubusercontent.com/timberio/log-event-json-schema/1.2.4/schema.json".freeze
|
7
7
|
|
8
|
-
attr_reader :context_snapshot, :event, :level, :message, :progname, :tags, :time
|
8
|
+
attr_reader :context_snapshot, :event, :level, :message, :progname, :tags, :time, :time_ms
|
9
9
|
|
10
10
|
# Creates a log entry suitable to be sent to the Timber API.
|
11
11
|
# @param severity [Integer] the log level / severity
|
@@ -17,12 +17,13 @@ module Timber
|
|
17
17
|
# @param event [Timber.Event] structured data representing the log line event. This should be
|
18
18
|
# an instance of `Timber.Event`.
|
19
19
|
# @return [LogEntry] the resulting LogEntry object
|
20
|
-
def initialize(level, time, progname, message, context_snapshot, event,
|
20
|
+
def initialize(level, time, progname, message, context_snapshot, event, options = {})
|
21
21
|
@level = level
|
22
22
|
@time = time.utc
|
23
23
|
@progname = progname
|
24
24
|
@message = message
|
25
|
-
@tags = tags
|
25
|
+
@tags = options[:tags]
|
26
|
+
@time_ms = options[:time_ms]
|
26
27
|
|
27
28
|
context_snapshot = {} if context_snapshot.nil?
|
28
29
|
system_context = Contexts::System.new(pid: Process.pid)
|
@@ -34,7 +35,8 @@ module Timber
|
|
34
35
|
|
35
36
|
def as_json(options = {})
|
36
37
|
options ||= {}
|
37
|
-
hash = {:level => level, :dt => formatted_dt, :message => message, :tags => tags
|
38
|
+
hash = {:level => level, :dt => formatted_dt, :message => message, :tags => tags,
|
39
|
+
:time_ms => time_ms}
|
38
40
|
|
39
41
|
if !event.nil?
|
40
42
|
hash[:event] = event
|
data/lib/timber/logger.rb
CHANGED
@@ -76,15 +76,24 @@ module Timber
|
|
76
76
|
def build_log_entry(severity, time, progname, msg)
|
77
77
|
level = SEVERITY_MAP.fetch(severity)
|
78
78
|
context_snapshot = CurrentContext.instance.snapshot
|
79
|
+
|
79
80
|
tags = extract_active_support_tagged_logging_tags
|
80
|
-
|
81
|
-
|
81
|
+
time_ms = nil
|
82
|
+
if msg.is_a?(Hash)
|
83
|
+
tags << msg.delete(:tag) if msg.key?(:tag)
|
84
|
+
tags += msg.delete(:tags) if msg.key?(:tags)
|
85
|
+
tags.uniq!
|
86
|
+
time_ms = msg.delete(:time_ms) if msg.key?(:time_ms)
|
87
|
+
|
88
|
+
msg = msg[:message] if msg.length == 1
|
89
|
+
end
|
90
|
+
|
82
91
|
event = Events.build(msg)
|
83
92
|
|
84
93
|
if event
|
85
|
-
LogEntry.new(level, time, progname, event.message, context_snapshot, event, tags)
|
94
|
+
LogEntry.new(level, time, progname, event.message, context_snapshot, event, tags: tags, time_ms: time_ms)
|
86
95
|
else
|
87
|
-
LogEntry.new(level, time, progname, msg, context_snapshot, nil, tags)
|
96
|
+
LogEntry.new(level, time, progname, msg, context_snapshot, nil, tags: tags, time_ms: time_ms)
|
88
97
|
end
|
89
98
|
end
|
90
99
|
|
@@ -104,10 +113,10 @@ module Timber
|
|
104
113
|
#
|
105
114
|
# Example message:
|
106
115
|
#
|
107
|
-
# My log message @
|
116
|
+
# My log message @metadata {"level":"info","dt":"2016-09-01T07:00:00.000000-05:00"}
|
108
117
|
#
|
109
118
|
class HybridFormatter < Formatter
|
110
|
-
METADATA_CALLOUT = "@
|
119
|
+
METADATA_CALLOUT = "@metadata".freeze
|
111
120
|
|
112
121
|
def call(severity, time, progname, msg)
|
113
122
|
log_entry = build_log_entry(severity, time, progname, msg)
|
data/lib/timber/version.rb
CHANGED
@@ -58,9 +58,9 @@ describe Timber::LogDevices::HTTP do
|
|
58
58
|
|
59
59
|
it "should add a request to the queue" do
|
60
60
|
http = described_class.new("MYKEY", threads: false)
|
61
|
-
log_entry = Timber::LogEntry.new("INFO", time, nil, "test log message 1", nil, nil
|
61
|
+
log_entry = Timber::LogEntry.new("INFO", time, nil, "test log message 1", nil, nil)
|
62
62
|
http.write(log_entry)
|
63
|
-
log_entry = Timber::LogEntry.new("INFO", time, nil, "test log message 2", nil, nil
|
63
|
+
log_entry = Timber::LogEntry.new("INFO", time, nil, "test log message 2", nil, nil)
|
64
64
|
http.write(log_entry)
|
65
65
|
http.send(:flush)
|
66
66
|
request_queue = http.instance_variable_get(:@request_queue)
|
@@ -109,9 +109,9 @@ describe Timber::LogDevices::HTTP do
|
|
109
109
|
to_return(:status => 200, :body => "", :headers => {})
|
110
110
|
|
111
111
|
http = described_class.new("MYKEY", flush_interval: 0.1)
|
112
|
-
log_entry = Timber::LogEntry.new("INFO", time, nil, "test log message 1", nil, nil
|
112
|
+
log_entry = Timber::LogEntry.new("INFO", time, nil, "test log message 1", nil, nil)
|
113
113
|
http.write(log_entry)
|
114
|
-
log_entry = Timber::LogEntry.new("INFO", time, nil, "test log message 2", nil, nil
|
114
|
+
log_entry = Timber::LogEntry.new("INFO", time, nil, "test log message 2", nil, nil)
|
115
115
|
http.write(log_entry)
|
116
116
|
sleep 0.3
|
117
117
|
|
@@ -7,7 +7,7 @@ describe Timber::LogEntry, :rails_23 => true do
|
|
7
7
|
it "should encode properly with an event and context" do
|
8
8
|
event = Timber::Events::Custom.new(type: :event_type, message: "event_message", data: {a: 1})
|
9
9
|
context = {custom: Timber::Contexts::Custom.new(type: :context_type, data: {b: 1})}
|
10
|
-
log_entry = described_class.new("INFO", time, nil, "log message", context, event
|
10
|
+
log_entry = described_class.new("INFO", time, nil, "log message", context, event)
|
11
11
|
msgpack = log_entry.to_msgpack
|
12
12
|
expect(msgpack).to start_with("\x86\xA5level\xA4INFO\xA2dt\xBB2016-09-01T12:00:00.000000Z".force_encoding("ASCII-8BIT"))
|
13
13
|
end
|
data/spec/timber/logger_spec.rb
CHANGED
@@ -15,7 +15,7 @@ describe Timber::Logger, :rails_23 => true do
|
|
15
15
|
|
16
16
|
it "should accept strings" do
|
17
17
|
logger.info("this is a test")
|
18
|
-
expect(io.string).to start_with("this is a test @
|
18
|
+
expect(io.string).to start_with("this is a test @metadata {\"level\":\"info\",\"dt\":\"2016-09-01T12:00:00.000000Z\"")
|
19
19
|
end
|
20
20
|
|
21
21
|
context "with a context" do
|
@@ -37,7 +37,7 @@ describe Timber::Logger, :rails_23 => true do
|
|
37
37
|
it "should snapshot and include the context" do
|
38
38
|
expect(Timber::CurrentContext.instance).to receive(:snapshot).and_call_original
|
39
39
|
logger.info("this is a test")
|
40
|
-
expect(io.string).to start_with("this is a test @
|
40
|
+
expect(io.string).to start_with("this is a test @metadata {\"level\":\"info\",\"dt\":\"2016-09-01T12:00:00.000000Z\"")
|
41
41
|
expect(io.string).to include("\"http\":{\"method\":\"POST\",\"path\":\"/checkout\",\"remote_addr\":\"123.456.789.10\",\"request_id\":\"abcd1234\"}")
|
42
42
|
end
|
43
43
|
end
|
@@ -46,28 +46,43 @@ describe Timber::Logger, :rails_23 => true do
|
|
46
46
|
message = {message: "payment rejected", payment_rejected: {customer_id: "abcde1234", amount: 100}}
|
47
47
|
expect(Timber::Events).to receive(:build).with(message).and_call_original
|
48
48
|
logger.info(message)
|
49
|
-
expect(io.string).to start_with("payment rejected @
|
49
|
+
expect(io.string).to start_with("payment rejected @metadata {\"level\":\"info\",\"dt\":\"2016-09-01T12:00:00.000000Z\",")
|
50
50
|
expect(io.string).to include("\"event\":{\"server_side_app\":{\"custom\":{\"payment_rejected\":{\"customer_id\":\"abcde1234\",\"amount\":100}}}}")
|
51
51
|
end
|
52
52
|
|
53
53
|
it "should log properly when an event is passed" do
|
54
54
|
message = Timber::Events::SQLQuery.new(sql: "select * from users", time_ms: 56, message: "select * from users")
|
55
55
|
logger.info(message)
|
56
|
-
expect(io.string).to start_with("select * from users @
|
56
|
+
expect(io.string).to start_with("select * from users @metadata {\"level\":\"info\",\"dt\":\"2016-09-01T12:00:00.000000Z\",")
|
57
57
|
expect(io.string).to include("\"event\":{\"server_side_app\":{\"sql_query\":{\"sql\":\"select * from users\",\"time_ms\":56.0}}}")
|
58
58
|
end
|
59
59
|
|
60
|
+
it "should allow :time_ms" do
|
61
|
+
logger.info(message: "event complete", time_ms: 54.5)
|
62
|
+
expect(io.string).to include("\"time_ms\":54.5")
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should allow :tag" do
|
66
|
+
logger.info(message: "event complete", tag: "tag1")
|
67
|
+
expect(io.string).to include("\"tags\":[\"tag1\"]")
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should allow :tags" do
|
71
|
+
logger.info(message: "event complete", tags: ["tag1", "tag2"])
|
72
|
+
expect(io.string).to include("\"tags\":[\"tag1\",\"tag2\"]")
|
73
|
+
end
|
74
|
+
|
60
75
|
it "should allow functions" do
|
61
76
|
logger.info do
|
62
77
|
{message: "payment rejected", payment_rejected: {customer_id: "abcde1234", amount: 100}}
|
63
78
|
end
|
64
|
-
expect(io.string).to start_with("payment rejected @
|
79
|
+
expect(io.string).to start_with("payment rejected @metadata {\"level\":\"info\",\"dt\":\"2016-09-01T12:00:00.000000Z\",")
|
65
80
|
expect(io.string).to include("\"event\":{\"server_side_app\":{\"custom\":{\"payment_rejected\":{\"customer_id\":\"abcde1234\",\"amount\":100}}}}")
|
66
81
|
end
|
67
82
|
|
68
83
|
it "should escape new lines" do
|
69
84
|
logger.info "first\nsecond"
|
70
|
-
expect(io.string).to start_with("first\\nsecond @
|
85
|
+
expect(io.string).to start_with("first\\nsecond @metadata")
|
71
86
|
end
|
72
87
|
end
|
73
88
|
|
@@ -57,9 +57,9 @@ describe Timber::Probes::ActionControllerLogSubscriber do
|
|
57
57
|
dispatch_rails_request("/log_subscriber")
|
58
58
|
lines = io.string.split("\n")
|
59
59
|
expect(lines.length).to eq(2)
|
60
|
-
expect(lines[0]).to start_with('Processing by LogSubscriberController#index as HTML @
|
60
|
+
expect(lines[0]).to start_with('Processing by LogSubscriberController#index as HTML @metadata {"level":"info","dt":"2016-09-01T12:00:00.000000Z"')
|
61
61
|
expect(lines[0]).to include('"event":{"server_side_app":{"controller_call":{"controller":"LogSubscriberController","action":"index"}}}')
|
62
|
-
expect(lines[1]).to start_with('Completed 200 OK in 0.0ms (Views: 1.0ms) @
|
62
|
+
expect(lines[1]).to start_with('Completed 200 OK in 0.0ms (Views: 1.0ms) @metadata {"level":"info","dt":"2016-09-01T12:00:00.000000Z"')
|
63
63
|
expect(lines[1]).to include('"event":{"server_side_app":{"http_server_response":{"status":200,"time_ms":0.0}}}')
|
64
64
|
end
|
65
65
|
end
|
@@ -38,7 +38,7 @@ describe Timber::Probes::ActionDispatchDebugExceptions do
|
|
38
38
|
dispatch_rails_request("/exception")
|
39
39
|
# Because constantly updating the line numbers sucks :/
|
40
40
|
expect(io.string).to include("RuntimeError (boom):\\n\\n")
|
41
|
-
expect(io.string).to include("@
|
41
|
+
expect(io.string).to include("@metadata")
|
42
42
|
expect(io.string).to include("\"event\":{\"server_side_app\":{\"exception\":{\"name\":\"RuntimeError\",\"message\":\"boom\",\"backtrace\":[\"")
|
43
43
|
end
|
44
44
|
|
@@ -51,7 +51,7 @@ describe Timber::Probes::ActionViewLogSubscriber do
|
|
51
51
|
it "should log the controller call event" do
|
52
52
|
allow_any_instance_of(Timber::Probes::ActionViewLogSubscriber::LogSubscriber).to receive(:logger).and_return(logger)
|
53
53
|
dispatch_rails_request("/action_view_log_subscriber")
|
54
|
-
expect(io.string).to start_with(" Rendered spec/support/rails/templates/template.html (0.0ms) @
|
54
|
+
expect(io.string).to start_with(" Rendered spec/support/rails/templates/template.html (0.0ms) @metadata {\"level\":\"info\"")
|
55
55
|
expect(io.string).to include("\"event\":{\"server_side_app\":{\"template_render\":{\"name\":\"spec/support/rails/templates/template.html\",\"time_ms\":0.0}}},")
|
56
56
|
end
|
57
57
|
end
|
@@ -36,12 +36,12 @@ describe Timber::Probes::ActiveRecordLogSubscriber do
|
|
36
36
|
|
37
37
|
it "should log the sql query" do
|
38
38
|
User.order("users.id DESC").all.collect # collect kicks the sql because it is lazily executed
|
39
|
-
message = " \e[1m\e[36mUser Load (0.0ms)\e[0m \e[1m\e[34mSELECT \"users\".* FROM \"users\" ORDER BY users.id DESC\e[0m @
|
39
|
+
message = " \e[1m\e[36mUser Load (0.0ms)\e[0m \e[1m\e[34mSELECT \"users\".* FROM \"users\" ORDER BY users.id DESC\e[0m @metadata {\"level\":\"debug\",\"dt\":\"2016-09-01T12:00:00.000000Z\",\"event\":{\"sql_query\":{\"sql\":\"SELECT \\\"users\\\".* FROM \\\"users\\\" ORDER BY users.id DESC\",\"time_ms\":0.0}}}\n"
|
40
40
|
# Rails 4.X adds random spaces :/
|
41
41
|
string = io.string.gsub(" ORDER BY", " ORDER BY")
|
42
42
|
string = string.gsub(" ORDER BY", " ORDER BY")
|
43
43
|
expect(string).to include("users.id DESC")
|
44
|
-
expect(string).to include("@
|
44
|
+
expect(string).to include("@metadata")
|
45
45
|
expect(string).to include("\"level\":\"debug\"")
|
46
46
|
expect(string).to include("\"sql\":")
|
47
47
|
end
|
@@ -38,7 +38,7 @@ describe Timber::Probes::RailsRackLogger do
|
|
38
38
|
allow(::Rails).to receive(:logger).and_return(logger) # Rails 3.2.X
|
39
39
|
allow_any_instance_of(::Rails::Rack::Logger).to receive(:logger).and_return(logger)
|
40
40
|
dispatch_rails_request("/rails_rack_logger")
|
41
|
-
expect(io.string).to start_with("Started GET \"/rails_rack_logger\" for 123.456.789.10 @
|
41
|
+
expect(io.string).to start_with("Started GET \"/rails_rack_logger\" for 123.456.789.10 @metadata {\"level\":\"info\",\"dt\":\"2016-09-01T12:00:00.000000Z\"")
|
42
42
|
expect(io.string).to include("\"event\":{\"server_side_app\":{\"http_request\":{\"host\":\"example.org\",\"method\":\"GET\",\"path\":\"/rails_rack_logger\",\"port\":80,\"headers\":{\"remote_addr\":\"123.456.789.10\",\"request_id\":\"unique-request-id-1234\"}}}")
|
43
43
|
end
|
44
44
|
end
|