traceable 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dc0fb1daa3bcf7f643a76c12767cb4b6879de019
4
+ data.tar.gz: a477c637eefd5b76b28105a83279d7faa5880650
5
+ SHA512:
6
+ metadata.gz: 3384130f5711e0c854c9b00d093b82cca25e8b9f7333c2be73b61d58eb741df9abab464ed07a5cbd3f50dafbaeee8b11aa062a1a9ef20d19699e5c06b9c883aa
7
+ data.tar.gz: 34a461846b43e0015c1e5c67b53eeb306233729270283f5c546d2e42140930bf54d4472b7726cb31b8502829c935e55f16be910ec7df039f7109f5215634634a
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Traceable
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ # Put an automatic '#trace' call around the specified method,
10
+ # so that entry and exit from the method will be automatically logged.
11
+ #
12
+ # Can be used when defining the method:
13
+ #
14
+ # class X
15
+ # include SISApp::Traceable
16
+ # traced_method def foo
17
+ # ...
18
+ # end
19
+ # end
20
+ #
21
+ # or it can be called after defining the method:
22
+ #
23
+ # class X
24
+ # include SISApp::Traceable
25
+ # def foo
26
+ # ...
27
+ # end
28
+ # traced_method :foo
29
+ # end
30
+ #
31
+ # The generated log message(s) will include the class name and method name
32
+ def traced_method(method_name)
33
+ klass = self
34
+ trace_name = "#{klass.name}##{method_name}"
35
+ orig_method = klass.instance_method(method_name)
36
+
37
+ klass.send(:define_method, method_name) do |*args, &block|
38
+ trace trace_name do
39
+ if block
40
+ orig_method.bind(self).call(*args) { |*block_args| block.call(*block_args) }
41
+ else
42
+ orig_method.bind(self).call(*args)
43
+ end
44
+ end
45
+ end
46
+
47
+ method_name
48
+ end
49
+
50
+ # For the (relatively rare) case of calling trace() in a class method
51
+ # of a Traceable class - creates a new Tracer instance
52
+ def trace(msg = nil, **tags)
53
+ tracer = Tracer.new(Tracer.default_parent)
54
+
55
+ if block_given?
56
+ tracer.do_block(msg, **tags) { yield }
57
+ elsif msg
58
+ tracer.info msg, **tags
59
+ else
60
+ tracer
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Traceable
6
+ class Config
7
+ attr_accessor :default_tags
8
+ attr_accessor :logger
9
+
10
+ def initialize
11
+ @logger = Logger.new(STDOUT)
12
+ @logger.level = Logger::INFO
13
+
14
+ @default_tags = {}
15
+ end
16
+ end
17
+
18
+ def self.configure(&_)
19
+ yield config
20
+ end
21
+
22
+ def self.config
23
+ @config ||= Config.new
24
+ end
25
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Traceable
4
+ # An instance of this class is used by each Traceable object to
5
+ # generate log entries. Each log entry consists of a key/value
6
+ # map ("tags"). Tags will be inherited from a parent instance,
7
+ # if provided.
8
+ class Tracer
9
+ attr_reader :logger
10
+ attr_reader :id
11
+ attr_reader :parent
12
+ attr_reader :tags
13
+
14
+ def initialize(parent_tracer, tags: nil)
15
+ @logger = Traceable.config.logger
16
+
17
+ @parent = case parent_tracer
18
+ when nil
19
+ nil
20
+ when Tracer
21
+ parent_tracer
22
+ when Traceable
23
+ parent_tracer.local_tracer
24
+ else
25
+ raise(ArgumentError, "#{parent_tracer} (#{parent_tracer.class})")
26
+ end
27
+
28
+ @tags = @parent ? @parent.tags.dup : Tracer.default_tags
29
+ @tags.merge!(tags) if tags
30
+ end
31
+
32
+ def self.default_tags
33
+ tags = Traceable.config.default_tags.merge(
34
+ trace: SecureRandom.uuid
35
+ )
36
+ tags.each_key do |k|
37
+ value = tags[k]
38
+ tags[k] = value.call if value.respond_to?(:call)
39
+ end
40
+ tags
41
+ end
42
+
43
+ def emit(method, msg, **tags)
44
+ final_tags = make_tags(message: msg, **tags)
45
+ emit_tags(method, final_tags)
46
+ end
47
+
48
+ def make_tags(**tags)
49
+ @tags.merge(tags)
50
+ end
51
+
52
+ def emit_tags(method, tags)
53
+ logger.send(method, tags)
54
+ rescue StandardError => ex
55
+ warn "EXCEPTION in trace: #{ex}"
56
+ end
57
+
58
+ def fatal(msg, **tags)
59
+ emit :fatal, msg, **tags
60
+ end
61
+
62
+ def error(msg, **tags)
63
+ emit :error, msg, **tags
64
+ end
65
+
66
+ def warn(msg, **tags)
67
+ emit :warn, msg, **tags
68
+ end
69
+
70
+ def info(msg, **tags)
71
+ emit :info, msg, **tags
72
+ end
73
+
74
+ def debug(msg, **tags)
75
+ emit :debug, msg, **tags
76
+ end
77
+
78
+ # rubocop:disable Metrics/AbcSize
79
+ # rubocop:disable Metrics/MethodLength
80
+ def do_block(msg, **tags, &_)
81
+ info "START: #{msg}", enter: true, **tags
82
+ block_start_time = Time.now.utc
83
+
84
+ begin
85
+ push
86
+ yield
87
+ rescue StandardError => ex
88
+ elapsed = Time.now.utc - block_start_time
89
+ if ex.instance_variable_defined?(:@traceable_rescued)
90
+ origin = ex.instance_variable_get(:@traceable_rescued)
91
+ ex_message = " [propagated from #{origin}]"
92
+ else
93
+ ex.instance_variable_set(:@traceable_rescued, msg)
94
+ ex_message = ", #{ex.message}"
95
+ tags[:backtrace] ||= ex.backtrace.join('\n')
96
+ end
97
+
98
+ warn "EXCEPTION: #{msg} => #{ex.class.name}#{ex_message}",
99
+ exception: true, elapsed: elapsed, class: ex.class.name, **tags
100
+
101
+ raise
102
+ ensure
103
+ pop
104
+
105
+ unless ex
106
+ elapsed = Time.now.utc - block_start_time
107
+ info "END: #{msg}", exit: true, elapsed: elapsed, **tags
108
+ end
109
+ end
110
+ end
111
+ # rubocop:enable Metrics/AbcSize
112
+ # rubocop:enable Metrics/MethodLength
113
+
114
+ def self.default_parent
115
+ tracer_stack.last # nil if nothing currently on the stack
116
+ end
117
+
118
+ # The tracer stack is a thread-local list of tracer instances, allowing the
119
+ # current tracer context to be automatically inherited by any other tracer
120
+ # instances created further down in the stack.
121
+ #
122
+ # The current tracer is pushed onto the stack when entering a traced block,
123
+ # then popped from the stack when leaving the traced block.
124
+
125
+ def self.tracer_stack
126
+ Thread.current[:tracer_stack] ||= []
127
+ end
128
+
129
+ def push
130
+ Tracer.tracer_stack.push self
131
+ end
132
+
133
+ def pop
134
+ Tracer.tracer_stack.pop
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Traceable
4
+ VERSION = '1.0.0'
5
+ end
data/lib/traceable.rb ADDED
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Traceable
6
+ # A module for enhanced logging facilities.
7
+ #
8
+ # The intent of the Traceable functionality is to have log
9
+ # messages that provide runtime diagnostics as well as
10
+ # code documentation.
11
+
12
+ # Generate a log message
13
+ # When called without a block, generates a single log message:
14
+ #
15
+ # trace "this is a single message"
16
+ # trace.error "something bad happened"
17
+ #
18
+ # When called with a block, the given message is used to compose
19
+ # a log output at the entry and exit of the block.
20
+ #
21
+ # trace "doing something nifty" do
22
+ # do_something
23
+ # end
24
+ def trace(msg = nil, **tags)
25
+ tracer = local_tracer
26
+
27
+ if block_given?
28
+ tracer.do_block(msg, **tags) { yield }
29
+ elsif msg
30
+ tracer.info msg, **tags
31
+ else
32
+ tracer
33
+ end
34
+ end
35
+
36
+ def local_tracer
37
+ @tracer ||= init_tracer
38
+ end
39
+
40
+ # Create the tracer instance used for generating log messages.
41
+ # If a parent is givent, tags and other settings will be
42
+ # inherited from it. If a parent is not given, it will automatically
43
+ # inherit from the next highest tracer in the call stack, if any.
44
+ #
45
+ # The default set of tags includes a unique ID string, so all
46
+ # log messages generated from that tracer will have the same
47
+ # ID string. In combination with auto-inheriting from a parent
48
+ # tracer, this means that all tracing messages starting from
49
+ # some common root will have the same ID string to be able
50
+ # to group together related messages in the log.
51
+ def init_tracer(parent: nil, tags: nil)
52
+ parent ||= Tracer.default_parent
53
+ @tracer = Tracer.new(parent, tags: tags)
54
+ end
55
+ end
56
+
57
+ require 'traceable/class_methods'
58
+ require 'traceable/config'
59
+ require 'traceable/tracer'
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ if /^2\.4/ =~ RUBY_VERSION # Limit coverage to one build
4
+ require 'simplecov'
5
+
6
+ SimpleCov.start do
7
+ add_filter 'lib/traceable/version.rb'
8
+ add_filter 'spec'
9
+ track_files 'lib/**/*.rb'
10
+ end
11
+
12
+ SimpleCov.minimum_coverage(100)
13
+ end
14
+
15
+ require 'traceable'
16
+
17
+ require 'byebug'
18
+ require 'pry'
19
+
20
+ RSpec.configure do |config|
21
+ config.expect_with(:rspec) do |c|
22
+ c.syntax = %i[should expect]
23
+ end
24
+
25
+ # Seed global randomization in this process using the `--seed` CLI option.
26
+ # Setting this allows you to use `--seed` to deterministically reproduce
27
+ # test failures related to randomization by passing the same `--seed` value
28
+ # as the one that triggered the failure.
29
+ config.order = :random
30
+ Kernel.srand config.seed
31
+ end
@@ -0,0 +1,327 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Traceable do
4
+ class FakeLogger
5
+ attr_reader :logs
6
+ def initialize
7
+ @logs = []
8
+ end
9
+
10
+ %i[info debug warn error fatal].each do |method|
11
+ define_method(method) do |tags|
12
+ logs << { method: method, tags: tags }
13
+ end
14
+ end
15
+ end
16
+
17
+ let(:fake_logger) { FakeLogger.new }
18
+ let(:logs) { fake_logger.logs }
19
+
20
+ before do
21
+ allow_any_instance_of(Traceable::Tracer).to receive(:logger) { fake_logger }
22
+ end
23
+
24
+ let(:subject_class) do
25
+ Class.new do
26
+ include Traceable
27
+
28
+ attr_reader :non_traced_called
29
+ def a_non_traced_method
30
+ @non_traced_called = true
31
+ :non_traced_return_value
32
+ end
33
+
34
+ attr_reader :traced_called
35
+ traced_method def a_traced_method
36
+ @traced_called = true
37
+ yield if block_given?
38
+ :traced_return_value
39
+ end
40
+
41
+ traced_method def a_traced_method_that_calls_a_block_with_a_parameter(val)
42
+ yield val
43
+ end
44
+
45
+ def trace_as_a_block
46
+ trace 'an example' do
47
+ trace 'a nested block' do
48
+ true
49
+ end
50
+ end
51
+ end
52
+
53
+ def self.a_class_method
54
+ trace 'in a class method'
55
+ trace 'in a class method with a block' do
56
+ trace.info 'test'
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ let(:contained_class) do
63
+ Class.new do
64
+ include Traceable
65
+
66
+ def initialize(parent)
67
+ init_tracer(parent: parent, tags: { inside: true })
68
+ end
69
+ end
70
+ end
71
+
72
+ subject do
73
+ subject_class.new
74
+ end
75
+
76
+ describe '#configure' do
77
+ it 'receives a Config instance' do
78
+ @config = nil
79
+ Traceable.configure { |c| @config = c }
80
+ expect(@config).to be_a Traceable::Config
81
+ end
82
+ end
83
+
84
+ describe '#config' do
85
+ end
86
+
87
+ describe '#trace' do
88
+ it 'responds to #trace' do
89
+ expect(subject.respond_to?(:trace)).to eq(true)
90
+ end
91
+
92
+ context 'when no block is given' do
93
+ it 'can be used like an explicit logger' do
94
+ subject.trace.info 'general info'
95
+ subject.trace.warn 'a warning'
96
+ subject.trace.error 'things look bad'
97
+ subject.trace.fatal 'fell on the floor'
98
+ subject.trace.debug 'fix me'
99
+ expect(logs[0][:method]).to eq(:info)
100
+ expect(logs[1][:method]).to eq(:warn)
101
+ expect(logs[2][:method]).to eq(:error)
102
+ expect(logs[3][:method]).to eq(:fatal)
103
+ expect(logs[4][:method]).to eq(:debug)
104
+ end
105
+
106
+ it 'logs as info as default' do
107
+ subject.trace 'blah', foo: :bar
108
+ expect(logs.size).to be(1)
109
+ expect(logs[0][:method]).to eq(:info)
110
+ expect(logs[0][:tags][:foo]).to be(:bar)
111
+ end
112
+ end
113
+
114
+ context 'when a block is given' do
115
+ it 'logs at start and end of the block' do
116
+ subject.trace_as_a_block
117
+ expect(logs.size).to be(4)
118
+ expect(logs[0][:tags].key?(:enter)).to be(true)
119
+ expect(logs[0][:tags][:message]).to eq('START: an example')
120
+ expect(logs[1][:tags].key?(:enter)).to be(true)
121
+ expect(logs[1][:tags][:message]).to eq('START: a nested block')
122
+ expect(logs[2][:tags].key?(:exit)).to be(true)
123
+ expect(logs[2][:tags][:message]).to eq('END: a nested block')
124
+ expect(logs[3][:tags].key?(:exit)).to be(true)
125
+ expect(logs[3][:tags][:message]).to eq('END: an example')
126
+ end
127
+
128
+ it 'reports the elapsed time of the block' do
129
+ subject.trace_as_a_block
130
+ count = 0
131
+ logs.select { |log| log[:tags].key?(:exit) }.each do |log|
132
+ expect(log[:tags][:elapsed]).to be_a(Numeric)
133
+ count += 1
134
+ end
135
+ expect(count).to be(2)
136
+ end
137
+ end
138
+
139
+ context 'in a class method' do
140
+ it 'works just like an instance method' do
141
+ expect { subject.class.a_class_method }.to change { logs.size }.by 4
142
+ end
143
+ end
144
+ end
145
+
146
+ describe '#local_tracer' do
147
+ it 'returns a Tracer object' do
148
+ expect(subject.local_tracer).to be_a(Traceable::Tracer)
149
+ end
150
+
151
+ it 'inherits a parent tracer' do
152
+ contained = contained_class.new(subject)
153
+ expect(contained.local_tracer).not_to eq(subject.local_tracer)
154
+ expect(contained.local_tracer.parent).to eq(subject.local_tracer)
155
+ end
156
+
157
+ it 'adds tags from the child' do
158
+ contained = contained_class.new(subject)
159
+ expect(subject.local_tracer.tags.key?(:inside)).to be(false)
160
+ expect(contained.local_tracer.tags.key?(:inside)).to be(true)
161
+ end
162
+
163
+ context 'tracer IDs' do
164
+ class Foo
165
+ include Traceable
166
+ def do_it
167
+ trace 'outside' do
168
+ trace 'inside' do
169
+ trace 'a message'
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ it 'creates a unique ID each time' do
176
+ (1..10).each do |_|
177
+ Foo.new.do_it
178
+ end
179
+
180
+ ids = Set.new
181
+ logs.each { |log| ids << log[:tags][:trace] }
182
+ expect(ids.size).to eq(10)
183
+ end
184
+ end
185
+ end
186
+
187
+ describe '.traced_method' do
188
+ context 'for non-traced methods' do
189
+ it 'does not interfere' do
190
+ expect(subject.a_non_traced_method).to eq(:non_traced_return_value)
191
+ expect(subject.non_traced_called).to eq(true)
192
+ end
193
+ end
194
+
195
+ context 'for traced methods' do
196
+ it 'does not interfere' do
197
+ expect(subject.a_traced_method).to eq(:traced_return_value)
198
+ expect(subject.traced_called).to eq(true)
199
+ end
200
+
201
+ it 'emits a log entry at start and end' do
202
+ subject.a_traced_method
203
+ expect(logs.size).to be(2)
204
+ expect(logs[0][:tags].key?(:enter)).to be(true)
205
+ expect(logs[1][:tags].key?(:exit)).to be(true)
206
+ end
207
+
208
+ it 'catches and raises exceptions' do
209
+ expect { subject.a_traced_method { raise 'oops' } }.to raise_error('oops')
210
+ expect(logs.size).to be(2)
211
+ expect(logs[0][:tags].key?(:enter)).to be(true)
212
+ expect(logs[1][:tags].key?(:exception)).to be(true)
213
+ expect(logs[1][:tags].key?(:elapsed)).to be(true)
214
+ expect(logs[1][:tags].key?(:backtrace)).to be(true)
215
+ end
216
+
217
+ it 'catches and raises exceptions through nested blocks' do
218
+ expect do
219
+ subject.trace 'level one' do
220
+ subject.a_traced_method do
221
+ subject.trace 'level two' do
222
+ raise 'oops'
223
+ end
224
+ end
225
+ end
226
+ end.to raise_error('oops')
227
+
228
+ origin = logs.find { |log| log[:tags][:exception] && log[:tags][:message].include?('level two') }
229
+ expect(origin[:tags].key?(:backtrace)).to be(true)
230
+
231
+ middle = logs.find { |log| log[:tags][:exception] && log[:tags][:message].include?('a_traced_method') }
232
+ expect(middle[:tags].key?(:backtrace)).to be(false)
233
+ expect(middle[:tags][:message].include?('propagated')).to be(true)
234
+
235
+ outer = logs.find { |log| log[:tags][:exception] && log[:tags][:message].include?('level one') }
236
+ expect(outer[:tags].key?(:backtrace)).to be(false)
237
+ expect(middle[:tags][:message].include?('propagated')).to be(true)
238
+ end
239
+
240
+ it 'properly passes a block through' do
241
+ yielded = false
242
+ subject.a_traced_method do
243
+ yielded = true
244
+ end
245
+ expect(yielded).to eq(true)
246
+ end
247
+
248
+ it 'properly passes a block with args through' do
249
+ yielded = false
250
+ subject.a_traced_method_that_calls_a_block_with_a_parameter(:blah) do |val|
251
+ yielded = val
252
+ end
253
+ expect(yielded).to eq(:blah)
254
+ end
255
+ end
256
+ end
257
+
258
+ describe Traceable::Tracer do
259
+ describe '#default_parent' do
260
+ class One
261
+ include Traceable
262
+ def initialize
263
+ init_tracer tags: { one: true }
264
+ end
265
+
266
+ traced_method def first
267
+ Two.new.second
268
+ Thread.new { Two.new.second }.join # New thread --> don't inherit tracer context
269
+ end
270
+ end
271
+
272
+ class Two
273
+ def second
274
+ Three.new.third
275
+ end
276
+ end
277
+
278
+ class Three
279
+ include Traceable
280
+ traced_method def third
281
+ trace 'in the third', three: true
282
+ end
283
+ end
284
+
285
+ it 'should include tags from the default parent' do
286
+ One.new.first
287
+ # expected logs:
288
+ # 0 START: One#first, :one => true
289
+ # 1 START: Three#third, :one => true, :three => true
290
+ # 2 'in the third', :one => true, :three => true
291
+ # 3 END: Three#third
292
+ # 4 START: Three#third, :three => true
293
+ # 5 'in the third', :three => true
294
+ # 6 END: Three#third
295
+ # 7 END: One#first, :one => true
296
+ expect(logs.count).to be(8)
297
+
298
+ expect(logs[1][:tags].key?(:one)).to be(true)
299
+ expect(logs[2][:tags].key?(:one)).to be(true)
300
+ expect(logs[1][:tags][:trace]).to eq(logs[0][:tags][:trace]) # Same parent, same trace tag
301
+
302
+ expect(logs[4][:tags].key?(:one)).to be(false)
303
+ expect(logs[5][:tags].key?(:one)).to be(false)
304
+ expect(logs[4][:tags][:trace]).to_not eq(logs[1][:tags][:trace]) # Diff parent, diff trace tag
305
+ end
306
+ end
307
+
308
+ describe '#initialize' do
309
+ it 'complains with invalid argument' do
310
+ expect { Traceable::Tracer.new('foo') }.to raise_error(ArgumentError)
311
+ end
312
+ end
313
+
314
+ describe '#emit_tags' do
315
+ context 'when logger chokes on the message' do
316
+ before do
317
+ allow(fake_logger).to receive(:info) { raise('blow chunks') }
318
+ end
319
+ it 'does not allow the exception to blow up' do
320
+ t = Traceable::Tracer.new(nil)
321
+ expect(t).to receive(:emit_tags).twice.and_call_original
322
+ t.info(message: 'just a test')
323
+ end
324
+ end
325
+ end
326
+ end
327
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: traceable
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Slade
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-02-23 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: 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: pry
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.4.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.4.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
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'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.14'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.14'
97
+ - !ruby/object:Gem::Dependency
98
+ name: wwtd
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 1.3.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 1.3.0
111
+ description:
112
+ email:
113
+ - jeremy@jkslade.net
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - lib/traceable.rb
119
+ - lib/traceable/class_methods.rb
120
+ - lib/traceable/config.rb
121
+ - lib/traceable/tracer.rb
122
+ - lib/traceable/version.rb
123
+ - spec/spec_helper.rb
124
+ - spec/traceable_spec.rb
125
+ homepage: https://github.com/instructure/inst-jobs-statsd
126
+ licenses:
127
+ - MIT
128
+ metadata: {}
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '2.3'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubyforge_project:
145
+ rubygems_version: 2.5.1
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: Instrument code with logging
149
+ test_files:
150
+ - spec/spec_helper.rb
151
+ - spec/traceable_spec.rb