Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ gem "lograge"

# For distributed tracing and telemetry
gem "opentelemetry-exporter-otlp", "~> 0.34.0"
gem "opentelemetry-exporter-otlp-metrics", "~> 0.10.0"
gem "opentelemetry-instrumentation-all", "~> 0.94.0"
gem "opentelemetry-metrics-sdk", "~> 0.15.0"
gem "opentelemetry-propagator-xray", "~> 0.27.0"
gem "opentelemetry-sdk", "~> 1.12"

Expand Down
20 changes: 20 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,15 @@ GEM
opentelemetry-common (~> 0.20)
opentelemetry-sdk (~> 1.10)
opentelemetry-semantic_conventions
opentelemetry-exporter-otlp-metrics (0.10.0)
google-protobuf (>= 3.18, < 5.0)
googleapis-common-protos-types (~> 1.3)
opentelemetry-api (~> 1.1)
opentelemetry-common (~> 0.20)
opentelemetry-metrics-api (~> 0.2)
opentelemetry-metrics-sdk (~> 0.5)
opentelemetry-sdk (~> 1.2)
opentelemetry-semantic_conventions
opentelemetry-helpers-mysql (0.6.0)
opentelemetry-api (~> 1.7)
opentelemetry-common (~> 0.21)
Expand Down Expand Up @@ -527,6 +536,12 @@ GEM
opentelemetry-helpers-sql-processor
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-semantic_conventions (>= 1.8.0)
opentelemetry-metrics-api (0.6.0)
opentelemetry-api (~> 1.0)
opentelemetry-metrics-sdk (0.15.0)
opentelemetry-api (~> 1.1)
opentelemetry-metrics-api (~> 0.2)
opentelemetry-sdk (~> 1.2)
opentelemetry-propagator-xray (0.27.0)
opentelemetry-api (~> 1.7)
opentelemetry-registry (0.6.0)
Expand Down Expand Up @@ -800,7 +815,9 @@ DEPENDENCIES
omniauth-rails_csrf_protection
omniauth_govuk_one_login!
opentelemetry-exporter-otlp (~> 0.34.0)
opentelemetry-exporter-otlp-metrics (~> 0.10.0)
opentelemetry-instrumentation-all (~> 0.94.0)
opentelemetry-metrics-sdk (~> 0.15.0)
opentelemetry-propagator-xray (~> 0.27.0)
opentelemetry-sdk (~> 1.12)
pg (~> 1.6)
Expand Down Expand Up @@ -947,6 +964,7 @@ CHECKSUMS
opentelemetry-api (1.10.0) sha256=99ee7c829b18381c31a817ee9bf6a160d737542d99cb8da55d443336d266bfa9
opentelemetry-common (0.25.0) sha256=73915362e58d337fc92acbe1abfdaee1f725442527125fdb2af1420417f1149d
opentelemetry-exporter-otlp (0.34.0) sha256=3b3cdf4329ba30f4389d849c7f13b8f9f983ecb4a030031c03997dffae1e2a60
opentelemetry-exporter-otlp-metrics (0.10.0) sha256=d8cbff9b8a3391eb61486b8be9b6ad74e3b9306a3c60fb4c906b28bc857167c8
opentelemetry-helpers-mysql (0.6.0) sha256=7eeb5e6950c434775a8cf28b5fde4defc12e8b865c86479ce3119fcf593d9337
opentelemetry-helpers-sql (0.4.0) sha256=b10e8c3a2cca28a98af951bbb3e4efdc59e68b25ba0825e055574af543420afb
opentelemetry-helpers-sql-processor (0.5.0) sha256=b199241bc9451fcbd9f00b2f454830af19d4ca27c2219ea379c9b0d53cd0e0f1
Expand Down Expand Up @@ -996,6 +1014,8 @@ CHECKSUMS
opentelemetry-instrumentation-sidekiq (0.29.0) sha256=b1d2a0cb9041a5e14239fe7c94d99e3dd07f870e2759460ab63592d7cdd8aadc
opentelemetry-instrumentation-sinatra (0.30.0) sha256=b67301153420f43264a0c68cdb3ca5bd77467cf5054e57b83a2bf891aaaa0361
opentelemetry-instrumentation-trilogy (0.69.0) sha256=0676dd720eeab284abfa52f273967442156fcac7084a1e1411373cf14ec026ad
opentelemetry-metrics-api (0.6.0) sha256=b9300821680a1370684098cb030c18423dd55909ea0206faadfa7bc47362df87
opentelemetry-metrics-sdk (0.15.0) sha256=611a9cd9f473c461095c7401b8c25f9774160d286a1acbfcbf044da2972aeada
opentelemetry-propagator-xray (0.27.0) sha256=753f756c7ad3146f182d428b06041084eecc77769edfd280f365e0bc09b9c4d1
opentelemetry-registry (0.6.0) sha256=5d3ed32ab9eee0fbdb30d4f0d0bb61ad11a4040b267b475ae815b80a8498a728
opentelemetry-sdk (1.12.0) sha256=a224abe0c59023d41cb7ac1c634d9d28843907efcd045ed1ae320796c48b864b
Expand Down
2 changes: 2 additions & 0 deletions app/services/form_submission_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ def create_submission_record

