l2meter 0.12.0 → 0.13.0

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
- SHA1:
3
- metadata.gz: a8758c9423aad4f0d392487036c92d4501e6a8fc
4
- data.tar.gz: 14250d1270dabf5f366ee22c7bbab6dff0e8abcf
2
+ SHA256:
3
+ metadata.gz: 1d6bbc8048445c11b2601b92ad8ff351ef058d842127dd53588e5e78f3219a80
4
+ data.tar.gz: 186c086e8782a191f32c638f7687008cc7d0cc08dea636a12234092736e826af
5
5
  SHA512:
6
- metadata.gz: ed94d0f0a82ec17c59e4b57a0598adaac7f0eb6bd2d4662e42bd9ec785a3f563cf56b1ee582bc5fb104b2f676f1ebd161aed76f45ce8c8d37823a8d895a5099b
7
- data.tar.gz: c53adc609873409a3a9f5edaf3acce2d6732cc811f347a16764ea3a41cee55b01f96cd2567e6a9b2d1597a64c04e3697c701d4bbb5e0d480b06e4cce59cd0936
6
+ metadata.gz: 1ec890f4bde056dfe4f85303530e8c8e5a013720778a202bcc81cec6c736ab04d059267c16401bdf1ef0c00b1154163c53f03692d0a87f0811f491880ceedf13
7
+ data.tar.gz: 98896fb1fa2e8af273168160048d888eac1ebad6bc5725826b873cf1aba0f0cc1b0222986e70ccd8430cd7ec8dca11ff3e1d9408173c5ce7708fa38ce9e09fe3
data/README.md CHANGED
@@ -1,49 +1,50 @@
1
1
  # L2meter
