From 49c56b43c324b6a7d5aedf8c5b5e0218293586cc Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sat, 10 Jan 2026 08:04:52 +1100 Subject: [PATCH 1/9] commit changes --- lib/outboxer/cloud_watch.rb | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 lib/outboxer/cloud_watch.rb diff --git a/lib/outboxer/cloud_watch.rb b/lib/outboxer/cloud_watch.rb new file mode 100644 index 00000000..868e2389 --- /dev/null +++ b/lib/outboxer/cloud_watch.rb @@ -0,0 +1,42 @@ +require "aws-sdk-cloudwatch" + +# env = ENV["APP_ENV"] || ENV["RAILS_ENV"] || "development" + +running = true + +["TERM", "INT"].each do |sig| + Signal.trap(sig) { running = false } +end + +cloud_watch = Aws::CloudWatch::Client.new + +interval = 10 +tick = 1 + +while running + metrics_by_status = Outboxer::Message.metrics_by_status + + cloud_watch.put_metric_data( + namespace: "Outboxer", + metric_data: [ + { + metric_name: "MessagesQueuedCount", + value: metrics_by_status[:queued][:count], + unit: "Count" + }, + { + metric_name: "MessagesQueuedLatencySeconds", + value: metrics_by_status[:queued][:latency] || 0, + unit: "Seconds" + } + ] + ) + + slept = 0 + + while running && slept < interval + sleep tick + + slept += tick + end +end From 57dabf4f3f25b9c3d633d1b2f28f06ca35a72679 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sat, 10 Jan 2026 10:29:23 +1100 Subject: [PATCH 2/9] update script --- Gemfile | 1 + Gemfile.lock | 17 +++++++++++++++++ lib/outboxer/cloud_watch.rb => bin/cloud_watch | 17 +++++++++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-) rename lib/outboxer/cloud_watch.rb => bin/cloud_watch (60%) mode change 100644 => 100755 diff --git a/Gemfile b/Gemfile index 57b85832..17888b63 100644 --- a/Gemfile +++ b/Gemfile @@ -11,6 +11,7 @@ gem "rackup", "~> 2.0" gem "sinatra", "~> 4.2" group :development, :test do + gem "aws-sdk-cloudwatch", "~> 1.128" gem "database_cleaner", "~> 2.0" gem "dotenv", "~> 3.0" gem "factory_bot", "~> 6.0" diff --git a/Gemfile.lock b/Gemfile.lock index da3e51fb..61c74076 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,6 +26,21 @@ GEM securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) ast (2.4.2) + aws-eventstream (1.4.0) + aws-partitions (1.1202.0) + aws-sdk-cloudwatch (1.128.0) + aws-sdk-core (~> 3, >= 3.241.3) + aws-sigv4 (~> 1.5) + aws-sdk-core (3.241.3) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal + jmespath (~> 1, >= 1.6.1) + logger + aws-sigv4 (1.12.1) + aws-eventstream (~> 1, >= 1.0.2) base64 (0.3.0) benchmark (0.4.1) bigdecimal (3.2.2) @@ -54,6 +69,7 @@ GEM foreman (0.87.2) i18n (1.14.7) concurrent-ruby (~> 1.0) + jmespath (1.6.2) json (2.9.1) language_server-protocol (3.17.0.3) listen (3.9.0) @@ -167,6 +183,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + aws-sdk-cloudwatch (~> 1.128) coveralls_reborn database_cleaner (~> 2.0) dotenv (~> 3.0) diff --git a/lib/outboxer/cloud_watch.rb b/bin/cloud_watch old mode 100644 new mode 100755 similarity index 60% rename from lib/outboxer/cloud_watch.rb rename to bin/cloud_watch index 868e2389..1fa9d3ad --- a/lib/outboxer/cloud_watch.rb +++ b/bin/cloud_watch @@ -1,6 +1,12 @@ +#!/usr/bin/env ruby + +require "bundler/setup" require "aws-sdk-cloudwatch" +require "outboxer" -# env = ENV["APP_ENV"] || ENV["RAILS_ENV"] || "development" +environment = ENV["RAILS_ENV"] || "development" +db_config = Outboxer::Database.config(environment: environment, pool: 1) +Outboxer::Database.connect(config: db_config) running = true @@ -8,7 +14,14 @@ Signal.trap(sig) { running = false } end -cloud_watch = Aws::CloudWatch::Client.new +cloud_watch = Aws::CloudWatch::Client.new( + credentials: Aws::Credentials.new( + ENV.fetch("AWS_ACCESS_KEY_ID"), + ENV.fetch("AWS_SECRET_ACCESS_KEY") + ), + region: ENV.fetch("AWS_REGION"), + ssl_verify_peer: false +) interval = 10 tick = 1 From d2f34b35a91b1e9ce6d5c180fc1c337ca75f605e Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sat, 10 Jan 2026 10:32:04 +1100 Subject: [PATCH 3/9] add logger --- bin/cloud_watch | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/cloud_watch b/bin/cloud_watch index 1fa9d3ad..acd27a66 100755 --- a/bin/cloud_watch +++ b/bin/cloud_watch @@ -26,6 +26,9 @@ cloud_watch = Aws::CloudWatch::Client.new( interval = 10 tick = 1 +logger = Outboxer::Logger.new($stdout, level: 1) +logger.info "Sending Outboxer metrics to CloudWatch..." + while running metrics_by_status = Outboxer::Message.metrics_by_status @@ -53,3 +56,5 @@ while running slept += tick end end + +logger.info "Shutting down" From 1ed88e6140d1504861c92e47d3bb654b8436e321 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sat, 10 Jan 2026 20:00:45 +1100 Subject: [PATCH 4/9] use dimensions for metrics --- bin/{cloud_watch => outboxer_cloud_watch} | 38 ++++++++++++++--------- lib/outboxer/web/views/publisher.erb | 2 +- 2 files changed, 24 insertions(+), 16 deletions(-) rename bin/{cloud_watch => outboxer_cloud_watch} (57%) diff --git a/bin/cloud_watch b/bin/outboxer_cloud_watch similarity index 57% rename from bin/cloud_watch rename to bin/outboxer_cloud_watch index acd27a66..1bf119b3 100755 --- a/bin/cloud_watch +++ b/bin/outboxer_cloud_watch @@ -32,21 +32,29 @@ logger.info "Sending Outboxer metrics to CloudWatch..." while running metrics_by_status = Outboxer::Message.metrics_by_status - cloud_watch.put_metric_data( - namespace: "Outboxer", - metric_data: [ - { - metric_name: "MessagesQueuedCount", - value: metrics_by_status[:queued][:count], - unit: "Count" - }, - { - metric_name: "MessagesQueuedLatencySeconds", - value: metrics_by_status[:queued][:latency] || 0, - unit: "Seconds" - } - ] - ) + metric_data = metrics_by_status.except(:total).map do |status, metrics| + { + metric_name: "MessageCount", + dimensions: [ + { name: "Status", value: status.to_s.capitalize }, + { name: "Environment", value: environment } + ], + value: metrics[:count], + unit: "Count" + } + end + [ + { + metric_name: "MessageLatency", + dimensions: [ + { name: "Status", value: "Queued" }, + { name: "Environment", value: environment } + ], + value: metrics_by_status[:queued][:latency] || 0, + unit: "Seconds" + } + ] + + cloud_watch.put_metric_data(namespace: "Outboxer", metric_data: metric_data) slept = 0 diff --git a/lib/outboxer/web/views/publisher.erb b/lib/outboxer/web/views/publisher.erb index 49056dbb..2f2c5f38 100644 --- a/lib/outboxer/web/views/publisher.erb +++ b/lib/outboxer/web/views/publisher.erb @@ -117,7 +117,7 @@ <%= Outboxer::Web.human_readable_size(kilobytes: publisher[:metrics]['rss']) %> - Database RTT + RTT <%= Outboxer::Web.pretty_duration_from_seconds(seconds: publisher[:metrics]['rtt']) %> From ce7f6fea153010042d55f5f4fff1f18242d3e46f Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sat, 10 Jan 2026 20:04:08 +1100 Subject: [PATCH 5/9] fix syntax --- bin/outboxer_cloud_watch | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/outboxer_cloud_watch b/bin/outboxer_cloud_watch index 1bf119b3..cbd65a99 100755 --- a/bin/outboxer_cloud_watch +++ b/bin/outboxer_cloud_watch @@ -16,8 +16,7 @@ end cloud_watch = Aws::CloudWatch::Client.new( credentials: Aws::Credentials.new( - ENV.fetch("AWS_ACCESS_KEY_ID"), - ENV.fetch("AWS_SECRET_ACCESS_KEY") + ENV.fetch("AWS_ACCESS_KEY_ID"), ENV.fetch("AWS_SECRET_ACCESS_KEY") ), region: ENV.fetch("AWS_REGION"), ssl_verify_peer: false From d0fd9bf007f7652144e3d091fed4c5e6e91f1d94 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sat, 10 Jan 2026 20:18:59 +1100 Subject: [PATCH 6/9] update cloud watch script --- bin/outboxer_cloud_watch | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bin/outboxer_cloud_watch b/bin/outboxer_cloud_watch index cbd65a99..373cfe4d 100755 --- a/bin/outboxer_cloud_watch +++ b/bin/outboxer_cloud_watch @@ -4,17 +4,20 @@ require "bundler/setup" require "aws-sdk-cloudwatch" require "outboxer" +put_interval = 60 +tick_interval = 1 + environment = ENV["RAILS_ENV"] || "development" db_config = Outboxer::Database.config(environment: environment, pool: 1) Outboxer::Database.connect(config: db_config) running = true -["TERM", "INT"].each do |sig| - Signal.trap(sig) { running = false } +["TERM", "INT"].each do |signal| + Signal.trap(signal) { running = false } end -cloud_watch = Aws::CloudWatch::Client.new( +cloud_watch_client = Aws::CloudWatch::Client.new( credentials: Aws::Credentials.new( ENV.fetch("AWS_ACCESS_KEY_ID"), ENV.fetch("AWS_SECRET_ACCESS_KEY") ), @@ -22,9 +25,6 @@ cloud_watch = Aws::CloudWatch::Client.new( ssl_verify_peer: false ) -interval = 10 -tick = 1 - logger = Outboxer::Logger.new($stdout, level: 1) logger.info "Sending Outboxer metrics to CloudWatch..." @@ -53,14 +53,14 @@ while running } ] - cloud_watch.put_metric_data(namespace: "Outboxer", metric_data: metric_data) + cloud_watch_client.put_metric_data(namespace: "Outboxer", metric_data: metric_data) slept = 0 - while running && slept < interval - sleep tick + while running && slept < put_interval + sleep tick_interval - slept += tick + slept += tick_interval end end From 76d7ea7c09e09681a659d7ca09c86abc3d28b860 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sat, 10 Jan 2026 20:39:09 +1100 Subject: [PATCH 7/9] rename file --- bin/{outboxer_cloud_watch => outboxer_cloudwatch_metrics} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename bin/{outboxer_cloud_watch => outboxer_cloudwatch_metrics} (100%) diff --git a/bin/outboxer_cloud_watch b/bin/outboxer_cloudwatch_metrics similarity index 100% rename from bin/outboxer_cloud_watch rename to bin/outboxer_cloudwatch_metrics From 45c0f6d52b37ea67fc1b4dfce3a699df25168048 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sat, 10 Jan 2026 21:48:13 +1100 Subject: [PATCH 8/9] add generator --- Gemfile | 1 - Gemfile.lock | 2 +- bin/outboxer_cloudwatch_metrics | 20 +++++++++++-- .../install_cloudwatch_metrics_generator.rb | 30 +++++++++++++++++++ outboxer.gemspec | 1 + 5 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 generators/install_cloudwatch_metrics_generator.rb diff --git a/Gemfile b/Gemfile index 17888b63..57b85832 100644 --- a/Gemfile +++ b/Gemfile @@ -11,7 +11,6 @@ gem "rackup", "~> 2.0" gem "sinatra", "~> 4.2" group :development, :test do - gem "aws-sdk-cloudwatch", "~> 1.128" gem "database_cleaner", "~> 2.0" gem "dotenv", "~> 3.0" gem "factory_bot", "~> 6.0" diff --git a/Gemfile.lock b/Gemfile.lock index 61c74076..4868afe2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -183,7 +183,7 @@ PLATFORMS x86_64-linux DEPENDENCIES - aws-sdk-cloudwatch (~> 1.128) + aws-sdk-cloudwatch coveralls_reborn database_cleaner (~> 2.0) dotenv (~> 3.0) diff --git a/bin/outboxer_cloudwatch_metrics b/bin/outboxer_cloudwatch_metrics index 373cfe4d..f4eec899 100755 --- a/bin/outboxer_cloudwatch_metrics +++ b/bin/outboxer_cloudwatch_metrics @@ -1,10 +1,24 @@ #!/usr/bin/env ruby require "bundler/setup" -require "aws-sdk-cloudwatch" + +begin + require "aws-sdk-cloudwatch" +rescue LoadError + abort <<~MSG + aws-sdk-cloudwatch is not installed. + + Add it to your Gemfile: + + gem "aws-sdk-cloudwatch" + + Then run bundle install. + MSG +end + require "outboxer" -put_interval = 60 +sleep_interval = 60 tick_interval = 1 environment = ENV["RAILS_ENV"] || "development" @@ -57,7 +71,7 @@ while running slept = 0 - while running && slept < put_interval + while running && slept < sleep_interval sleep tick_interval slept += tick_interval diff --git a/generators/install_cloudwatch_metrics_generator.rb b/generators/install_cloudwatch_metrics_generator.rb new file mode 100644 index 00000000..65253c4c --- /dev/null +++ b/generators/install_cloudwatch_metrics_generator.rb @@ -0,0 +1,30 @@ +module Outboxer + class InstallCloudwatchMetricsGenerator < Rails::Generators::Base + source_root File.expand_path("../", __dir__) + + def copy_bin_file + template "bin/outboxer_cloudwatch_metrics", "bin/outboxer_cloudwatch_metrics" + run "chmod +x bin/outboxer_cloudwatch_metrics" + end + + def print_instructions + say <<~MSG + + To complete setup: + + 1. Add to Gemfile: + gem "aws-sdk-cloudwatch" + + 2. Run: + bundle install + + 3. Start: + bin/outboxer_cloudwatch_metrics + + MSG + end + end +end + +# bundle exec rails g outboxer:install_cloudwatch_metrics +# bin/outboxer_cloudwatch_metrics diff --git a/outboxer.gemspec b/outboxer.gemspec index e810d7c1..c01c087c 100644 --- a/outboxer.gemspec +++ b/outboxer.gemspec @@ -33,4 +33,5 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency "activerecord", ">= 7.0.8.6" + spec.add_development_dependency "aws-sdk-cloudwatch" end From 8ec03ad4e57656eef02bd6109cda7933e890f330 Mon Sep 17 00:00:00 2001 From: bedrock-adam Date: Sat, 10 Jan 2026 21:49:13 +1100 Subject: [PATCH 9/9] fix rubocop --- Gemfile | 1 + Gemfile.lock | 2 +- outboxer.gemspec | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 57b85832..17888b63 100644 --- a/Gemfile +++ b/Gemfile @@ -11,6 +11,7 @@ gem "rackup", "~> 2.0" gem "sinatra", "~> 4.2" group :development, :test do + gem "aws-sdk-cloudwatch", "~> 1.128" gem "database_cleaner", "~> 2.0" gem "dotenv", "~> 3.0" gem "factory_bot", "~> 6.0" diff --git a/Gemfile.lock b/Gemfile.lock index 4868afe2..61c74076 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -183,7 +183,7 @@ PLATFORMS x86_64-linux DEPENDENCIES - aws-sdk-cloudwatch + aws-sdk-cloudwatch (~> 1.128) coveralls_reborn database_cleaner (~> 2.0) dotenv (~> 3.0) diff --git a/outboxer.gemspec b/outboxer.gemspec index c01c087c..e810d7c1 100644 --- a/outboxer.gemspec +++ b/outboxer.gemspec @@ -33,5 +33,4 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency "activerecord", ">= 7.0.8.6" - spec.add_development_dependency "aws-sdk-cloudwatch" end