timber 2.1.1 → 2.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +56 -156
- data/lib/timber/config.rb +7 -8
- data/lib/timber/config/integrations/rack.rb +4 -4
- data/lib/timber/contexts/runtime.rb +3 -2
- data/lib/timber/contexts/system.rb +2 -2
- data/lib/timber/current_context.rb +5 -0
- data/lib/timber/events.rb +3 -5
- data/lib/timber/events/controller_call.rb +7 -5
- data/lib/timber/events/{exception.rb → error.rb} +13 -12
- data/lib/timber/events/{http_server_request.rb → http_request.rb} +5 -5
- data/lib/timber/events/{http_server_response.rb → http_response.rb} +3 -3
- data/lib/timber/events/sql_query.rb +3 -0
- data/lib/timber/integrations/action_dispatch.rb +2 -2
- data/lib/timber/integrations/rack.rb +3 -3
- data/lib/timber/integrations/rack/{exception_event.rb → error_event.rb} +6 -6
- data/lib/timber/integrations/rack/http_events.rb +10 -10
- data/lib/timber/log_devices.rb +1 -0
- data/lib/timber/log_devices/http.rb +5 -1
- data/lib/timber/log_devices/multi.rb +34 -0
- data/lib/timber/log_entry.rb +3 -1
- data/lib/timber/logger.rb +37 -11
- data/lib/timber/util/http_event.rb +4 -5
- data/lib/timber/version.rb +1 -1
- data/spec/timber/contexts/system_spec.rb +2 -2
- data/spec/timber/events/{exception_spec.rb → error_spec.rb} +4 -4
- data/spec/timber/events/{http_server_request_spec.rb → http_request_spec.rb} +1 -1
- data/spec/timber/integrations/action_dispatch/debug_exceptions_spec.rb +1 -1
- data/spec/timber/logger_spec.rb +9 -0
- metadata +11 -12
- data/lib/timber/events/http_client_request.rb +0 -65
- data/lib/timber/events/http_client_response.rb +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbf04b7803b278fe7386975994f5c16d019b125b
|
4
|
+
data.tar.gz: 27b112405610ebb0b2c948ba4e1ae0f698fd5ac0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a474845f4dc5c7c328d860458ce93d5a2769cd850ebb7117cf442662565ab654e1c57f3a209037d43f80a8f5b0b27e54cebfb581f6505aad8f0939c8e14285a1
|
7
|
+
data.tar.gz: 0f60bc9a972900139d780a22c9c10c0e35afaa46689c5cc8a4bacd22ee1db454fbb34f3ef003d5a92a070255c7f309d0c2fcd3e38e4ffce6c48bfbdff9de2f96
|
data/README.md
CHANGED
@@ -1,41 +1,28 @@
|
|
1
|
-
# 🌲 Timber -
|
1
|
+
# 🌲 Timber - Log Better. Solve Problems Faster.
|
2
2
|
|
3
3
|
[![ISC License](https://img.shields.io/badge/license-ISC-ff69b4.svg)](LICENSE.md)
|
4
|
+
[![Hex.pm](https://img.shields.io/hexpm/v/timber.svg?maxAge=18000=plastic)](https://hex.pm/packages/timber)
|
5
|
+
[![Documentation](https://img.shields.io/badge/hexdocs-latest-blue.svg)](https://hexdocs.pm/timber/index.html)
|
4
6
|
[![Build Status](https://travis-ci.org/timberio/timber-ruby.svg?branch=master)](https://travis-ci.org/timberio/timber-ruby)
|
5
|
-
[![Code Climate](https://codeclimate.com/github/timberio/timber-ruby/badges/gpa.svg)](https://codeclimate.com/github/timberio/timber-ruby)
|
6
|
-
[![View docs](https://img.shields.io/badge/docs-viewdocs-blue.svg?style=flat-square "Viewdocs")](http://www.rubydoc.info/github/timberio/timber-ruby)
|
7
|
-
|
8
|
-
* [Timber website](https://timber.io)
|
9
|
-
* [Timber docs](https://timber.io/docs)
|
10
|
-
* [Library docs](http://www.rubydoc.info/github/timberio/timber-ruby)
|
11
|
-
* [Support](mailto:support@timber.io)
|
12
|
-
|
13
7
|
|
14
8
|
## Overview
|
15
9
|
|
16
|
-
Timber
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
1. **Easy setup.** - `bundle exec timber install`, [get setup in seconds](#installation).
|
21
|
-
|
22
|
-
2. **Automatically structures yours logs.** - Third-party and in-app logs are all structured
|
23
|
-
in a consistent format. See [how it works](#how-it-works) below.
|
10
|
+
[Timber](https://timber.io) is a logging platform with one major difference: Instead of parsing,
|
11
|
+
which relies on unreadable, unpredictable, hard to use text logs, Timber integrates directly with
|
12
|
+
your application, producing rich structured events containing metadata and context you couldn't
|
13
|
+
capture otherwise. It fundamentally changes the way you use your logs.
|
24
14
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
4. **Pairs with a modern structured-logging console.** - Designed specifically for structured data,
|
29
|
-
hosted, instantly usable, tail users, trace requests.
|
30
|
-
[Checkout the docs](https://timber.io/docs/app/tutorials/).
|
15
|
+
1. [**Easy setup** - `mix timber.install`](#installation)
|
16
|
+
2. [**Seamlessly integrates with popular libraries and frameworks**](#jibber-jabber)
|
17
|
+
3. [**Modern fast console, designed specifically for your application**](#the-timber-console)
|
31
18
|
|
32
19
|
|
33
20
|
## Installation
|
34
21
|
|
35
|
-
1. In `Gemfile`, add the `timber` gem:
|
22
|
+
1. In your `Gemfile`, add the `timber` gem:
|
36
23
|
|
37
24
|
```ruby
|
38
|
-
gem 'timber', '~> 2.
|
25
|
+
gem 'timber', '~> 2.1'
|
39
26
|
```
|
40
27
|
|
41
28
|
2. In your `shell`, run `bundle install`
|
@@ -43,95 +30,6 @@ focusing on logging.
|
|
43
30
|
3. In your `shell`, run `bundle exec timber install`
|
44
31
|
|
45
32
|
|
46
|
-
## How it works
|
47
|
-
|
48
|
-
Let's start with an example. Timber turns this production log line:
|
49
|
-
|
50
|
-
```
|
51
|
-
I, [2017-06-04T18:04:53.653812 #42348] INFO -- : [my.host.com] [df88dbaa-50fd-4178-85d7-d66279ea33b6] [192.32.23.12] [bfa8242cd9733bf0211e334be203f0d0] Sent 200 in 45.2ms
|
52
|
-
```
|
53
|
-
|
54
|
-
Into a structured [`http_server_response` event](https://timber.io/docs/ruby/events-and-context/http-server-response-event/).
|
55
|
-
|
56
|
-
```
|
57
|
-
Sent 200 in 45.2ms @metadata {"dt": "2017-02-02T01:33:21.154345Z", "level": "info", "context": {"http": {"method": "GET", "path": "/path", "remote_addr": "192.32.23.12", "request_id": "df88dbaa-50fd-4178-85d7-d66279ea33b6"}, "session": {"id": "bfa8242cd9733bf0211e334be203f0d0"}, "system": {"hostname": "my.host.com", "pid": "254354"}, "user": {"id": 1, "name": "Ben Johnson", "email": "bens@email.com"}}, "event": {"http_server_response": {"status": 200, "time_ms": 45.2}}}
|
58
|
-
```
|
59
|
-
|
60
|
-
Notice that instead of completely replacing your log messages,
|
61
|
-
Timber _augments_ your logs with structured metadata. Turning them into
|
62
|
-
[rich events with context](https://timber.io/docs/ruby/events-and-context) without sacrificing
|
63
|
-
readability. And you have [complete control over which data is captured](#configuration).
|
64
|
-
|
65
|
-
This is all accomplished by using the
|
66
|
-
[Timber::Logger](http://www.rubydoc.info/github/timberio/timber-ruby/Timber/Logger):
|
67
|
-
|
68
|
-
```ruby
|
69
|
-
logger = Timber::Logger.new(STDOUT)
|
70
|
-
logger.info("Sent 200 in 45.2ms")
|
71
|
-
```
|
72
|
-
|
73
|
-
Here's a better look at the metadata:
|
74
|
-
|
75
|
-
```js
|
76
|
-
{
|
77
|
-
"dt": "2017-02-02T01:33:21.154345Z",
|
78
|
-
"level": "info",
|
79
|
-
"context": {
|
80
|
-
"http": {
|
81
|
-
"method": "GET",
|
82
|
-
"path": "/path",
|
83
|
-
"remote_addr": "192.32.23.12",
|
84
|
-
"request_id": "abcd1234"
|
85
|
-
},
|
86
|
-
"session": {
|
87
|
-
"id": "bfa8242cd9733bf0211e334be203f0d0"
|
88
|
-
},
|
89
|
-
"system": {
|
90
|
-
"hostname": "1.server.com",
|
91
|
-
"pid": "254354"
|
92
|
-
},
|
93
|
-
"user": { // user identifiable logs :O
|
94
|
-
"id": 1,
|
95
|
-
"name": "Ben Johnson",
|
96
|
-
"email": "bens@email.com"
|
97
|
-
},
|
98
|
-
},
|
99
|
-
"event": {
|
100
|
-
"http_server_response": {
|
101
|
-
"status": 200,
|
102
|
-
"time_ms": 45.2
|
103
|
-
}
|
104
|
-
}
|
105
|
-
}
|
106
|
-
```
|
107
|
-
|
108
|
-
This structure isn't arbitrary either, it follows the
|
109
|
-
[simple log event JSON schema](https://github.com/timberio/log-event-json-schema), which
|
110
|
-
formalizes the data structure, creates a contract with downstream consumers, and
|
111
|
-
improves stability.
|
112
|
-
|
113
|
-
So what can you do with this data?
|
114
|
-
|
115
|
-
1. [**Tail a user** - `user.id:1`](https://timber.io/docs/app/tutorials/tail-a-user/)
|
116
|
-
2. [**Trace a request** - `http.request_id:abcd1234`](https://timber.io/docs/app/tutorials/view-in-request-context/)
|
117
|
-
3. **Narrow by host** - `system.hostname:1.server.com`
|
118
|
-
4. **View slow responses** - `http_server_response.time_ms:>=1000`
|
119
|
-
5. **Filter by log level** - `level:error`
|
120
|
-
6. **Quickly find exceptions** - `is:exception`
|
121
|
-
|
122
|
-
For a complete overview, see the [Timber for Ruby docs](https://timber.io/docs/ruby/overview/).
|
123
|
-
|
124
|
-
|
125
|
-
## Third-party integrations
|
126
|
-
|
127
|
-
1. **Rails**: Structures ([HTTP requests](https://timber.io/docs/ruby/events-and-context/http-server-request-event/), [HTTP respones](https://timber.io/docs/ruby/events-and-context/http-server-response-event/), [controller calls](https://timber.io/docs/ruby/events-and-context/controller-call-event/), [template renders](https://timber.io/docs/ruby/events-and-context/template-render-event/), and [sql queries](https://timber.io/docs/ruby/events-and-context/sql-query-event/)).
|
128
|
-
2. **Rack**: Structures [exceptions](https://timber.io/docs/ruby/events-and-context/exception-event/), captures [HTTP context](https://timber.io/docs/ruby/events-and-context/http-context/), captures [user context](https://timber.io/docs/ruby/events-and-context/user-context/), captures [session context](https://timber.io/docs/ruby/events-and-context/session-context/).
|
129
|
-
3. **Devise, Omniauth, Clearance**: captures [user context](https://timber.io/docs/ruby/events-and-context/user-context/)
|
130
|
-
5. **Heroku**: Captures [release context](https://timber.io/docs/ruby/events-and-context/release-context/) via [Heroku dyno metadata](https://devcenter.heroku.com/articles/dyno-metadata).
|
131
|
-
|
132
|
-
...and more. Timber will continue to evolve and support more libraries.
|
133
|
-
|
134
|
-
|
135
33
|
## Usage
|
136
34
|
|
137
35
|
<details><summary><strong>Basic logging</strong></summary><p>
|
@@ -139,7 +37,6 @@ For a complete overview, see the [Timber for Ruby docs](https://timber.io/docs/r
|
|
139
37
|
Use the `Timber::Logger` just like you would `::Logger`:
|
140
38
|
|
141
39
|
```ruby
|
142
|
-
logger = Timber::Logger.new(STDOUT)
|
143
40
|
logger.info("My log message") # use warn, error, debug, etc.
|
144
41
|
|
145
42
|
# => My log message @metadata {"level": "info", "context": {...}}
|
@@ -155,7 +52,6 @@ Custom events allow you to extend beyond events already defined in
|
|
155
52
|
the [`Timber::Events`](lib/timber/events) namespace.
|
156
53
|
|
157
54
|
```ruby
|
158
|
-
logger = Timber::Logger.new(STDOUT)
|
159
55
|
logger.warn "Payment rejected", payment_rejected: {customer_id: "abcd1234", amount: 100, reason: "Card expired"}
|
160
56
|
|
161
57
|
# => Payment rejected @metadata {"level": "warn", "event": {"payment_rejected": {"customer_id": "abcd1234", "amount": 100, "reason": "Card expired"}}, "context": {...}}
|
@@ -163,7 +59,7 @@ logger.warn "Payment rejected", payment_rejected: {customer_id: "abcd1234", amou
|
|
163
59
|
|
164
60
|
* Notice the `:payment_rejected` root key. Timber will classify this event as such.
|
165
61
|
* In the [Timber console](https://app.timber.io) use the query: `type:payment_rejected` or `payment_rejected.amount:>100`.
|
166
|
-
* See more details on our [custom events docs page](https://timber.io/docs/ruby/custom-events/)
|
62
|
+
* See more details on our [custom events docs page](https://timber.io/docs/ruby/usage/custom-events/)
|
167
63
|
|
168
64
|
---
|
169
65
|
|
@@ -177,7 +73,6 @@ Custom contexts allow you to extend beyond contexts already defined in
|
|
177
73
|
the [`Timber::Contexts`](lib/timber/contexts) namespace.
|
178
74
|
|
179
75
|
```ruby
|
180
|
-
logger = Timber::Logger.new(STDOUT)
|
181
76
|
logger.with_context(build: {version: "1.0.0"}) do
|
182
77
|
logger.info("My log message")
|
183
78
|
end
|
@@ -187,7 +82,7 @@ end
|
|
187
82
|
|
188
83
|
* Notice the `:build` root key. Timber will classify this context as such.
|
189
84
|
* In the [Timber console](https://app.timber.io) use queries like: `build.version:1.0.0`
|
190
|
-
* See more details on our [custom contexts docs page](https://timber.io/docs/ruby/custom-contexts/)
|
85
|
+
* See more details on our [custom contexts docs page](https://timber.io/docs/ruby/usage/custom-contexts/)
|
191
86
|
|
192
87
|
---
|
193
88
|
|
@@ -204,7 +99,6 @@ Here's a timing example. Notice how Timber automatically calculates the time and
|
|
204
99
|
to the message.
|
205
100
|
|
206
101
|
```ruby
|
207
|
-
logger = Timber::Logger.new(STDOUT)
|
208
102
|
timer = Timber::Timer.start
|
209
103
|
# ... code to time ...
|
210
104
|
logger.info("Processed background job", background_job: {time_ms: timer})
|
@@ -221,7 +115,6 @@ logger.info("Processed background job", background_job: {time_ms: 45.6})
|
|
221
115
|
Lastly, metrics aren't limited to timings. You can capture any metric you want:
|
222
116
|
|
223
117
|
```ruby
|
224
|
-
logger = Timber::Logger.new(STDOUT)
|
225
118
|
logger.info("Credit card charged", credit_card_charge: {amount: 123.23})
|
226
119
|
|
227
120
|
# => Credit card charged @metadata {"level": "info", "event": {"credit_card_charge": {"amount": 123.23}}}
|
@@ -331,7 +224,6 @@ Simply set the formatter like you would with any other logger:
|
|
331
224
|
|
332
225
|
```ruby
|
333
226
|
# This is set in your various environment files
|
334
|
-
logger = Timber::Logger.new(STDOUT)
|
335
227
|
logger.formatter = Timber::Logger::JSONFormatter.new
|
336
228
|
```
|
337
229
|
|
@@ -407,60 +299,68 @@ All variables are optional, but at least one must be present.
|
|
407
299
|
|
408
300
|
## Jibber-Jabber
|
409
301
|
|
410
|
-
<details><summary><strong>Which events
|
302
|
+
<details><summary><strong>Which log events does Timber structure for me?</strong></summary><p>
|
411
303
|
|
412
|
-
Out of the box you get everything in the
|
413
|
-
[`Timber::Events`](http://www.rubydoc.info/github/timberio/timber-ruby/Timber/Events) namespace.
|
304
|
+
Out of the box you get everything in the [`Timber.Events`](lib/timber/events) namespace.
|
414
305
|
|
415
|
-
We also add context to every log, everything in the
|
416
|
-
[`Timber::Contexts`](http://www.rubydoc.info/github/timberio/timber-ruby/Timber/Contexts)
|
306
|
+
We also add context to every log, everything in the [`Timber.Contexts`](lib/timber/contexts)
|
417
307
|
namespace. Context is structured data representing the current environment when the log line
|
418
|
-
was written. It is included in every log line. Think of it like join data for your logs.
|
419
|
-
how Timber is able to accomplished tailing users (`context.user.id:1`).
|
420
|
-
|
421
|
-
Lastly, you can checkout how we capture these events in
|
422
|
-
[`Timber::Integrations`](lib/timber/integrations).
|
308
|
+
was written. It is included in every log line. Think of it like join data for your logs.
|
423
309
|
|
424
310
|
---
|
425
311
|
|
426
312
|
</p></details>
|
427
313
|
|
428
|
-
<details><summary><strong>
|
314
|
+
<details><summary><strong>What about my current log statements?</strong></summary><p>
|
429
315
|
|
430
|
-
|
431
|
-
|
432
|
-
of things to note:
|
316
|
+
They'll continue to work as expected. Timber adheres strictly to the default `Logger` interface
|
317
|
+
and will never deviate in *any* way.
|
433
318
|
|
434
|
-
|
435
|
-
|
436
|
-
even silence logs that are not of value to you. (see [configuration](#configuration) for examples).
|
437
|
-
2. Timber lets you pick exactly which events and contexts you want.
|
438
|
-
(see [configuration](#configuration) for examples)
|
439
|
-
3. Your logging provider should be compressing your data and charging you accordingly. Log data
|
440
|
-
is notoriously repetitive, and the context Timber generates is repetitive.
|
441
|
-
Because of compression we've seen somes apps only incur a ~15% increase in data size.
|
319
|
+
In fact, traditional log statements for non-meaningful events, debug statements, etc, are
|
320
|
+
encouraged. In cases where the data is meaningful, consider [logging a custom event](#usage).
|
442
321
|
|
443
|
-
|
322
|
+
</p></details>
|
444
323
|
|
445
|
-
|
324
|
+
<details><summary><strong>When to use metadata or events?</strong></summary><p>
|
446
325
|
|
447
|
-
|
326
|
+
At it's basic level, both metadata and events serve the same purpose: they add structured
|
327
|
+
data to your logs. And anyone that's implemented structured logging know's this can quickly get
|
328
|
+
out of hand. This is why we created events. Here's how we recommend using them:
|
448
329
|
|
449
|
-
|
330
|
+
1. Use `events` when the log cleanly maps to an event that you'd like to alert on, graph, or use
|
331
|
+
in a meaningful way. Typically something that is core to your business or application.
|
332
|
+
2. Use metadata for debugging purposes; when you simply want additional insight without
|
333
|
+
polluting the message.
|
450
334
|
|
451
|
-
|
452
|
-
Your previous logger calls will work as they always do. Just swap in `Timber::Logger` and
|
453
|
-
you're good to go.
|
335
|
+
### Example 1: Logging that a payment was rejected
|
454
336
|
|
455
|
-
|
456
|
-
|
337
|
+
This is clearly an event that is meaningful to your business. You'll probably want to alert and
|
338
|
+
graph this data. So let's log it as an official event:
|
339
|
+
|
340
|
+
```ruby
|
341
|
+
logger.info("Payment rejected", payment_rejected: {customer_id: "xiaus1934", amount: 1900, currency: "USD"})
|
342
|
+
```
|
343
|
+
|
344
|
+
### Example 2: Logging that an email was changed
|
345
|
+
|
346
|
+
This is definitely log worthy, but not something that is core to your business or application.
|
347
|
+
Instead of an event, use metadata:
|
348
|
+
|
349
|
+
```ruby
|
350
|
+
logger.info("Email successfully changed", old_email: old_email, new_email: new_email)
|
351
|
+
```
|
457
352
|
|
458
353
|
---
|
459
354
|
|
460
355
|
</p></details>
|
461
356
|
|
462
|
-
|
357
|
+
|
358
|
+
## The Timber Console
|
359
|
+
|
360
|
+
[![Timber Console](http://files.timber.io/images/readme-interface7.gif)](https://app.timber.io)
|
361
|
+
|
362
|
+
## Your Moment of Zen
|
463
363
|
|
464
364
|
<p align="center" style="background: #221f40;">
|
465
|
-
<a href="http://github.com/timberio/timber-
|
466
|
-
</p>
|
365
|
+
<a href="http://github.com/timberio/timber-ruby"><img src="http://files.timber.io/images/readme-log-truth.png" height="947" /></a>
|
366
|
+
</p>
|
data/lib/timber/config.rb
CHANGED
@@ -36,7 +36,7 @@ module Timber
|
|
36
36
|
|
37
37
|
# @private
|
38
38
|
def initialize
|
39
|
-
@http_body_limit =
|
39
|
+
@http_body_limit = 2048
|
40
40
|
end
|
41
41
|
|
42
42
|
# Convenience method for logging debug statements to the debug logger
|
@@ -130,16 +130,15 @@ module Timber
|
|
130
130
|
@http_header_filters ||= []
|
131
131
|
end
|
132
132
|
|
133
|
-
# Truncates captured HTTP bodies to this specified limit. The default is `
|
134
|
-
# If you want to capture more data, you can raise this to a maximum of `
|
135
|
-
# or lower this to be more efficient with data. `
|
136
|
-
# idea of the body content.
|
137
|
-
# network throughput.
|
133
|
+
# Truncates captured HTTP bodies to this specified limit. The default is `2048`.
|
134
|
+
# If you want to capture more data, you can raise this to a maximum of `8192`,
|
135
|
+
# or lower this to be more efficient with data. `2048` characters should give you a good
|
136
|
+
# idea of the body content.
|
138
137
|
#
|
139
138
|
# @example Rails
|
140
|
-
# config.timber.http_body_limit =
|
139
|
+
# config.timber.http_body_limit = 2048
|
141
140
|
# @example Everything else
|
142
|
-
# Timber::Config.instance.http_body_limit =
|
141
|
+
# Timber::Config.instance.http_body_limit = 2048
|
143
142
|
def http_body_limit=(value)
|
144
143
|
@http_body_limit = value
|
145
144
|
end
|
@@ -14,15 +14,15 @@ module Timber
|
|
14
14
|
module Rack
|
15
15
|
extend self
|
16
16
|
|
17
|
-
# Convenience method for accessing the {Timber::Integrations::Rack::
|
17
|
+
# Convenience method for accessing the {Timber::Integrations::Rack::ErrorEvent}
|
18
18
|
# middleware class specific configuration. See {Timber::Integrations::Rack::ExceptionEvent}
|
19
19
|
# for a list of methods available.
|
20
20
|
#
|
21
21
|
# @example
|
22
22
|
# config = Timber::Config.instance
|
23
|
-
# config.integrations.rack.
|
24
|
-
def
|
25
|
-
Timber::Integrations::Rack::
|
23
|
+
# config.integrations.rack.error_event.enabled = false
|
24
|
+
def error_event
|
25
|
+
Timber::Integrations::Rack::ErrorEvent
|
26
26
|
end
|
27
27
|
|
28
28
|
# Convenience method for accessing the {Timber::Integrations::Rack::HTTPContext}
|
@@ -9,7 +9,7 @@ module Timber
|
|
9
9
|
class Runtime < Context
|
10
10
|
@keyspace = :runtime
|
11
11
|
|
12
|
-
attr_reader :application, :class_name, :file, :function, :line, :module_name
|
12
|
+
attr_reader :application, :class_name, :file, :function, :line, :module_name, :vm_pid
|
13
13
|
|
14
14
|
def initialize(attributes)
|
15
15
|
@application = attributes[:application]
|
@@ -18,12 +18,13 @@ module Timber
|
|
18
18
|
@function = attributes[:function]
|
19
19
|
@line = attributes[:line]
|
20
20
|
@module_name = attributes[:module_name]
|
21
|
+
@vm_pid = attributes[:vm_pid]
|
21
22
|
end
|
22
23
|
|
23
24
|
# Builds a hash representation containing simple objects, suitable for serialization (JSON).
|
24
25
|
def as_json(_options = {})
|
25
26
|
{application: application, class_name: class_name, file: file, function: function,
|
26
|
-
line: line, module_name: module_name}
|
27
|
+
line: line, module_name: module_name, vm_pid: vm_pid}
|
27
28
|
end
|
28
29
|
end
|
29
30
|
end
|
@@ -15,12 +15,12 @@ module Timber
|
|
15
15
|
def initialize(attributes)
|
16
16
|
@hostname = attributes[:hostname]
|
17
17
|
@pid = attributes[:pid]
|
18
|
-
@pid = @pid
|
18
|
+
@pid = Timber::Util::Object.try(@pid, :to_i)
|
19
19
|
end
|
20
20
|
|
21
21
|
# Builds a hash representation containing simple objects, suitable for serialization (JSON).
|
22
22
|
def as_json(_options = {})
|
23
|
-
{hostname: hostname, pid: Timber::Util::Object.try(pid, :
|
23
|
+
{hostname: hostname, pid: Timber::Util::Object.try(pid, :to_i)}
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -158,6 +158,11 @@ module Timber
|
|
158
158
|
system_context = Contexts::System.new(hostname: hostname, pid: pid)
|
159
159
|
add_to!(new_hash, system_context)
|
160
160
|
|
161
|
+
# Runtime context
|
162
|
+
thread_object_id = Thread.current.object_id
|
163
|
+
runtime_context = Contexts::System.new(vm_pid: thread_object_id)
|
164
|
+
add_to!(new_hash, runtime_context)
|
165
|
+
|
161
166
|
new_hash
|
162
167
|
end
|
163
168
|
|
data/lib/timber/events.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
require "timber/events/controller_call"
|
2
2
|
require "timber/events/custom"
|
3
|
-
require "timber/events/
|
4
|
-
require "timber/events/
|
5
|
-
require "timber/events/
|
6
|
-
require "timber/events/http_server_request"
|
7
|
-
require "timber/events/http_server_response"
|
3
|
+
require "timber/events/error"
|
4
|
+
require "timber/events/http_request"
|
5
|
+
require "timber/events/http_response"
|
8
6
|
require "timber/events/sql_query"
|
9
7
|
require "timber/events/template_render"
|
10
8
|
|
@@ -10,6 +10,7 @@ module Timber
|
|
10
10
|
# @note This event should be installed automatically through integrations,
|
11
11
|
# such as the {Integrations::ActionController::LogSubscriber} integration.
|
12
12
|
class ControllerCall < Timber::Event
|
13
|
+
PARAMS_JSON_MAX_BYTES = 8192.freeze
|
13
14
|
PASSWORD_NAME = 'password'.freeze
|
14
15
|
|
15
16
|
attr_reader :controller, :action, :params, :format
|
@@ -44,11 +45,12 @@ module Timber
|
|
44
45
|
|
45
46
|
private
|
46
47
|
def params_json
|
47
|
-
@params_json ||=
|
48
|
-
nil
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
@params_json ||=
|
49
|
+
if params.nil? || params == {}
|
50
|
+
nil
|
51
|
+
else
|
52
|
+
params.to_json.byteslice(0, PARAMS_JSON_MAX_BYTES)
|
53
|
+
end
|
52
54
|
end
|
53
55
|
|
54
56
|
def sanitize_params(params)
|
@@ -3,38 +3,39 @@ require "timber/util"
|
|
3
3
|
|
4
4
|
module Timber
|
5
5
|
module Events
|
6
|
-
# The
|
6
|
+
# The error event is used to track errors and exceptions.
|
7
7
|
#
|
8
8
|
# @note This event should be installed automatically through integrations,
|
9
9
|
# such as the {Integrations::ActionDispatch::DebugExceptions} integration.
|
10
|
-
class
|
11
|
-
|
10
|
+
class Error < Timber::Event
|
11
|
+
MAX_MESSAGE_BYTES = 8192.freeze
|
12
|
+
|
13
|
+
attr_reader :name, :error_message, :backtrace
|
12
14
|
|
13
15
|
def initialize(attributes)
|
14
16
|
@name = attributes[:name] || raise(ArgumentError.new(":name is required"))
|
15
|
-
|
17
|
+
|
18
|
+
@error_message = attributes[:error_message] || raise(ArgumentError.new(":error_message is required"))
|
19
|
+
@error_message = @error_message.byteslice(0, MAX_MESSAGE_BYTES)
|
16
20
|
|
17
21
|
backtrace = attributes[:backtrace]
|
18
|
-
if backtrace.nil?
|
19
|
-
|
22
|
+
if !backtrace.nil? && backtrace != []
|
23
|
+
@backtrace = backtrace[0..9].collect { |line| parse_backtrace_line(line) }
|
20
24
|
end
|
21
|
-
|
22
|
-
# 10 items max
|
23
|
-
@backtrace = backtrace[0..9].collect { |line| parse_backtrace_line(line) }
|
24
25
|
end
|
25
26
|
|
26
27
|
def to_hash
|
27
|
-
{name: name, message:
|
28
|
+
{name: name, message: error_message, backtrace: backtrace}
|
28
29
|
end
|
29
30
|
alias to_h to_hash
|
30
31
|
|
31
32
|
# Builds a hash representation containing simple objects, suitable for serialization (JSON).
|
32
33
|
def as_json(_options = {})
|
33
|
-
{:
|
34
|
+
{:error => to_hash}
|
34
35
|
end
|
35
36
|
|
36
37
|
def message
|
37
|
-
"#{name} (#{
|
38
|
+
"#{name} (#{error_message})"
|
38
39
|
end
|
39
40
|
|
40
41
|
private
|
@@ -8,19 +8,19 @@ module Timber
|
|
8
8
|
#
|
9
9
|
# @note This event should be installed automatically through integrations,
|
10
10
|
# such as the {Integrations::ActionController::LogSubscriber} integration.
|
11
|
-
class
|
11
|
+
class HTTPRequest < Timber::Event
|
12
12
|
attr_reader :body, :headers, :host, :method, :path, :port, :query_string, :request_id,
|
13
|
-
:scheme
|
13
|
+
:scheme, :service_name
|
14
14
|
|
15
15
|
def initialize(attributes)
|
16
16
|
@body = attributes[:body] && Util::HTTPEvent.normalize_body(attributes[:body])
|
17
17
|
@headers = Util::HTTPEvent.normalize_headers(attributes[:headers])
|
18
|
-
@host = attributes[:host]
|
18
|
+
@host = attributes[:host]
|
19
19
|
@method = Util::HTTPEvent.normalize_method(attributes[:method]) || raise(ArgumentError.new(":method is required"))
|
20
20
|
@path = attributes[:path]
|
21
21
|
@port = attributes[:port]
|
22
22
|
@query_string = Util::HTTPEvent.normalize_query_string(attributes[:query_string])
|
23
|
-
@scheme = attributes[:scheme]
|
23
|
+
@scheme = attributes[:scheme]
|
24
24
|
@request_id = attributes[:request_id]
|
25
25
|
end
|
26
26
|
|
@@ -32,7 +32,7 @@ module Timber
|
|
32
32
|
|
33
33
|
# Builds a hash representation containing simple objects, suitable for serialization (JSON).
|
34
34
|
def as_json(_options = {})
|
35
|
-
{:
|
35
|
+
{:http_request => to_hash}
|
36
36
|
end
|
37
37
|
|
38
38
|
def message
|
@@ -8,8 +8,8 @@ module Timber
|
|
8
8
|
#
|
9
9
|
# @note This event should be installed automatically through integrations,
|
10
10
|
# such as the {Integrations::ActionController::LogSubscriber} integration.
|
11
|
-
class
|
12
|
-
attr_reader :body, :headers, :http_context, :request_id, :status, :time_ms
|
11
|
+
class HTTPResponse < Timber::Event
|
12
|
+
attr_reader :body, :headers, :http_context, :request_id, :service_name, :status, :time_ms
|
13
13
|
|
14
14
|
def initialize(attributes)
|
15
15
|
@body = attributes[:body] && Util::HTTPEvent.normalize_body(attributes[:body])
|
@@ -28,7 +28,7 @@ module Timber
|
|
28
28
|
|
29
29
|
# Builds a hash representation containing simple objects, suitable for serialization (JSON).
|
30
30
|
def as_json(_options = {})
|
31
|
-
{:
|
31
|
+
{:http_response => to_hash}
|
32
32
|
end
|
33
33
|
|
34
34
|
# Returns the human readable log message for this event.
|
@@ -7,10 +7,13 @@ module Timber
|
|
7
7
|
# @note This event should be installed automatically through integrations,
|
8
8
|
# such as the {Integrations::ActiveRecord::LogSubscriber} integration.
|
9
9
|
class SQLQuery < Timber::Event
|
10
|
+
MAX_QUERY_BYTES = 1024.freeze
|
11
|
+
|
10
12
|
attr_reader :sql, :time_ms, :message
|
11
13
|
|
12
14
|
def initialize(attributes)
|
13
15
|
@sql = attributes[:sql] || raise(ArgumentError.new(":sql is required"))
|
16
|
+
@sql = @sql.byteslice(0, MAX_QUERY_BYTES)
|
14
17
|
@time_ms = attributes[:time_ms] || raise(ArgumentError.new(":time_ms is required"))
|
15
18
|
@time_ms = @time_ms.round(6)
|
16
19
|
@message = attributes[:message] || raise(ArgumentError.new(":message is required"))
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require "timber/integration"
|
2
|
-
require "timber/integrations/rack/
|
2
|
+
require "timber/integrations/rack/error_event"
|
3
3
|
require "timber/integrations/action_dispatch/debug_exceptions"
|
4
4
|
|
5
5
|
module Timber
|
@@ -10,7 +10,7 @@ module Timber
|
|
10
10
|
# works as expected.
|
11
11
|
module ActionDispatch
|
12
12
|
def self.enabled?
|
13
|
-
Rack::
|
13
|
+
Rack::ErrorEvent.enabled?
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.integrate!
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require "timber/integrations/rack/
|
1
|
+
require "timber/integrations/rack/error_event"
|
2
2
|
require "timber/integrations/rack/http_context"
|
3
3
|
require "timber/integrations/rack/http_events"
|
4
4
|
require "timber/integrations/rack/session_context"
|
@@ -9,7 +9,7 @@ module Timber
|
|
9
9
|
module Rack
|
10
10
|
# Enable / disable all Rack middlewares with a single setting.
|
11
11
|
def self.enabled=(value)
|
12
|
-
|
12
|
+
ErrorEvent.enabled = value
|
13
13
|
HTTPContext.enabled = value
|
14
14
|
HTTPEvents.enabled = value
|
15
15
|
SessionContext.enabled = value
|
@@ -20,7 +20,7 @@ module Timber
|
|
20
20
|
# context are added first so that context is included in subsequent log lines.
|
21
21
|
def self.middlewares
|
22
22
|
@middlewares ||= [HTTPContext, SessionContext, UserContext,
|
23
|
-
HTTPEvents,
|
23
|
+
HTTPEvents, ErrorEvent].select(&:enabled?)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -7,16 +7,16 @@ rescue Exception
|
|
7
7
|
end
|
8
8
|
|
9
9
|
require "timber/config"
|
10
|
-
require "timber/events/
|
10
|
+
require "timber/events/error"
|
11
11
|
require "timber/integrations/rack/middleware"
|
12
12
|
require "timber/util"
|
13
13
|
|
14
14
|
module Timber
|
15
15
|
module Integrations
|
16
16
|
module Rack
|
17
|
-
# A Rack middleware that is reponsible for capturing
|
18
|
-
# {Timber::Events::
|
19
|
-
class
|
17
|
+
# A Rack middleware that is reponsible for capturing exception and error events
|
18
|
+
# {Timber::Events::Error}.
|
19
|
+
class ErrorEvent < Middleware
|
20
20
|
# We determine this when the app loads to avoid the overhead on a per request basis.
|
21
21
|
EXCEPTION_WRAPPER_TAKES_CLEANER = if Gem.loaded_specs["rails"]
|
22
22
|
Gem.loaded_specs["rails"].version >= Gem::Version.new('5.0.0')
|
@@ -31,9 +31,9 @@ module Timber
|
|
31
31
|
Config.instance.logger.fatal do
|
32
32
|
backtrace = extract_backtrace(env, exception)
|
33
33
|
|
34
|
-
Events::
|
34
|
+
Events::Error.new(
|
35
35
|
name: exception.class.name,
|
36
|
-
|
36
|
+
error_message: exception.message,
|
37
37
|
backtrace: backtrace
|
38
38
|
)
|
39
39
|
end
|
@@ -3,15 +3,15 @@ require "set"
|
|
3
3
|
require "timber/config"
|
4
4
|
require "timber/contexts/http"
|
5
5
|
require "timber/current_context"
|
6
|
-
require "timber/events/
|
7
|
-
require "timber/events/
|
6
|
+
require "timber/events/http_request"
|
7
|
+
require "timber/events/http_response"
|
8
8
|
require "timber/integrations/rack/middleware"
|
9
9
|
|
10
10
|
module Timber
|
11
11
|
module Integrations
|
12
12
|
module Rack
|
13
13
|
# A Rack middleware that is reponsible for capturing and logging HTTP server requests and
|
14
|
-
# response events. The {Events::
|
14
|
+
# response events. The {Events::HTTPRequest} and {Events::HTTPResponse} events
|
15
15
|
# respectively.
|
16
16
|
class HTTPEvents < Middleware
|
17
17
|
class << self
|
@@ -26,7 +26,7 @@ module Timber
|
|
26
26
|
# helpful and redundant to the body captured here.
|
27
27
|
#
|
28
28
|
# If you opt to capture bodies, you can also truncate the size to reduce the data
|
29
|
-
# captured. See {Events::
|
29
|
+
# captured. See {Events::HTTPRequest}.
|
30
30
|
#
|
31
31
|
# @example
|
32
32
|
# Timber::Integrations::Rack::HTTPEvents.capture_request_body = true
|
@@ -39,7 +39,7 @@ module Timber
|
|
39
39
|
@capture_request_body == true
|
40
40
|
end
|
41
41
|
|
42
|
-
# Just like {#capture_request_body=} but for the {Events::
|
42
|
+
# Just like {#capture_request_body=} but for the {Events::HTTPResponse} event.
|
43
43
|
# Please see {#capture_request_body=} for more details. The documentation there also
|
44
44
|
# applies here.
|
45
45
|
def capture_response_body=(value)
|
@@ -67,9 +67,9 @@ module Timber
|
|
67
67
|
#
|
68
68
|
# Get "/" sent 200 OK in 79ms
|
69
69
|
#
|
70
|
-
# The single event is still a {Timber::Events::
|
70
|
+
# The single event is still a {Timber::Events::HTTPResponse} event. Because
|
71
71
|
# we capture HTTP context, you still get the HTTP details, but you will not get
|
72
|
-
# all of the request details that the {Timber::Events::
|
72
|
+
# all of the request details that the {Timber::Events::HTTPRequest} event would
|
73
73
|
# provide.
|
74
74
|
#
|
75
75
|
# @example
|
@@ -128,7 +128,7 @@ module Timber
|
|
128
128
|
http_context = CurrentContext.fetch(http_context_key)
|
129
129
|
time_ms = (Time.now - start) * 1000.0
|
130
130
|
|
131
|
-
Events::
|
131
|
+
Events::HTTPResponse.new(
|
132
132
|
headers: headers,
|
133
133
|
http_context: http_context,
|
134
134
|
request_id: request.request_id,
|
@@ -145,7 +145,7 @@ module Timber
|
|
145
145
|
Config.instance.logger.info do
|
146
146
|
event_body = capture_request_body? ? request.body_content : nil
|
147
147
|
|
148
|
-
Events::
|
148
|
+
Events::HTTPRequest.new(
|
149
149
|
body: event_body,
|
150
150
|
headers: request.headers,
|
151
151
|
host: request.host,
|
@@ -164,7 +164,7 @@ module Timber
|
|
164
164
|
time_ms = (Time.now - start) * 1000.0
|
165
165
|
event_body = capture_response_body? ? body : nil
|
166
166
|
|
167
|
-
Events::
|
167
|
+
Events::HTTPResponse.new(
|
168
168
|
body: event_body,
|
169
169
|
headers: headers,
|
170
170
|
request_id: request.request_id,
|
data/lib/timber/log_devices.rb
CHANGED
@@ -214,7 +214,11 @@ module Timber
|
|
214
214
|
def build_http
|
215
215
|
http = Net::HTTP.new(@timber_url.host, @timber_url.port)
|
216
216
|
http.set_debug_output(Config.instance.debug_logger) if Config.instance.debug_logger
|
217
|
-
|
217
|
+
if @timber_url.scheme == 'https'
|
218
|
+
http.use_ssl = true
|
219
|
+
# Verification on Windows fails despite having a valid certificate.
|
220
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
221
|
+
end
|
218
222
|
http.read_timeout = 30
|
219
223
|
http.ssl_timeout = 10
|
220
224
|
http.open_timeout = 10
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Timber
|
2
|
+
module LogDevices
|
3
|
+
# @private
|
4
|
+
#
|
5
|
+
# A log device that writes to multiple IO devices.
|
6
|
+
#
|
7
|
+
# Note, you should not have to instantiate this class directly. Simply pass multiple
|
8
|
+
# arguments to the `Timber::Logger#new` method.
|
9
|
+
#
|
10
|
+
# See the {Timber::Logger#new} for examples.
|
11
|
+
class Multi
|
12
|
+
def initialize(targets)
|
13
|
+
@targets = targets
|
14
|
+
end
|
15
|
+
|
16
|
+
def write(*args)
|
17
|
+
@targets.each { |t| t.write(*args) }
|
18
|
+
@targets.first
|
19
|
+
end
|
20
|
+
|
21
|
+
def sync=(value)
|
22
|
+
@targets.each do |t|
|
23
|
+
if t.respond_to?(:sync=)
|
24
|
+
t.sync = value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def close
|
30
|
+
@targets.each(&:close)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/timber/log_entry.rb
CHANGED
@@ -8,7 +8,8 @@ module Timber
|
|
8
8
|
# `Logger` and the log device that you set it up with.
|
9
9
|
class LogEntry #:nodoc:
|
10
10
|
DT_PRECISION = 6.freeze
|
11
|
-
|
11
|
+
MESSAGE_MAX_BYTES = 8192.freeze
|
12
|
+
SCHEMA = "https://raw.githubusercontent.com/timberio/log-event-json-schema/v3.0.7/schema.json".freeze
|
12
13
|
|
13
14
|
attr_reader :context_snapshot, :event, :level, :message, :progname, :tags, :time, :time_ms
|
14
15
|
|
@@ -31,6 +32,7 @@ module Timber
|
|
31
32
|
# This follows the default behavior set by ::Logger
|
32
33
|
# See: https://github.com/ruby/ruby/blob/trunk/lib/logger.rb#L615
|
33
34
|
@message = message.is_a?(String) ? message : message.inspect
|
35
|
+
@message = @message.byteslice(0, MESSAGE_MAX_BYTES)
|
34
36
|
@tags = options[:tags]
|
35
37
|
@time_ms = options[:time_ms]
|
36
38
|
@context_snapshot = context_snapshot
|
data/lib/timber/logger.rb
CHANGED
@@ -4,7 +4,7 @@ require "msgpack"
|
|
4
4
|
require "timber/config"
|
5
5
|
require "timber/current_context"
|
6
6
|
require "timber/event"
|
7
|
-
require "timber/log_devices
|
7
|
+
require "timber/log_devices"
|
8
8
|
require "timber/log_entry"
|
9
9
|
|
10
10
|
module Timber
|
@@ -185,24 +185,50 @@ module Timber
|
|
185
185
|
include ::ActiveSupport::LoggerThreadSafeLevel if defined?(::ActiveSupport::LoggerThreadSafeLevel)
|
186
186
|
include ::LoggerSilence if defined?(::LoggerSilence)
|
187
187
|
|
188
|
-
# Creates a new Timber::Logger
|
189
|
-
#
|
190
|
-
# a different formatter is easy. For example, if you prefer your logs in JSON.
|
188
|
+
# Creates a new Timber::Logger instance where the passed argument is an IO device. That is,
|
189
|
+
# anything that responds to `#write` and `#close`.
|
191
190
|
#
|
192
|
-
#
|
191
|
+
# Note, this method does *not* accept the same arguments as the standard Ruby `::Logger`.
|
192
|
+
# The Ruby `::Logger` accepts additional options controlling file rotation if the first argument
|
193
|
+
# is a file *name*. This is a design flaw that Timber does not assume. Logging to a file, or
|
194
|
+
# multiple IO devices is demonstrated in the examples below.
|
195
|
+
#
|
196
|
+
# @example Logging to STDOUT
|
193
197
|
# logger = Timber::Logger.new(STDOUT)
|
194
|
-
#
|
195
|
-
|
196
|
-
|
198
|
+
#
|
199
|
+
# @example Logging to the Timber HTTP device
|
200
|
+
# http_device = Timber::LogDevices::HTTP.new("my-timber-api-key")
|
201
|
+
# logger = Timber::Logger.new(http_device)
|
202
|
+
#
|
203
|
+
# @example Logging to a file (with rotation)
|
204
|
+
# file_device = Logger::LogDevice.new("path/to/file.log")
|
205
|
+
# logger = Timber::Logger.new(file_device)
|
206
|
+
#
|
207
|
+
# @example Logging to a file and the Timber HTTP device (multiple log devices)
|
208
|
+
# http_device = Timber::LogDevices::HTTP.new("my-timber-api-key")
|
209
|
+
# file_device = Logger::LogDevice.new("path/to/file.log")
|
210
|
+
# logger = Timber::Logger.new(http_device, file_device)
|
211
|
+
def initialize(*io_devices)
|
212
|
+
io_device = \
|
213
|
+
if io_devices.size == 0
|
214
|
+
raise ArgumentError.new("At least one IO device must be provided when instantiating " +
|
215
|
+
"a Timber::Logger. Ex: Timber::Logger.new(STDOUT).")
|
216
|
+
elsif io_devices.size > 1
|
217
|
+
LogDevices::Multi.new(io_devices)
|
218
|
+
else
|
219
|
+
io_devices.first
|
220
|
+
end
|
221
|
+
|
222
|
+
super(io_device)
|
197
223
|
|
198
224
|
# Ensure we sync STDOUT to avoid buffering
|
199
|
-
if
|
200
|
-
|
225
|
+
if io_device.respond_to?(:"sync=")
|
226
|
+
io_device.sync = true
|
201
227
|
end
|
202
228
|
|
203
229
|
# Set the default formatter. The formatter cannot be set during
|
204
230
|
# initialization, and can be changed with #formatter=.
|
205
|
-
if
|
231
|
+
if io_device.is_a?(LogDevices::HTTP)
|
206
232
|
self.formatter = PassThroughFormatter.new
|
207
233
|
elsif Config.instance.development? || Config.instance.test?
|
208
234
|
self.formatter = MessageOnlyFormatter.new
|
@@ -1,10 +1,9 @@
|
|
1
1
|
module Timber
|
2
2
|
module Util
|
3
|
-
# Utility module for dealing with HTTP events {Events::
|
4
|
-
# {Events::HTTPServerResponse}, {Events::HTTPClientRequest}, {Events::HTTPClientResponse}.
|
3
|
+
# Utility module for dealing with HTTP events {Events::HTTPRequest} and {Events::HTTPResponse}.
|
5
4
|
module HTTPEvent
|
6
5
|
HEADERS_TO_SANITIZE = ['authorization', 'x-amz-security-token'].freeze
|
7
|
-
|
6
|
+
MAX_QUERY_STRING_BYTES = 2048.freeze
|
8
7
|
STRING_CLASS_NAME = 'String'.freeze
|
9
8
|
|
10
9
|
extend self
|
@@ -25,7 +24,7 @@ module Timber
|
|
25
24
|
|
26
25
|
limit = Config.instance.http_body_limit
|
27
26
|
if limit
|
28
|
-
body
|
27
|
+
body.byteslice(0, limit)
|
29
28
|
else
|
30
29
|
body
|
31
30
|
end
|
@@ -63,7 +62,7 @@ module Timber
|
|
63
62
|
query_string = query_string.to_s
|
64
63
|
end
|
65
64
|
|
66
|
-
query_string && query_string
|
65
|
+
query_string && query_string.byteslice(0, MAX_QUERY_STRING_BYTES)
|
67
66
|
end
|
68
67
|
end
|
69
68
|
end
|
data/lib/timber/version.rb
CHANGED
@@ -3,9 +3,9 @@ require "spec_helper"
|
|
3
3
|
describe Timber::Contexts::System, :rails_23 => true do
|
4
4
|
describe ".as_json" do
|
5
5
|
it "should coerce pid into an string" do
|
6
|
-
custom_context = described_class.new(:pid => 1)
|
6
|
+
custom_context = described_class.new(:pid => "1")
|
7
7
|
json = custom_context.as_json()
|
8
|
-
expect(json[:pid]).to eq(
|
8
|
+
expect(json[:pid]).to eq(1)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
-
describe Timber::Events::
|
3
|
+
describe Timber::Events::Error, :rails_23 => true do
|
4
4
|
describe ".initialize" do
|
5
5
|
it "should clean the backtrace" do
|
6
6
|
backtrace = [
|
@@ -8,7 +8,7 @@ describe Timber::Events::Exception, :rails_23 => true do
|
|
8
8
|
"path/to/file2.rb:86:in `function2'"
|
9
9
|
]
|
10
10
|
|
11
|
-
exception_event = described_class.new(name: "RuntimeError",
|
11
|
+
exception_event = described_class.new(name: "RuntimeError", error_message: "Boom", backtrace: backtrace)
|
12
12
|
expect(exception_event.backtrace).to eq([{:file=>"/path/to/file1.rb", :line=>26, :function=>"function1"}, {:file=>"path/to/file2.rb", :line=>86, :function=>"function2"}])
|
13
13
|
end
|
14
14
|
|
@@ -18,7 +18,7 @@ describe Timber::Events::Exception, :rails_23 => true do
|
|
18
18
|
"path/to/file2.rb:86" # function names are optional
|
19
19
|
]
|
20
20
|
|
21
|
-
exception_event = described_class.new(name: "RuntimeError",
|
21
|
+
exception_event = described_class.new(name: "RuntimeError", error_message: "Boom", backtrace: backtrace)
|
22
22
|
expect(exception_event.backtrace).to eq([{:file=>"/path/to/file1.rb", :line=>26, :function=>"function1"}, {:file=>"path/to/file2.rb", :line=>86}])
|
23
23
|
end
|
24
24
|
|
@@ -27,7 +27,7 @@ describe Timber::Events::Exception, :rails_23 => true do
|
|
27
27
|
"malformed"
|
28
28
|
]
|
29
29
|
|
30
|
-
exception_event = described_class.new(name: "RuntimeError",
|
30
|
+
exception_event = described_class.new(name: "RuntimeError", error_message: "Boom", backtrace: backtrace)
|
31
31
|
expect(exception_event.backtrace).to eq([{:file=>"malformed"}])
|
32
32
|
end
|
33
33
|
end
|
@@ -41,7 +41,7 @@ if defined?(::ActionDispatch)
|
|
41
41
|
lines = clean_lines(io.string.split("\n"))
|
42
42
|
expect(lines.length).to eq(3)
|
43
43
|
expect(lines[2]).to start_with('RuntimeError (boom) @metadata {"level":"fatal",')
|
44
|
-
expect(lines[2]).to include("\"event\":{\"
|
44
|
+
expect(lines[2]).to include("\"event\":{\"error\":{\"name\":\"RuntimeError\",\"message\":\"boom\",\"backtrace\":[")
|
45
45
|
end
|
46
46
|
|
47
47
|
# Remove blank lines since Rails does this to space out requests in the logs
|
data/spec/timber/logger_spec.rb
CHANGED
@@ -20,6 +20,15 @@ describe Timber::Logger, :rails_23 => true do
|
|
20
20
|
expect(logger.formatter).to be_kind_of(Timber::Logger::MessageOnlyFormatter)
|
21
21
|
end
|
22
22
|
end
|
23
|
+
|
24
|
+
it "should use the Multi log device" do
|
25
|
+
io1 = StringIO.new
|
26
|
+
io2 = StringIO.new
|
27
|
+
logger = Timber::Logger.new(STDOUT, io1, io2)
|
28
|
+
logger.info("hello world")
|
29
|
+
expect(io1.string).to start_with("hello world @metadata {")
|
30
|
+
expect(io2.string).to start_with("hello world @metadata {")
|
31
|
+
end
|
23
32
|
end
|
24
33
|
|
25
34
|
describe "#add" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timber
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Timber Technologies, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-07-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|
@@ -182,11 +182,9 @@ files:
|
|
182
182
|
- lib/timber/events.rb
|
183
183
|
- lib/timber/events/controller_call.rb
|
184
184
|
- lib/timber/events/custom.rb
|
185
|
-
- lib/timber/events/
|
186
|
-
- lib/timber/events/
|
187
|
-
- lib/timber/events/
|
188
|
-
- lib/timber/events/http_server_request.rb
|
189
|
-
- lib/timber/events/http_server_response.rb
|
185
|
+
- lib/timber/events/error.rb
|
186
|
+
- lib/timber/events/http_request.rb
|
187
|
+
- lib/timber/events/http_response.rb
|
190
188
|
- lib/timber/events/sql_query.rb
|
191
189
|
- lib/timber/events/template_render.rb
|
192
190
|
- lib/timber/frameworks.rb
|
@@ -205,7 +203,7 @@ files:
|
|
205
203
|
- lib/timber/integrations/active_record/log_subscriber.rb
|
206
204
|
- lib/timber/integrations/active_record/log_subscriber/timber_log_subscriber.rb
|
207
205
|
- lib/timber/integrations/rack.rb
|
208
|
-
- lib/timber/integrations/rack/
|
206
|
+
- lib/timber/integrations/rack/error_event.rb
|
209
207
|
- lib/timber/integrations/rack/http_context.rb
|
210
208
|
- lib/timber/integrations/rack/http_events.rb
|
211
209
|
- lib/timber/integrations/rack/middleware.rb
|
@@ -218,6 +216,7 @@ files:
|
|
218
216
|
- lib/timber/log_devices/http.rb
|
219
217
|
- lib/timber/log_devices/http/dropping_sized_queue.rb
|
220
218
|
- lib/timber/log_devices/http/flushable_sized_queue.rb
|
219
|
+
- lib/timber/log_devices/multi.rb
|
221
220
|
- lib/timber/log_entry.rb
|
222
221
|
- lib/timber/logger.rb
|
223
222
|
- lib/timber/overrides.rb
|
@@ -261,8 +260,8 @@ files:
|
|
261
260
|
- spec/timber/event_spec.rb
|
262
261
|
- spec/timber/events/controller_call_spec.rb
|
263
262
|
- spec/timber/events/custom_spec.rb
|
264
|
-
- spec/timber/events/
|
265
|
-
- spec/timber/events/
|
263
|
+
- spec/timber/events/error_spec.rb
|
264
|
+
- spec/timber/events/http_request_spec.rb
|
266
265
|
- spec/timber/events_spec.rb
|
267
266
|
- spec/timber/integrations/action_controller/log_subscriber_spec.rb
|
268
267
|
- spec/timber/integrations/action_dispatch/debug_exceptions_spec.rb
|
@@ -329,8 +328,8 @@ test_files:
|
|
329
328
|
- spec/timber/event_spec.rb
|
330
329
|
- spec/timber/events/controller_call_spec.rb
|
331
330
|
- spec/timber/events/custom_spec.rb
|
332
|
-
- spec/timber/events/
|
333
|
-
- spec/timber/events/
|
331
|
+
- spec/timber/events/error_spec.rb
|
332
|
+
- spec/timber/events/http_request_spec.rb
|
334
333
|
- spec/timber/events_spec.rb
|
335
334
|
- spec/timber/integrations/action_controller/log_subscriber_spec.rb
|
336
335
|
- spec/timber/integrations/action_dispatch/debug_exceptions_spec.rb
|
@@ -1,65 +0,0 @@
|
|
1
|
-
require "timber/event"
|
2
|
-
require "timber/util"
|
3
|
-
|
4
|
-
module Timber
|
5
|
-
module Events
|
6
|
-
# The HTTP client request event tracks *outgoing* HTTP requests giving you structured insight
|
7
|
-
# into communication with external services.
|
8
|
-
#
|
9
|
-
# @note This event should be installed automatically through integrations,
|
10
|
-
# such as the {Integrations::NetHTTP} integration.
|
11
|
-
class HTTPClientRequest < Timber::Event
|
12
|
-
attr_reader :body, :headers, :host, :method, :path, :port, :query_string, :request_id,
|
13
|
-
:scheme, :service_name
|
14
|
-
|
15
|
-
def initialize(attributes)
|
16
|
-
@headers = Util::HTTPEvent.normalize_headers(attributes[:headers])
|
17
|
-
@host = attributes[:host] || raise(ArgumentError.new(":host is required"))
|
18
|
-
@method = Util::HTTPEvent.normalize_method(attributes[:method]) || raise(ArgumentError.new(":method is required"))
|
19
|
-
@path = attributes[:path]
|
20
|
-
@port = attributes[:port]
|
21
|
-
@query_string = Util::HTTPEvent.normalize_query_string(attributes[:query_string])
|
22
|
-
@request_id = attributes[:request_id]
|
23
|
-
@scheme = attributes[:scheme] || raise(ArgumentError.new(":scheme is required"))
|
24
|
-
@service_name = attributes[:service_name]
|
25
|
-
|
26
|
-
@body = Util::HTTPEvent.normalize_body(@headers["content-type"], attributes[:body])
|
27
|
-
end
|
28
|
-
|
29
|
-
def to_hash
|
30
|
-
{headers: headers, host: host, method: method, parsed_body_json: parsed_body_json,
|
31
|
-
path: path, port: port, query_string: query_string, request_id: request_id,
|
32
|
-
scheme: scheme, service_name: service_name}
|
33
|
-
end
|
34
|
-
alias to_h to_hash
|
35
|
-
|
36
|
-
# Builds a hash representation containing simple objects, suitable for serialization (JSON).
|
37
|
-
def as_json(_options = {})
|
38
|
-
{:http_client_request => to_hash}
|
39
|
-
end
|
40
|
-
|
41
|
-
def message
|
42
|
-
message = 'Outgoing HTTP request to '
|
43
|
-
|
44
|
-
if service_name
|
45
|
-
mesage << " #{service_name} [#{method}] #{full_path}"
|
46
|
-
else
|
47
|
-
message << " [#{method}] #{full_url}"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def status_description
|
52
|
-
Rack::Utils::HTTP_STATUS_CODES[status]
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
def full_path
|
57
|
-
Util::HTTPEvent.full_path(path, query_string)
|
58
|
-
end
|
59
|
-
|
60
|
-
def full_url
|
61
|
-
"#{scheme}#{host}#{full_path}"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
@@ -1,51 +0,0 @@
|
|
1
|
-
require "timber/event"
|
2
|
-
require "timber/util"
|
3
|
-
|
4
|
-
module Timber
|
5
|
-
module Events
|
6
|
-
# The HTTP client response event tracks responses for *outgoing* HTTP *requests*.
|
7
|
-
# This gives you structured insight into communication with external services.
|
8
|
-
#
|
9
|
-
# @note This event should be installed automatically through integrations,
|
10
|
-
# such as the {Integrations::NetHTTP} integration.
|
11
|
-
class HTTPClientResponse < Timber::Event
|
12
|
-
attr_reader :body, :headers, :request_id, :service_name, :status, :time_ms
|
13
|
-
|
14
|
-
def initialize(attributes)
|
15
|
-
@headers = Util::HTTPEvent.normalize_headers(attributes[:headers])
|
16
|
-
@request_id = attributes[:request_id]
|
17
|
-
@service_name = attributes[:service_name]
|
18
|
-
@status = attributes[:status] || raise(ArgumentError.new(":status is required"))
|
19
|
-
@time_ms = attributes[:time_ms] || raise(ArgumentError.new(":time_ms is required"))
|
20
|
-
@time_ms = @time_ms.round(6)
|
21
|
-
|
22
|
-
@body = Util::HTTPEvent.normalize_body(@headers["content-type"], attributes[:body])
|
23
|
-
end
|
24
|
-
|
25
|
-
def to_hash
|
26
|
-
{body: body, headers: headers, request_id: request_id, service_name: service_name,
|
27
|
-
status: status, time_ms: time_ms}
|
28
|
-
end
|
29
|
-
alias to_h to_hash
|
30
|
-
|
31
|
-
# Builds a hash representation containing simple objects, suitable for serialization (JSON).
|
32
|
-
def as_json(_options = {})
|
33
|
-
{:http_client_response => to_hash}
|
34
|
-
end
|
35
|
-
|
36
|
-
def message
|
37
|
-
message = "Outgoing HTTP response"
|
38
|
-
|
39
|
-
if service_name
|
40
|
-
message << " from #{service_name}"
|
41
|
-
end
|
42
|
-
|
43
|
-
message << " #{status_description} in #{time_ms}ms"
|
44
|
-
end
|
45
|
-
|
46
|
-
def status_description
|
47
|
-
Rack::Utils::HTTP_STATUS_CODES[status]
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|