sidekiq_publisher 1.6.4 → 1.7.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '067609f016376825479264cb9151d9dd8d306cdbbc019b2f2dbe08fa503eb01e'
4
- data.tar.gz: 6b93fa60214589688a2e6e993bebbb92ba1d321f67dd9a6497a654b5e1cde438
3
+ metadata.gz: 857fd87d65ea4d55711d45e269148eeb0b14454a27ae4315c507815b22c86ea4
4
+ data.tar.gz: 7d3d092e6e2df1c64734c6a60a8bc8b084fba68c8e684f4e0e578c99fdea117b
5
5
  SHA512:
6
- metadata.gz: 35eecd771b2561add98ffc2efa8661800e2b60bb6664918cd6de09906a6839bf515c4cff640e317ba16903b7ad4b36ceccfbbf894dcfaa2d2c1b8709b2615a9a
7
- data.tar.gz: 508cb5e0d9e9af56c3a068c87520f1085b85dda2477fb49a467a00d9fc846b885f27c5ceffc732a4dd5e06a981ed247b76af0a33bfa86daa7ef64c45d3e8e7eb
6
+ metadata.gz: 2938169bd4db7a074936bc48f46456c4ec2700a5bfc60adf4039ffc87666f3ba99f5c42504e72dfa34c55fc71568f4e7ef695489d3c9165db66c996a3a6363bb
7
+ data.tar.gz: da2bbee1abf0fb70a7afb05b48d5c7c692ea9911ae5b26f35500eb69792a54872213d509c7842564bcc5b659c1070415cb0bd52d71e4cfcdb5acb6705a2fafa3
@@ -0,0 +1 @@
1
+ * @ezcater/core-platform @tjwp
@@ -1,5 +1,11 @@
1
1
  # sidekiq_publisher
2
2
 
3
+ ## v1.7.0 (unreleased)
4
+ - Add instrumentation using `ActiveSupport::Notifications`.
5
+ - Reimplement `metrics_reporter` and `exception_reporter` support using
6
+ `ActiveSupport::Subscriber`.
7
+ - Add optional integration with Datadog APM.
8
+
3
9
  ## v1.6.4
4
10
  - Expand sidekiq support to v5.0.x-v6.x.x.
5
11
 
@@ -0,0 +1,4 @@
1
+ FROM ezcater-production.jfrog.io/ruby:f08726283c
2
+ RUN mkdir /usr/src/gem
3
+ WORKDIR /usr/src/gem
4
+ ADD . /usr/src/gem
data/README.md CHANGED
@@ -72,6 +72,34 @@ SidekiqPublisher::ReportUnpublishedCount.call
72
72
  It is recommended to call this method periodically using something like
