traceable 1.0.0

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.
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