timber 1.1.0 → 1.1.1

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