73
73
  cron or [clockwork](https://github.com/Rykian/clockwork).
74
74
 
75
+ ## Instrumentation
76
+
77
+ Instrumentation of this library is implemented using
78
+ [ActiveSupport::Notifications](https://api.rubyonrails.org/classes/ActiveSupport/Notifications.html).
79
+
80
+ The support for the configurable [metrics_reporter](lib/sidekiq_publisher/metrics_reporter.rb) and
81
+ [exception_reporter](lib/sidekiq_publisher/exception_reporter.rb) options is implemented using
82
+ [ActiveSupport::Subscriber](https://api.rubyonrails.org/classes/ActiveSupport/Subscriber.html).
83
+
84
+ If an alternate integration is required for metrics or error reporting then it can be implemented using outside this
85
+ library based on these examples.
86
+
87
+ ### Tracing
88
+
89
+ The instrumentation in the library also supports integration with application tracing products, such as
90
+ [Datadog APM](https://www.datadoghq.com/product/apm/).
91
+
92
+ There is an optional integration with Datadog APM that can be required:
93
+
94
+ ```ruby
95
+ require "sidekiq_publisher/datadog_apm"
96
+ ```
97
+
98
+ This file must be required in addition including the `sidekiq_publisher` gem or requiring `sidekiq_publisher`.
99
+
100
+ This integration covers all of the sections of the library that are instrumented and serves an
101
+ [example](lib/sidekiq_publisher/datadog_apm) for implementing trace reporting for other products outside this library.
102
+
75
103
  ## Usage
76
104
 
77
105
  ### ActiveJob Adapter
@@ -0,0 +1,43 @@
1
+ version: "3.4"
2
+ volumes:
3
+ bundle-volume:
4
+ shared-volume:
5
+ x-environment: &default-environment
6
+ PRYRC: /usr/src/app/.docker-pryrc
7
+ BUNDLE_IGNORE_CONFIG: 1
8
+ BUNDLE_DISABLE_SHARED_GEMS: "true"
9
+ PGUSER: postgres
10
+ PGHOST: sidekiq-publisher-postgres
11
+ PGPORT: 5432
12
+ PGDATABASE: sidekiq_publisher_test
13
+ REDIS_URL: redis://sidekiq-publisher-redis:6379
14
+ x-service: &default-service
15
+ build:
16
+ context: .
17
+ args:
18
+ - BUNDLE_EZCATER__JFROG__IO
19
+ volumes:
20
+ - .:/usr/src/gem
21
+ - bundle-volume:/usr/local/bundle:delegated
22
+ - shared-volume:/usr/src/shared:delegated
23
+ tty: true
24
+ stdin_open: true
25
+ services:
26
+ sidekiq-publisher-redis:
27
+ container_name: sidekiq-publisher-redis_1
28
+ image: redis:5.0.9-alpine
29
+ sidekiq-publisher-postgres:
30
+ container_name: sidekiq-publisher-postgres_1
31
+ image: postgres:11.6
32
+ environment:
33
+ POSTGRES_USER: postgres
34
+ POSTGRES_DB: sidekiq_publisher_test
35
+ sidekiq-publisher-console:
36
+ <<: *default-service
37
+ container_name: sidekiq-publisher-console_1
38
+ environment:
39
+ <<: *default-environment
40
+ command: bash
41
+ depends_on:
42
+ - sidekiq-publisher-redis
43
+ - sidekiq-publisher-postgres
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support"
3
4
  require "sidekiq_publisher/version"
5
+ require "sidekiq_publisher/instrumenter"
6
+ require "sidekiq_publisher/metrics_reporter"
7
+ require "sidekiq_publisher/exception_reporter"
4
8
  require "sidekiq_publisher/report_unpublished_count"
5
9
  require "sidekiq_publisher/job"
6
10
  require "sidekiq_publisher/worker"
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/subscriber"
4
+ require "ddtrace"
5
+
6
+ module SidekiqPublisher
7
+ module DatadogAPM
8
+ class << self
9
+ attr_writer :service
10
+
11
+ def service
12
+ @service || "sidekiq-publisher"
13
+ end
14
+ end
15
+
16
+ class Subscriber
17
+ def self.subscribe_to(pattern)
18
+ ActiveSupport::Notifications.subscribe(pattern, new)
19
+ end
20
+
21
+ def finish(_name, _id, payload)
22
+ finish_span(payload)
23
+ end
24
+
25
+ private
26
+
27
+ def start_span(operation, payload)
28
+ # Internal sanity check
29
+ raise "Fix operation name: #{operation}" if operation.end_with?("sidekiq_publisher")
30
+
31
+ payload[:datadog_span] = Datadog.tracer.trace(operation, service: service)
32
+ end
33
+
34
+ def finish_span(payload)
35
+ payload[:datadog_span]&.set_error(payload[:exception_object]) if payload.key?(:exception_object)
36
+ payload[:datadog_span]&.finish
37
+ end
38
+
39
+ def service
40
+ SidekiqPublisher::DatadogAPM.service
41
+ end
42
+ end
43
+
44
+ class ListenerSubscriber < Subscriber
45
+ def start(_name, _id, payload)
46
+ start_span("listener.timeout", payload)
47
+ end
48
+
49
+ subscribe_to "timeout.listener.sidekiq_publisher"
50
+ end
51
+
52
+ class RunnerSubscriber < Subscriber
53
+ def start(name, _id, payload)
54
+ op_name = name.split(".").first
55
+ start_span("publisher.#{op_name}", payload)
56
+ end
57
+
58
+ subscribe_to "start.publisher.sidekiq_publisher"
59
+ subscribe_to "notify.publisher.sidekiq_publisher"
60
+ subscribe_to "timeout.publisher.sidekiq_publisher"
61
+ end
62
+
63
+ class PublisherSubscriber < Subscriber
64
+ def start(name, _id, payload)
65
+ op_name = name.split(".").first
66
+ start_span("publisher.#{op_name}", payload)
67
+ end
68
+
69
+ def finish(name, id, payload)
70
+ payload[:datadog_span]&.set_tag(:published_count, payload[:published_count]) if payload.key?(:published_count)
71
+ super
72
+ end
73
+
74
+ subscribe_to "publish_batch.publisher.sidekiq_publisher"
75
+ subscribe_to "enqueue_batch.publisher.sidekiq_publisher"
76
+ end
77
+
78
+ class JobSubscriber < Subscriber
79
+ def start(_name, _id, payload)
80
+ start_span("job.purge", payload)
81
+ end
82
+
83
+ def finish(_name, _id, payload)
84
+ payload[:datadog_span]&.set_tag(:purged_count, payload[:purged_count]) if payload.key?(:purged_count)
85
+
86
+ super
87
+ end
88
+
89
+ subscribe_to "purge.job.sidekiq_publisher"
90
+ end
91
+
92
+ # This subscriber is different from the classes above because it is an ActiveSupport::Subscriber
93
+ # and responds to the error(.publisher.sidekiq_publisher) event.
94
+ class PublisherErrorSubscriber < ActiveSupport::Subscriber
95
+ def error(event)
96
+ Datadog.tracer.active_span&.set_error(event.payload[:exception_object])
97
+ end
98
+
99
+ attach_to "publisher.sidekiq_publisher"
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/subscriber"
4
+
5
+ module SidekiqPublisher
6
+ module ExceptionReporter
7
+ class PublisherErrorSubscriber < ActiveSupport::Subscriber
8
+ def error(event)
9
+ SidekiqPublisher.exception_reporter&.call(event.payload[:exception_object])
10
+ end
11
+
12
+ attach_to "publisher.sidekiq_publisher"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/notifications"
4
+
5
+ module SidekiqPublisher
6
+ class Instrumenter
7
+ NAMESPACE = "sidekiq_publisher"
8
+
9
+ def instrument(event_name, payload = {}, &block)
10
+ ActiveSupport::Notifications.instrument("#{event_name}.#{NAMESPACE}", payload, &block)
11
+ end
12
+ end
13
+ end
@@ -39,11 +39,12 @@ module SidekiqPublisher
39
39
  where(id: ids).update_all(published_at: Time.now.utc)
40
40
  end
41
41
 
42
- def self.purge_expired_published!
42
+ def self.purge_expired_published!(instrumenter: Instrumenter.new)
43
43
  SidekiqPublisher.logger.info("#{name} purging expired published jobs.")
44
- count = purgeable.delete_all
44
+ count = instrumenter.instrument("purge.job") do |notification|
45
+ notification[:purged_count] = purgeable.delete_all
46
+ end
45
47
  SidekiqPublisher.logger.info("#{name} purged #{count} expired published jobs.")
46
- SidekiqPublisher.metrics_reporter.try(:count, "sidekiq_publisher.purged", count)
47
48
  end
48
49
 
49
50
  def self.unpublished_batches(batch_size: SidekiqPublisher.batch_size)
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/subscriber"
4
+
5
+ module SidekiqPublisher
6
+ module MetricsReporter
7
+ class Subscriber < ActiveSupport::Subscriber
8
+ private
9
+
10
+ def count(metric, value)
11
+ SidekiqPublisher.metrics_reporter&.try(:count, metric, value) unless value.nil?
12
+ end
13
+ end
14
+
15
+ class PublisherSubscriber < Subscriber
16
+ def enqueue_batch(event)
17
+ count("sidekiq_publisher.published", event.payload[:published_count])
18
+ end
19
+
20
+ attach_to "publisher.sidekiq_publisher"
21
+ end
22
+
23
+ class JobSubscriber < Subscriber
24
+ def purge(event)
25
+ count("sidekiq_publisher.purged", event.payload[:purged_count])
26
+ end
27
+
28
+ attach_to "job.sidekiq_publisher"
29
+ end
30
+
31
+ class UnpublishedSubscriber < Subscriber
32
+ def unpublished(event)
33
+ SidekiqPublisher.metrics_reporter&.
34
+ try(:gauge, "sidekiq_publisher.unpublished_count", event.payload[:unpublished_count])
35
+ end
36
+
37
+ attach_to "reporter.sidekiq_publisher"
38
+ end
39
+ end
40
+ end
@@ -5,26 +5,31 @@ require "active_support/core_ext/object/try"
5
5
 
6
6
  module SidekiqPublisher
7
7
  class Publisher
8
- def initialize
8
+ def initialize(instrumenter: Instrumenter.new)
9
+ @instrumenter = instrumenter
9
10
  @client = SidekiqPublisher::Client.new
10
11
  @job_class_cache = {}
11
12
  end
12
13
 
13
14
  def publish
14
15
  Job.unpublished_batches do |batch|
15
- items = batch.map do |job|
16
- {
17
- "jid" => job[:job_id],
18
- "class" => lookup_job_class(job[:job_class]),
19
- "args" => job[:args],
20
- "at" => job[:run_at],
21
- "queue" => job[:queue],
22
- "wrapped" => job[:wrapped],
23
- "created_at" => job[:created_at].to_f,
24
- }.tap(&:compact!)
25
- end
16
+ instrumenter.instrument("publish_batch.publisher") do
17
+ items = batch.map do |job|
18
+ {
19
+ "jid" => job[:job_id],
20
+ "class" => lookup_job_class(job[:job_class]),
21
+ "args" => job[:args],
22
+ "at" => job[:run_at],
23
+ "queue" => job[:queue],
24
+ "wrapped" => job[:wrapped],
25
+ "created_at" => job[:created_at].to_f,
26
+ }.tap(&:compact!)
27
+ end
26
28
 
27
- publish_batch(batch, items)
29
+ instrumenter.instrument("enqueue_batch.publisher") do |notification|
30
+ enqueue_batch(batch, items, notification)
31
+ end
32
+ end
28
33
  end
29
34
  purge_expired_published_jobs
30
35
  rescue StandardError => ex
@@ -33,16 +38,16 @@ module SidekiqPublisher
33
38
 
34
39
  private
35
40
 
36
- attr_reader :client, :job_class_cache
41
+ attr_reader :client, :job_class_cache, :instrumenter
37
42
 
38
- def publish_batch(batch, items)
43
+ def enqueue_batch(batch, items, notification)
39
44
  pushed_count = client.bulk_push(items)
40
45
  published_count = update_jobs_as_published!(batch)
41
46
  rescue StandardError => ex
42
47
  failure_warning(__method__, ex)
43
48
  ensure
44
49
  published_count = update_jobs_as_published!(batch) if pushed_count.present? && published_count.nil?
45
- metrics_reporter.try(:count, "sidekiq_publisher.published", published_count) if published_count.present?
50
+ notification[:published_count] = published_count if published_count.present?
46
51
  end
47
52
 
48
53
  def lookup_job_class(name)
@@ -56,7 +61,7 @@ module SidekiqPublisher
56
61
  end
57
62
 
58
63
  def purge_expired_published_jobs
59
- Job.purge_expired_published! if perform_purge?
64
+ Job.purge_expired_published!(instrumenter: instrumenter) if perform_purge?
60
65
  end
61
66
 
62
67
  def perform_purge?
@@ -64,17 +69,13 @@ module SidekiqPublisher
64
69
  end
65
70
 
66
71
  def failure_warning(method, ex)
67
- logger.warn("#{self.class.name}: msg=\"#{method} failed\" error=#{ex.class} error_msg=#{ex.message.inspect}\n"\
68
- "#{ex.backtrace.join("\n")}")
69
- SidekiqPublisher.exception_reporter&.call(ex)
72
+ logger.warn("#{self.class.name}: msg=\"#{method} failed\" error=#{ex.class} error_msg=#{ex.message.inspect}\n")
73
+ instrumenter.instrument("error.publisher",
74
+ exception_object: ex, exception: [ex.class.name, ex.message])
70
75
  end
71
76
 
72
77
  def logger
73
78
  SidekiqPublisher.logger
74
79
  end
75
-
76
- def metrics_reporter
77
- SidekiqPublisher.metrics_reporter
78
- end
79
80
  end
80
81
  end
@@ -2,10 +2,9 @@
2
2
 
3
3
  module SidekiqPublisher
4
4
  module ReportUnpublishedCount
5
- def self.call
6
- SidekiqPublisher.metrics_reporter.
7
- gauge("sidekiq_publisher.unpublished_count",
8
- SidekiqPublisher::Job.unpublished.count)
5
+ def self.call(instrumenter: Instrumenter.new)
6
+ instrumenter.instrument("unpublished.reporter",
7
+ unpublished_count: SidekiqPublisher::Job.unpublished.count)
9
8
  end
10
9
  end
11
10
  end
@@ -7,12 +7,13 @@ module SidekiqPublisher
7
7
  LISTENER_TIMEOUT_SECONDS = 60
8
8
  CHANNEL_NAME = "sidekiq_publisher_job"
9
9
 
10
- def self.run
11
- new.run
10
+ def self.run(instrumenter = Instrumenter.new)
11
+ new(instrumenter).run
12
12
  end
13
13
 
14
- def initialize
15
- @publisher = Publisher.new
14
+ def initialize(instrumenter = Instrumenter.new)
15
+ @instrumenter = instrumenter
16
+ @publisher = Publisher.new(instrumenter: @instrumenter)
16
17
  end
17
18
 
18
19
  def run
@@ -20,24 +21,32 @@ module SidekiqPublisher
20
21
  CHANNEL_NAME,
21
22
  listen_timeout: LISTENER_TIMEOUT_SECONDS
22
23
  ) do |listener|
23
- listener.on_start { publisher.publish }
24
- listener.on_notify { publisher.publish }
24
+ listener.on_start { call_publish("start") }
25
+ listener.on_notify { call_publish("notify") }
25
26
  listener.on_timeout { listener_timeout }
26
27
  end
27
28
  end
28
29
 
29
30
  private
30
31
 
31
- attr_reader :publisher
32
+ attr_reader :publisher, :instrumenter
32
33
 
33
- def listener_timeout
34
- if Job.unpublished.exists?
35
- SidekiqPublisher.logger&.warn(
36
- "#{self.class.name}: msg='publishing pending jobs at timeout'"
37
- )
34
+ def call_publish(event)
35
+ instrumenter.instrument("#{event}.publisher") do
38
36
  publisher.publish
39
- else
40
- Job.purge_expired_published!
37
+ end
38
+ end
39
+
40
+ def listener_timeout
41
+ instrumenter.instrument("timeout.listener") do
42
+ if Job.unpublished.exists?
43
+ SidekiqPublisher.logger&.warn(
44
+ "#{self.class.name}: msg='publishing pending jobs at timeout'"
45
+ )
46
+ call_publish("timeout")
47
+ else
48
+ Job.purge_expired_published!(instrumenter: instrumenter)
49
+ end
41
50
  end
42
51
  end
43
52
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SidekiqPublisher
4
- VERSION = "1.6.4"
4
+ VERSION = "1.7.0.rc0"
5
5
  end
@@ -45,6 +45,7 @@ Gem::Specification.new do |spec|
45
45
  spec.add_development_dependency "appraisal"
46
46
  spec.add_development_dependency "bundler", "~> 1.12"
47
47
  spec.add_development_dependency "database_cleaner"
48
+ spec.add_development_dependency "ddtrace", ">= 0.39.0"
48
49
  spec.add_development_dependency "ezcater_matchers"
49
50
  spec.add_development_dependency "ezcater_rubocop", "1.0.2"
50
51
  spec.add_development_dependency "factory_bot"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq_publisher
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.4
4
+ version: 1.7.0.rc0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ezCater, Inc
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-01 00:00:00.000000000 Z
11
+ date: 2020-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: ddtrace
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 0.39.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.39.0
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: ezcater_matchers
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -268,11 +282,14 @@ extensions: []
268
282
  extra_rdoc_files: []
269
283
  files:
270
284
  - ".codeclimate.yml"
285
+ - ".github/CODEOWNERS"
271
286
  - Appraisals
272
287
  - CHANGELOG.md
288
+ - Dockerfile
273
289
  - Gemfile
274
290
  - LICENSE.txt
275
291
  - README.md
292
+ - docker-compose.yml
276
293
  - gemfiles/rails_5.1.gemfile
277
294
  - gemfiles/rails_5.2.gemfile
278
295
  - gemfiles/rails_5.2_sidekiq_5.0.gemfile
@@ -288,7 +305,11 @@ files:
288
305
  - lib/generators/sidekiq_publisher/templates/create_sidekiq_publisher_jobs.rb
289
306
  - lib/sidekiq_publisher.rb
290
307
  - lib/sidekiq_publisher/client.rb
308
+ - lib/sidekiq_publisher/datadog_apm.rb
309
+ - lib/sidekiq_publisher/exception_reporter.rb
310
+ - lib/sidekiq_publisher/instrumenter.rb
291
311
  - lib/sidekiq_publisher/job.rb
312
+ - lib/sidekiq_publisher/metrics_reporter.rb
292
313
  - lib/sidekiq_publisher/publisher.rb
293
314
  - lib/sidekiq_publisher/railtie.rb
294
315
  - lib/sidekiq_publisher/report_unpublished_count.rb
@@ -304,7 +325,7 @@ licenses:
304
325
  - MIT
305
326
  metadata:
306
327
  allowed_push_host: https://rubygems.org
307
- post_install_message:
328
+ post_install_message:
308
329
  rdoc_options: []
309
330
  require_paths:
310
331
  - lib
@@ -315,12 +336,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
315
336
  version: '0'
316
337
  required_rubygems_version: !ruby/object:Gem::Requirement
317
338
  requirements:
318
- - - ">="
339
+ - - ">"
319
340
  - !ruby/object:Gem::Version
320
- version: '0'
341
+ version: 1.3.1
321
342
  requirements: []
322
- rubygems_version: 3.0.3
323
- signing_key:
343
+ rubygems_version: 3.1.4
344
+ signing_key:
324
345
  specification_version: 4
325
346
  summary: Publisher for enqueuing jobs to Sidekiq
326
347
  test_files: []