diff --git a/README.md b/README.md index 75a1e5d..d2b7c39 100644 --- a/README.md +++ b/README.md @@ -148,15 +148,15 @@ bundle exec site_maps generate monthly_posts --config-file config/sitemap.rb --c You can subscribe to the internal events to receive notifications about the sitemap generation. The following events are available: -* `sitemaps.runner.enqueue_process` - Triggered when a process is enqueued. -* `sitemaps.runner.before_process_execution` - Triggered before a process starts execution -* `sitemaps.runner.process_execution` - Triggered when a process finishes execution. -* `sitemaps.builder.finalize_urlset` - Triggered when the sitemap builder finishes the URL set. +* `sitemaps.enqueue_process` - Triggered when a process is enqueued. +* `sitemaps.before_process_execution` - Triggered before a process starts execution +* `sitemaps.process_execution` - Triggered when a process finishes execution. +* `sitemaps.finalize_urlset` - Triggered when the sitemap builder finishes the URL set. You can subscribe to the events using the following code: ```ruby -SiteMaps::Notification.subscribe("sitemaps.runner.enqueue_process") do |event| +SiteMaps::Notification.subscribe("sitemaps.enqueue_process") do |event| puts "Enqueueing process #{event.payload[:name]}" end ``` diff --git a/lib/site_maps/notification.rb b/lib/site_maps/notification.rb index b26eac9..77a39ad 100644 --- a/lib/site_maps/notification.rb +++ b/lib/site_maps/notification.rb @@ -28,9 +28,9 @@ def initialize(object_or_event_id) include Publisher - register_event "sitemaps.builder.finalize_urlset" - register_event "sitemaps.runner.before_process_execution" - register_event "sitemaps.runner.enqueue_process" - register_event "sitemaps.runner.process_execution" + register_event "sitemaps.finalize_urlset" + register_event "sitemaps.before_process_execution" + register_event "sitemaps.enqueue_process" + register_event "sitemaps.process_execution" end end diff --git a/lib/site_maps/process.rb b/lib/site_maps/process.rb index 4edf1a4..d95546b 100644 --- a/lib/site_maps/process.rb +++ b/lib/site_maps/process.rb @@ -1,7 +1,13 @@ # frozen_string_literal: true +require "securerandom" + module SiteMaps Process = Concurrent::ImmutableStruct.new(:name, :location_template, :kwargs_template, :block) do + def id + @id ||= SecureRandom.hex(4) + end + def location(**kwargs) return unless location_template diff --git a/lib/site_maps/runner.rb b/lib/site_maps/runner.rb index b4edf17..1d86693 100644 --- a/lib/site_maps/runner.rb +++ b/lib/site_maps/runner.rb @@ -18,7 +18,7 @@ def enqueue(process_name = :default, **kwargs) raise ArgumentError, "Process :#{process_name} not found" end kwargs = process.keyword_arguments(kwargs) - SiteMaps::Notification.instrument("sitemaps.runner.enqueue_process") do |payload| + SiteMaps::Notification.instrument("sitemaps.enqueue_process") do |payload| payload[:process] = process payload[:kwargs] = kwargs if process.dynamic? @@ -53,15 +53,16 @@ def run futures = [] @execution.each do |_process_name, items| items.each do |process, kwargs| - SiteMaps::Notification.publish("sitemaps.runner.before_process_execution", process: process, kwargs: kwargs) + SiteMaps::Notification.publish("sitemaps.before_process_execution", process: process, kwargs: kwargs) futures << Concurrent::Future.execute(executor: pool) do wrap_process_execution(process) do - SiteMaps::Notification.instrument("sitemaps.runner.process_execution") do |payload| + SiteMaps::Notification.instrument("sitemaps.process_execution") do |payload| payload[:process] = process payload[:kwargs] = kwargs builder = SiteMaps::SitemapBuilder.new( adapter: adapter, - location: process.location(**kwargs) + location: process.location(**kwargs), + notification_payload: { process: process } ) process.call(builder, **kwargs) builder.finalize! diff --git a/lib/site_maps/runner/event_listener.rb b/lib/site_maps/runner/event_listener.rb index 46b0e11..7ebad1e 100644 --- a/lib/site_maps/runner/event_listener.rb +++ b/lib/site_maps/runner/event_listener.rb @@ -13,54 +13,58 @@ def [](event_name) method(:"on_#{method_name}") end - def on_sitemaps_runner_enqueue_process(event) + def on_sitemaps_enqueue_process(event) process = event[:process] kwargs = event[:kwargs] location = process.location(**kwargs) print_message( - "Enqueue process %s#{" at %s" if location}", + "[%s] Enqueue process %s#{" at %s" if location}", + id: process.id, name: colorize(process.name, :bold), location: colorize(location, :lightgray) ) if kwargs.any? - print_message("└── Keyword Arguments: {%s}", kwargs: kwargs.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")) + print_message(" └── Keyword Arguments: {%s}", kwargs: kwargs.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")) end end - def on_sitemaps_runner_before_process_execution(event) + def on_sitemaps_before_process_execution(event) process = event[:process] kwargs = event[:kwargs] location = process.location(**kwargs) print_message( - "Executing process %s#{" at %s" if location}", + "[%s] Executing process %s#{" at %s" if location}", + id: process.id, name: colorize(process.name, :bold), location: colorize(location, :lightgray) ) if kwargs.any? - print_message("└── Keyword Arguments: {%s}", kwargs: kwargs.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")) + print_message(" └── Keyword Arguments: {%s}", kwargs: kwargs.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")) end end - def on_sitemaps_runner_process_execution(event) + def on_sitemaps_process_execution(event) process = event[:process] kwargs = event[:kwargs] location = process.location(**kwargs) print_message( - "[%s] Executed process %s#{" at %s" if location}", + "[%s][%s] Executed process %s#{" at %s" if location}", + id: process.id, name: colorize(process.name, :bold), location: colorize(location, :lightgray), runtime: formatted_runtime(event[:runtime]) ) if kwargs.any? - print_message("└── Keyword Arguments: {%s}", kwargs: kwargs.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")) + print_message(" └── Keyword Arguments: {%s}", kwargs: kwargs.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")) end end - def on_sitemaps_builder_finalize_urlset(event) + def on_sitemaps_finalize_urlset(event) + process = event[:process] links_count = event[:links_count] news_count = event[:news_count] url = event[:url] - text = +"[%s] Finalize URLSet with " + text = +"[%s][%s] Finalize URLSet with " text << "%d links" if links_count > 0 text << " and " if links_count > 0 && news_count > 0 text << "%d news" if news_count > 0 @@ -68,6 +72,7 @@ def on_sitemaps_builder_finalize_urlset(event) print_message( text, + id: process.id, links: links_count, news: news_count, url: colorize(url, :lightgray), diff --git a/lib/site_maps/sitemap_builder.rb b/lib/site_maps/sitemap_builder.rb index a087b26..657a0b3 100644 --- a/lib/site_maps/sitemap_builder.rb +++ b/lib/site_maps/sitemap_builder.rb @@ -4,11 +4,12 @@ module SiteMaps class SitemapBuilder extend Forwardable - def initialize(adapter:, location: nil) + def initialize(adapter:, location: nil, notification_payload: {}) @adapter = adapter @url_set = SiteMaps::Builder::URLSet.new @location = location @mutex = Mutex.new + @notification_payload = notification_payload end def add(path, params: nil, **options) @@ -31,7 +32,7 @@ def finalize! raw_data = url_set.finalize! - SiteMaps::Notification.instrument("sitemaps.builder.finalize_urlset") do |payload| + SiteMaps::Notification.instrument("sitemaps.finalize_urlset", notification_payload) do |payload| payload[:links_count] = url_set.links_count payload[:news_count] = url_set.news_count payload[:last_modified] = url_set.last_modified @@ -50,13 +51,13 @@ def finalize! protected - attr_reader :url_set, :adapter, :location + attr_reader :url_set, :adapter, :location, :notification_payload def_delegators :adapter, :sitemap_index, :config, :repo def finalize_and_start_next_urlset! raw_data = url_set.finalize! - SiteMaps::Notification.instrument("sitemaps.builder.finalize_urlset") do |payload| + SiteMaps::Notification.instrument("sitemaps.finalize_urlset", notification_payload) do |payload| sitemap_url = repo.generate_url(location) payload[:url] = sitemap_url payload[:links_count] = url_set.links_count diff --git a/spec/site_maps/process_spec.rb b/spec/site_maps/process_spec.rb index e2825e2..81616d3 100644 --- a/spec/site_maps/process_spec.rb +++ b/spec/site_maps/process_spec.rb @@ -3,6 +3,13 @@ require "spec_helper" RSpec.describe SiteMaps::Process do + describe "#id" do + it "returns a unique id" do + process = described_class.new(:name, nil, nil, nil) + expect(process.id).to match(/\A[0-9a-f]{8}\z/) + end + end + describe "#location" do it "returns the location" do process = described_class.new(:name, "/path/%{year}-%{month}", {}, nil) diff --git a/spec/site_maps/runner/event_listener_spec.rb b/spec/site_maps/runner/event_listener_spec.rb index 13f5280..05c146f 100644 --- a/spec/site_maps/runner/event_listener_spec.rb +++ b/spec/site_maps/runner/event_listener_spec.rb @@ -18,7 +18,7 @@ describe ".[]" do it "returns event method" do - expect(described_class["sitemaps.runner.enqueue_process"]).to eq(described_class.method(:on_sitemaps_runner_enqueue_process)) + expect(described_class["sitemaps.enqueue_process"]).to eq(described_class.method(:on_sitemaps_enqueue_process)) end it "returns nil when listener does not implement the event method" do @@ -26,177 +26,188 @@ end end - describe ".on_sitemaps_runner_enqueue_process" do - let(:event_id) { "sitemaps.runner.enqueue_process" } + describe ".on_sitemaps_enqueue_process" do + let(:event_id) { "sitemaps.enqueue_process" } context "with a static process" do + let(:process) { adapter.processes[:default] } let(:payload) do { runtime: 1.32, - process: adapter.processes[:default], + process: process, kwargs: {} } end it "prints message" do expect { call! }.to output(<<~MSG).to_stdout - Enqueue process #{colorize("default", :bold)} + [#{process.id}] Enqueue process #{colorize("default", :bold)} MSG end end context "with a static process with a location" do + let(:process) { adapter.processes[:categories] } let(:payload) do { runtime: 1.32, - process: adapter.processes[:categories], + process: process, kwargs: {} } end it "prints message" do expect { call! }.to output(<<~MSG).to_stdout - Enqueue process #{colorize("categories", :bold)} at #{colorize("categories/sitemap.xml", :lightgray)} + [#{process.id}] Enqueue process #{colorize("categories", :bold)} at #{colorize("categories/sitemap.xml", :lightgray)} MSG end end context "with a dynamic process" do + let(:process) { adapter.processes[:posts] } let(:payload) do { runtime: 1.32, - process: adapter.processes[:posts], + process: process, kwargs: {year: 2024, month: 11} } end it "prints message" do expect { call! }.to output(<<~MSG).to_stdout - Enqueue process #{colorize("posts", :bold)} at #{colorize("posts/2024-11/sitemap.xml", :lightgray)} - └── Keyword Arguments: {year: 2024, month: 11} + [#{process.id}] Enqueue process #{colorize("posts", :bold)} at #{colorize("posts/2024-11/sitemap.xml", :lightgray)} + └── Keyword Arguments: {year: 2024, month: 11} MSG end end end - describe ".on_sitemaps_runner_before_process_execution" do - let(:event_id) { "sitemaps.runner.before_process_execution" } + describe ".on_sitemaps_before_process_execution" do + let(:event_id) { "sitemaps.before_process_execution" } context "with a static process" do + let(:process) { adapter.processes[:default] } let(:payload) do { - process: adapter.processes[:default], + process: process, kwargs: {} } end it "prints message" do expect { call! }.to output(<<~MSG).to_stdout - Executing process #{colorize("default", :bold)} + [#{process.id}] Executing process #{colorize("default", :bold)} MSG end end context "with a static process with a location" do + let(:process) { adapter.processes[:categories] } let(:payload) do { - process: adapter.processes[:categories], + process: process, kwargs: {} } end it "prints message" do expect { call! }.to output(<<~MSG).to_stdout - Executing process #{colorize("categories", :bold)} at #{colorize("categories/sitemap.xml", :lightgray)} + [#{process.id}] Executing process #{colorize("categories", :bold)} at #{colorize("categories/sitemap.xml", :lightgray)} MSG end end context "with a dynamic process" do + let(:process) { adapter.processes[:posts] } let(:payload) do { - process: adapter.processes[:posts], + process: process, kwargs: {year: 2024, month: 11} } end it "prints message" do expect { call! }.to output(<<~MSG).to_stdout - Executing process #{colorize("posts", :bold)} at #{colorize("posts/2024-11/sitemap.xml", :lightgray)} - └── Keyword Arguments: {year: 2024, month: 11} + [#{process.id}] Executing process #{colorize("posts", :bold)} at #{colorize("posts/2024-11/sitemap.xml", :lightgray)} + └── Keyword Arguments: {year: 2024, month: 11} MSG end end end - describe ".on_sitemaps_runner_process_execution" do - let(:event_id) { "sitemaps.runner.process_execution" } + describe ".on_sitemaps_process_execution" do + let(:event_id) { "sitemaps.process_execution" } context "with a static process" do + let(:process) { adapter.processes[:default] } let(:payload) do { runtime: 1.32, - process: adapter.processes[:default], + process: process, kwargs: {} } end it "prints message" do expect { call! }.to output(<<~MSG).to_stdout - [#{formatted_runtime(1.32)}] Executed process #{colorize("default", :bold)} + [#{process.id}][#{formatted_runtime(1.32)}] Executed process #{colorize("default", :bold)} MSG end end context "with a static process with a location" do + let(:process) { adapter.processes[:categories] } let(:payload) do { runtime: 1.32, - process: adapter.processes[:categories], + process: process, kwargs: {} } end it "prints message" do expect { call! }.to output(<<~MSG).to_stdout - [#{formatted_runtime(1.32)}] Executed process #{colorize("categories", :bold)} at #{colorize("categories/sitemap.xml", :lightgray)} + [#{process.id}][#{formatted_runtime(1.32)}] Executed process #{colorize("categories", :bold)} at #{colorize("categories/sitemap.xml", :lightgray)} MSG end end context "with a dynamic process" do + let(:process) { adapter.processes[:posts] } let(:payload) do { runtime: 1.32, - process: adapter.processes[:posts], + process: process, kwargs: {year: 2024, month: 11} } end it "prints message" do expect { call! }.to output(<<~MSG).to_stdout - [#{formatted_runtime(1.32)}] Executed process #{colorize("posts", :bold)} at #{colorize("posts/2024-11/sitemap.xml", :lightgray)} - └── Keyword Arguments: {year: 2024, month: 11} + [#{process.id}][#{formatted_runtime(1.32)}] Executed process #{colorize("posts", :bold)} at #{colorize("posts/2024-11/sitemap.xml", :lightgray)} + └── Keyword Arguments: {year: 2024, month: 11} MSG end end end - describe ".on_sitemaps_builder_finalize_urlset" do - let(:event_id) { "sitemaps.builder.finalize_urlset" } + describe ".on_sitemaps_finalize_urlset" do + let(:event_id) { "sitemaps.finalize_urlset" } + let(:process) { adapter.processes[:default] } let(:payload) do { runtime: 1.32, links_count: 10, news_count: 2, - url: "https://example.com/site/sitemap1.xml" + url: "https://example.com/site/sitemap1.xml", + process: process, } end it "prints message" do expect { call! }.to output(<<~MSG).to_stdout - [#{formatted_runtime(1.32)}] Finalize URLSet with 10 links and 2 news URLs at #{colorize("https://example.com/site/sitemap1.xml", :lightgray)} + [#{process.id}][#{formatted_runtime(1.32)}] Finalize URLSet with 10 links and 2 news URLs at #{colorize("https://example.com/site/sitemap1.xml", :lightgray)} MSG end end diff --git a/spec/site_maps/runner_spec.rb b/spec/site_maps/runner_spec.rb index bab8060..7a27773 100644 --- a/spec/site_maps/runner_spec.rb +++ b/spec/site_maps/runner_spec.rb @@ -54,12 +54,12 @@ end end - describe "#enqueue", events: %w[sitemaps.runner.enqueue_process] do + describe "#enqueue", events: %w[sitemaps.enqueue_process] do it "raises an error when the process is not found" do expect { runner.enqueue(:unknown) }.to raise_error(ArgumentError, "Process :unknown not found") - refute_event "sitemaps.runner.enqueue_process" + refute_event "sitemaps.enqueue_process" end it "adds the default process when no process name is provided" do @@ -71,7 +71,7 @@ an_instance_of(SiteMaps::Process), {} ]) - assert_event "sitemaps.runner.enqueue_process", process: adapter.processes[:default], kwargs: {} + assert_event "sitemaps.enqueue_process", process: adapter.processes[:default], kwargs: {} end it "adds the process to the execution" do @@ -83,7 +83,7 @@ an_instance_of(SiteMaps::Process), {} ]) - assert_event "sitemaps.runner.enqueue_process", process: adapter.processes[:categories], kwargs: {} + assert_event "sitemaps.enqueue_process", process: adapter.processes[:categories], kwargs: {} end it "does not add the process to the execution if static process is already defined" do @@ -103,7 +103,7 @@ an_instance_of(SiteMaps::Process), {year: 2020, month: 1} ]) - assert_event "sitemaps.runner.enqueue_process", process: adapter.processes[:posts], kwargs: {year: 2020, month: 1} + assert_event "sitemaps.enqueue_process", process: adapter.processes[:posts], kwargs: {year: 2020, month: 1} end it "adds multiple dynamic processes to the execution" do @@ -115,12 +115,12 @@ [an_instance_of(SiteMaps::Process), {year: 2020, month: 1}], [an_instance_of(SiteMaps::Process), {year: 2020, month: 2}] ) - assert_event "sitemaps.runner.enqueue_process", process: adapter.processes[:posts], kwargs: {year: 2020, month: 1} - assert_event "sitemaps.runner.enqueue_process", process: adapter.processes[:posts], kwargs: {year: 2020, month: 2} + assert_event "sitemaps.enqueue_process", process: adapter.processes[:posts], kwargs: {year: 2020, month: 1} + assert_event "sitemaps.enqueue_process", process: adapter.processes[:posts], kwargs: {year: 2020, month: 2} end end - describe "#enqueue_remaining", events: %w[sitemaps.runner.enqueue_process] do + describe "#enqueue_remaining", events: %w[sitemaps.enqueue_process] do it "adds all processes to the execution" do runner.enqueue_remaining @@ -128,9 +128,9 @@ expect(queue).to have_key(:default) expect(queue).to have_key(:categories) expect(queue).to have_key(:posts) - assert_event "sitemaps.runner.enqueue_process", process: adapter.processes[:default], kwargs: {} - assert_event "sitemaps.runner.enqueue_process", process: adapter.processes[:categories], kwargs: {} - assert_event "sitemaps.runner.enqueue_process", process: adapter.processes[:posts], kwargs: {} + assert_event "sitemaps.enqueue_process", process: adapter.processes[:default], kwargs: {} + assert_event "sitemaps.enqueue_process", process: adapter.processes[:categories], kwargs: {} + assert_event "sitemaps.enqueue_process", process: adapter.processes[:posts], kwargs: {} end it "does not add the process to the execution if static process is already defined" do @@ -164,7 +164,7 @@ end end - describe "#run", events: %w[sitemaps.runner.process_execution] do + describe "#run", events: %w[sitemaps.process_execution] do it "executes the processes" do runner.enqueue(:default) @@ -179,7 +179,7 @@ expect { runner.run }.not_to raise_error queue.all? do |process| expect(process).to have_received(:call) - assert_event "sitemaps.runner.process_execution", process: process, kwargs: {} + assert_event "sitemaps.process_execution", process: process, kwargs: {} end end @@ -236,8 +236,8 @@ expect(failure).to have_received(:call) expect(default).not_to have_received(:call) - refute_event "sitemaps.runner.process_execution", process: failure, kwargs: {} - refute_event "sitemaps.runner.process_execution", process: default, kwargs: {} + refute_event "sitemaps.process_execution", process: failure, kwargs: {} + refute_event "sitemaps.process_execution", process: default, kwargs: {} end end