submission.deliveries.create!(delivery_schedule: :immediate)

Metrics::SubmissionCounter.record(form_id: form.id, form_name: form.name, mode:)

submission
end

Expand Down
44 changes: 44 additions & 0 deletions app/services/metrics/submission_counter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module Metrics
class SubmissionCounter
METRIC_NAME = "SubmissionCount".freeze
METER_NAME = "forms-runner".freeze
METER_VERSION = "1.0".freeze

class << self
def record(form_id:, form_name:, mode:, meter_provider: OpenTelemetry.meter_provider)
return if mode.preview?

counter(meter_provider).add(
1,
attributes: metric_attributes(form_id:, form_name:),
)
end

private

def counter(meter_provider)
counters[meter_provider] ||= meter(meter_provider).create_counter(
METRIC_NAME,
unit: "1",
description: "Number of form submissions",
)
end

def counters
@counters ||= {}
end

def meter(meter_provider)
meter_provider.meter(METER_NAME, version: METER_VERSION)
end

def metric_attributes(form_id:, form_name:)
{
"Environment" => Settings.forms_env.downcase,
"FormId" => form_id.to_s,
"FormName" => form_name.to_s,
}
end
end
end
end
10 changes: 10 additions & 0 deletions config/initializers/opentelemetry.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
require "opentelemetry/sdk"
require "opentelemetry/instrumentation/all"
require "opentelemetry-metrics-sdk"
require "opentelemetry-exporter-otlp-metrics"

return unless ENV["ENABLE_OTEL"] == "true"

Expand All @@ -12,6 +14,14 @@
c.id_generator = OpenTelemetry::Propagator::XRay::IDGenerator
end

unless ENV.fetch("OTEL_METRICS_EXPORTER", "otlp") == "none"
c.add_metric_reader(
OpenTelemetry::SDK::Metrics::Export::PeriodicMetricReader.new(
exporter: OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter.new,
),
)
end

# Disable logging for Rake tasks to avoid cluttering output
c.logger = Logger.new(File::NULL) if Rails.const_defined?(:Rake) && Rake.application.top_level_tasks.any?
end
10 changes: 10 additions & 0 deletions spec/services/form_submission_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@
expect(log_line["submission_reference"]).to eq(reference)
end

it "records a submission count metric" do
expect(Metrics::SubmissionCounter).to receive(:record).with(
form_id: form.id,
form_name: form.name,
mode:,
)

service.submit
end

shared_examples "logging" do
it "logs submission" do
allow(LogEventService).to receive(:log_submit).once
Expand Down
88 changes: 88 additions & 0 deletions spec/services/metrics/submission_counter_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
require "rails_helper"
require "opentelemetry-metrics-sdk"

describe Metrics::SubmissionCounter do
let(:meter_provider) { OpenTelemetry::SDK::Metrics::MeterProvider.new }
let(:metric_exporter) { OpenTelemetry::SDK::Metrics::Export::InMemoryMetricPullExporter.new }
let(:forms_env) { "test" }
let(:form_id) { 42 }
let(:form_name) { "Test Form" }
let(:mode) { Mode.new("form") }

before do
allow(Settings).to receive(:forms_env).and_return(forms_env)
meter_provider.add_metric_reader(metric_exporter)
described_class.send(:counters).clear
end

describe ".record" do
it "records a submission count metric" do
described_class.record(form_id:, form_name:, mode:, meter_provider:)

expect(exported_data_points).to contain_exactly(
have_attributes(
value: 1,
attributes: {
"Environment" => forms_env,
"FormId" => form_id.to_s,
"FormName" => form_name,
},
),
)
end

context "when mode is preview" do
let(:mode) { Mode.new("preview-live") }

it "does not record a metric" do
described_class.record(form_id:, form_name:, mode:, meter_provider:)

expect(exported_data_points).to be_empty
end
end

it "accumulates counts for the same form" do
2.times { described_class.record(form_id:, form_name:, mode:, meter_provider:) }

expect(exported_data_points).to contain_exactly(
have_attributes(
value: 2,
attributes: {
"Environment" => forms_env,
"FormId" => form_id.to_s,
"FormName" => form_name,
},
),
)
end

it "records separate counts per form" do
described_class.record(form_id:, form_name:, mode:, meter_provider:)
described_class.record(form_id: 99, form_name: "Other Form", mode:, meter_provider:)

expect(exported_data_points).to contain_exactly(
have_attributes(
value: 1,
attributes: {
"Environment" => forms_env,
"FormId" => form_id.to_s,
"FormName" => form_name,
},
),
have_attributes(
value: 1,
attributes: {
"Environment" => forms_env,
"FormId" => "99",
"FormName" => "Other Form",
},
),
)
end
end

def exported_data_points
metric_exporter.pull
metric_exporter.metric_snapshots.flat_map(&:data_points)
end
end