Skip to content

Commit 3b64cc2

Browse files
dingsdaxclaudeClaude Sonnet 4.6github-advanced-security[bot]
authored
feat(yabeda): Add sentry-yabeda adapter gem (#2925)
* feat(yabeda): Add sentry-yabeda adapter gem Introduces sentry-yabeda, a Yabeda adapter that forwards metrics to Sentry. Covers all four Yabeda metric types (counter, gauge, histogram, summary), a periodic collector to drive gauge collection in push-based environments, and a full spec suite including unit and integration tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(yabeda): Replace endless method syntax for Ruby 2.7 compatibility Endless method syntax (def m() = val) requires Ruby 3.0+. Replace with conventional empty method bodies (def m; end) so RuboCop using the Ruby 2.7 parser does not reject the file. Co-Authored-By: Claude Sonnet 4.6 <noreply@example.com> * fix(yabeda): Use ActionController::API in yabeda-mini app The app is configured as api_only but inherited from ActionController::Base, which includes CSRF protection middleware. Switch to ActionController::API to align with the api_only setting and eliminate the CSRF warning. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Potential fix for code scanning alert no. 22: CSRF protection not enabled Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * chore(yabeda): Remove unused yabeda-mini manual test app The app was never referenced by any spec or CI step and carried maintenance weight (Gemfile.lock, SQLite DB, log files). The sentry-yabeda adapter is already covered by unit and integration specs in sentry-yabeda/spec/. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(yabeda): Add GitHub Actions workflow for sentry-yabeda Add sentry_yabeda_test.yml following the same pattern as other gem workflows (resque, delayed_job, opentelemetry). Wire it into tests.yml so it runs on every PR and push to master, and is included in the CodeCov notification gate. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(yabeda): Add sentry-yabeda to root README and trim per-gem README Replace the detailed standalone sentry-yabeda README with a minimal one matching the per-gem style used across the repo. Add sentry-yabeda to the badge table, install snippet, and integrations list in the root README. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(yabeda): Add sentry-yabeda to sentry-ruby/README.md Same badge table row, install snippet, and integrations list entry as the root README — keeps the gem-level README in sync. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ref(yabeda): Auto-start collector via Sentry::Configuration callback Remove the manual start_collector!/stop_collector! API. The periodic gauge collector now starts automatically when Sentry is initialized with enable_metrics: true, via an after(:configured) hook — consistent with how other Sentry integrations wire into the SDK lifecycle. Collector follows the same initialization pattern as SessionFlusher and BackpressureMonitor, taking a configuration positional argument and calling super(configuration.sdk_logger, interval). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(yabeda): Fire :closed callbacks on Sentry.close for final flush Add a :closed callback event to Sentry::Configuration, fired by Sentry.close before client.flush. sentry-yabeda registers an after(:closed) hook that performs one final Yabeda.collect! and then kills the collector, ensuring any gauge values set by collection blocks are captured in the last envelope. The existing after(:configured) guard (collector&.kill) is kept to handle re-initialisation without an explicit close call. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(yabeda): Add How it works section to README Explain the two metric modes: inline metrics (counters, histograms, summaries, direct gauge sets) forward to Sentry immediately, while collector blocks need a periodic trigger since Yabeda was designed for Prometheus's pull model. Documents the 15s background thread and the lack of trace context on collector-driven metrics. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Claude Sonnet 4.6 <noreply@example.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent 89514c0 commit 3b64cc2

21 files changed

Lines changed: 886 additions & 2 deletions
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: sentry-yabeda Test
2+
3+
on:
4+
workflow_dispatch:
5+
workflow_call:
6+
outputs:
7+
matrix-result:
8+
description: "Matrix job result"
9+
value: ${{ jobs.test.outputs.matrix-result }}
10+
inputs:
11+
versions:
12+
required: true
13+
type: string
14+
# Cancel in progress workflows on pull_requests.
15+
# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value
16+
concurrency:
17+
group: sentry-yabeda-test-${{ github.head_ref || github.run_id }}
18+
cancel-in-progress: true
19+
jobs:
20+
test:
21+
defaults:
22+
run:
23+
working-directory: sentry-yabeda
24+
name: Ruby ${{ matrix.ruby_version }}, options - ${{ toJson(matrix.options) }}
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 10
27+
env:
28+
RUBYOPT: ${{ matrix.options.rubyopt }}
29+
BUNDLE_GEMFILE: ${{ github.workspace }}/sentry-yabeda/Gemfile
30+
BUNDLE_WITHOUT: rubocop
31+
JRUBY_OPTS: "--debug" # for more accurate test coverage
32+
strategy:
33+
fail-fast: false
34+
matrix:
35+
ruby_version: ${{ fromJson(inputs.versions) }}
36+
include:
37+
- ruby_version: "3.2"
38+
options:
39+
rubyopt: "--enable-frozen-string-literal --debug=frozen-string-literal"
40+
exclude:
41+
- ruby_version: 'jruby'
42+
- ruby_version: 'jruby-head'
43+
steps:
44+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
45+
46+
- name: Set up Ruby ${{ matrix.ruby_version }}
47+
uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1
48+
with:
49+
ruby-version: ${{ matrix.ruby_version }}
50+
bundler-cache: true
51+
52+
- name: Run specs
53+
run: bundle exec rake
54+
55+
- name: Upload Coverage
56+
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
57+
with:
58+
token: ${{ secrets.CODECOV_TOKEN }}

.github/workflows/tests.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ jobs:
6363
versions: ${{ needs.ruby-versions.outputs.versions }}
6464
secrets: inherit
6565

66+
yabeda-tests:
67+
needs: ruby-versions
68+
uses: ./.github/workflows/sentry_yabeda_test.yml
69+
with:
70+
versions: ${{ needs.ruby-versions.outputs.versions }}
71+
secrets: inherit
72+
6673
codecov:
6774
name: CodeCov
6875
runs-on: ubuntu-latest
@@ -73,6 +80,7 @@ jobs:
7380
- delayed_job-tests
7481
- resque-tests
7582
- opentelemetry-tests
83+
- yabeda-tests
7684
steps:
7785
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
7886

.gitignore

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,15 @@ Gemfile.lock
1818
node_modules
1919
.vite
2020

21+
.DS_Store
22+
23+
mise.toml
24+
2125
.devcontainer/.env
2226
vendor/gems
27+
2328
sentry-rails/Gemfile-*.lock
24-
mise.toml
29+
30+
sentry-yabeda/.DS_Store
31+
sentry-yabeda/.rspec_status
32+
sentry-yabeda/Gemfile-*.lock

sentry-ruby/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Sentry SDK for Ruby
2121
| [![Gem Version](https://img.shields.io/gem/v/sentry-delayed_job?label=sentry-delayed_job)](https://rubygems.org/gems/sentry-delayed_job) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml) | [![codecov](https://codecov.io/gh/getsentry/sentry-ruby/graph/badge.svg?token=ZePzrpZFP6&component=sentry-delayed_job)](https://codecov.io/gh/getsentry/sentry-ruby) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-delayed_job) |
2222
| [![Gem Version](https://img.shields.io/gem/v/sentry-resque?label=sentry-resque)](https://rubygems.org/gems/sentry-resque) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml) | [![codecov](https://codecov.io/gh/getsentry/sentry-ruby/graph/badge.svg?token=ZePzrpZFP6&component=sentry-resque)](https://codecov.io/gh/getsentry/sentry-ruby) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-resque) |
2323
| [![Gem Version](https://img.shields.io/gem/v/sentry-opentelemetry?label=sentry-opentelemetry)](https://rubygems.org/gems/sentry-opentelemetry) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml) | [![codecov](https://codecov.io/gh/getsentry/sentry-ruby/graph/badge.svg?token=ZePzrpZFP6&component=sentry-opentelemetry)](https://codecov.io/gh/getsentry/sentry-ruby) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-opentelemetry) |
24+
| [![Gem Version](https://img.shields.io/gem/v/sentry-yabeda?label=sentry-yabeda)](https://rubygems.org/gems/sentry-yabeda) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml) | [![codecov](https://codecov.io/gh/getsentry/sentry-ruby/graph/badge.svg?token=ZePzrpZFP6&component=sentry-yabeda)](https://codecov.io/gh/getsentry/sentry-ruby) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-yabeda) |
2425

2526

2627

@@ -53,6 +54,7 @@ gem "sentry-sidekiq"
5354
gem "sentry-delayed_job"
5455
gem "sentry-resque"
5556
gem "sentry-opentelemetry"
57+
gem "sentry-yabeda"
5658
```
5759

5860
### Configuration
@@ -93,6 +95,7 @@ To learn more about sampling transactions, please visit the [official documentat
9395
- [DelayedJob](https://docs.sentry.io/platforms/ruby/guides/delayed_job/)
9496
- [Resque](https://docs.sentry.io/platforms/ruby/guides/resque/)
9597
- [OpenTelemetry](https://docs.sentry.io/platforms/ruby/performance/instrumentation/opentelemetry/)
98+
- [Yabeda](https://docs.sentry.io/platforms/ruby/guides/yabeda/)
9699

97100
### Enriching Events
98101

sentry-ruby/lib/sentry-ruby.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ def close
268268
end
269269

270270
if client = get_current_client
271+
client.configuration.run_after_close_callbacks
271272
client.flush
272273

273274
if client.configuration.include_local_variables

sentry-ruby/lib/sentry/configuration.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,8 @@ def after(event, &block)
461461
def callbacks
462462
@callbacks ||= {
463463
initialize: { before: [], after: [] },
464-
configured: { before: [], after: [] }
464+
configured: { before: [], after: [] },
465+
closed: { before: [], after: [] }
465466
}
466467
end
467468

@@ -798,6 +799,11 @@ def error_messages
798799
@errors.join(", ")
799800
end
800801

802+
# @api private
803+
def run_after_close_callbacks
804+
run_callbacks(:after, :closed)
805+
end
806+
801807
private
802808

803809
def init_dsn(dsn_string)

sentry-ruby/spec/sentry/configuration_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,20 @@ class SentryConfigurationSample < Sentry::Configuration
569569
end
570570
end
571571

572+
describe '#run_after_close_callbacks' do
573+
it 'calls hooks registered with after(:closed)' do
574+
called = false
575+
576+
config = Class.new(Sentry::Configuration) do
577+
after(:closed) do
578+
called = true
579+
end
580+
end.new
581+
582+
expect { config.run_after_close_callbacks }.to change { called }.from(false).to(true)
583+
end
584+
end
585+
572586
describe "#skip_rake_integration" do
573587
it "returns false by default" do
574588
expect(subject.skip_rake_integration).to eq(false)

sentry-yabeda/Gemfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
git_source(:github) { |name| "https://github.com/#{name}.git" }
5+
6+
eval_gemfile "../Gemfile.dev"
7+
8+
# Specify your gem's dependencies in sentry-yabeda.gemspec
9+
gemspec
10+
11+
gem "sentry-ruby", path: "../sentry-ruby"
12+
13+
gem "timecop"

sentry-yabeda/LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2020 Sentry
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

sentry-yabeda/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<p align="center">
2+
<a href="https://sentry.io" target="_blank" align="center">
3+
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280">
4+
</a>
5+
<br>
6+
</p>
7+
8+
# sentry-yabeda, the Yabeda integration for Sentry's Ruby client
9+
10+
---
11+
12+
[![Gem Version](https://img.shields.io/gem/v/sentry-yabeda.svg)](https://rubygems.org/gems/sentry-yabeda)
13+
![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_yabeda_test.yml/badge.svg)
14+
[![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master)
15+
[![Gem](https://img.shields.io/gem/dt/sentry-yabeda.svg)](https://rubygems.org/gems/sentry-yabeda/)
16+
17+
18+
[Documentation](https://docs.sentry.io/platforms/ruby/) | [Bug Tracker](https://github.com/getsentry/sentry-ruby/issues) | [Forum](https://forum.sentry.io/) | IRC: irc.freenode.net, #sentry
19+
20+
The official Ruby-language client and integration layer for the [Sentry](https://github.com/getsentry/sentry) error reporting API.
21+
22+
23+
## Getting Started
24+
25+
### Install
26+
27+
```ruby
28+
gem "sentry-ruby"
29+
gem "sentry-yabeda"
30+
```
31+
32+
Then initialize Sentry with metrics enabled:
33+
34+
```ruby
35+
Sentry.init do |config|
36+
config.dsn = ENV["SENTRY_DSN"]
37+
config.enable_metrics = true
38+
end
39+
```
40+
41+
That's it! All Yabeda metrics will automatically flow to Sentry.
42+
43+
## How it works
44+
45+
Counters, histograms, summaries, and directly-set gauges all forward to Sentry inline when your app calls them. Yabeda summaries map to Sentry distributions, as Sentry has no summary type.
46+
47+
Collector blocks (`Yabeda.configure { collect { ... } }`) are Yabeda's pull hook — in Prometheus they're triggered by a scrape request. Since Sentry is push-based, `sentry-yabeda` runs a background thread that calls `Yabeda.collect!` every 15 seconds. Metrics populated this way (typically gauges for GC stats, thread counts, etc.) won't carry trace context.

0 commit comments

Comments
 (0)