2
2
  [![Gem Version](https://img.shields.io/gem/v/l2meter.svg)](https://rubygems.org/gems/l2meter)
3
- [![Build Status](https://img.shields.io/travis/rwz/l2meter.svg)](http://travis-ci.org/rwz/l2meter)
4
- [![Code Climate](https://img.shields.io/codeclimate/github/rwz/l2meter.svg)](https://codeclimate.com/github/rwz/l2meter)
3
+ [![Build Status](https://travis-ci.com/heroku/l2meter.svg?branch=master)](http://travis-ci.com/heroku/l2meter)
5
4
 
6
- L2meter is a little gem that helps you build loggers that outputs things in
7
- l2met-friendly format.
5
+ L2meter is a little gem for building [logfmt]-compatiable loggers.
6
+
7
+ [logfmt]: https://www.brandur.org/logfmt
8
8
 
9
9
  ### Basics
10
10
 
11
11
  A new logger might be created like so:
12
12
 
13
13
  ```ruby
14
- Metrics = L2meter.build
14
+ logger = L2meter.build
15
15
  ```
16
16
 
17
- If you plan to use it globally across different components of your app,consider
18
- making it constant.
17
+ Consider making the logger a constant to make it easier to use across different
18
+ components of the app or globally.
19
19
 
20
- The base `log` method accepts two type of things: bare values and key-value
20
+ The base `log` method accepts two type of arguments: bare values and key-value
21
21
  pairs in form of hashes.
22
22
 
23
23
  ```ruby
24
- Metrics.log "Hello world" # => hello-world
25
- Metrics.log :db_query, result: :success # => db-query result=success
24
+ logger.log "Hello world" # => hello-world
25
+ logger.log :db_query, result: :success # => db-query result=success
26
26
  ```
27
27
 
28
- It can also take a block. In this case the message will be emitted twice, once
29
- at the start of the execution and another at the end. The end result might look
30
- like so:
28
+ The method also takes a block. In this case the message will be emitted twice,
29
+ once at the start of the execution and once at the end. The end result might
30
+ look like so:
31
31
 
32
32
  ```ruby
33
- Metrics.log :doing_work do # => doing-work at=start
34
- do_some_work #
35
- Metrics.log :work_done # => work-done
36
- end # => doing-work at=finish elapsed=1.2345
33
+ logger.log :doing_work do # => doing-work at=start
34
+ do_some_work #
35
+ logger.log :work_done # => work-done
36
+ end # => doing-work at=finish elapsed=1.2345
37
37
  ```
38
38
 
39
- In case the exception is raised inside the block, l2meter will report is like
40
- so:
39
+ In case of the exception inside the block, all relevant information is logged
40
+ and then exception is re-raised.
41
41
 
42
42
  ```ruby
43
- Metrics.log :doing_work do # => doing-work at=start
43
+ logger.log :doing_work do # => doing-work at=start
44
44
  raise ArgumentError, \ #
45
45
  "something is wrong" #
46
46
  end # => doing-work at=exception exception=ArgumentError message="something is wrong" elapsed=1.2345
47
+ # ArgumentError: something is wrong
47
48
  ```
48
49
 
49
50
  ## Context
@@ -54,7 +55,7 @@ L2meter allows setting context for a block. It might work something like this:
54
55
  def do_work_with_retries
55
56
  attempt = 1
56
57
  begin
57
- Metrics.context attempt: attempt do
58
+ logger.context attempt: attempt do
58
59
  do_some_work # => doing-work attempt=1
59
60
  # => doing-work attempt=2
60
61
  # => doing-work attempt=3
@@ -69,12 +70,12 @@ end
69
70
  L2meter supports dynamic contexts as well. You can pass a proc instead of raw
70
71
  value in order to use it.
71
72
 
72
- The same example as above could be re-written like this instead:
73
+ The example above could be re-written like this instead:
73
74
 
74
75
  ```ruby
75
76
  def do_work_with_retries
76
77
  attempt = 1
77
- Metrics.context ->{{ attempt: attempt }} do
78
+ logger.context ->{{ attempt: attempt }} do
78
79
  begin
79
80
  do_some_work
80
81
  rescue => error
@@ -85,71 +86,73 @@ def do_work_with_retries
85
86
  end
86
87
  ```
87
88
 
88
- ## Contexted logging
89
-
90
- Sometimes you want another copy of the logger with a specific context on it.
91
- You can create one like so:
89
+ It's possbile to create a dedicated copy of the logger with some specific
90
+ context attached to it.
92
91
 
93
92
  ```ruby
94
- logger = Metrics.context(:super_worker, username: "joe")
93
+ worker_logger = logger.context(component: :worker, worker_id: 123)
95
94
 
96
- SuperWorker.new(logger: logger).run # => super-worker username=joe some-other=superworker-output
95
+ MyWorker.new(logger: worker_logger).run # => component=worker worker_id=123 status="doing work"
97
96
  ```
98
97
 
99
98
  ## Batching
100
99
 
101
- There's also a way to batch several calls into a single log line:
100
+ There's a way to batch several calls into a single log line:
102
101
 
103
102
  ```ruby
104
- Metrics.batch do
105
- Metrics.log foo: :bar
106
- Metrics.unique :registeration, "user@example.com"
107
- Metrics.count :thing, 10
108
- Metrics.sample :other_thing, 20
103
+ logger.batch do
104
+ logger.log foo: :bar
105
+ logger.unique :registration, "user@example.com"
106
+ logger.count :thing, 10
107
+ logger.sample :other_thing, 20
109
108
  end # => foo=bar unique#registration=user@example.com count#thing=10 sample#other-thing=20
110
109
  ```
111
110
 
112
- ## Other
111
+ ## Metrics
112
+
113
+ Some [l2met]-specific metrics are supported.
113
114
 
114
- Some other l2met-specific methods are supported.
115
+ [l2met]: https://r.32k.io/l2met-introduction
115
116
 
116
117
  ```ruby
117
- Metrics.count :user_registered # => count#user-registered=1
118
- Metrics.count :registered_users, 10 # => count#registered-users=10
118
+ logger.count :user_registered # => count#user-registered=1
119
+ logger.count :registered_users, 10 # => count#registered-users=10
119
120
 
120
- Metrics.measure :connection_count, 20 # => measure#connection-count=20
121
- Metrics.measure :db_query, 235, unit: :ms, # => measure#db-query.ms=235
121
+ logger.measure :connection_count, 20 # => measure#connection-count=20
122
+ logger.measure :db_query, 235, unit: :ms, # => measure#db-query.ms=235
122
123
 
123
- Metrics.sample :connection_count, 20, # => sample#connection-count=235
124
- Metrics.sample :db_query, 235, unit: :ms, # => sample#db-query.ms=235
124
+ logger.sample :connection_count, 20, # => sample#connection-count=235
125
+ logger.sample :db_query, 235, unit: :ms, # => sample#db-query.ms=235
125
126
 
126
- Metrics.unique :user, "bob@example.com" # => unique#user=bob@example.com
127
+ logger.unique :user, "bob@example.com" # => unique#user=bob@example.com
127
128
  ```
128
129
 
129
- L2meter also allows to append elapsed time to your log messages automatically.
130
+ ## Measuring Time
131
+
132
+ L2meter allows to append elapsed time to log messages automatically.
130
133
 
131
134
  ```ruby
132
- Metrics.with_elapsed do
135
+ logger.with_elapsed do
133
136
  do_work_step_1
134
- Metrics.log :step_1_done # => step-1-done elapsed=1.2345
137
+ logger.log :step_1_done # => step-1-done elapsed=1.2345
135
138
  do_work_step_2
136
- Metrics.log :step_2_done # => step-2-done elapsed=2.3456
139
+ logger.log :step_2_done # => step-2-done elapsed=2.3456
137
140
  end
138
141
  ```
139
142
 
140
- ### Configuration
143
+ ## Configuration
141
144
 
142
- L2meter supports configuration. Here's how you can configure things:
145
+ L2meter supports customizable configuration.
143
146
 
144
147
  ```ruby
145
- Metrics = L2meter.build do |config|
148
+ logger = L2meter.build do |config|
146
149
  # configuration happens here
147
150
  end
148
151
  ```
149
152
 
150
- Here's the list of all configurable things:
153
+ Here's the full list of available settings.
151
154
 
152
- #### Global context
155
+ ### Global context
153
156
 
154
157
  Global context works similary to context method, but globally:
155
158
 
@@ -158,71 +161,75 @@ config.context = { app_name: "my-app-name" }
158
161
 
159
162
  # ...
160
163
 
161
- Metrics.log foo: :bar # => app-name=my-app-name foo=bar
164
+ logger.log foo: :bar # => app-name=my-app-name foo=bar
162
165
  ```
163
166
 
164
167
  Dynamic context is also supported:
165
168
 
166
169
  ```ruby
167
170
  config.context do
168
- { request_id: CurrentContext.request_id }
171
+ { request_id: SecureRandom.uuid }
169
172
  end
173
+
174
+ logger.log :hello # => hello request_id=4209ba28-4a7c-40d6-af69-c2c1ddf51f19
175
+ logger.log :world # => world request_id=b6836b1b-5710-4f5f-926d-91ab9988a7c1
170
176
  ```
171
177
 
172
- #### Sorting
178
+ ### Sorting
173
179
 
174
180
  By default l2meter doesn't sort tokens before output, putting them in the order
175
- they're passed. But you can make it sorted like so:
181
+ they're passed. But it's possible to sort them like so:
176
182
 
177
183
  ```ruby
178
184
  config.sort = true
179
185
 
180
186
  # ...
181
187
 
182
- Metrics.log :c, :b, :a # => a b c
188
+ logger.log :c, :b, :a # => a b c
183
189
  ```
184
190
 
185
- #### Source
191
+ ### Source
186
192
 
187
193
  Source is a special parameter that'll be appended to all emitted messages.
188
194
 
189
195
  ```ruby
190
- config.source = "production"
196
+ config.source = "com.heroku.my-application.staging"
191
197
 
192
198
  # ...
193
199
 
194
- Metrics.log foo: :bar # => source=production foo=bar
200
+ logger.log foo: :bar # => source=com.heroku.my-application.staging foo=bar
195
201
  ```
196
202
 
197
- #### Prefix
203
+ ### Prefix
198
204
 
199
- Prefix allows namespacing your measure/count/unique/sample calls.
205
+ Prefix allows to add namespacing to measure/count/unique/sample calls.
200
206
 
201
207
  ```ruby
202
208
  config.prefix = "my-app"
203
209
 
204
210
  # ...
205
211
 
206
- Metrics.count :users, 100500 # => count#my-app.users=100500
212
+ logger.count :users, 100500 # => count#my-app.users=100500
207
213
  ```
208
214
 
209
- ## Scrubbing
215
+ ### Scrubbing
210
216
 
211
217
  L2meter allows plugging in custom scrubbing logic that might be useful in
212
- environments where logging compliance is important.
218
+ environments where logging compliance is important to prevent accidentally
219
+ leaking sensitive information.
213
220
 
214
221
  ```ruby
215
- config.scrubber = ->(key, value) do
222
+ config.scrubber = -> (key, value) do
216
223
  begin
217
224
  uri = URI.parse(value)
218
- uri.password = "scrubbed" if uri.password
225
+ uri.password = "redacted" if uri.password
219
226
  uri.to_s
220
227
  rescue URI::Error
221
228
  value
222
229
  end
223
230
  end
224
231
 
225
- Metric.log my_url: "https://user:password@example.com"
232
+ logger.log my_url: "https://user:password@example.com"
226
233
  # => my-url="https://user:redacted@example.com"
227
234
  ```
228
235
 
@@ -234,13 +241,13 @@ There's a way to temporary silence the log emitter. This might be userful for
234
241
  tests for example.
235
242
 
236
243
  ```ruby
237
- Metrics.silence do
244
+ logger.silence do
238
245
  # logger is completely silenced
239
- Metrics.log "hello world" # nothing is emitted here
246
+ logger.log "hello world" # nothing is emitted here
240
247
  end
241
248
 
242
249
  # works normally again
243
- Metrics.log :foo # => foo
250
+ logger.log :foo # => foo
244
251
  ```
245
252
 
246
253
  The typical setup for RSpec might look like this:
@@ -248,16 +255,17 @@ The typical setup for RSpec might look like this:
248
255
  ```ruby
249
256
  RSpec.configure do |config|
250
257
  config.around :each do |example|
251
- Metrics.silence &example
258
+ MyLogger.silence &example
252
259
  end
253
260
  end
254
261
  ```
255
262
 
256
- Note that this code will only silence logger in the current thread. It'll still
257
- produce output if you fire up a new thread. To silence it completely, use
258
- `disable!` method, like so:
263
+ Note that silence method will only suppress logging in the current thread.
264
+ It'll still produce output if you fire up a new thread. To silence it
265
+ completely, use `disable!` method. This will completely silence the logger
266
+ across all threads.
259
267
 
260
268
  ```ruby
261
269
  # spec/spec_helper.rb
262
- Metrics.disable!
270
+ MyLogger.disable!
263
271
  ```
data/lib/l2meter.rb CHANGED
@@ -5,12 +5,10 @@ module L2meter
5
5
 
6
6
  autoload :Configuration, "l2meter/configuration"
7
7
  autoload :Emitter, "l2meter/emitter"
8
- autoload :NullObject, "l2meter/null_object"
9
- autoload :ThreadSafe, "l2meter/thread_safe"
8
+ autoload :NullOutput, "l2meter/null_output"
10
9
 
11
10
  def build(configuration: Configuration.new)
12
11
  yield configuration if block_given?
13
- emitter = Emitter.new(configuration: configuration.freeze)
14
- ThreadSafe.new(emitter)
12
+ Emitter.new(configuration: configuration.freeze)
15
13
  end
16
14
  end
@@ -1,4 +1,5 @@
1
1
  require "time"
2
+ require "thread"
2
3
 
3
4
  module L2meter
4
5
  class Emitter
@@ -6,14 +7,10 @@ module L2meter
6
7
 
7
8
  def initialize(configuration: Configuration.new)
8
9
  @configuration = configuration
9
- @buffer = {}
10
- @autoflush = true
11
- @contexts = []
12
- @outputs = []
13
10
  end
14
11
 
15
12
  def log(*args)
16
- merge! *current_contexts, *args
13
+ merge! current_context, *args
17
14
 
18
15
  if block_given?
19
16
  wrap &proc
@@ -22,35 +19,55 @@ module L2meter
22
19
  end
23
20
  end
24
21
 
25
- def with_elapsed(start_time = Time.now, &block)
26
- context(elapsed_context(start_time), &block)
22
+ def context(*context_data)
23
+ if block_given?
24
+ wrap_context(context_data, &proc)
25
+ else
26
+ contexted(context_data)
27
+ end
27
28
  end
28
29
 
29
- def with_output(output)
30
- @outputs.push output
31
- yield
32
- ensure
33
- @outputs.pop
30
+ def with_elapsed
31
+ context elapsed: elapse do
32
+ yield
33
+ end
34
34
  end
35
35
 
36
36
  def silence
37
- with_output(NullObject.new, &proc)
37
+ with_output(NullOutput.new, &proc)
38
38
  end
39
39
 
40
40
  def silence!
41
- @outputs.push NullObject.new
41
+ set_output NullOutput.new
42
42
  end
43
43
 
44
44
  def unsilence!
45
- @outputs.pop
45
+ set_output nil
46
46
  end
47
47
 
48
- def measure(metric, value, unit: nil)
49
- log_with_prefix :measure, metric, value, unit: unit
48
+ def with_output(new_output)
49
+ old_output = output
50
+ set_output new_output
51
+ yield
52
+ ensure
53
+ set_output old_output
50
54
  end
51
55
 
52
- def sample(metric, value, unit: nil)
53
- log_with_prefix :sample, metric, value, unit: unit
56
+ def batch
57
+ old_state = in_batch?
58
+ in_batch!
59
+ yield
60
+ ensure
61
+ reset_in_batch old_state
62
+ write
63
+ end
64
+
65
+ def measure(*args)
66
+ log_with_prefix :measure, *args
67
+ end
68
+
69
+ def sample(*args)
70
+ log_with_prefix :sample, *args
54
71
  end
55
72
 
56
73
  def count(metric, value = 1)
@@ -61,167 +78,228 @@ module L2meter
61
78
  log_with_prefix :unique, metric, value
62
79
  end
63
80
 
64
- def context(*context_data)
65
- return clone_with_context(context_data) unless block_given?
66
- push_context context_data
67
- yield
68
- ensure
69
- context_data.length.times { @contexts.pop } if block_given?
81
+ def clone
82
+ original_contexts = dynamic_contexts
83
+ original_output = output
84
+ self.class.new(configuration: configuration).tap do |clone|
85
+ clone.instance_eval do
86
+ dynamic_contexts.concat original_contexts
87
+ set_output original_output
88
+ end
89
+ end
70
90
  end
71
91
 
72
- def clone
73
- cloned_contexts = @contexts.clone
74
- cloned_outputs = @outputs.clone
75
- self.class.new(configuration: configuration).instance_eval do
76
- @contexts = cloned_contexts
77
- @outputs = cloned_outputs
78
- self
92
+ private
93
+
94
+ def log_with_prefix(method, key, value, unit: nil)
95
+ key = [configuration.prefix, key, unit].compact.join(?.)
96
+ log Hash["#{method}##{key}", value]
97
+ end
98
+
99
+ def elapse(since = Time.now)
100
+ -> { Time.now - since }
101
+ end
102
+
103
+ def write(*args)
104
+ merge! *args
105
+ fire! unless in_batch?
106
+ end
107
+
108
+ def wrap
109
+ elapsed = elapse
110
+ cloned_buffer = buffer.clone
111
+ write at: :start
112
+ result, exception = capture(&proc)
113
+ merge! cloned_buffer
114
+ if exception
115
+ write unwrap_exception(exception), elapsed: elapsed
116
+ raise exception
117
+ else
118
+ write at: :finish, elapsed: elapsed
119
+ result
79
120
  end
80
121
  end
81
122
 
82
- def batch
83
- autoflush = @autoflush
84
- @autoflush = false
123
+ def capture
124
+ [yield, nil]
125
+ rescue Object => exception
126
+ [nil, exception]
127
+ end
128
+
129
+ def wrap_context(context_data)
130
+ dynamic_contexts.concat context_data
85
131
  yield
86
132
  ensure
87
- @autoflush = autoflush
88
- write
133
+ context_data.each { dynamic_contexts.pop }
89
134
  end
90
135
 
91
- def merge!(*args)
92
- unwrap(args).each do |key, value|
93
- key = configuration.key_formatter.call(key)
94
- @buffer[key] = value
136
+ def contexted(context_data)
137
+ clone.instance_eval do
138
+ dynamic_contexts.concat context_data
139
+ self
95
140
  end
96
141
  end
97
142
 
98
- def fire!
99
- tokens = @buffer.map { |key, value| build_token(key, value) }
100
- tokens.compact!
101
- tokens.sort! if configuration.sort?
143
+ def unwrap_exception(exception)
144
+ {
145
+ at: :exception,
146
+ exception: exception.class,
147
+ message: exception.message
148
+ }
149
+ end
102
150
 
103
- output_queue.last.print tokens.join(SPACE) << NL if tokens.any?
104
- ensure
105
- @buffer.clear
151
+ def current_context
152
+ unwrap(resolved_contexts)
106
153
  end
107
154
 
108
- protected
155
+ def current_contexts
156
+ [
157
+ source_context,
158
+ configuration.context,
159
+ *dynamic_contexts
160
+ ].compact
161
+ end
109
162
 
110
- def push_context(context_data)
111
- @contexts.concat context_data
163
+ def source_context
164
+ { source: configuration.source }
112
165
  end
113
166
 
114
- private
167
+ def resolved_contexts
168
+ current_contexts.map { |c| Proc === c ? c.call : c }
169
+ end
170
+
171
+ def fire!
172
+ tokens = buffer.map { |k, v| build_token(k, v) }.compact
173
+ tokens.sort! if configuration.sort?
174
+ return if tokens.empty?
175
+ output.print tokens.join(SPACE) << NL
176
+ ensure
177
+ buffer.clear
178
+ end
115
179
 
116
180
  SPACE = " ".freeze
117
181
  NL = "\n".freeze
118
182
 
119
183
  private_constant :SPACE, :NL
120
184
 
121
- def unwrap(args)
122
- args.each_with_object({}) do |context, result|
123
- next if context.nil?
124
- context = Hash[context, true] unless Hash === context
125
- result.merge! context
126
- end
185
+ def scrub_value(key, value)
186
+ scrubber = configuration.scrubber
187
+ scrubber ? scrubber.call(key, value) : value
127
188
  end
128
189
 
129
190
  def build_token(key, value)
130
- value = value.call if Proc === value
131
- value = scrub_value(key, value)
132
- return if value.nil?
133
- value == true ? key : "#{key}=#{format_value(value)}"
134
- end
135
-
136
- def format_float(value, unit: nil)
137
- "%.#{configuration.float_precision}f#{unit}" % value
138
- end
139
-
140
- def clone_with_context(context)
141
- clone.tap do |emitter|
142
- emitter.push_context context
191
+ case value
192
+ when Proc
193
+ build_token(key, value.call)
194
+ else
195
+ value = scrub_value(key, value)
196
+ format_token(key, value)
143
197
  end
144
198
  end
145
199
 
146
- def current_contexts
147
- contexts_queue.map do |context|
148
- context = context.call if context.respond_to?(:call)
149
- context
200
+ def format_token(key, value)
201
+ case value
202
+ when TrueClass
203
+ key
204
+ when FalseClass, NilClass
205
+ nil
206
+ else
207
+ value = format_value(value)
208
+ "#{key}=#{value}"
150
209
  end
151
210
  end
152
211
 
153
212
  def format_value(value)
154
213
  case value
155
214
  when Float
156
- format_float(value)
215
+ format_float_value(value)
216
+ when String
217
+ format_string_value(value)
157
218
  when Time
158
- value.iso8601
219
+ format_time_value(value)
159
220
  when Array
160
221
  value.map(&method(:format_value)).join(?,)
161
222
  else
162
- format_string_value(value.to_s)
223
+ format_value(value.to_s)
163
224
  end
164
225
  end
165
226
 
227
+ def format_time_value(value)
228
+ value.iso8601
229
+ end
230
+
231
+ def format_float_value(value)
232
+ format = "%.#{configuration.float_precision}f"
233
+ sprintf(format, value)
234
+ end
235
+
166
236
  def format_string_value(value)
167
- value =~ /[^\w,.:@\-\]\[]/ ? value.strip.gsub(/\s+/, " ").inspect : value.to_s
237
+ value =~ /[^\w,.:@\-\]\[]/ ?
238
+ value.strip.gsub(/\s+/, " ").inspect :
239
+ value.to_s
168
240
  end
169
241
 
170
- def write(params = nil)
171
- merge! params
172
- fire! if @autoflush
242
+ def merge!(*args)
243
+ unwrap(args.compact).each do |key, value|
244
+ key = format_key(key)
245
+ buffer[key] = value
246
+ end
173
247
  end
174
248
 
175
- def log_with_prefix(method, key, value, unit: nil)
176
- key = [configuration.prefix, key, unit].compact * ?.
177
- log Hash["#{method}##{key}", value]
249
+ def format_key(key)
250
+ configuration.key_formatter.call(key)
178
251
  end
179
252
 
180
- def wrap
181
- start_time = Time.now
182
- params = @buffer.clone
183
- write at: :start
184
- result = exception = nil
185
-
186
- begin
187
- result = yield
188
- merge! params, at: :finish
189
- rescue Object => exception
190
- merge! params, \
191
- at: :exception,
192
- exception: exception.class,
193
- message: exception.message
253
+ def unwrap(args)
254
+ Hash.new.tap do |result|
255
+ args.each do |arg|
256
+ next if arg.nil?
257
+ arg = Hash[arg, true] unless Hash === arg
258
+ arg.each do |key, value|
259
+ result[key] = value
260
+ end
261
+ end
194
262
  end
263
+ end
195
264
 
196
- write elapsed_context(start_time)
265
+ def thread_state
266
+ @mutex ||= Mutex.new
267
+ @mutex.synchronize do
268
+ @threads ||= {}
197
269
 
198
- raise exception if exception
270
+ # cleaning up state from dead threads
271
+ @threads.delete_if { |t, _| !t.alive? }
199
272
 
200
- result
273
+ @threads[Thread.current] ||= {}
274
+ end
201
275
  end
202
276
 
203
- def contexts_queue
204
- [configuration.context, source_context, *@contexts].compact
277
+ def buffer
278
+ thread_state[:buffer] ||= {}
205
279
  end
206
280
 
207
- def output_queue
208
- [configuration.output, *@outputs].compact
281
+ def dynamic_contexts
282
+ thread_state[:dynamic_contexts] ||= []
209
283
  end
210
284
 
211
- def source_context
212
- { source: configuration.source }
285
+ def output
286
+ thread_state[:output] ||= configuration.output
213
287
  end
214
288
 
215
- def elapsed_context(since = Time.now)
216
- { elapsed: -> { Time.now - since } }
289
+ def set_output(new_output)
290
+ thread_state[:output] = new_output
217
291
  end
218
292
 
219
- def scrub_value(key, value)
220
- if !value.nil? && scrubber = configuration.scrubber
221
- scrubber.call(key, value)
222
- else
223
- value
224
- end
293
+ def in_batch?
294
+ !!thread_state[:in_batch]
295
+ end
296
+
297
+ def in_batch!
298
+ reset_in_batch true
299
+ end
300
+
301
+ def reset_in_batch(new_value)
302
+ thread_state[:in_batch] = new_value
225
303
  end
226
304
  end
227
305
  end
@@ -0,0 +1,5 @@
1
+ module L2meter
2
+ class NullOutput
3
+ def print(*); end
4
+ end
5
+ end
@@ -1,3 +1,3 @@
1
1
  module L2meter
2
- VERSION = "0.12.0".freeze
2
+ VERSION = "0.13.0".freeze
3
3
  end
metadata CHANGED
@@ -1,15 +1,85 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: l2meter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pavel Pravosud
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-07 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2019-05-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry-byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.8.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.8.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: timecop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
13
83
  description:
14
84
  email:
15
85
  - pavel@pravosud.com
@@ -22,10 +92,9 @@ files:
22
92
  - lib/l2meter.rb
23
93
  - lib/l2meter/configuration.rb
24
94
  - lib/l2meter/emitter.rb
25
- - lib/l2meter/null_object.rb
26
- - lib/l2meter/thread_safe.rb
95
+ - lib/l2meter/null_output.rb
27
96
  - lib/l2meter/version.rb
28
- homepage: https://github.com/rwz/l2meter
97
+ homepage: https://github.com/heroku/l2meter
29
98
  licenses:
30
99
  - MIT
31
100
  metadata: {}
@@ -44,8 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
44
113
  - !ruby/object:Gem::Version
45
114
  version: '0'
46
115
  requirements: []
47
- rubyforge_project:
48
- rubygems_version: 2.6.12
116
+ rubygems_version: 3.0.3
49
117
  signing_key:
50
118
  specification_version: 4
51
119
  summary: L2met friendly log formatter
@@ -1,11 +0,0 @@
1
- module L2meter
2
- class NullObject
3
- Emitter.instance_methods(false).each do |method_name|
4
- define_method method_name do |*, &block|
5
- block && block.call
6
- end
7
- end
8
-
9
- def print(*); end
10
- end
11
- end
@@ -1,54 +0,0 @@
1
- require "forwardable"
2
-
3
- module L2meter
4
- # This class is a wrapper around Emitter that makes sure that we have a
5
- # completely separate clone of Emitter per thread running. It doesn't truly
6
- # make Emitter thread-safe, it makes sure that you don't access the same
7
- # instance of emitter from different threads.
8
- class ThreadSafe
9
- extend Forwardable
10
-
11
- def initialize(emitter)
12
- @emitter = emitter.freeze
13
- end
14
-
15
- def_delegators :receiver, *Emitter.instance_methods(false)
16
-
17
- def context(*args, &block)
18
- value = current_emitter.context(*args, &block)
19
- Emitter === value ? clone_with_emitter(value) : value
20
- end
21
-
22
- def disable!
23
- @disabled = true
24
- end
25
-
26
- protected
27
-
28
- attr_writer :emitter
29
-
30
- private
31
-
32
- attr_reader :emitter
33
-
34
- def clone_with_emitter(emitter)
35
- self.class.new(emitter).tap { |ts| ts.disable! if @disabled }
36
- end
37
-
38
- def receiver
39
- @disabled ? null_emitter : current_emitter
40
- end
41
-
42
- def current_emitter
43
- Thread.current[thread_key] ||= emitter.clone
44
- end
45
-
46
- def null_emitter
47
- @null_emitter ||= NullObject.new
48
- end
49
-
50
- def thread_key
51
- @thread_key ||= "_l2meter_emitter_#{emitter.object_id}".freeze
52
- end
53
- end
54
- end