timber 2.1.1 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +56 -156
  3. data/lib/timber/config.rb +7 -8
  4. data/lib/timber/config/integrations/rack.rb +4 -4
  5. data/lib/timber/contexts/runtime.rb +3 -2
  6. data/lib/timber/contexts/system.rb +2 -2
  7. data/lib/timber/current_context.rb +5 -0
  8. data/lib/timber/events.rb +3 -5
  9. data/lib/timber/events/controller_call.rb +7 -5
  10. data/lib/timber/events/{exception.rb → error.rb} +13 -12
  11. data/lib/timber/events/{http_server_request.rb → http_request.rb} +5 -5
  12. data/lib/timber/events/{http_server_response.rb → http_response.rb} +3 -3
  13. data/lib/timber/events/sql_query.rb +3 -0
  14. data/lib/timber/integrations/action_dispatch.rb +2 -2
  15. data/lib/timber/integrations/rack.rb +3 -3
  16. data/lib/timber/integrations/rack/{exception_event.rb → error_event.rb} +6 -6
  17. data/lib/timber/integrations/rack/http_events.rb +10 -10
  18. data/lib/timber/log_devices.rb +1 -0
  19. data/lib/timber/log_devices/http.rb +5 -1
  20. data/lib/timber/log_devices/multi.rb +34 -0
  21. data/lib/timber/log_entry.rb +3 -1
  22. data/lib/timber/logger.rb +37 -11
  23. data/lib/timber/util/http_event.rb +4 -5
  24. data/lib/timber/version.rb +1 -1
  25. data/spec/timber/contexts/system_spec.rb +2 -2
  26. data/spec/timber/events/{exception_spec.rb → error_spec.rb} +4 -4
  27. data/spec/timber/events/{http_server_request_spec.rb → http_request_spec.rb} +1 -1
  28. data/spec/timber/integrations/action_dispatch/debug_exceptions_spec.rb +1 -1
  29. data/spec/timber/logger_spec.rb +9 -0
  30. metadata +11 -12
  31. data/lib/timber/events/http_client_request.rb +0 -65
  32. 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: a6efcc9b1ff96b7bfea713d3e87e36b7bfc061fd
4
- data.tar.gz: 6ef8b32449171a85062421fc42da7c4417b7fdf8
3
+ metadata.gz: fbf04b7803b278fe7386975994f5c16d019b125b
4
+ data.tar.gz: 27b112405610ebb0b2c948ba4e1ae0f698fd5ac0
5
5
  SHA512:
6
- metadata.gz: 79c590f521790d5f24349bdfb2cd9058cd78b18955bfa61164338d75b70cb7e58a24594a31f93780af6a1f27bbafd124c5a3249d3dac9d331debfb3181792e63
7
- data.tar.gz: 6e284e8801b5e9643fe36e65200f1425c2da8ab078a6bb60f29339323da86ef371f7ca8a32634f8f7d22932672422e35bc4883e59f814ae06d131503a08ec076
6
+ metadata.gz: a474845f4dc5c7c328d860458ce93d5a2769cd850ebb7117cf442662565ab654e1c57f3a209037d43f80a8f5b0b27e54cebfb581f6505aad8f0939c8e14285a1
7
+ data.tar.gz: 0f60bc9a972900139d780a22c9c10c0e35afaa46689c5cc8a4bacd22ee1db454fbb34f3ef003d5a92a070255c7f309d0c2fcd3e38e4ffce6c48bfbdff9de2f96
data/README.md CHANGED
@@ -1,41 +1,28 @@
1
- # 🌲 Timber - Simple Ruby Structured Logging
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 solves ruby structured logging so you don't have to. Go from raw text logs to rich
17
- structured events in seconds. Spend more time focusing on your app and less time
18
- focusing on logging.
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
- 3. **Seamlessly integrates with popular libraries and frameworks.** - Rails, Rack, Devise,
26
- Omniauth, etc. [Automatically captures user context, HTTP context, and event data.](#third-party-integrations)
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.0'
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 and contexts does Timber capture for me?</strong></summary><p>
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. It's
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>Won't this increase the size of my log data?</strong></summary><p>
314
+ <details><summary><strong>What about my current log statements?</strong></summary><p>
429
315
 
430
- Yes, but it's no different than adding any other useful data to your logs, such as
431
- [tags](http://api.rubyonrails.org/classes/ActiveSupport/TaggedLogging.html). A few
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
- 1. Timber generally _reduces_ the amount of logs your app generates, trading quality for quantity.
435
- It does so by providing options to consolidate request / response logs, template logs, and
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
- Finally, log what is useful to you. Quality over quantity certainly applies to logging.
322
+ </p></details>
444
323
 
445
- ---
324
+ <details><summary><strong>When to use metadata or events?</strong></summary><p>
446
325
 
447
- </p></details>
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
- <details><summary><strong>What about my current log statements?</strong></summary><p>
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
- They'll continue to work as expected. Timber adheres to the default `::Logger` interface.
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
- In fact, traditional log statements for non-meaningful events, debug statements, etc, are
456
- encouraged. In cases where the data is meaningful, consider [logging a custom event](#usage).
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-elixir"><img src="http://files.timber.io/images/ruby-library-readme-log-truth.png" height="947" /></a>
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>
@@ -36,7 +36,7 @@ module Timber
36
36
 
37
37
  # @private
38
38
  def initialize
39
- @http_body_limit = 2000
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 `2000`.
134
- # If you want to capture more data, you can raise this to a maximum of `5000`,
135
- # or lower this to be more efficient with data. `2000` characters should give you a good
136
- # idea of the body content. If you need to raise to `5000` you're only constraint is
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 = 500
139
+ # config.timber.http_body_limit = 2048
141
140
  # @example Everything else
142
- # Timber::Config.instance.http_body_limit = 500
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::ExceptionEvent}
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.exception_event.enabled = false
24
- def exception_event
25
- Timber::Integrations::Rack::ExceptionEvent
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.to_s
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, :to_s)}
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
 
