loga 2.5.2 → 2.6.1
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 +4 -4
- data/.circleci/config.yml +17 -56
- data/Appraisals +26 -10
- data/CHANGELOG.md +20 -0
- data/Guardfile +1 -1
- data/README.md +1 -1
- data/gemfiles/rails60.gemfile +1 -1
- data/gemfiles/rails61.gemfile +11 -0
- data/gemfiles/rails70.gemfile +11 -0
- data/gemfiles/sidekiq51.gemfile +1 -1
- data/gemfiles/sidekiq6.gemfile +1 -1
- data/gemfiles/sidekiq61.gemfile +11 -0
- data/gemfiles/sinatra14.gemfile +1 -1
- data/gemfiles/unit.gemfile +1 -1
- data/lib/loga/configuration.rb +1 -0
- data/lib/loga/formatters/gelf_formatter.rb +16 -1
- data/lib/loga/rack/logger.rb +31 -44
- data/lib/loga/railtie.rb +1 -1
- data/lib/loga/sidekiq.rb +12 -4
- data/lib/loga/{sidekiq → sidekiq5}/job_logger.rb +1 -3
- data/lib/loga/sidekiq6/job_logger.rb +55 -0
- data/lib/loga/version.rb +1 -1
- data/loga.gemspec +4 -4
- data/spec/fixtures/rails61.rb +80 -0
- data/spec/fixtures/rails70.rb +80 -0
- data/spec/integration/sidekiq5_spec.rb +143 -0
- data/spec/integration/sidekiq6_spec.rb +164 -0
- data/spec/integration/sinatra_spec.rb +1 -1
- data/spec/loga/{sidekiq → sidekiq5}/job_logger_spec.rb +2 -1
- data/spec/loga/sidekiq6/job_logger_spec.rb +123 -0
- data/spec/loga/sidekiq_spec.rb +10 -3
- data/spec/spec_helper.rb +17 -8
- data/spec/unit/loga/formatters/gelf_formatter_spec.rb +43 -0
- data/spec/unit/loga/rack/logger_spec.rb +56 -36
- metadata +28 -17
- data/spec/integration/sidekiq_spec.rb +0 -147
@@ -166,6 +166,49 @@ describe Loga::Formatters::GELFFormatter do
|
|
166
166
|
end
|
167
167
|
end
|
168
168
|
|
169
|
+
context 'when working with sidekiq context' do
|
170
|
+
let(:options) { { message: 'Wooden house' } }
|
171
|
+
let(:message) { Loga::Event.new(options) }
|
172
|
+
let(:sidekiq_context) { { class: 'MyWorker', jid: '123' } }
|
173
|
+
|
174
|
+
before do
|
175
|
+
klass = Class.new do
|
176
|
+
class << self
|
177
|
+
attr_accessor :current
|
178
|
+
end
|
179
|
+
end
|
180
|
+
klass.current = sidekiq_context
|
181
|
+
stub_const('::Sidekiq::Context', klass)
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'includes the ::Sidekiq::Context.current data' do
|
185
|
+
expect(json['_class']).to eq('MyWorker')
|
186
|
+
expect(json['_jid']).to eq('123')
|
187
|
+
end
|
188
|
+
|
189
|
+
include_examples 'valid GELF message'
|
190
|
+
|
191
|
+
describe 'overwriting sidekiq context data with manual one' do
|
192
|
+
let(:options) { { message: 'Test', data: { class: 'CoolTest' } } }
|
193
|
+
|
194
|
+
it 'uses the manual data instead of the sidekiq context' do
|
195
|
+
expect(json['_class']).to eq('CoolTest')
|
196
|
+
end
|
197
|
+
|
198
|
+
include_examples 'valid GELF message'
|
199
|
+
end
|
200
|
+
|
201
|
+
describe ':elapsed in the sidekiq context' do
|
202
|
+
let(:sidekiq_context) { { class: 'MyWorker', jid: '123', elapsed: '22.2' } }
|
203
|
+
|
204
|
+
it 'transforms it to _duration' do
|
205
|
+
expect(json['_duration']).to eq(22.2)
|
206
|
+
end
|
207
|
+
|
208
|
+
include_examples 'valid GELF message'
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
169
212
|
{
|
170
213
|
'DEBUG' => 7,
|
171
214
|
'INFO' => 6,
|
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'rack/test'
|
3
3
|
|
4
|
-
# rubocop:disable RSpec/
|
4
|
+
# rubocop:disable RSpec/VerifiedDoubles RSpec/MessageSpies
|
5
5
|
describe Loga::Rack::Logger do
|
6
|
-
subject { described_class.new(app) }
|
6
|
+
subject(:middleware) { described_class.new(app) }
|
7
7
|
|
8
8
|
let(:env) { Rack::MockRequest.env_for('/about_us?limit=1', options) }
|
9
9
|
let(:options) { {} }
|
@@ -21,18 +21,25 @@ describe Loga::Rack::Logger do
|
|
21
21
|
)
|
22
22
|
end
|
23
23
|
|
24
|
-
|
24
|
+
let(:started_at) { Time.new(2021, 1, 2, 9, 30, 4.500, '+00:00') }
|
25
|
+
|
26
|
+
around do |example|
|
27
|
+
Timecop.freeze(Time.new(2021, 1, 2, 9, 30, 5.000, '+00:00'), &example)
|
28
|
+
end
|
29
|
+
|
30
|
+
before do
|
31
|
+
allow(Loga).to receive(:configuration).and_return(configuration)
|
32
|
+
end
|
25
33
|
|
26
34
|
shared_examples 'logs the event' do |details|
|
27
35
|
let(:level) { details[:level] }
|
28
36
|
|
29
|
-
before do
|
30
|
-
allow(subject).to receive(:started_at).and_return(:timestamp)
|
31
|
-
allow(subject).to receive(:duration_in_ms).with(any_args).and_return(5)
|
32
|
-
end
|
33
|
-
|
34
37
|
it 'instantiates a Loga::Event' do
|
35
|
-
|
38
|
+
allow(Loga::Event).to receive(:new).and_call_original
|
39
|
+
|
40
|
+
middleware.call(env, started_at)
|
41
|
+
|
42
|
+
expect(Loga::Event).to have_received(:new).with(
|
36
43
|
data: {
|
37
44
|
request: {
|
38
45
|
'status' => response_status,
|
@@ -42,21 +49,19 @@ describe Loga::Rack::Logger do
|
|
42
49
|
'request_id' => nil,
|
43
50
|
'request_ip' => nil,
|
44
51
|
'user_agent' => nil,
|
45
|
-
'duration' =>
|
52
|
+
'duration' => 500,
|
46
53
|
},
|
47
54
|
},
|
48
55
|
exception: logged_exception,
|
49
56
|
message: %r{^GET \/about_us\?limit=1 #{response_status} in \d+ms$},
|
50
|
-
timestamp:
|
57
|
+
timestamp: started_at,
|
51
58
|
type: 'request',
|
52
59
|
)
|
53
|
-
|
54
|
-
subject.call(env)
|
55
60
|
end
|
56
61
|
|
57
62
|
it "logs the Loga::Event with severity #{details[:level]}" do
|
58
63
|
allow(logger).to receive(level)
|
59
|
-
|
64
|
+
middleware.call(env, started_at)
|
60
65
|
expect(logger).to have_received(level).with(an_instance_of(Loga::Event))
|
61
66
|
end
|
62
67
|
end
|
@@ -69,9 +74,31 @@ describe Loga::Rack::Logger do
|
|
69
74
|
|
70
75
|
context 'when an exception is raised' do
|
71
76
|
let(:app) { ->(_env) { raise exception_class } }
|
72
|
-
|
73
|
-
|
74
|
-
|
77
|
+
let(:response_status) { 500 }
|
78
|
+
|
79
|
+
it 'raises error, but still logs an event' do
|
80
|
+
allow(Loga::Event).to receive(:new).and_call_original
|
81
|
+
|
82
|
+
expect { middleware.call(env, started_at) }.to raise_error(exception_class)
|
83
|
+
|
84
|
+
expect(Loga::Event).to have_received(:new).with(
|
85
|
+
data: {
|
86
|
+
request: {
|
87
|
+
'status' => response_status,
|
88
|
+
'method' => 'GET',
|
89
|
+
'path' => '/about_us',
|
90
|
+
'params' => { 'limit' => '1' },
|
91
|
+
'request_id' => nil,
|
92
|
+
'request_ip' => nil,
|
93
|
+
'user_agent' => nil,
|
94
|
+
'duration' => 500,
|
95
|
+
},
|
96
|
+
},
|
97
|
+
exception: logged_exception,
|
98
|
+
message: %r{^GET \/about_us\?limit=1 #{response_status} in \d+ms$},
|
99
|
+
timestamp: started_at,
|
100
|
+
type: 'request',
|
101
|
+
)
|
75
102
|
end
|
76
103
|
end
|
77
104
|
|
@@ -113,28 +140,21 @@ describe Loga::Rack::Logger do
|
|
113
140
|
end
|
114
141
|
|
115
142
|
context 'when the logger is tagged' do
|
116
|
-
let(:logger) {
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
block.call
|
123
|
-
end
|
124
|
-
end
|
143
|
+
let(:logger) { Loga::TaggedLogging.new(Logger.new('/dev/null')) }
|
144
|
+
let(:fake_tag_proc) { double(:proc, call: true) }
|
145
|
+
|
146
|
+
let(:tags) { [->(request) { fake_tag_proc.call(request) }] }
|
147
|
+
|
148
|
+
include_examples 'logs the event', level: :info
|
125
149
|
|
126
|
-
|
127
|
-
|
150
|
+
it 'calls the tags and computes them' do
|
151
|
+
middleware.call(env)
|
128
152
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
expect(logger).to have_received(:tagged).with(:tag) do |&block|
|
133
|
-
expect(block.call).to eq(:response)
|
134
|
-
end
|
135
|
-
end
|
153
|
+
expect(fake_tag_proc)
|
154
|
+
.to have_received(:call)
|
155
|
+
.with(instance_of(Loga::Rack::Request))
|
136
156
|
end
|
137
157
|
end
|
138
158
|
end
|
139
159
|
end
|
140
|
-
# rubocop:enable RSpec/
|
160
|
+
# rubocop:enable RSpec/VerifiedDoubles RSpec/MessageSpies
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: loga
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Funding Circle
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -44,26 +44,26 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 2.
|
47
|
+
version: '2.4'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 2.
|
54
|
+
version: '2.4'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: bundler
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '1.6'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.6'
|
69
69
|
- !ruby/object:Gem::Dependency
|
@@ -100,14 +100,14 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 4.7
|
103
|
+
version: '4.7'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 4.7
|
110
|
+
version: '4.7'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: guard-rubocop
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -184,14 +184,14 @@ dependencies:
|
|
184
184
|
requirements:
|
185
185
|
- - "~>"
|
186
186
|
- !ruby/object:Gem::Version
|
187
|
-
version: 3.7
|
187
|
+
version: '3.7'
|
188
188
|
type: :development
|
189
189
|
prerelease: false
|
190
190
|
version_requirements: !ruby/object:Gem::Requirement
|
191
191
|
requirements:
|
192
192
|
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
|
-
version: 3.7
|
194
|
+
version: '3.7'
|
195
195
|
- !ruby/object:Gem::Dependency
|
196
196
|
name: rubocop
|
197
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -260,8 +260,11 @@ files:
|
|
260
260
|
- gemfiles/rails50.gemfile
|
261
261
|
- gemfiles/rails52.gemfile
|
262
262
|
- gemfiles/rails60.gemfile
|
263
|
+
- gemfiles/rails61.gemfile
|
264
|
+
- gemfiles/rails70.gemfile
|
263
265
|
- gemfiles/sidekiq51.gemfile
|
264
266
|
- gemfiles/sidekiq6.gemfile
|
267
|
+
- gemfiles/sidekiq61.gemfile
|
265
268
|
- gemfiles/sinatra14.gemfile
|
266
269
|
- gemfiles/unit.gemfile
|
267
270
|
- lib/loga.rb
|
@@ -281,7 +284,8 @@ files:
|
|
281
284
|
- lib/loga/railtie.rb
|
282
285
|
- lib/loga/service_version_strategies.rb
|
283
286
|
- lib/loga/sidekiq.rb
|
284
|
-
- lib/loga/
|
287
|
+
- lib/loga/sidekiq5/job_logger.rb
|
288
|
+
- lib/loga/sidekiq6/job_logger.rb
|
285
289
|
- lib/loga/tagged_logging.rb
|
286
290
|
- lib/loga/utilities.rb
|
287
291
|
- lib/loga/version.rb
|
@@ -293,13 +297,17 @@ files:
|
|
293
297
|
- spec/fixtures/rails50.rb
|
294
298
|
- spec/fixtures/rails52.rb
|
295
299
|
- spec/fixtures/rails60.rb
|
300
|
+
- spec/fixtures/rails61.rb
|
301
|
+
- spec/fixtures/rails70.rb
|
296
302
|
- spec/fixtures/random_bin
|
297
303
|
- spec/integration/rails/action_mailer_spec.rb
|
298
304
|
- spec/integration/rails/railtie_spec.rb
|
299
305
|
- spec/integration/rails/request_spec.rb
|
300
|
-
- spec/integration/
|
306
|
+
- spec/integration/sidekiq5_spec.rb
|
307
|
+
- spec/integration/sidekiq6_spec.rb
|
301
308
|
- spec/integration/sinatra_spec.rb
|
302
|
-
- spec/loga/
|
309
|
+
- spec/loga/sidekiq5/job_logger_spec.rb
|
310
|
+
- spec/loga/sidekiq6/job_logger_spec.rb
|
303
311
|
- spec/loga/sidekiq_spec.rb
|
304
312
|
- spec/spec_helper.rb
|
305
313
|
- spec/support/gethostname_shared.rb
|
@@ -336,8 +344,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
336
344
|
- !ruby/object:Gem::Version
|
337
345
|
version: '0'
|
338
346
|
requirements: []
|
339
|
-
|
340
|
-
rubygems_version: 2.7.7
|
347
|
+
rubygems_version: 3.2.32
|
341
348
|
signing_key:
|
342
349
|
specification_version: 4
|
343
350
|
summary: Facilitate log aggregation via unified logging
|
@@ -349,13 +356,17 @@ test_files:
|
|
349
356
|
- spec/fixtures/rails50.rb
|
350
357
|
- spec/fixtures/rails52.rb
|
351
358
|
- spec/fixtures/rails60.rb
|
359
|
+
- spec/fixtures/rails61.rb
|
360
|
+
- spec/fixtures/rails70.rb
|
352
361
|
- spec/fixtures/random_bin
|
353
362
|
- spec/integration/rails/action_mailer_spec.rb
|
354
363
|
- spec/integration/rails/railtie_spec.rb
|
355
364
|
- spec/integration/rails/request_spec.rb
|
356
|
-
- spec/integration/
|
365
|
+
- spec/integration/sidekiq5_spec.rb
|
366
|
+
- spec/integration/sidekiq6_spec.rb
|
357
367
|
- spec/integration/sinatra_spec.rb
|
358
|
-
- spec/loga/
|
368
|
+
- spec/loga/sidekiq5/job_logger_spec.rb
|
369
|
+
- spec/loga/sidekiq6/job_logger_spec.rb
|
359
370
|
- spec/loga/sidekiq_spec.rb
|
360
371
|
- spec/spec_helper.rb
|
361
372
|
- spec/support/gethostname_shared.rb
|
@@ -1,147 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'timecop'
|
3
|
-
require 'fakeredis'
|
4
|
-
|
5
|
-
dummy_redis_config = ConnectionPool.new(size: 5) { Redis.new }
|
6
|
-
|
7
|
-
Sidekiq.configure_client do |config|
|
8
|
-
config.redis = dummy_redis_config
|
9
|
-
end
|
10
|
-
|
11
|
-
Sidekiq.configure_server do |config|
|
12
|
-
config.redis = dummy_redis_config
|
13
|
-
end
|
14
|
-
|
15
|
-
class MySidekiqWorker
|
16
|
-
include Sidekiq::Worker
|
17
|
-
|
18
|
-
def perform(_name); end
|
19
|
-
end
|
20
|
-
|
21
|
-
describe 'Sidekiq client logger' do
|
22
|
-
let(:target) { StringIO.new }
|
23
|
-
|
24
|
-
def read_json_log(line:)
|
25
|
-
target.rewind
|
26
|
-
JSON.parse(target.each_line.drop(line - 1).first)
|
27
|
-
end
|
28
|
-
|
29
|
-
before do
|
30
|
-
Redis.current.flushall
|
31
|
-
|
32
|
-
Loga.reset
|
33
|
-
|
34
|
-
Loga.configure(
|
35
|
-
service_name: 'hello_world_app',
|
36
|
-
service_version: '1.0',
|
37
|
-
device: target,
|
38
|
-
format: :gelf,
|
39
|
-
)
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'has the proper job logger' do
|
43
|
-
job_logger = Loga::Sidekiq::JobLogger
|
44
|
-
|
45
|
-
expect(Sidekiq.options[:job_logger]).to eq job_logger
|
46
|
-
end
|
47
|
-
|
48
|
-
it 'has the proper logger for Sidekiq.logger' do
|
49
|
-
expect(Sidekiq.logger).to eq Loga.logger
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'pushes a new element in the default queue' do
|
53
|
-
MySidekiqWorker.perform_async('Bob')
|
54
|
-
|
55
|
-
last_element = JSON.parse(Redis.current.lpop('queue:default'))
|
56
|
-
|
57
|
-
aggregate_failures do
|
58
|
-
expect(last_element['class']).to eq 'MySidekiqWorker'
|
59
|
-
expect(last_element['args']).to eq ['Bob']
|
60
|
-
expect(last_element['retry']).to eq true
|
61
|
-
expect(last_element['queue']).to eq 'default'
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
if ENV['BUNDLE_GEMFILE'] =~ /sidekiq51/
|
66
|
-
it 'has the proper logger Sidekiq::Logging.logger' do
|
67
|
-
expect(Sidekiq::Logging.logger).to eq Loga.logger
|
68
|
-
end
|
69
|
-
|
70
|
-
# https://github.com/mperham/sidekiq/blob/97363210b47a4f8a1d8c1233aaa059d6643f5040/test/test_actors.rb#L57-L79
|
71
|
-
let(:mgr) do
|
72
|
-
Class.new do
|
73
|
-
attr_reader :latest_error, :mutex, :cond
|
74
|
-
|
75
|
-
def initialize
|
76
|
-
@mutex = ::Mutex.new
|
77
|
-
@cond = ::ConditionVariable.new
|
78
|
-
end
|
79
|
-
|
80
|
-
def processor_died(_inst, err)
|
81
|
-
@latest_error = err
|
82
|
-
|
83
|
-
@mutex.synchronize { @cond.signal }
|
84
|
-
end
|
85
|
-
|
86
|
-
def processor_stopped(_inst)
|
87
|
-
@mutex.synchronize { @cond.signal }
|
88
|
-
end
|
89
|
-
|
90
|
-
def options
|
91
|
-
{
|
92
|
-
concurrency: 3,
|
93
|
-
queues: ['default'],
|
94
|
-
job_logger: Loga::Sidekiq::JobLogger,
|
95
|
-
}
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
it 'logs the job processing event' do
|
101
|
-
MySidekiqWorker.perform_async('Bob')
|
102
|
-
|
103
|
-
require 'sidekiq/processor'
|
104
|
-
Sidekiq::Processor.new(mgr.new).start
|
105
|
-
sleep 0.5
|
106
|
-
|
107
|
-
expected_attributes = {
|
108
|
-
'_queue'=> 'default',
|
109
|
-
'_retry'=> true,
|
110
|
-
'_params'=> ['Bob'],
|
111
|
-
'_class'=> 'MySidekiqWorker',
|
112
|
-
'_type'=> 'sidekiq',
|
113
|
-
'_service.name'=> 'hello_world_app',
|
114
|
-
'_service.version'=> '1.0',
|
115
|
-
'_tags'=> '',
|
116
|
-
'level'=> 6,
|
117
|
-
'version'=> '1.1',
|
118
|
-
}
|
119
|
-
|
120
|
-
json_line = read_json_log(line: 1)
|
121
|
-
|
122
|
-
aggregate_failures do
|
123
|
-
expect(json_line).to include(expected_attributes)
|
124
|
-
|
125
|
-
%w[_created_at _enqueued_at _jid _duration timestamp host].each do |key|
|
126
|
-
expect(json_line).to have_key(key)
|
127
|
-
end
|
128
|
-
|
129
|
-
expect(json_line['short_message']).to match(/MySidekiqWorker with jid:*/)
|
130
|
-
end
|
131
|
-
|
132
|
-
# This was a bug - the duration was constantly incresing based on when
|
133
|
-
# the logger was created. https://github.com/FundingCircle/loga/pull/117
|
134
|
-
#
|
135
|
-
# Test that after sleeping for few seconds the duration is still under 500ms
|
136
|
-
sleep 1
|
137
|
-
|
138
|
-
MySidekiqWorker.perform_async('Bob')
|
139
|
-
|
140
|
-
sleep 1
|
141
|
-
|
142
|
-
json_line = read_json_log(line: 2)
|
143
|
-
|
144
|
-
expect(json_line['_duration']).to be < 500
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|