Aggiunge monitoraggio ops: health check, log scanner, dashboard e notifiche.

Introduce incidenti con dedup, job Sidekiq ogni 3 min, scan log cron, /up/deep,
dashboard /admin/ops e push ntfy; include Sentry opzionale e fix test suite.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-12 08:16:47 +02:00
parent ce8939fffb
commit 52cfffb83f
44 changed files with 1396 additions and 24 deletions

View File

@@ -13,7 +13,7 @@ Rails.application.configure do
config.ssl_options = {
redirect: {
exclude: ->(request) {
request.path == "/up" || request.path == "/cable" || request.path.start_with?("/hls/")
request.path == "/up" || request.path == "/up/deep" || request.path == "/cable" || request.path.start_with?("/hls/")
}
}
}
@@ -34,7 +34,7 @@ Rails.application.configure do
config.host_authorization = {
exclude: ->(request) {
request.path == "/up" || request.path.start_with?("/cable") || request.path.start_with?("/hls/")
request.path == "/up" || request.path == "/up/deep" || request.path.start_with?("/cable") || request.path.start_with?("/hls/")
}
}

View File

@@ -0,0 +1,21 @@
if Rails.env.production? && ENV["OPS_LOG_SUBSCRIBER"] == "true"
ActiveSupport::Notifications.subscribe("process_action.action_controller") do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
next unless event.payload[:status].to_i >= 500
Ops::IncidentRecorder.record(
kind: "log_pattern",
severity: "warning",
title: "HTTP 500 in Rails",
message: "#{event.payload[:method]} #{event.payload[:path]}#{event.payload[:status]}",
metadata: {
"controller" => event.payload[:controller],
"action" => event.payload[:action],
"status" => event.payload[:status]
},
fingerprint: "rails_500:#{event.payload[:controller]}##{event.payload[:action]}"
)
rescue StandardError => e
Rails.logger.warn("[Ops::LogSubscriber] #{e.message}")
end
end

View File

@@ -0,0 +1,33 @@
if ENV["SENTRY_DSN"].present? && !Rails.env.test?
begin
require "sentry-ruby"
require "sentry-rails"
Sentry.init do |config|
config.dsn = ENV["SENTRY_DSN"]
config.breadcrumbs_logger = %i[active_support_logger http_logger]
config.traces_sample_rate = ENV.fetch("SENTRY_TRACES_SAMPLE_RATE", "0.1").to_f
config.environment = Rails.env
config.before_send = lambda do |event, _hint|
Ops::IncidentRecorder.record(
kind: "log_pattern",
severity: "warning",
title: "Sentry: #{event.message}",
message: event.message,
metadata: {
"sentry_event_id" => event.event_id,
"level" => event.level
},
fingerprint: "sentry:#{event.fingerprint&.first || event.event_id}"
)
event
rescue StandardError => e
Rails.logger.warn("[Sentry] ops incident hook: #{e.message}")
event
end
end
rescue LoadError
Rails.logger.warn("[Sentry] gem non installata — imposta SENTRY_DSN dopo bundle add sentry-ruby sentry-rails")
end
end

View File

@@ -5,6 +5,7 @@ Sidekiq.configure_server do |config|
config.on(:startup) do
StreamPublisherSyncJob.ensure_chain
Ops::HealthMonitorJob.ensure_chain
end
end

View File

@@ -0,0 +1,16 @@
patterns:
- name: http_500
severity: warning
regex: 'Completed 500'
- name: disk_full
severity: critical
regex: 'No space left on device'
- name: db_connection
severity: critical
regex: 'PG::(ConnectionBad|UnableToSend)'
- name: sidekiq_failure
severity: warning
regex: 'WARN.*fail'
- name: fatal_error
severity: critical
regex: '\bFATAL\b'

View File

@@ -2,6 +2,7 @@ Rails.application.routes.draw do
mount ActionCable.server => "/cable"
get "up" => "rails/health#show", as: :rails_health_check
get "up/deep" => "ops/health#show", as: :ops_deep_health
namespace :api do
namespace :v1 do
@@ -76,6 +77,13 @@ Rails.application.routes.draw do
root to: "dashboard#index"
get "metrics", to: "dashboard#metrics"
resources :ops, only: [:index], controller: "ops" do
member do
post :acknowledge
post :resolve
post :mute
end
end
get "billing", to: "billing#index", as: :billing
post "billing/payments/:payment_id/attach_pdf", to: "billing#attach_pdf", as: :billing_payment_attach_pdf
resources :teams, only: %i[show]