@@ -1,10 +1,8 @@
1
1
  require "timber/events/controller_call"
2
2
  require "timber/events/custom"
3
- require "timber/events/exception"
4
- require "timber/events/http_client_request"
5
- require "timber/events/http_client_response"
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 ||= if params.nil? || params == {}
48
- nil
49
- else
50
- params.to_json
51
- end
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 exception event is used to track exceptions.
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 Exception < Timber::Event
11
- attr_reader :name, :exception_message, :backtrace
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
- @exception_message = attributes[:exception_message] || raise(ArgumentError.new(":exception_message is required"))
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? || backtrace == []
19
- raise(ArgumentError.new(":backtrace is required"))
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: exception_message, backtrace: backtrace}
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
- {:exception => to_hash}
34
+ {:error => to_hash}
34
35
  end
35
36
 
36
37
  def message
37
- "#{name} (#{exception_message})"
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 HTTPServerRequest < Timber::Event
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] || raise(ArgumentError.new(":host is required"))
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] || raise(ArgumentError.new(":scheme is required"))
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
- {:http_server_request => to_hash}
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 HTTPServerResponse < Timber::Event
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
- {:http_server_response => to_hash}
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/exception_event"
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::ExceptionEvent.enabled?
13
+ Rack::ErrorEvent.enabled?
14
14
  end
15
15
 
16
16
  def self.integrate!
@@ -1,4 +1,4 @@
1
- require "timber/integrations/rack/exception_event"
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
- ExceptionEvent.enabled = value
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, ExceptionEvent].select(&:enabled?)
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/exception"
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 exceptions events
18
- # {Timber::Events::Exception}.
19
- class ExceptionEvent < Middleware
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::Exception.new(
34
+ Events::Error.new(
35
35
  name: exception.class.name,
36
- exception_message: exception.message,
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/http_server_request"
7
- require "timber/events/http_server_response"
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::HTTPServerRequest} and {Events::HTTPServerResponse} 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::HTTPServerRequest}.
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::HTTPServerResponse} event.
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::HTTPServerResponse} event. Because
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::HTTPServerRequest} event would
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::HTTPServerResponse.new(
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::HTTPServerRequest.new(
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::HTTPServerResponse.new(
167
+ Events::HTTPResponse.new(
168
168
  body: event_body,
169
169
  headers: headers,
170
170
  request_id: request.request_id,
@@ -1,4 +1,5 @@
1
1
  require "timber/log_devices/http"
2
+ require "timber/log_devices/multi"
2
3
 
3
4
  module Timber
4
5
  # Namespace for all log devices.
@@ -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
- http.use_ssl = true if @timber_url.scheme == 'https'
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
@@ -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
- SCHEMA = "https://raw.githubusercontent.com/timberio/log-event-json-schema/v2.0.4/schema.json".freeze
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
@@ -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/http"
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 instances. Accepts the same arguments as `::Logger.new`.
189
- # The only difference is that it default the formatter to {AugmentedFormatter}. Using
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
- # @example Changing your formatter
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
- # logger.formatter = Timber::Logger::JSONFormatter.new
195
- def initialize(*args)
196
- super(*args)
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 args.size == 1 and args.first.respond_to?(:"sync=")
200
- args.first.sync = true
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 args.size == 1 and args.first.is_a?(LogDevices::HTTP)
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::HTTPServerRequest},
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
- QUERY_STRING_LIMIT = 5_000.freeze
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[0..(limit - 1)]
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[0..(QUERY_STRING_LIMIT - 1)]
65
+ query_string && query_string.byteslice(0, MAX_QUERY_STRING_BYTES)
67
66
  end
68
67
  end
69
68
  end
@@ -1,3 +1,3 @@
1
1
  module Timber
2
- VERSION = "2.1.1"
2
+ VERSION = "2.1.2"
3
3
  end
@@ -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("1")
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::Exception, :rails_23 => true do
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", exception_message: "Boom", backtrace: backtrace)
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", exception_message: "Boom", backtrace: backtrace)
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", exception_message: "Boom", backtrace: backtrace)
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- describe Timber::Events::HTTPServerRequest, :rails_23 => true do
5
+ describe Timber::Events::HTTPRequest, :rails_23 => true do
6
6
  describe ".initialize" do
7
7
  context "with a header filters" do
8
8
  around(:each) do |example|
@@ -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\":{\"exception\":{\"name\":\"RuntimeError\",\"message\":\"boom\",\"backtrace\":[")
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
@@ -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.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-06-28 00:00:00.000000000 Z
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/exception.rb
186
- - lib/timber/events/http_client_request.rb
187
- - lib/timber/events/http_client_response.rb
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/exception_event.rb
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/exception_spec.rb
265
- - spec/timber/events/http_server_request_spec.rb
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/exception_spec.rb
333
- - spec/timber/events/http_server_request_spec.rb
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