sentry-ruby-core-5.3.0/ 0000755 0000041 0000041 00000000000 14232765714 015013 5 ustar www-data www-data sentry-ruby-core-5.3.0/Makefile 0000644 0000041 0000041 00000000132 14232765714 016447 0 ustar www-data www-data build:
bundle install
gem build sentry-ruby-core.gemspec
gem build sentry-ruby.gemspec
sentry-ruby-core-5.3.0/.rspec 0000644 0000041 0000041 00000000065 14232765714 016131 0 ustar www-data www-data --format documentation
--color
--require spec_helper
sentry-ruby-core-5.3.0/README.md 0000644 0000041 0000041 00000015266 14232765714 016304 0 ustar www-data www-data
_Bad software is everywhere, and we're tired of it. Sentry is on a mission to help developers write better software faster, so we can get back to enjoying technology. If you want to join us [**Check out our open positions**](https://sentry.io/careers/)_
Sentry SDK for Ruby
===========
| current version | build | coverage | downloads |
| --- | ----- | -------- | --------- |
| [](https://rubygems.org/gems/sentry-ruby) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-ruby/) |
| [](https://rubygems.org/gems/sentry-rails) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-rails/) |
| [](https://rubygems.org/gems/sentry-sidekiq) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-sidekiq/) |
| [](https://rubygems.org/gems/sentry-delayed_job) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-delayed_job/) |
| [](https://rubygems.org/gems/sentry-resque) | [](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml) | [](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [](https://rubygems.org/gems/sentry-resque/) |
## Migrate From sentry-raven
**The old `sentry-raven` client has entered maintenance mode and was moved to [here](https://github.com/getsentry/sentry-ruby/tree/master/sentry-raven).**
If you're using `sentry-raven`, we recommend you to migrate to this new SDK. You can find the benefits of migrating and how to do it in our [migration guide](https://docs.sentry.io/platforms/ruby/migration/).
## Requirements
We test on Ruby 2.4, 2.5, 2.6, 2.7, 3.0, and 3.1 at the latest patchlevel/teeny version. We also support JRuby 9.0.
If you use self-hosted Sentry, please also make sure its version is above `20.6.0`.
## Getting Started
### Install
```ruby
gem "sentry-ruby"
```
and depends on the integrations you want to have, you might also want to install these:
```ruby
gem "sentry-rails"
gem "sentry-sidekiq"
gem "sentry-delayed_job"
gem "sentry-resque"
```
### Configuration
You can use `Sentry.init` to initialize and configure your SDK:
```ruby
Sentry.init do |config|
config.dsn = "MY_DSN"
end
```
To learn more about available configuration options, please visit the [official documentation](https://docs.sentry.io/platforms/ruby/configuration/options/).
### Performance Monitoring
You can activate [performance monitoring](https://docs.sentry.io/platforms/ruby/performance) by enabling traces sampling:
```ruby
Sentry.init do |config|
# set a uniform sample rate between 0.0 and 1.0
config.traces_sample_rate = 0.2
# you can also use traces_sampler for more fine-grained sampling
# please click the link below to learn more
end
```
To learn more about sampling transactions, please visit the [official documentation](https://docs.sentry.io/platforms/ruby/configuration/sampling/#configuring-the-transaction-sample-rate).
### [Migration Guide](https://docs.sentry.io/platforms/ruby/migration/)
### Integrations
- [Rack](https://docs.sentry.io/platforms/ruby/guides/rack/)
- [Rails](https://docs.sentry.io/platforms/ruby/guides/rails/)
- [Sidekiq](https://docs.sentry.io/platforms/ruby/guides/sidekiq/)
- [DelayedJob](https://docs.sentry.io/platforms/ruby/guides/delayed_job/)
- [Resque](https://docs.sentry.io/platforms/ruby/guides/resque/)
### Enriching Events
- [Add more data to the current scope](https://docs.sentry.io/platforms/ruby/guides/rack/enriching-events/scopes/)
- [Add custom breadcrumbs](https://docs.sentry.io/platforms/ruby/guides/rack/enriching-events/breadcrumbs/)
- [Add contextual data](https://docs.sentry.io/platforms/ruby/guides/rack/enriching-events/context/)
- [Add tags](https://docs.sentry.io/platforms/ruby/guides/rack/enriching-events/tags/)
## Resources
* [](https://docs.sentry.io/platforms/ruby/)
* [](https://forum.sentry.io/c/sdks)
* [](https://discord.gg/PXa5Apfe7K)
* [](https://stackoverflow.com/questions/tagged/sentry)
* [](https://twitter.com/intent/follow?screen_name=getsentry)
sentry-ruby-core-5.3.0/bin/ 0000755 0000041 0000041 00000000000 14232765714 015563 5 ustar www-data www-data sentry-ruby-core-5.3.0/bin/console 0000755 0000041 0000041 00000000730 14232765714 017153 0 ustar www-data www-data #!/usr/bin/env ruby
require "bundler/setup"
require "sentry-ruby"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
# Sentry.init do |config|
# config.dsn = 'https://2fb45f003d054a7ea47feb45898f7649@o447951.ingest.sentry.io/5434472'
# end
require "irb"
IRB.start(__FILE__)
sentry-ruby-core-5.3.0/bin/setup 0000755 0000041 0000041 00000000203 14232765714 016644 0 ustar www-data www-data #!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
bundle install
# Do any other automated setup that you need to do here
sentry-ruby-core-5.3.0/CHANGELOG.md 0000644 0000041 0000041 00000036150 14232765714 016631 0 ustar www-data www-data # Changelog
Individual gem's changelog has been deprecated. Please check the [project changelog](https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md).
## 4.4.2
- Fix NoMethodError when SDK's dsn is nil [#1433](https://github.com/getsentry/sentry-ruby/pull/1433)
- fix: Update protocol version to 7 [#1434](https://github.com/getsentry/sentry-ruby/pull/1434)
- Fixes [#867](https://github.com/getsentry/sentry-ruby/issues/867)
## 4.4.1
- Apply patches when initializing the SDK [#1432](https://github.com/getsentry/sentry-ruby/pull/1432)
## 4.4.0
### Features
#### Support category-based rate limiting [#1336](https://github.com/getsentry/sentry-ruby/pull/1336)
Sentry rate limits different types of events. And when rate limiting is enabled, it sends back a `429` response to the SDK. Currently, the SDK would then raise an error like this:
```
Unable to record event with remote Sentry server (Sentry::Error - the server responded with status 429
body: {"detail":"event rejected due to rate limit"}):
```
This change improves the SDK's handling on such responses by:
- Not treating them as errors, so you don't see the noise anymore.
- Halting event sending for a while according to the duration provided in the response. And warns you with a message like:
```
Envelope [event] not sent: Excluded by random sample
```
#### Record request span from Net::HTTP library [#1381](https://github.com/getsentry/sentry-ruby/pull/1381)
Now any outgoing requests will be recorded as a tracing span. Example:
#### Record breadcrumb for Net::HTTP requests [#1394](https://github.com/getsentry/sentry-ruby/pull/1394)
With the new `http_logger` breadcrumbs logger:
```ruby
config.breadcrumbs_logger = [:http_logger]
```
The SDK now records a new `net.http` breadcrumb whenever the user makes a request with the `Net::HTTP` library.
#### Support config.debug configuration option [#1400](https://github.com/getsentry/sentry-ruby/pull/1400)
It'll determine whether the SDK should run in the debugging mode. Default is `false`. When set to true, SDK errors will be logged with backtrace.
#### Add the third tracing state [#1402](https://github.com/getsentry/sentry-ruby/pull/1402)
- `rate == 0` - Tracing enabled. Rejects all locally created transactions but respects sentry-trace.
- `1 > rate > 0` - Tracing enabled. Samples locally created transactions with the rate and respects sentry-trace.
- `rate < 0` or `rate > 1` - Tracing disabled.
### Refactorings
- Let Transaction constructor take an optional hub argument [#1384](https://github.com/getsentry/sentry-ruby/pull/1384)
- Introduce LoggingHelper [#1385](https://github.com/getsentry/sentry-ruby/pull/1385)
- Raise exception if a Transaction is initialized without a hub [#1391](https://github.com/getsentry/sentry-ruby/pull/1391)
- Make hub a required argument for Transaction constructor [#1401](https://github.com/getsentry/sentry-ruby/pull/1401)
### Bug Fixes
- Check `Scope#set_context`'s value argument [#1415](https://github.com/getsentry/sentry-ruby/pull/1415)
- Disable tracing if events are not allowed to be sent [#1421](https://github.com/getsentry/sentry-ruby/pull/1421)
## 4.3.2
- Correct type attribute's usages [#1354](https://github.com/getsentry/sentry-ruby/pull/1354)
- Fix sampling decision precedence [#1335](https://github.com/getsentry/sentry-ruby/pull/1335)
- Fix set_contexts [#1375](https://github.com/getsentry/sentry-ruby/pull/1375)
- Use thread variable instead of fiber variable to store the hub [#1380](https://github.com/getsentry/sentry-ruby/pull/1380)
- Fixes [#1374](https://github.com/getsentry/sentry-ruby/issues/1374)
- Fix Span/Transaction's nesting issue [#1382](https://github.com/getsentry/sentry-ruby/pull/1382)
- Fixes [#1372](https://github.com/getsentry/sentry-ruby/issues/1372)
## 4.3.1
- Add Sentry.set_context helper [#1337](https://github.com/getsentry/sentry-ruby/pull/1337)
- Fix handle the case where the logger messages is not of String type [#1341](https://github.com/getsentry/sentry-ruby/pull/1341)
- Don't report Sentry::ExternalError to Sentry [#1353](https://github.com/getsentry/sentry-ruby/pull/1353)
- Sentry.add_breadcrumb should call Hub#add_breadcrumb [#1358](https://github.com/getsentry/sentry-ruby/pull/1358)
- Fixes [#1357](https://github.com/getsentry/sentry-ruby/issues/1357)
## 4.3.0
### Features
- Allow configuring BreadcrumbBuffer's size limit [#1310](https://github.com/getsentry/sentry-ruby/pull/1310)
```ruby
# the SDK will only store 10 breadcrumbs (default is 100)
config.max_breadcrumbs = 10
```
- Compress event payload by default [#1314](https://github.com/getsentry/sentry-ruby/pull/1314)
### Refatorings
- Refactor interface construction [#1296](https://github.com/getsentry/sentry-ruby/pull/1296)
- Refactor tracing implementation [#1309](https://github.com/getsentry/sentry-ruby/pull/1309)
### Bug Fixes
- Improve SDK's error handling [#1298](https://github.com/getsentry/sentry-ruby/pull/1298)
- Fixes [#1246](https://github.com/getsentry/sentry-ruby/issues/1246) and [#1289](https://github.com/getsentry/sentry-ruby/issues/1289)
- Please read [#1290](https://github.com/getsentry/sentry-ruby/issues/1290) to see the full specification
- Treat query string as pii too [#1302](https://github.com/getsentry/sentry-ruby/pull/1302)
- Fixes [#1301](https://github.com/getsentry/sentry-ruby/issues/1301)
- Ignore sentry-trace when tracing is not enabled [#1308](https://github.com/getsentry/sentry-ruby/pull/1308)
- Fixes [#1307](https://github.com/getsentry/sentry-ruby/issues/1307)
- Return nil from logger methods instead of breadcrumb buffer [#1299](https://github.com/getsentry/sentry-ruby/pull/1299)
- Exceptions with nil message shouldn't cause issues [#1327](https://github.com/getsentry/sentry-ruby/pull/1327)
- Fixes [#1323](https://github.com/getsentry/sentry-ruby/issues/1323)
- Fix sampling decision with sentry-trace and add more tests [#1326](https://github.com/getsentry/sentry-ruby/pull/1326)
## 4.2.2
- Add thread_id to Exception interface [#1291](https://github.com/getsentry/sentry-ruby/pull/1291)
- always convert trusted proxies to string [#1288](https://github.com/getsentry/sentry-ruby/pull/1288)
- fixes [#1274](https://github.com/getsentry/sentry-ruby/issues/1274)
## 4.2.1
### Bug Fixes
- Ignore invalid values for sentry-trace header that don't match the required format [#1265](https://github.com/getsentry/sentry-ruby/pull/1265)
- Transaction created by `.from_sentry_trace` should inherit sampling decision [#1269](https://github.com/getsentry/sentry-ruby/pull/1269)
- Transaction's sample rate should accept any numeric value [#1278](https://github.com/getsentry/sentry-ruby/pull/1278)
## 4.2.0
### Features
- Add configuration option for trusted proxies [#1126](https://github.com/getsentry/sentry-ruby/pull/1126)
```ruby
config.trusted_proxies = ["2.2.2.2"] # this ip address will be skipped when computing users' ip addresses
```
- Add ThreadsInterface [#1178](https://github.com/getsentry/sentry-ruby/pull/1178)
- Support `config.before_breadcrumb` [#1253](https://github.com/getsentry/sentry-ruby/pull/1253)
```ruby
# this will be called before every breadcrumb is added to the breadcrumb buffer
# you can use it to
# - remove the data you don't want to send
# - add additional info to the data
config.before_breadcrumb = lambda do |breadcrumb, hint|
breadcrumb.message = "foo"
breadcrumb
end
```
- Add ability to have many post initialization callbacks [#1261](https://github.com/getsentry/sentry-ruby/pull/1261)
### Bug Fixes
- Inspect exception cause by default & don't exclude ActiveJob::DeserializationError [#1180](https://github.com/getsentry/sentry-ruby/pull/1180)
- Fixes [#1071](https://github.com/getsentry/sentry-ruby/issues/1071)
## 4.1.6
- Don't detect project root for Rails apps [#1243](https://github.com/getsentry/sentry-ruby/pull/1243)
- Separate individual breadcrumb's data serialization [#1250](https://github.com/getsentry/sentry-ruby/pull/1250)
- Capture sentry-trace with the correct http header key [#1260](https://github.com/getsentry/sentry-ruby/pull/1260)
## 4.1.5
- Serialize event hint before passing it to the async block [#1231](https://github.com/getsentry/sentry-ruby/pull/1231)
- Fixes [#1227](https://github.com/getsentry/sentry-ruby/issues/1227)
- Require the English library [#1233](https://github.com/getsentry/sentry-ruby/pull/1233) (by @dentarg)
- Allow `Sentry.init` without block argument [#1235](https://github.com/getsentry/sentry-ruby/pull/1235) (by @sue445)
## 4.1.5-beta.1
- No change
## 4.1.5-beta.0
- Inline global method [#1213](https://github.com/getsentry/sentry-ruby/pull/1213) (by @tricknotes)
- Event message and exception message should have a size limit [#1221](https://github.com/getsentry/sentry-ruby/pull/1221)
- Add sentry-ruby-core as a more flexible option [#1226](https://github.com/getsentry/sentry-ruby/pull/1226)
## 4.1.4
- Fix headers serialization for sentry-ruby [#1197](https://github.com/getsentry/sentry-ruby/pull/1197) (by @moofkit)
- Support capturing "sentry-trace" header from the middleware [#1205](https://github.com/getsentry/sentry-ruby/pull/1205)
- Document public APIs on the Sentry module [#1208](https://github.com/getsentry/sentry-ruby/pull/1208)
- Check the argument type of capture_exception and capture_event helpers [#1209](https://github.com/getsentry/sentry-ruby/pull/1209)
## 4.1.3
- rm reference to old constant (from Rails v2.2) [#1184](https://github.com/getsentry/sentry-ruby/pull/1184)
- Use copied env in events [#1186](https://github.com/getsentry/sentry-ruby/pull/1186)
- Fixes [#1183](https://github.com/getsentry/sentry-ruby/issues/1183)
- Refactor RequestInterface [#1187](https://github.com/getsentry/sentry-ruby/pull/1187)
- Supply event hint to async callback when possible [#1189](https://github.com/getsentry/sentry-ruby/pull/1189)
- Fixes [#1188](https://github.com/getsentry/sentry-ruby/issues/1188)
- Refactor stacktrace parsing and increase test coverage [#1190](https://github.com/getsentry/sentry-ruby/pull/1190)
- Sentry.send_event should also take a hint [#1192](https://github.com/getsentry/sentry-ruby/pull/1192)
## 4.1.2
- before_send callback shouldn't be applied to transaction events [#1167](https://github.com/getsentry/sentry-ruby/pull/1167)
- Transaction improvements [#1170](https://github.com/getsentry/sentry-ruby/pull/1170)
- Support Ruby 3 [#1172](https://github.com/getsentry/sentry-ruby/pull/1172)
- Add Integrable module [#1177](https://github.com/getsentry/sentry-ruby/pull/1177)
## 4.1.1
- Fix NoMethodError when sending is not allowed [#1161](https://github.com/getsentry/sentry-ruby/pull/1161)
- Add notification for users who still use deprecated middlewares [#1160](https://github.com/getsentry/sentry-ruby/pull/1160)
- Improve top-level api safety [#1162](https://github.com/getsentry/sentry-ruby/pull/1162)
## 4.1.0
- Separate rack integration [#1138](https://github.com/getsentry/sentry-ruby/pull/1138)
- Fixes [#1136](https://github.com/getsentry/sentry-ruby/pull/1136)
- Fix event sampling [#1144](https://github.com/getsentry/sentry-ruby/pull/1144)
- Merge & rename 2 Rack middlewares [#1147](https://github.com/getsentry/sentry-ruby/pull/1147)
- Fixes [#1153](https://github.com/getsentry/sentry-ruby/pull/1153)
- Removed `Sentry::Rack::Tracing` middleware and renamed `Sentry::Rack::CaptureException` to `Sentry::Rack::CaptureExceptions`.
- Deep-copy spans [#1148](https://github.com/getsentry/sentry-ruby/pull/1148)
- Move span recorder related code from Span to Transaction [#1149](https://github.com/getsentry/sentry-ruby/pull/1149)
- Check SDK initialization before running integrations [#1151](https://github.com/getsentry/sentry-ruby/pull/1151)
- Fixes [#1145](https://github.com/getsentry/sentry-ruby/pull/1145)
- Refactor transport [#1154](https://github.com/getsentry/sentry-ruby/pull/1154)
- Implement non-blocking event sending [#1155](https://github.com/getsentry/sentry-ruby/pull/1155)
- Added `background_worker_threads` configuration option.
### Noticeable Changes
#### Middleware Changes
`Sentry::Rack::Tracing` is now removed. And `Sentry::Rack::CaptureException` has been renamed to `Sentry::Rack::CaptureExceptions`.
#### Events Are Sent Asynchronously
`sentry-ruby` now sends events asynchronously by default. The functionality works like this:
1. When the SDK is initialized, a `Sentry::BackgroundWorker` will be initialized too.
2. When an event is passed to `Client#capture_event`, instead of sending it directly with `Client#send_event`, we'll let the worker do it.
3. The worker will have a number of threads. And the one of the idle threads will pick the job and call `Client#send_event`.
- If all the threads are busy, new jobs will be put into a queue, which has a limit of 30.
- If the queue size is exceeded, new events will be dropped.
However, if you still prefer to use your own async approach, that's totally fine. If you have `config.async` set, the worker won't initialize a thread pool and won't be used either.
This functionality also introduces a new `background_worker_threads` config option. It allows you to decide how many threads should the worker hold. By default, the value will be the number of the processors your machine has. For example, if your machine has 4 processors, the value would be 4.
Of course, you can always override the value to fit your use cases, like
```ruby
config.background_worker_threads = 5 # the worker will have 5 threads for sending events
```
You can also disable this new non-blocking behaviour by giving a `0` value:
```ruby
config.background_worker_threads = 0 # all events will be sent synchronously
```
## 4.0.1
- Add rake integration: [1137](https://github.com/getsentry/sentry-ruby/pull/1137)
- Make Event's interfaces accessible: [1135](https://github.com/getsentry/sentry-ruby/pull/1135)
- ActiveSupportLogger should only record events that has a started time: [1132](https://github.com/getsentry/sentry-ruby/pull/1132)
## 4.0.0
- Only documents update for the official release and no API/feature changes.
## 0.3.0
- Major API changes: [1123](https://github.com/getsentry/sentry-ruby/pull/1123)
- Support event hint: [1122](https://github.com/getsentry/sentry-ruby/pull/1122)
- Add request-id tag to events: [1120](https://github.com/getsentry/sentry-ruby/pull/1120) (by @tvec)
## 0.2.0
- Multiple fixes and refactorings
- Tracing support
## 0.1.3
Fix require reference
## 0.1.2
- Fix: Fix async callback [1098](https://github.com/getsentry/sentry-ruby/pull/1098)
- Refactor: Some code cleanup [1100](https://github.com/getsentry/sentry-ruby/pull/1100)
- Refactor: Remove Event options [1101](https://github.com/getsentry/sentry-ruby/pull/1101)
## 0.1.1
- Feature: Allow passing custom scope to Hub#capture* helpers [1086](https://github.com/getsentry/sentry-ruby/pull/1086)
## 0.1.0
First version
sentry-ruby-core-5.3.0/.gitignore 0000644 0000041 0000041 00000000161 14232765714 017001 0 ustar www-data www-data /.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
# rspec failure tracking
.rspec_status
sentry-ruby-core-5.3.0/CODE_OF_CONDUCT.md 0000644 0000041 0000041 00000006237 14232765714 017622 0 ustar www-data www-data # Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at stan001212@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [https://contributor-covenant.org/version/1/4][version]
[homepage]: https://contributor-covenant.org
[version]: https://contributor-covenant.org/version/1/4/
sentry-ruby-core-5.3.0/Rakefile 0000644 0000041 0000041 00000000416 14232765714 016461 0 ustar www-data www-data require "rake/clean"
CLOBBER.include "pkg"
require "bundler/gem_helper"
Bundler::GemHelper.install_tasks(name: "sentry-ruby")
require "rspec/core/rake_task"
RSpec::Core::RakeTask.new(:spec).tap do |task|
task.rspec_opts = "--order rand"
end
task :default => :spec
sentry-ruby-core-5.3.0/sentry-ruby-core.gemspec 0000644 0000041 0000041 00000001734 14232765714 021616 0 ustar www-data www-data require_relative "lib/sentry/version"
Gem::Specification.new do |spec|
spec.name = "sentry-ruby-core"
spec.version = Sentry::VERSION
spec.authors = ["Sentry Team"]
spec.description = spec.summary = "A gem that provides a client interface for the Sentry error logger"
spec.email = "accounts@sentry.io"
spec.license = 'MIT'
spec.homepage = "https://github.com/getsentry/sentry-ruby"
spec.platform = Gem::Platform::RUBY
spec.required_ruby_version = '>= 2.4'
spec.extra_rdoc_files = ["README.md", "LICENSE.txt"]
spec.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples)'`.split("\n")
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
spec.add_dependency "concurrent-ruby"
end
sentry-ruby-core-5.3.0/lib/ 0000755 0000041 0000041 00000000000 14232765714 015561 5 ustar www-data www-data sentry-ruby-core-5.3.0/lib/sentry/ 0000755 0000041 0000041 00000000000 14232765714 017105 5 ustar www-data www-data sentry-ruby-core-5.3.0/lib/sentry/core_ext/ 0000755 0000041 0000041 00000000000 14232765714 020715 5 ustar www-data www-data sentry-ruby-core-5.3.0/lib/sentry/core_ext/object/ 0000755 0000041 0000041 00000000000 14232765714 022163 5 ustar www-data www-data sentry-ruby-core-5.3.0/lib/sentry/core_ext/object/deep_dup.rb 0000644 0000041 0000041 00000002424 14232765714 024277 0 ustar www-data www-data # frozen_string_literal: true
return if Object.method_defined?(:deep_dup)
require 'sentry/core_ext/object/duplicable'
#########################################
# This file was copied from Rails 5.2 #
#########################################
class Object
# Returns a deep copy of object if it's duplicable. If it's
# not duplicable, returns +self+.
#
# object = Object.new
# dup = object.deep_dup
# dup.instance_variable_set(:@a, 1)
#
# object.instance_variable_defined?(:@a) # => false
# dup.instance_variable_defined?(:@a) # => true
def deep_dup
duplicable? ? dup : self
end
end
class Array
# Returns a deep copy of array.
#
# array = [1, [2, 3]]
# dup = array.deep_dup
# dup[1][2] = 4
#
# array[1][2] # => nil
# dup[1][2] # => 4
def deep_dup
map(&:deep_dup)
end
end
class Hash
# Returns a deep copy of hash.
#
# hash = { a: { b: 'b' } }
# dup = hash.deep_dup
# dup[:a][:c] = 'c'
#
# hash[:a][:c] # => nil
# dup[:a][:c] # => "c"
def deep_dup
hash = dup
each_pair do |key, value|
if key.frozen? && ::String === key
hash[key] = value.deep_dup
else
hash.delete(key)
hash[key.deep_dup] = value.deep_dup
end
end
hash
end
end
sentry-ruby-core-5.3.0/lib/sentry/core_ext/object/duplicable.rb 0000644 0000041 0000041 00000006406 14232765714 024622 0 ustar www-data www-data # frozen_string_literal: true
return if Object.method_defined?(:duplicable?)
#########################################
# This file was copied from Rails 5.2 #
#########################################
#--
# Most objects are cloneable, but not all. For example you can't dup methods:
#
# method(:puts).dup # => TypeError: allocator undefined for Method
#
# Classes may signal their instances are not duplicable removing +dup+/+clone+
# or raising exceptions from them. So, to dup an arbitrary object you normally
# use an optimistic approach and are ready to catch an exception, say:
#
# arbitrary_object.dup rescue object
#
# Rails dups objects in a few critical spots where they are not that arbitrary.
# That rescue is very expensive (like 40 times slower than a predicate), and it
# is often triggered.
#
# That's why we hardcode the following cases and check duplicable? instead of
# using that rescue idiom.
#++
class Object
# Can you safely dup this object?
#
# False for method objects;
# true otherwise.
def duplicable?
true
end
end
class NilClass
begin
nil.dup
rescue TypeError
# +nil+ is not duplicable:
#
# nil.duplicable? # => false
# nil.dup # => TypeError: can't dup NilClass
def duplicable?
false
end
end
end
class FalseClass
begin
false.dup
rescue TypeError
# +false+ is not duplicable:
#
# false.duplicable? # => false
# false.dup # => TypeError: can't dup FalseClass
def duplicable?
false
end
end
end
class TrueClass
begin
true.dup
rescue TypeError
# +true+ is not duplicable:
#
# true.duplicable? # => false
# true.dup # => TypeError: can't dup TrueClass
def duplicable?
false
end
end
end
class Symbol
begin
:symbol.dup # Ruby 2.4.x.
"symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.
rescue TypeError
# Symbols are not duplicable:
#
# :my_symbol.duplicable? # => false
# :my_symbol.dup # => TypeError: can't dup Symbol
def duplicable?
false
end
end
end
class Numeric
begin
1.dup
rescue TypeError
# Numbers are not duplicable:
#
# 3.duplicable? # => false
# 3.dup # => TypeError: can't dup Integer
def duplicable?
false
end
end
end
require "bigdecimal"
class BigDecimal
# BigDecimals are duplicable:
#
# BigDecimal("1.2").duplicable? # => true
# BigDecimal("1.2").dup # => #
def duplicable?
true
end
end
class Method
# Methods are not duplicable:
#
# method(:puts).duplicable? # => false
# method(:puts).dup # => TypeError: allocator undefined for Method
def duplicable?
false
end
end
class Complex
begin
Complex(1).dup
rescue TypeError
# Complexes are not duplicable:
#
# Complex(1).duplicable? # => false
# Complex(1).dup # => TypeError: can't copy Complex
def duplicable?
false
end
end
end
class Rational
begin
Rational(1).dup
rescue TypeError
# Rationals are not duplicable:
#
# Rational(1).duplicable? # => false
# Rational(1).dup # => TypeError: can't copy Rational
def duplicable?
false
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/session.rb 0000644 0000041 0000041 00000001441 14232765714 021115 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
class Session
attr_reader :started, :status
# TODO-neel add :crashed after adding handled mechanism
STATUSES = %i(ok errored exited)
AGGREGATE_STATUSES = %i(errored exited)
def initialize
@started = Sentry.utc_now
@status = :ok
end
# TODO-neel add :crashed after adding handled mechanism
def update_from_exception(_exception = nil)
@status = :errored
end
def close
@status = :exited if @status == :ok
end
# truncate seconds from the timestamp since we only care about
# minute level granularity for aggregation
def aggregation_key
Time.utc(started.year, started.month, started.day, started.hour, started.min)
end
def deep_dup
dup
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/interface.rb 0000644 0000041 0000041 00000000631 14232765714 021372 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
class Interface
# @return [Hash]
def to_hash
Hash[instance_variables.map { |name| [name[1..-1].to_sym, instance_variable_get(name)] }]
end
end
end
require "sentry/interfaces/exception"
require "sentry/interfaces/request"
require "sentry/interfaces/single_exception"
require "sentry/interfaces/stacktrace"
require "sentry/interfaces/threads"
sentry-ruby-core-5.3.0/lib/sentry/version.rb 0000644 0000041 0000041 00000000105 14232765714 021113 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
VERSION = "5.3.0"
end
sentry-ruby-core-5.3.0/lib/sentry/transport.rb 0000644 0000041 0000041 00000013601 14232765714 021467 0 ustar www-data www-data # frozen_string_literal: true
require "json"
require "base64"
require "sentry/envelope"
module Sentry
class Transport
PROTOCOL_VERSION = '7'
USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
CLIENT_REPORT_INTERVAL = 30
# https://develop.sentry.dev/sdk/client-reports/#envelope-item-payload
CLIENT_REPORT_REASONS = [
:ratelimit_backoff,
:queue_overflow,
:cache_overflow, # NA
:network_error,
:sample_rate,
:before_send,
:event_processor
]
include LoggingHelper
attr_reader :rate_limits, :discarded_events, :last_client_report_sent
# @deprecated Use Sentry.logger to retrieve the current logger instead.
attr_reader :logger
def initialize(configuration)
@logger = configuration.logger
@transport_configuration = configuration.transport
@dsn = configuration.dsn
@rate_limits = {}
@send_client_reports = configuration.send_client_reports
if @send_client_reports
@discarded_events = Hash.new(0)
@last_client_report_sent = Time.now
end
end
def send_data(data, options = {})
raise NotImplementedError
end
def send_event(event)
envelope = envelope_from_event(event)
send_envelope(envelope)
event
end
def send_envelope(envelope)
reject_rate_limited_items(envelope)
return if envelope.items.empty?
data, serialized_items = serialize_envelope(envelope)
if data
log_info("[Transport] Sending envelope with items [#{serialized_items.map(&:type).join(', ')}] #{envelope.event_id} to Sentry")
send_data(data)
end
end
def serialize_envelope(envelope)
serialized_items = []
serialized_results = []
envelope.items.each do |item|
result = item.to_s
if result.bytesize > Event::MAX_SERIALIZED_PAYLOAD_SIZE
if item.payload.key?(:breadcrumbs)
item.payload.delete(:breadcrumbs)
elsif item.payload.key?("breadcrumbs")
item.payload.delete("breadcrumbs")
end
result = item.to_s
end
if result.bytesize > Event::MAX_SERIALIZED_PAYLOAD_SIZE
size_breakdown = item.payload.map do |key, value|
"#{key}: #{JSON.generate(value).bytesize}"
end.join(", ")
log_debug("Envelope item [#{item.type}] is still oversized without breadcrumbs: {#{size_breakdown}}")
next
end
serialized_results << result
serialized_items << item
end
data = [JSON.generate(envelope.headers), *serialized_results].join("\n") unless serialized_results.empty?
[data, serialized_items]
end
def is_rate_limited?(item_type)
# check category-specific limit
category_delay =
case item_type
when "transaction"
@rate_limits["transaction"]
when "sessions"
@rate_limits["session"]
else
@rate_limits["error"]
end
# check universal limit if not category limit
universal_delay = @rate_limits[nil]
delay =
if category_delay && universal_delay
if category_delay > universal_delay
category_delay
else
universal_delay
end
elsif category_delay
category_delay
else
universal_delay
end
!!delay && delay > Time.now
end
def generate_auth_header
now = Sentry.utc_now.to_i
fields = {
'sentry_version' => PROTOCOL_VERSION,
'sentry_client' => USER_AGENT,
'sentry_timestamp' => now,
'sentry_key' => @dsn.public_key
}
fields['sentry_secret'] = @dsn.secret_key if @dsn.secret_key
'Sentry ' + fields.map { |key, value| "#{key}=#{value}" }.join(', ')
end
def envelope_from_event(event)
# Convert to hash
event_payload = event.to_hash
event_id = event_payload[:event_id] || event_payload["event_id"]
item_type = event_payload[:type] || event_payload["type"]
envelope = Envelope.new(
{
event_id: event_id,
dsn: @dsn.to_s,
sdk: Sentry.sdk_meta,
sent_at: Sentry.utc_now.iso8601
}
)
envelope.add_item(
{ type: item_type, content_type: 'application/json' },
event_payload
)
client_report_headers, client_report_payload = fetch_pending_client_report
envelope.add_item(client_report_headers, client_report_payload) if client_report_headers
envelope
end
def record_lost_event(reason, item_type)
return unless @send_client_reports
return unless CLIENT_REPORT_REASONS.include?(reason)
@discarded_events[[reason, item_type]] += 1
end
private
def fetch_pending_client_report
return nil unless @send_client_reports
return nil if @last_client_report_sent > Time.now - CLIENT_REPORT_INTERVAL
return nil if @discarded_events.empty?
discarded_events_hash = @discarded_events.map do |key, val|
reason, type = key
# 'event' has to be mapped to 'error'
category = type == 'transaction' ? 'transaction' : 'error'
{ reason: reason, category: category, quantity: val }
end
item_header = { type: 'client_report' }
item_payload = {
timestamp: Sentry.utc_now.iso8601,
discarded_events: discarded_events_hash
}
@discarded_events = Hash.new(0)
@last_client_report_sent = Time.now
[item_header, item_payload]
end
def reject_rate_limited_items(envelope)
envelope.items.reject! do |item|
if is_rate_limited?(item.type)
log_info("[Transport] Envelope item [#{item.type}] not sent: rate limiting")
record_lost_event(:ratelimit_backoff, item.type)
true
else
false
end
end
end
end
end
require "sentry/transport/dummy_transport"
require "sentry/transport/http_transport"
sentry-ruby-core-5.3.0/lib/sentry/rake.rb 0000644 0000041 0000041 00000001456 14232765714 020362 0 ustar www-data www-data # frozen_string_literal: true
require "rake"
require "rake/task"
module Sentry
module Rake
module Application
# @api private
def display_error_message(ex)
Sentry.capture_exception(ex) do |scope|
task_name = top_level_tasks.join(' ')
scope.set_transaction_name(task_name)
scope.set_tag("rake_task", task_name)
end if Sentry.initialized? && !Sentry.configuration.skip_rake_integration
super
end
end
module Task
# @api private
def execute(args=nil)
return super unless Sentry.initialized? && Sentry.get_current_hub
super
end
end
end
end
# @api private
module Rake
class Application
prepend(Sentry::Rake::Application)
end
class Task
prepend(Sentry::Rake::Task)
end
end
sentry-ruby-core-5.3.0/lib/sentry/utils/ 0000755 0000041 0000041 00000000000 14232765714 020245 5 ustar www-data www-data sentry-ruby-core-5.3.0/lib/sentry/utils/custom_inspection.rb 0000644 0000041 0000041 00000000611 14232765714 024335 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
module CustomInspection
def inspect
attr_strings = (instance_variables - self.class::SKIP_INSPECTION_ATTRIBUTES).each_with_object([]) do |attr, result|
value = instance_variable_get(attr)
result << "#{attr}=#{value.inspect}" if value
end
"#<#{self.class.name} #{attr_strings.join(", ")}>"
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/utils/request_id.rb 0000644 0000041 0000041 00000000640 14232765714 022736 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
module Utils
module RequestId
REQUEST_ID_HEADERS = %w(action_dispatch.request_id HTTP_X_REQUEST_ID).freeze
# Request ID based on ActionDispatch::RequestId
def self.read_from(env)
REQUEST_ID_HEADERS.each do |key|
request_id = env[key]
return request_id if request_id
end
nil
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/utils/exception_cause_chain.rb 0000644 0000041 0000041 00000000633 14232765714 025114 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
module Utils
module ExceptionCauseChain
def self.exception_to_array(exception)
exceptions = [exception]
while exception.cause
exception = exception.cause
break if exceptions.any? { |e| e.object_id == exception.object_id }
exceptions << exception
end
exceptions
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/utils/argument_checking_helper.rb 0000644 0000041 0000041 00000000520 14232765714 025603 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
module ArgumentCheckingHelper
private
def check_argument_type!(argument, expected_type)
unless argument.is_a?(expected_type)
raise ArgumentError, "expect the argument to be a #{expected_type}, got #{argument.class} (#{argument.inspect})"
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/utils/real_ip.rb 0000644 0000041 0000041 00000005337 14232765714 022215 0 ustar www-data www-data # frozen_string_literal: true
require 'ipaddr'
# Based on ActionDispatch::RemoteIp. All security-related precautions from that
# middleware have been removed, because the Event IP just needs to be accurate,
# and spoofing an IP here only makes data inaccurate, not insecure. Don't re-use
# this module if you have to *trust* the IP address.
module Sentry
module Utils
class RealIp
LOCAL_ADDRESSES = [
"127.0.0.1", # localhost IPv4
"::1", # localhost IPv6
"fc00::/7", # private IPv6 range fc00::/7
"10.0.0.0/8", # private IPv4 range 10.x.x.x
"172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
"192.168.0.0/16", # private IPv4 range 192.168.x.x
]
attr_reader :ip
def initialize(
remote_addr: nil,
client_ip: nil,
real_ip: nil,
forwarded_for: nil,
trusted_proxies: []
)
@remote_addr = remote_addr
@client_ip = client_ip
@real_ip = real_ip
@forwarded_for = forwarded_for
@trusted_proxies = (LOCAL_ADDRESSES + Array(trusted_proxies)).map do |proxy|
if proxy.is_a?(IPAddr)
proxy
else
IPAddr.new(proxy.to_s)
end
end.uniq
end
def calculate_ip
# CGI environment variable set by Rack
remote_addr = ips_from(@remote_addr).last
# Could be a CSV list and/or repeated headers that were concatenated.
client_ips = ips_from(@client_ip)
real_ips = ips_from(@real_ip)
# The first address in this list is the original client, followed by
# the IPs of successive proxies. We want to search starting from the end
# until we find the first proxy that we do not trust.
forwarded_ips = ips_from(@forwarded_for).reverse
ips = [client_ips, real_ips, forwarded_ips, remote_addr].flatten.compact
# If every single IP option is in the trusted list, just return REMOTE_ADDR
@ip = filter_trusted_proxy_addresses(ips).first || remote_addr
end
protected
def ips_from(header)
# Split the comma-separated list into an array of strings
ips = header ? header.strip.split(/[,\s]+/) : []
ips.select do |ip|
begin
# Only return IPs that are valid according to the IPAddr#new method
range = IPAddr.new(ip).to_range
# we want to make sure nobody is sneaking a netmask in
range.begin == range.end
rescue ArgumentError
nil
end
end
end
def filter_trusted_proxy_addresses(ips)
ips.reject { |ip| @trusted_proxies.any? { |proxy| proxy === ip } }
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/utils/logging_helper.rb 0000644 0000041 0000041 00000001073 14232765714 023560 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
module LoggingHelper
def log_error(message, exception, debug: false)
message = "#{message}: #{exception.message}"
message += "\n#{exception.backtrace.join("\n")}" if debug
@logger.error(LOGGER_PROGNAME) do
message
end
end
def log_info(message)
@logger.info(LOGGER_PROGNAME) { message }
end
def log_debug(message)
@logger.debug(LOGGER_PROGNAME) { message }
end
def log_warn(message)
@logger.warn(LOGGER_PROGNAME) { message }
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/exceptions.rb 0000644 0000041 0000041 00000000172 14232765714 021613 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
class Error < StandardError
end
class ExternalError < Error
end
end
sentry-ruby-core-5.3.0/lib/sentry/breadcrumb.rb 0000644 0000041 0000041 00000003263 14232765714 021544 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
class Breadcrumb
DATA_SERIALIZATION_ERROR_MESSAGE = "[data were removed due to serialization issues]"
# @return [String, nil]
attr_accessor :category
# @return [Hash, nil]
attr_accessor :data
# @return [String, nil]
attr_accessor :level
# @return [Time, Integer, nil]
attr_accessor :timestamp
# @return [String, nil]
attr_accessor :type
# @return [String, nil]
attr_reader :message
# @param category [String, nil]
# @param data [Hash, nil]
# @param message [String, nil]
# @param timestamp [Time, Integer, nil]
# @param level [String, nil]
# @param type [String, nil]
def initialize(category: nil, data: nil, message: nil, timestamp: nil, level: nil, type: nil)
@category = category
@data = data || {}
@level = level
@timestamp = timestamp || Sentry.utc_now.to_i
@type = type
self.message = message
end
# @return [Hash]
def to_hash
{
category: @category,
data: serialized_data,
level: @level,
message: @message,
timestamp: @timestamp,
type: @type
}
end
# @param message [String]
# @return [void]
def message=(message)
@message = (message || "").byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
end
private
def serialized_data
begin
::JSON.parse(::JSON.generate(@data))
rescue Exception => e
Sentry.logger.debug(LOGGER_PROGNAME) do
<<~MSG
can't serialize breadcrumb data because of error: #{e}
data: #{@data}
MSG
end
DATA_SERIALIZATION_ERROR_MESSAGE
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/interfaces/ 0000755 0000041 0000041 00000000000 14232765714 021230 5 ustar www-data www-data sentry-ruby-core-5.3.0/lib/sentry/interfaces/request.rb 0000644 0000041 0000041 00000010652 14232765714 023251 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
class RequestInterface < Interface
REQUEST_ID_HEADERS = %w(action_dispatch.request_id HTTP_X_REQUEST_ID).freeze
CONTENT_HEADERS = %w(CONTENT_TYPE CONTENT_LENGTH).freeze
IP_HEADERS = [
"REMOTE_ADDR",
"HTTP_CLIENT_IP",
"HTTP_X_REAL_IP",
"HTTP_X_FORWARDED_FOR"
].freeze
# See Sentry server default limits at
# https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
MAX_BODY_LIMIT = 4096 * 4
# @return [String]
attr_accessor :url
# @return [String]
attr_accessor :method
# @return [Hash]
attr_accessor :data
# @return [String]
attr_accessor :query_string
# @return [String]
attr_accessor :cookies
# @return [Hash]
attr_accessor :headers
# @return [Hash]
attr_accessor :env
# @param env [Hash]
# @param send_default_pii [Boolean]
# @param rack_env_whitelist [Array]
# @see Configuration#send_default_pii
# @see Configuration#rack_env_whitelist
def initialize(env:, send_default_pii:, rack_env_whitelist:)
env = env.dup
unless send_default_pii
# need to completely wipe out ip addresses
RequestInterface::IP_HEADERS.each do |header|
env.delete(header)
end
end
request = ::Rack::Request.new(env)
if send_default_pii
self.data = read_data_from(request)
self.cookies = request.cookies
self.query_string = request.query_string
end
self.url = request.scheme && request.url.split('?').first
self.method = request.request_method
self.headers = filter_and_format_headers(env, send_default_pii)
self.env = filter_and_format_env(env, rack_env_whitelist)
end
private
def read_data_from(request)
if request.form_data?
request.POST
elsif request.body # JSON requests, etc
data = request.body.read(MAX_BODY_LIMIT)
data = encode_to_utf_8(data.to_s)
request.body.rewind
data
end
rescue IOError => e
e.message
end
def filter_and_format_headers(env, send_default_pii)
env.each_with_object({}) do |(key, value), memo|
begin
key = key.to_s # rack env can contain symbols
next memo['X-Request-Id'] ||= Utils::RequestId.read_from(env) if Utils::RequestId::REQUEST_ID_HEADERS.include?(key)
next if is_server_protocol?(key, value, env["SERVER_PROTOCOL"])
next if is_skippable_header?(key)
next if key == "HTTP_AUTHORIZATION" && !send_default_pii
# Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
key = key.sub(/^HTTP_/, "")
key = key.split('_').map(&:capitalize).join('-')
memo[key] = encode_to_utf_8(value.to_s)
rescue StandardError => e
# Rails adds objects to the Rack env that can sometimes raise exceptions
# when `to_s` is called.
# See: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/remote_ip.rb#L134
Sentry.logger.warn(LOGGER_PROGNAME) { "Error raised while formatting headers: #{e.message}" }
next
end
end
end
def encode_to_utf_8(value)
if value.encoding != Encoding::UTF_8 && value.respond_to?(:force_encoding)
value = value.dup.force_encoding(Encoding::UTF_8)
end
if !value.valid_encoding?
value = value.scrub
end
value
end
def is_skippable_header?(key)
key.upcase != key || # lower-case envs aren't real http headers
key == "HTTP_COOKIE" || # Cookies don't go here, they go somewhere else
!(key.start_with?('HTTP_') || CONTENT_HEADERS.include?(key))
end
# Rack adds in an incorrect HTTP_VERSION key, which causes downstream
# to think this is a Version header. Instead, this is mapped to
# env['SERVER_PROTOCOL']. But we don't want to ignore a valid header
# if the request has legitimately sent a Version header themselves.
# See: https://github.com/rack/rack/blob/028438f/lib/rack/handler/cgi.rb#L29
# NOTE: This will be removed in version 3.0+
def is_server_protocol?(key, value, protocol_version)
key == 'HTTP_VERSION' && value == protocol_version
end
def filter_and_format_env(env, rack_env_whitelist)
return env if rack_env_whitelist.empty?
env.select do |k, _v|
rack_env_whitelist.include? k.to_s
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/interfaces/stacktrace_builder.rb 0000644 0000041 0000041 00000004436 14232765714 025416 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
class StacktraceBuilder
# @return [String]
attr_reader :project_root
# @return [Regexp, nil]
attr_reader :app_dirs_pattern
# @return [LineCache]
attr_reader :linecache
# @return [Integer, nil]
attr_reader :context_lines
# @return [Proc, nil]
attr_reader :backtrace_cleanup_callback
# @param project_root [String]
# @param app_dirs_pattern [Regexp, nil]
# @param linecache [LineCache]
# @param context_lines [Integer, nil]
# @param backtrace_cleanup_callback [Proc, nil]
# @see Configuration#project_root
# @see Configuration#app_dirs_pattern
# @see Configuration#linecache
# @see Configuration#context_lines
# @see Configuration#backtrace_cleanup_callback
def initialize(project_root:, app_dirs_pattern:, linecache:, context_lines:, backtrace_cleanup_callback: nil)
@project_root = project_root
@app_dirs_pattern = app_dirs_pattern
@linecache = linecache
@context_lines = context_lines
@backtrace_cleanup_callback = backtrace_cleanup_callback
end
# Generates a StacktraceInterface with the given backtrace.
# You can pass a block to customize/exclude frames:
#
# @example
# builder.build(backtrace) do |frame|
# if frame.module.match?(/a_gem/)
# nil
# else
# frame
# end
# end
# @param backtrace [Array]
# @param frame_callback [Proc]
# @yieldparam frame [StacktraceInterface::Frame]
# @return [StacktraceInterface]
def build(backtrace:, &frame_callback)
parsed_lines = parse_backtrace_lines(backtrace).select(&:file)
frames = parsed_lines.reverse.map do |line|
frame = convert_parsed_line_into_frame(line)
frame = frame_callback.call(frame) if frame_callback
frame
end.compact
StacktraceInterface.new(frames: frames)
end
private
def convert_parsed_line_into_frame(line)
frame = StacktraceInterface::Frame.new(project_root, line)
frame.set_context(linecache, context_lines) if context_lines
frame
end
def parse_backtrace_lines(backtrace)
Backtrace.parse(
backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback
).lines
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/interfaces/threads.rb 0000644 0000041 0000041 00000002210 14232765714 023202 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
class ThreadsInterface
# @param crashed [Boolean]
# @param stacktrace [Array]
def initialize(crashed: false, stacktrace: nil)
@id = Thread.current.object_id
@name = Thread.current.name
@current = true
@crashed = crashed
@stacktrace = stacktrace
end
# @return [Hash]
def to_hash
{
values: [
{
id: @id,
name: @name,
crashed: @crashed,
current: @current,
stacktrace: @stacktrace&.to_hash
}
]
}
end
# Builds the ThreadsInterface with given backtrace and stacktrace_builder.
# Patch this method if you want to change a threads interface's stacktrace frames.
# @see StacktraceBuilder.build
# @param backtrace [Array]
# @param stacktrace_builder [StacktraceBuilder]
# @param crashed [Hash]
# @return [ThreadsInterface]
def self.build(backtrace:, stacktrace_builder:, **options)
stacktrace = stacktrace_builder.build(backtrace: backtrace) if backtrace
new(**options, stacktrace: stacktrace)
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/interfaces/exception.rb 0000644 0000041 0000041 00000002424 14232765714 023555 0 ustar www-data www-data # frozen_string_literal: true
require "set"
module Sentry
class ExceptionInterface < Interface
# @param exceptions [Array]
def initialize(exceptions:)
@values = exceptions
end
# @return [Hash]
def to_hash
data = super
data[:values] = data[:values].map(&:to_hash) if data[:values]
data
end
# Builds ExceptionInterface with given exception and stacktrace_builder.
# @param exception [Exception]
# @param stacktrace_builder [StacktraceBuilder]
# @see SingleExceptionInterface#build_with_stacktrace
# @see SingleExceptionInterface#initialize
# @return [ExceptionInterface]
def self.build(exception:, stacktrace_builder:)
exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exception).reverse
processed_backtrace_ids = Set.new
exceptions = exceptions.map do |e|
if e.backtrace && !processed_backtrace_ids.include?(e.backtrace.object_id)
processed_backtrace_ids << e.backtrace.object_id
SingleExceptionInterface.build_with_stacktrace(exception: e, stacktrace_builder: stacktrace_builder)
else
SingleExceptionInterface.new(exception: exception)
end
end
new(exceptions: exceptions)
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/interfaces/single_exception.rb 0000644 0000041 0000041 00000003262 14232765714 025117 0 ustar www-data www-data # frozen_string_literal: true
require "sentry/utils/exception_cause_chain"
module Sentry
class SingleExceptionInterface < Interface
include CustomInspection
SKIP_INSPECTION_ATTRIBUTES = [:@stacktrace]
PROBLEMATIC_LOCAL_VALUE_REPLACEMENT = "[ignored due to error]".freeze
OMISSION_MARK = "...".freeze
MAX_LOCAL_BYTES = 1024
attr_reader :type, :value, :module, :thread_id, :stacktrace
def initialize(exception:, stacktrace: nil)
@type = exception.class.to_s
@value = (exception.message || "").byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
@module = exception.class.to_s.split('::')[0...-1].join('::')
@thread_id = Thread.current.object_id
@stacktrace = stacktrace
end
def to_hash
data = super
data[:stacktrace] = data[:stacktrace].to_hash if data[:stacktrace]
data
end
# patch this method if you want to change an exception's stacktrace frames
# also see `StacktraceBuilder.build`.
def self.build_with_stacktrace(exception:, stacktrace_builder:)
stacktrace = stacktrace_builder.build(backtrace: exception.backtrace)
if locals = exception.instance_variable_get(:@sentry_locals)
locals.each do |k, v|
locals[k] =
begin
v = v.inspect unless v.is_a?(String)
if v.length >= MAX_LOCAL_BYTES
v = v.byteslice(0..MAX_LOCAL_BYTES - 1) + OMISSION_MARK
end
v
rescue StandardError
PROBLEMATIC_LOCAL_VALUE_REPLACEMENT
end
end
stacktrace.frames.last.vars = locals
end
new(exception: exception, stacktrace: stacktrace)
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/interfaces/stacktrace.rb 0000644 0000041 0000041 00000004313 14232765714 023702 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
class StacktraceInterface
# @return []
attr_reader :frames
# @param frames []
def initialize(frames:)
@frames = frames
end
# @return [Hash]
def to_hash
{ frames: @frames.map(&:to_hash) }
end
# @return [String]
def inspect
@frames.map(&:to_s)
end
private
# Not actually an interface, but I want to use the same style
class Frame < Interface
attr_accessor :abs_path, :context_line, :function, :in_app, :filename,
:lineno, :module, :pre_context, :post_context, :vars
def initialize(project_root, line)
@project_root = project_root
@abs_path = line.file
@function = line.method if line.method
@lineno = line.number
@in_app = line.in_app
@module = line.module_name if line.module_name
@filename = compute_filename
end
def to_s
"#{@filename}:#{@lineno}"
end
def compute_filename
return if abs_path.nil?
prefix =
if under_project_root? && in_app
@project_root
elsif under_project_root?
longest_load_path || @project_root
else
longest_load_path
end
prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
end
def set_context(linecache, context_lines)
return unless abs_path
@pre_context, @context_line, @post_context = \
linecache.get_file_context(abs_path, lineno, context_lines)
end
def to_hash(*args)
data = super(*args)
data.delete(:vars) unless vars && !vars.empty?
data.delete(:pre_context) unless pre_context && !pre_context.empty?
data.delete(:post_context) unless post_context && !post_context.empty?
data.delete(:context_line) unless context_line && !context_line.empty?
data
end
private
def under_project_root?
@project_root && abs_path.start_with?(@project_root)
end
def longest_load_path
$LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/error_event.rb 0000644 0000041 0000041 00000001727 14232765714 021773 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
# ErrorEvent represents error or normal message events.
class ErrorEvent < Event
# @return [ExceptionInterface]
attr_reader :exception
# @return [ThreadsInterface]
attr_reader :threads
# @return [Hash]
def to_hash
data = super
data[:threads] = threads.to_hash if threads
data[:exception] = exception.to_hash if exception
data
end
# @!visibility private
def add_threads_interface(backtrace: nil, **options)
@threads = ThreadsInterface.build(
backtrace: backtrace,
stacktrace_builder: @stacktrace_builder,
**options
)
end
# @!visibility private
def add_exception_interface(exception)
if exception.respond_to?(:sentry_context)
@extra.merge!(exception.sentry_context)
end
@exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder)
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/net/ 0000755 0000041 0000041 00000000000 14232765714 017673 5 ustar www-data www-data sentry-ruby-core-5.3.0/lib/sentry/net/http.rb 0000644 0000041 0000041 00000006474 14232765714 021212 0 ustar www-data www-data # frozen_string_literal: true
require "net/http"
module Sentry
# @api private
module Net
module HTTP
OP_NAME = "http.client"
BREADCRUMB_CATEGORY = "net.http"
# To explain how the entire thing works, we need to know how the original Net::HTTP#request works
# Here's part of its definition. As you can see, it usually calls itself inside a #start block
#
# ```
# def request(req, body = nil, &block)
# unless started?
# start {
# req['connection'] ||= 'close'
# return request(req, body, &block) # <- request will be called for the second time from the first call
# }
# end
# # .....
# end
# ```
#
# So we're only instrumenting request when `Net::HTTP` is already started
def request(req, body = nil, &block)
return super unless started?
sentry_span = start_sentry_span
set_sentry_trace_header(req, sentry_span)
super.tap do |res|
record_sentry_breadcrumb(req, res)
record_sentry_span(req, res, sentry_span)
end
end
private
def set_sentry_trace_header(req, sentry_span)
return unless sentry_span
trace = Sentry.get_current_client.generate_sentry_trace(sentry_span)
req[SENTRY_TRACE_HEADER_NAME] = trace if trace
end
def record_sentry_breadcrumb(req, res)
return unless Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
return if from_sentry_sdk?
request_info = extract_request_info(req)
crumb = Sentry::Breadcrumb.new(
level: :info,
category: BREADCRUMB_CATEGORY,
type: :info,
data: {
status: res.code.to_i,
**request_info
}
)
Sentry.add_breadcrumb(crumb)
end
def record_sentry_span(req, res, sentry_span)
return unless Sentry.initialized? && sentry_span
request_info = extract_request_info(req)
sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
sentry_span.set_data(:status, res.code.to_i)
finish_sentry_span(sentry_span)
end
def start_sentry_span
return unless Sentry.initialized? && span = Sentry.get_current_scope.get_span
return if from_sentry_sdk?
return if span.sampled == false
span.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
end
def finish_sentry_span(sentry_span)
return unless Sentry.initialized? && sentry_span
sentry_span.set_timestamp(Sentry.utc_now.to_f)
end
def from_sentry_sdk?
dsn = Sentry.configuration.dsn
dsn && dsn.host == self.address
end
def extract_request_info(req)
uri = req.uri || URI.parse("#{use_ssl? ? 'https' : 'http'}://#{address}#{req.path}")
url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
result = { method: req.method, url: url }
if Sentry.configuration.send_default_pii
result[:url] = result[:url] + "?#{uri.query}"
result[:body] = req.body
end
result
end
end
end
end
Sentry.register_patch do
patch = Sentry::Net::HTTP
Net::HTTP.send(:prepend, patch) unless Net::HTTP.ancestors.include?(patch)
end
sentry-ruby-core-5.3.0/lib/sentry/dsn.rb 0000644 0000041 0000041 00000002227 14232765714 020221 0 ustar www-data www-data # frozen_string_literal: true
require "uri"
module Sentry
class DSN
PORT_MAP = { 'http' => 80, 'https' => 443 }.freeze
REQUIRED_ATTRIBUTES = %w(host path public_key project_id).freeze
attr_reader :scheme, :secret_key, :port, *REQUIRED_ATTRIBUTES
def initialize(dsn_string)
@raw_value = dsn_string
uri = URI.parse(dsn_string)
uri_path = uri.path.split('/')
if uri.user
# DSN-style string
@project_id = uri_path.pop
@public_key = uri.user
@secret_key = !(uri.password.nil? || uri.password.empty?) ? uri.password : nil
end
@scheme = uri.scheme
@host = uri.host
@port = uri.port if uri.port
@path = uri_path.join('/')
end
def valid?
REQUIRED_ATTRIBUTES.all? { |k| public_send(k) }
end
def to_s
@raw_value
end
def server
server = "#{scheme}://#{host}"
server += ":#{port}" unless port == PORT_MAP[scheme]
server
end
def csp_report_uri
"#{server}/api/#{project_id}/security/?sentry_key=#{public_key}"
end
def envelope_endpoint
"#{path}/api/#{project_id}/envelope/"
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/rack/ 0000755 0000041 0000041 00000000000 14232765714 020025 5 ustar www-data www-data sentry-ruby-core-5.3.0/lib/sentry/rack/capture_exceptions.rb 0000644 0000041 0000041 00000004034 14232765714 024257 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
module Rack
class CaptureExceptions
def initialize(app)
@app = app
end
def call(env)
return @app.call(env) unless Sentry.initialized?
# make sure the current thread has a clean hub
Sentry.clone_hub_to_current_thread
Sentry.with_scope do |scope|
Sentry.with_session_tracking do
scope.clear_breadcrumbs
scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
scope.set_rack_env(env)
transaction = start_transaction(env, scope)
scope.set_span(transaction) if transaction
begin
response = @app.call(env)
rescue Sentry::Error
finish_transaction(transaction, 500)
raise # Don't capture Sentry errors
rescue Exception => e
capture_exception(e)
finish_transaction(transaction, 500)
raise
end
exception = collect_exception(env)
capture_exception(exception) if exception
finish_transaction(transaction, response[0])
response
end
end
end
private
def collect_exception(env)
env['rack.exception'] || env['sinatra.error']
end
def transaction_op
"rack.request".freeze
end
def capture_exception(exception)
Sentry.capture_exception(exception)
end
def start_transaction(env, scope)
sentry_trace = env["HTTP_SENTRY_TRACE"]
options = { name: scope.transaction_name, op: transaction_op }
transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, **options) if sentry_trace
Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
end
def finish_transaction(transaction, status_code)
return unless transaction
transaction.set_http_status(status_code)
transaction.finish
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/client.rb 0000644 0000041 0000041 00000014755 14232765714 020724 0 ustar www-data www-data # frozen_string_literal: true
require "sentry/transport"
module Sentry
class Client
include LoggingHelper
# The Transport object that'll send events for the client.
# @return [Transport]
attr_reader :transport
# @!macro configuration
attr_reader :configuration
# @deprecated Use Sentry.logger to retrieve the current logger instead.
attr_reader :logger
# @param configuration [Configuration]
def initialize(configuration)
@configuration = configuration
@logger = configuration.logger
if transport_class = configuration.transport.transport_class
@transport = transport_class.new(configuration)
else
@transport =
case configuration.dsn&.scheme
when 'http', 'https'
HTTPTransport.new(configuration)
else
DummyTransport.new(configuration)
end
end
end
# Applies the given scope's data to the event and sends it to Sentry.
# @param event [Event] the event to be sent.
# @param scope [Scope] the scope with contextual data that'll be applied to the event before it's sent.
# @param hint [Hash] the hint data that'll be passed to `before_send` callback and the scope's event processors.
# @return [Event, nil]
def capture_event(event, scope, hint = {})
return unless configuration.sending_allowed?
unless event.is_a?(TransactionEvent) || configuration.sample_allowed?
transport.record_lost_event(:sample_rate, 'event')
return
end
event_type = event.is_a?(Event) ? event.type : event["type"]
event = scope.apply_to_event(event, hint)
if event.nil?
log_info("Discarded event because one of the event processors returned nil")
transport.record_lost_event(:event_processor, event_type)
return
end
if async_block = configuration.async
dispatch_async_event(async_block, event, hint)
elsif configuration.background_worker_threads != 0 && hint.fetch(:background, true)
queued = dispatch_background_event(event, hint)
transport.record_lost_event(:queue_overflow, event_type) unless queued
else
send_event(event, hint)
end
event
rescue => e
log_error("Event capturing failed", e, debug: configuration.debug)
nil
end
# Initializes an Event object with the given exception. Returns `nil` if the exception's class is excluded from reporting.
# @param exception [Exception] the exception to be reported.
# @param hint [Hash] the hint data that'll be passed to `before_send` callback and the scope's event processors.
# @return [Event, nil]
def event_from_exception(exception, hint = {})
return unless @configuration.sending_allowed? && @configuration.exception_class_allowed?(exception)
integration_meta = Sentry.integrations[hint[:integration]]
ErrorEvent.new(configuration: configuration, integration_meta: integration_meta).tap do |event|
event.add_exception_interface(exception)
event.add_threads_interface(crashed: true)
event.level = :error
end
end
# Initializes an Event object with the given message.
# @param message [String] the message to be reported.
# @param hint [Hash] the hint data that'll be passed to `before_send` callback and the scope's event processors.
# @return [Event]
def event_from_message(message, hint = {}, backtrace: nil)
return unless @configuration.sending_allowed?
integration_meta = Sentry.integrations[hint[:integration]]
event = ErrorEvent.new(configuration: configuration, integration_meta: integration_meta, message: message)
event.add_threads_interface(backtrace: backtrace || caller)
event.level = :error
event
end
# Initializes an Event object with the given Transaction object.
# @param transaction [Transaction] the transaction to be recorded.
# @return [TransactionEvent]
def event_from_transaction(transaction)
TransactionEvent.new(configuration: configuration).tap do |event|
event.transaction = transaction.name
event.contexts.merge!(trace: transaction.get_trace_context)
event.timestamp = transaction.timestamp
event.start_timestamp = transaction.start_timestamp
event.tags = transaction.tags
finished_spans = transaction.span_recorder.spans.select { |span| span.timestamp && span != transaction }
event.spans = finished_spans.map(&:to_hash)
end
end
# @!macro send_event
def send_event(event, hint = nil)
event_type = event.is_a?(Event) ? event.type : event["type"]
if event_type != TransactionEvent::TYPE && configuration.before_send
event = configuration.before_send.call(event, hint)
if event.nil?
log_info("Discarded event because before_send returned nil")
transport.record_lost_event(:before_send, 'event')
return
end
end
transport.send_event(event)
event
rescue => e
loggable_event_type = event_type.capitalize
log_error("#{loggable_event_type} sending failed", e, debug: configuration.debug)
event_info = Event.get_log_message(event.to_hash)
log_info("Unreported #{loggable_event_type}: #{event_info}")
transport.record_lost_event(:network_error, event_type)
raise
end
# Generates a Sentry trace for distribted tracing from the given Span.
# Returns `nil` if `config.propagate_traces` is `false`.
# @param span [Span] the span to generate trace from.
# @return [String, nil]
def generate_sentry_trace(span)
return unless configuration.propagate_traces
trace = span.to_sentry_trace
log_debug("[Tracing] Adding #{SENTRY_TRACE_HEADER_NAME} header to outgoing request: #{trace}")
trace
end
private
def dispatch_background_event(event, hint)
Sentry.background_worker.perform do
send_event(event, hint)
end
end
def dispatch_async_event(async_block, event, hint)
# We have to convert to a JSON-like hash, because background job
# processors (esp ActiveJob) may not like weird types in the event hash
event_hash = event.to_json_compatible
if async_block.arity == 2
hint = JSON.parse(JSON.generate(hint))
async_block.call(event_hash, hint)
else
async_block.call(event_hash)
end
rescue => e
log_error("Async #{event_hash["type"]} sending failed", e, debug: configuration.debug)
send_event(event, hint)
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/breadcrumb/ 0000755 0000041 0000041 00000000000 14232765714 021213 5 ustar www-data www-data sentry-ruby-core-5.3.0/lib/sentry/breadcrumb/sentry_logger.rb 0000644 0000041 0000041 00000005064 14232765714 024430 0 ustar www-data www-data # frozen_string_literal: true
require 'logger'
module Sentry
class Breadcrumb
module SentryLogger
LEVELS = {
::Logger::DEBUG => 'debug',
::Logger::INFO => 'info',
::Logger::WARN => 'warn',
::Logger::ERROR => 'error',
::Logger::FATAL => 'fatal'
}.freeze
def add(*args, &block)
super
add_breadcrumb(*args, &block)
nil
end
def add_breadcrumb(severity, message = nil, progname = nil)
# because the breadcrumbs now belongs to different Hub's Scope in different threads
# we need to make sure the current thread's Hub has been set before adding breadcrumbs
return unless Sentry.get_current_hub
category = "logger"
# this is because the nature of Ruby Logger class:
#
# when given 1 argument, the argument will become both message and progname
#
# ```
# logger.info("foo")
# # message == progname == "foo"
# ```
#
# and to specify progname with a different message,
# we need to pass the progname as the argument and pass the message as a proc
#
# ```
# logger.info("progname") { "the message" }
# ```
#
# so the condition below is to replicate the similar behavior
if message.nil?
if block_given?
message = yield
category = progname
else
message = progname
end
end
return if ignored_logger?(progname) || message == ""
# some loggers will add leading/trailing space as they (incorrectly, mind you)
# think of logging as a shortcut to std{out,err}
message = message.to_s.strip
last_crumb = current_breadcrumbs.peek
# try to avoid dupes from logger broadcasts
if last_crumb.nil? || last_crumb.message != message
level = Sentry::Breadcrumb::SentryLogger::LEVELS.fetch(severity, nil)
crumb = Sentry::Breadcrumb.new(
level: level,
category: category,
message: message,
type: severity >= 3 ? "error" : level
)
Sentry.add_breadcrumb(crumb, hint: { severity: severity })
end
end
private
def ignored_logger?(progname)
progname == LOGGER_PROGNAME ||
Sentry.configuration.exclude_loggers.include?(progname)
end
def current_breadcrumbs
Sentry.get_current_scope.breadcrumbs
end
end
end
end
::Logger.send(:prepend, Sentry::Breadcrumb::SentryLogger)
sentry-ruby-core-5.3.0/lib/sentry/background_worker.rb 0000644 0000041 0000041 00000003755 14232765714 023154 0 ustar www-data www-data # frozen_string_literal: true
require "concurrent/executor/thread_pool_executor"
require "concurrent/executor/immediate_executor"
require "concurrent/configuration"
module Sentry
class BackgroundWorker
include LoggingHelper
attr_reader :max_queue, :number_of_threads
# @deprecated Use Sentry.logger to retrieve the current logger instead.
attr_reader :logger
attr_accessor :shutdown_timeout
def initialize(configuration)
@max_queue = 30
@shutdown_timeout = 1
@number_of_threads = configuration.background_worker_threads
@logger = configuration.logger
@debug = configuration.debug
@shutdown_callback = nil
@executor =
if configuration.async
log_debug("config.async is set, BackgroundWorker is disabled")
Concurrent::ImmediateExecutor.new
elsif @number_of_threads == 0
log_debug("config.background_worker_threads is set to 0, all events will be sent synchronously")
Concurrent::ImmediateExecutor.new
else
log_debug("Initializing the background worker with #{@number_of_threads} threads")
executor = Concurrent::ThreadPoolExecutor.new(
min_threads: 0,
max_threads: @number_of_threads,
max_queue: @max_queue,
fallback_policy: :discard
)
@shutdown_callback = proc do
executor.shutdown
executor.wait_for_termination(@shutdown_timeout)
end
executor
end
end
# if you want to monkey-patch this method, please override `_perform` instead
def perform(&block)
@executor.post do
begin
_perform(&block)
rescue Exception => e
log_error("exception happened in background worker", e, debug: @debug)
end
end
end
def shutdown
log_debug("Shutting down background worker")
@shutdown_callback&.call
end
private
def _perform(&block)
block.call
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/release_detector.rb 0000644 0000041 0000041 00000002154 14232765714 022745 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
# @api private
class ReleaseDetector
class << self
def detect_release(project_root:, running_on_heroku:)
detect_release_from_env ||
detect_release_from_git ||
detect_release_from_capistrano(project_root) ||
detect_release_from_heroku(running_on_heroku)
end
def detect_release_from_heroku(running_on_heroku)
return unless running_on_heroku
ENV['HEROKU_SLUG_COMMIT']
end
def detect_release_from_capistrano(project_root)
revision_file = File.join(project_root, 'REVISION')
revision_log = File.join(project_root, '..', 'revisions.log')
if File.exist?(revision_file)
File.read(revision_file).strip
elsif File.exist?(revision_log)
File.open(revision_log).to_a.last.strip.sub(/.*as release ([0-9]+).*/, '\1')
end
end
def detect_release_from_git
Sentry.sys_command("git rev-parse --short HEAD") if File.directory?(".git")
end
def detect_release_from_env
ENV['SENTRY_RELEASE']
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/configuration.rb 0000644 0000041 0000041 00000036216 14232765714 022311 0 ustar www-data www-data # frozen_string_literal: true
require "concurrent/utility/processor_counter"
require "sentry/utils/exception_cause_chain"
require 'sentry/utils/custom_inspection'
require "sentry/dsn"
require "sentry/release_detector"
require "sentry/transport/configuration"
require "sentry/linecache"
require "sentry/interfaces/stacktrace_builder"
module Sentry
class Configuration
include CustomInspection
include LoggingHelper
# Directories to be recognized as part of your app. e.g. if you
# have an `engines` dir at the root of your project, you may want
# to set this to something like /(app|config|engines|lib)/
#
# @return [Regexp, nil]
attr_accessor :app_dirs_pattern
# Provide an object that responds to `call` to send events asynchronously.
# E.g.: lambda { |event| Thread.new { Sentry.send_event(event) } }
#
# @deprecated It will be removed in the next major release. Please read https://github.com/getsentry/sentry-ruby/issues/1522 for more information
# @return [Proc, nil]
attr_reader :async
# to send events in a non-blocking way, sentry-ruby has its own background worker
# by default, the worker holds a thread pool that has [the number of processors] threads
# but you can configure it with this configuration option
# E.g.: config.background_worker_threads = 5
#
# if you want to send events synchronously, set the value to 0
# E.g.: config.background_worker_threads = 0
# @return [Integer]
attr_accessor :background_worker_threads
# a proc/lambda that takes an array of stack traces
# it'll be used to silence (reduce) backtrace of the exception
#
# @example
# config.backtrace_cleanup_callback = lambda do |backtrace|
# Rails.backtrace_cleaner.clean(backtrace)
# end
#
# @return [Proc, nil]
attr_accessor :backtrace_cleanup_callback
# Optional Proc, called before adding the breadcrumb to the current scope
# @example
# config.before = lambda do |breadcrumb, hint|
# breadcrumb.message = 'a'
# breadcrumb
# end
# @return [Proc]
attr_reader :before_breadcrumb
# Optional Proc, called before sending an event to the server
# @example
# config.before_send = lambda do |event, hint|
# # skip ZeroDivisionError exceptions
# # note: hint[:exception] would be a String if you use async callback
# if hint[:exception].is_a?(ZeroDivisionError)
# nil
# else
# event
# end
# end
# @return [Proc]
attr_reader :before_send
# An array of breadcrumbs loggers to be used. Available options are:
# - :sentry_logger
# - :http_logger
# - :redis_logger
#
# And if you also use sentry-rails:
# - :active_support_logger
# - :monotonic_active_support_logger
#
# @return [Array]
attr_reader :breadcrumbs_logger
# Whether to capture local variables from the raised exception's frame. Default is false.
# @return [Boolean]
attr_accessor :capture_exception_frame_locals
# Max number of breadcrumbs a breadcrumb buffer can hold
# @return [Integer]
attr_accessor :max_breadcrumbs
# Number of lines of code context to capture, or nil for none
# @return [Integer, nil]
attr_accessor :context_lines
# RACK_ENV by default.
# @return [String]
attr_reader :environment
# Whether the SDK should run in the debugging mode. Default is false.
# If set to true, SDK errors will be logged with backtrace
# @return [Boolean]
attr_accessor :debug
# the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
# @return [String]
attr_reader :dsn
# Whitelist of enabled_environments that will send notifications to Sentry. Array of Strings.
# @return [Array]
attr_accessor :enabled_environments
# Logger 'progname's to exclude from breadcrumbs
# @return [Array]
attr_accessor :exclude_loggers
# Array of exception classes that should never be sent. See IGNORE_DEFAULT.
# You should probably append to this rather than overwrite it.
# @return [Array]
attr_accessor :excluded_exceptions
# Boolean to check nested exceptions when deciding if to exclude. Defaults to true
# @return [Boolean]
attr_accessor :inspect_exception_causes_for_exclusion
alias inspect_exception_causes_for_exclusion? inspect_exception_causes_for_exclusion
# You may provide your own LineCache for matching paths with source files.
# This may be useful if you need to get source code from places other than the disk.
# @see LineCache
# @return [LineCache]
attr_accessor :linecache
# Logger used by Sentry. In Rails, this is the Rails logger, otherwise
# Sentry provides its own Sentry::Logger.
# @return [Logger]
attr_accessor :logger
# Project directory root for in_app detection. Could be Rails root, etc.
# Set automatically for Rails.
# @return [String]
attr_accessor :project_root
# Insert sentry-trace to outgoing requests' headers
# @return [Boolean]
attr_accessor :propagate_traces
# Array of rack env parameters to be included in the event sent to sentry.
# @return [Array]
attr_accessor :rack_env_whitelist
# Release tag to be passed with every event sent to Sentry.
# We automatically try to set this to a git SHA or Capistrano release.
# @return [String]
attr_accessor :release
# The sampling factor to apply to events. A value of 0.0 will not send
# any events, and a value of 1.0 will send 100% of events.
# @return [Float]
attr_accessor :sample_rate
# Include module versions in reports - boolean.
# @return [Boolean]
attr_accessor :send_modules
# When send_default_pii's value is false (default), sensitive information like
# - user ip
# - user cookie
# - request body
# - query string
# will not be sent to Sentry.
# @return [Boolean]
attr_accessor :send_default_pii
# Allow to skip Sentry emails within rake tasks
# @return [Boolean]
attr_accessor :skip_rake_integration
# IP ranges for trusted proxies that will be skipped when calculating IP address.
attr_accessor :trusted_proxies
# @return [String]
attr_accessor :server_name
# Return a Transport::Configuration object for transport-related configurations.
# @return [Transport]
attr_reader :transport
# Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
# @return [Float]
attr_accessor :traces_sample_rate
# Take a Proc that controls the sample rate for every tracing event, e.g.
# @example
# config.traces_sampler = lambda do |tracing_context|
# # tracing_context[:transaction_context] contains the information about the transaction
# # tracing_context[:parent_sampled] contains the transaction's parent's sample decision
# true # return value can be a boolean or a float between 0.0 and 1.0
# end
# @return [Proc]
attr_accessor :traces_sampler
# Send diagnostic client reports about dropped events, true by default
# tries to attach to an existing envelope max once every 30s
# @return [Boolean]
attr_accessor :send_client_reports
# Track sessions in request/response cycles automatically
# @return [Boolean]
attr_accessor :auto_session_tracking
# these are not config options
# @!visibility private
attr_reader :errors, :gem_specs
# Most of these errors generate 4XX responses. In general, Sentry clients
# only automatically report 5xx responses.
IGNORE_DEFAULT = [
'Mongoid::Errors::DocumentNotFound',
'Rack::QueryParser::InvalidParameterError',
'Rack::QueryParser::ParameterTypeError',
'Sinatra::NotFound'
].freeze
RACK_ENV_WHITELIST_DEFAULT = %w(
REMOTE_ADDR
SERVER_NAME
SERVER_PORT
).freeze
HEROKU_DYNO_METADATA_MESSAGE = "You are running on Heroku but haven't enabled Dyno Metadata. For Sentry's "\
"release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`".freeze
LOG_PREFIX = "** [Sentry] ".freeze
MODULE_SEPARATOR = "::".freeze
SKIP_INSPECTION_ATTRIBUTES = [:@linecache, :@stacktrace_builder]
# Post initialization callbacks are called at the end of initialization process
# allowing extending the configuration of sentry-ruby by multiple extensions
@@post_initialization_callbacks = []
def initialize
self.app_dirs_pattern = nil
self.debug = false
self.background_worker_threads = Concurrent.processor_count
self.backtrace_cleanup_callback = nil
self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
self.breadcrumbs_logger = []
self.context_lines = 3
self.capture_exception_frame_locals = false
self.environment = environment_from_env
self.enabled_environments = []
self.exclude_loggers = []
self.excluded_exceptions = IGNORE_DEFAULT.dup
self.inspect_exception_causes_for_exclusion = true
self.linecache = ::Sentry::LineCache.new
self.logger = ::Sentry::Logger.new(STDOUT)
self.project_root = Dir.pwd
self.propagate_traces = true
self.sample_rate = 1.0
self.send_modules = true
self.send_default_pii = false
self.skip_rake_integration = false
self.send_client_reports = true
self.auto_session_tracking = true
self.trusted_proxies = []
self.dsn = ENV['SENTRY_DSN']
self.server_name = server_name_from_env
self.before_send = nil
self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
self.traces_sample_rate = nil
self.traces_sampler = nil
@transport = Transport::Configuration.new
@gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
run_post_initialization_callbacks
end
def dsn=(value)
@dsn = init_dsn(value)
end
alias server= dsn=
def async=(value)
check_callable!("async", value)
@async = value
end
def breadcrumbs_logger=(logger)
loggers =
if logger.is_a?(Array)
logger
else
Array(logger)
end
require "sentry/breadcrumb/sentry_logger" if loggers.include?(:sentry_logger)
@breadcrumbs_logger = logger
end
def before_send=(value)
check_callable!("before_send", value)
@before_send = value
end
def before_breadcrumb=(value)
check_callable!("before_breadcrumb", value)
@before_breadcrumb = value
end
def environment=(environment)
@environment = environment.to_s
end
def sending_allowed?
@errors = []
valid? && capture_in_environment?
end
def sample_allowed?
return true if sample_rate == 1.0
Random.rand < sample_rate
end
def exception_class_allowed?(exc)
if exc.is_a?(Sentry::Error)
# Try to prevent error reporting loops
log_debug("Refusing to capture Sentry error: #{exc.inspect}")
false
elsif excluded_exception?(exc)
log_debug("User excluded error: #{exc.inspect}")
false
else
true
end
end
def enabled_in_current_env?
enabled_environments.empty? || enabled_environments.include?(environment)
end
def tracing_enabled?
!!((@traces_sample_rate && @traces_sample_rate >= 0.0 && @traces_sample_rate <= 1.0) || @traces_sampler) && sending_allowed?
end
# @return [String, nil]
def csp_report_uri
if dsn && dsn.valid?
uri = dsn.csp_report_uri
uri += "&sentry_release=#{CGI.escape(release)}" if release && !release.empty?
uri += "&sentry_environment=#{CGI.escape(environment)}" if environment && !environment.empty?
uri
end
end
# @api private
def stacktrace_builder
@stacktrace_builder ||= StacktraceBuilder.new(
project_root: @project_root.to_s,
app_dirs_pattern: @app_dirs_pattern,
linecache: @linecache,
context_lines: @context_lines,
backtrace_cleanup_callback: @backtrace_cleanup_callback
)
end
# @api private
def detect_release
return unless sending_allowed?
self.release ||= ReleaseDetector.detect_release(project_root: project_root, running_on_heroku: running_on_heroku?)
if running_on_heroku? && release.nil?
log_warn(HEROKU_DYNO_METADATA_MESSAGE)
end
rescue => e
log_error("Error detecting release", e, debug: debug)
end
# @api private
def error_messages
@errors = [@errors[0]] + @errors[1..-1].map(&:downcase) # fix case of all but first
@errors.join(", ")
end
private
def check_callable!(name, value)
unless value == nil || value.respond_to?(:call)
raise ArgumentError, "#{name} must be callable (or nil to disable)"
end
end
def init_dsn(dsn_string)
return if dsn_string.nil? || dsn_string.empty?
DSN.new(dsn_string)
end
def excluded_exception?(incoming_exception)
excluded_exception_classes.any? do |excluded_exception|
matches_exception?(excluded_exception, incoming_exception)
end
end
def excluded_exception_classes
@excluded_exception_classes ||= excluded_exceptions.map { |e| get_exception_class(e) }
end
def get_exception_class(x)
x.is_a?(Module) ? x : safe_const_get(x)
end
def matches_exception?(excluded_exception_class, incoming_exception)
if inspect_exception_causes_for_exclusion?
Sentry::Utils::ExceptionCauseChain.exception_to_array(incoming_exception).any? { |cause| excluded_exception_class === cause }
else
excluded_exception_class === incoming_exception
end
end
def safe_const_get(x)
x = x.to_s unless x.is_a?(String)
Object.const_get(x)
rescue NameError # There's no way to safely ask if a constant exist for an unknown string
nil
end
def capture_in_environment?
return true if enabled_in_current_env?
@errors << "Not configured to send/capture in environment '#{environment}'"
false
end
def valid?
if @dsn&.valid?
true
else
@errors << "DSN not set or not valid"
false
end
end
def environment_from_env
ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
end
def server_name_from_env
if running_on_heroku?
ENV['DYNO']
else
# Try to resolve the hostname to an FQDN, but fall back to whatever
# the load name is.
Socket.gethostname || Socket.gethostbyname(hostname).first rescue server_name
end
end
def running_on_heroku?
File.directory?("/etc/heroku") && !ENV["CI"]
end
def run_post_initialization_callbacks
self.class.post_initialization_callbacks.each do |hook|
instance_eval(&hook)
end
end
# allow extensions to add their hooks to the Configuration class
def self.add_post_initialization_callback(&block)
self.post_initialization_callbacks << block
end
protected
def self.post_initialization_callbacks
@@post_initialization_callbacks
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/transport/ 0000755 0000041 0000041 00000000000 14232765714 021141 5 ustar www-data www-data sentry-ruby-core-5.3.0/lib/sentry/transport/configuration.rb 0000644 0000041 0000041 00000001170 14232765714 024334 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
class Transport
class Configuration
attr_accessor :timeout, :open_timeout, :proxy, :ssl, :ssl_ca_file, :ssl_verification, :encoding
attr_reader :transport_class
def initialize
@ssl_verification = true
@open_timeout = 1
@timeout = 2
@encoding = HTTPTransport::GZIP_ENCODING
end
def transport_class=(klass)
unless klass.is_a?(Class)
raise Sentry::Error.new("config.transport.transport_class must a class. got: #{klass.class}")
end
@transport_class = klass
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/transport/http_transport.rb 0000644 0000041 0000041 00000011514 14232765714 024563 0 ustar www-data www-data # frozen_string_literal: true
require "net/http"
require "zlib"
module Sentry
class HTTPTransport < Transport
GZIP_ENCODING = "gzip"
GZIP_THRESHOLD = 1024 * 30
CONTENT_TYPE = 'application/x-sentry-envelope'
DEFAULT_DELAY = 60
RETRY_AFTER_HEADER = "retry-after"
RATE_LIMIT_HEADER = "x-sentry-rate-limits"
USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
def initialize(*args)
super
@endpoint = @dsn.envelope_endpoint
log_debug("Sentry HTTP Transport will connect to #{@dsn.server}")
end
def send_data(data)
encoding = ""
if should_compress?(data)
data = Zlib.gzip(data)
encoding = GZIP_ENCODING
end
headers = {
'Content-Type' => CONTENT_TYPE,
'Content-Encoding' => encoding,
'X-Sentry-Auth' => generate_auth_header,
'User-Agent' => USER_AGENT
}
response = conn.start do |http|
request = ::Net::HTTP::Post.new(@endpoint, headers)
request.body = data
http.request(request)
end
if response.code.match?(/\A2\d{2}/)
if has_rate_limited_header?(response)
handle_rate_limited_response(response)
end
else
error_info = "the server responded with status #{response.code}"
if response.code == "429"
handle_rate_limited_response(response)
else
error_info += "\nbody: #{response.body}"
error_info += " Error in headers is: #{response['x-sentry-error']}" if response['x-sentry-error']
end
raise Sentry::ExternalError, error_info
end
rescue SocketError => e
raise Sentry::ExternalError.new(e.message)
end
private
def has_rate_limited_header?(headers)
headers[RETRY_AFTER_HEADER] || headers[RATE_LIMIT_HEADER]
end
def handle_rate_limited_response(headers)
rate_limits =
if rate_limits = headers[RATE_LIMIT_HEADER]
parse_rate_limit_header(rate_limits)
elsif retry_after = headers[RETRY_AFTER_HEADER]
# although Sentry doesn't send a date string back
# based on HTTP specification, this could be a date string (instead of an integer)
retry_after = retry_after.to_i
retry_after = DEFAULT_DELAY if retry_after == 0
{ nil => Time.now + retry_after }
else
{ nil => Time.now + DEFAULT_DELAY }
end
rate_limits.each do |category, limit|
if current_limit = @rate_limits[category]
if current_limit < limit
@rate_limits[category] = limit
end
else
@rate_limits[category] = limit
end
end
end
def parse_rate_limit_header(rate_limit_header)
time = Time.now
result = {}
limits = rate_limit_header.split(",")
limits.each do |limit|
next if limit.nil? || limit.empty?
begin
retry_after, categories = limit.strip.split(":").first(2)
retry_after = time + retry_after.to_i
categories = categories.split(";")
if categories.empty?
result[nil] = retry_after
else
categories.each do |category|
result[category] = retry_after
end
end
rescue StandardError
end
end
result
end
def should_compress?(data)
@transport_configuration.encoding == GZIP_ENCODING && data.bytesize >= GZIP_THRESHOLD
end
def conn
server = URI(@dsn.server)
connection =
if proxy = normalize_proxy(@transport_configuration.proxy)
::Net::HTTP.new(server.hostname, server.port, proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
else
::Net::HTTP.new(server.hostname, server.port, nil)
end
connection.use_ssl = server.scheme == "https"
connection.read_timeout = @transport_configuration.timeout
connection.write_timeout = @transport_configuration.timeout if connection.respond_to?(:write_timeout)
connection.open_timeout = @transport_configuration.open_timeout
ssl_configuration.each do |key, value|
connection.send("#{key}=", value)
end
connection
end
def normalize_proxy(proxy)
return proxy unless proxy
case proxy
when String
uri = URI(proxy)
{ uri: uri, user: uri.user, password: uri.password }
when URI
{ uri: proxy, user: proxy.user, password: proxy.password }
when Hash
proxy
end
end
def ssl_configuration
configuration = {
verify: @transport_configuration.ssl_verification,
ca_file: @transport_configuration.ssl_ca_file
}.merge(@transport_configuration.ssl || {})
configuration[:verify_mode] = configuration.delete(:verify) ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
configuration
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/transport/dummy_transport.rb 0000644 0000041 0000041 00000000524 14232765714 024736 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
class DummyTransport < Transport
attr_accessor :events, :envelopes
def initialize(*)
super
@events = []
@envelopes = []
end
def send_event(event)
@events << event
end
def send_envelope(envelope)
@envelopes << envelope
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/event.rb 0000644 0000041 0000041 00000012576 14232765714 020566 0 ustar www-data www-data # frozen_string_literal: true
require 'socket'
require 'securerandom'
require 'sentry/interface'
require 'sentry/backtrace'
require 'sentry/utils/real_ip'
require 'sentry/utils/request_id'
require 'sentry/utils/custom_inspection'
module Sentry
# This is an abstract class that defines the shared attributes of an event.
# Please don't use it directly. The user-facing classes are its child classes.
class Event
TYPE = "event"
# These are readable attributes.
SERIALIZEABLE_ATTRIBUTES = %i(
event_id level timestamp
release environment server_name modules
message user tags contexts extra
fingerprint breadcrumbs transaction
platform sdk type
)
# These are writable attributes.
WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i(type timestamp level)
MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 200
SKIP_INSPECTION_ATTRIBUTES = [:@modules, :@stacktrace_builder, :@send_default_pii, :@trusted_proxies, :@rack_env_whitelist]
include CustomInspection
attr_writer(*WRITER_ATTRIBUTES)
attr_reader(*SERIALIZEABLE_ATTRIBUTES)
# @return [RequestInterface]
attr_reader :request
# @param configuration [Configuration]
# @param integration_meta [Hash, nil]
# @param message [String, nil]
def initialize(configuration:, integration_meta: nil, message: nil)
# Set some simple default values
@event_id = SecureRandom.uuid.delete("-")
@timestamp = Sentry.utc_now.iso8601
@platform = :ruby
@type = self.class::TYPE
@sdk = integration_meta || Sentry.sdk_meta
@user = {}
@extra = {}
@contexts = {}
@tags = {}
@fingerprint = []
# configuration data that's directly used by events
@server_name = configuration.server_name
@environment = configuration.environment
@release = configuration.release
@modules = configuration.gem_specs if configuration.send_modules
# configuration options to help events process data
@send_default_pii = configuration.send_default_pii
@trusted_proxies = configuration.trusted_proxies
@stacktrace_builder = configuration.stacktrace_builder
@rack_env_whitelist = configuration.rack_env_whitelist
@message = (message || "").byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
end
class << self
# @!visibility private
def get_log_message(event_hash)
message = event_hash[:message] || event_hash['message']
return message unless message.nil? || message.empty?
message = get_message_from_exception(event_hash)
return message unless message.nil? || message.empty?
message = event_hash[:transaction] || event_hash["transaction"]
return message unless message.nil? || message.empty?
''
end
# @!visibility private
def get_message_from_exception(event_hash)
if exception = event_hash.dig(:exception, :values, 0)
"#{exception[:type]}: #{exception[:value]}"
elsif exception = event_hash.dig("exception", "values", 0)
"#{exception["type"]}: #{exception["value"]}"
end
end
end
# @deprecated This method will be removed in v5.0.0. Please just use Sentry.configuration
# @return [Configuration]
def configuration
Sentry.configuration
end
# Sets the event's timestamp.
# @param time [Time, Float]
# @return [void]
def timestamp=(time)
@timestamp = time.is_a?(Time) ? time.to_f : time
end
# Sets the event's level.
# @param level [String, Symbol]
# @return [void]
def level=(level) # needed to meet the Sentry spec
@level = level.to_s == "warn" ? :warning : level
end
# Sets the event's request environment data with RequestInterface.
# @see RequestInterface
# @param env [Hash]
# @return [void]
def rack_env=(env)
unless request || env.empty?
add_request_interface(env)
if @send_default_pii
user[:ip_address] = calculate_real_ip_from_rack(env)
end
if request_id = Utils::RequestId.read_from(env)
tags[:request_id] = request_id
end
end
end
# @return [Hash]
def to_hash
data = serialize_attributes
data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
data[:request] = request.to_hash if request
data
end
# @return [Hash]
def to_json_compatible
JSON.parse(JSON.generate(to_hash))
end
private
def add_request_interface(env)
@request = Sentry::RequestInterface.new(env: env, send_default_pii: @send_default_pii, rack_env_whitelist: @rack_env_whitelist)
end
def serialize_attributes
self.class::SERIALIZEABLE_ATTRIBUTES.each_with_object({}) do |att, memo|
if value = public_send(att)
memo[att] = value
end
end
end
# When behind a proxy (or if the user is using a proxy), we can't use
# REMOTE_ADDR to determine the Event IP, and must use other headers instead.
def calculate_real_ip_from_rack(env)
Utils::RealIp.new(
:remote_addr => env["REMOTE_ADDR"],
:client_ip => env["HTTP_CLIENT_IP"],
:real_ip => env["HTTP_X_REAL_IP"],
:forwarded_for => env["HTTP_X_FORWARDED_FOR"],
:trusted_proxies => @trusted_proxies
).calculate_ip
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/scope.rb 0000644 0000041 0000041 00000016774 14232765714 020562 0 ustar www-data www-data # frozen_string_literal: true
require "sentry/breadcrumb_buffer"
require "etc"
module Sentry
class Scope
include ArgumentCheckingHelper
ATTRIBUTES = [:transaction_names, :contexts, :extra, :tags, :user, :level, :breadcrumbs, :fingerprint, :event_processors, :rack_env, :span, :session]
attr_reader(*ATTRIBUTES)
# @param max_breadcrumbs [Integer] the maximum number of breadcrumbs to be stored in the scope.
def initialize(max_breadcrumbs: nil)
@max_breadcrumbs = max_breadcrumbs
set_default_value
end
# Resets the scope's attributes to defaults.
# @return [void]
def clear
set_default_value
end
# Applies stored attributes and event processors to the given event.
# @param event [Event]
# @param hint [Hash] the hint data that'll be passed to event processors.
# @return [Event]
def apply_to_event(event, hint = nil)
event.tags = tags.merge(event.tags)
event.user = user.merge(event.user)
event.extra = extra.merge(event.extra)
event.contexts = contexts.merge(event.contexts)
event.transaction = transaction_name if transaction_name
if span
event.contexts[:trace] = span.get_trace_context
end
event.fingerprint = fingerprint
event.level = level
event.breadcrumbs = breadcrumbs
event.rack_env = rack_env if rack_env
unless @event_processors.empty?
@event_processors.each do |processor_block|
event = processor_block.call(event, hint)
end
end
event
end
# Adds the breadcrumb to the scope's breadcrumbs buffer.
# @param breadcrumb [Breadcrumb]
# @return [void]
def add_breadcrumb(breadcrumb)
breadcrumbs.record(breadcrumb)
end
# Clears the scope's breadcrumbs buffer
# @return [void]
def clear_breadcrumbs
set_new_breadcrumb_buffer
end
# @return [Scope]
def dup
copy = super
copy.breadcrumbs = breadcrumbs.dup
copy.contexts = contexts.deep_dup
copy.extra = extra.deep_dup
copy.tags = tags.deep_dup
copy.user = user.deep_dup
copy.transaction_names = transaction_names.deep_dup
copy.fingerprint = fingerprint.deep_dup
copy.span = span.deep_dup
copy.session = session.deep_dup
copy
end
# Updates the scope's data from a given scope.
# @param scope [Scope]
# @return [void]
def update_from_scope(scope)
self.breadcrumbs = scope.breadcrumbs
self.contexts = scope.contexts
self.extra = scope.extra
self.tags = scope.tags
self.user = scope.user
self.transaction_names = scope.transaction_names
self.fingerprint = scope.fingerprint
self.span = scope.span
end
# Updates the scope's data from the given options.
# @param contexts [Hash]
# @param extras [Hash]
# @param tags [Hash]
# @param user [Hash]
# @param level [String, Symbol]
# @param fingerprint [Array]
# @return [void]
def update_from_options(
contexts: nil,
extra: nil,
tags: nil,
user: nil,
level: nil,
fingerprint: nil
)
self.contexts.merge!(contexts) if contexts
self.extra.merge!(extra) if extra
self.tags.merge!(tags) if tags
self.user = user if user
self.level = level if level
self.fingerprint = fingerprint if fingerprint
end
# Sets the scope's rack_env attribute.
# @param env [Hash]
# @return [Hash]
def set_rack_env(env)
env = env || {}
@rack_env = env
end
# Sets the scope's span attribute.
# @param span [Span]
# @return [Span]
def set_span(span)
check_argument_type!(span, Span)
@span = span
end
# @!macro set_user
def set_user(user_hash)
check_argument_type!(user_hash, Hash)
@user = user_hash
end
# @!macro set_extras
def set_extras(extras_hash)
check_argument_type!(extras_hash, Hash)
@extra.merge!(extras_hash)
end
# Adds a new key-value pair to current extras.
# @param key [String, Symbol]
# @param value [Object]
# @return [Hash]
def set_extra(key, value)
set_extras(key => value)
end
# @!macro set_tags
def set_tags(tags_hash)
check_argument_type!(tags_hash, Hash)
@tags.merge!(tags_hash)
end
# Adds a new key-value pair to current tags.
# @param key [String, Symbol]
# @param value [Object]
# @return [Hash]
def set_tag(key, value)
set_tags(key => value)
end
# Updates the scope's contexts attribute by merging with the old value.
# @param contexts [Hash]
# @return [Hash]
def set_contexts(contexts_hash)
check_argument_type!(contexts_hash, Hash)
@contexts.merge!(contexts_hash) do |key, old, new|
old.merge(new)
end
end
# @!macro set_context
def set_context(key, value)
check_argument_type!(value, Hash)
set_contexts(key => value)
end
# Sets the scope's level attribute.
# @param level [String, Symbol]
# @return [void]
def set_level(level)
@level = level
end
# Appends a new transaction name to the scope.
# The "transaction" here does not refer to `Transaction` objects.
# @param transaction_name [String]
# @return [void]
def set_transaction_name(transaction_name)
@transaction_names << transaction_name
end
# Sets the currently active session on the scope.
# @param session [Session, nil]
# @return [void]
def set_session(session)
@session = session
end
# Returns current transaction name.
# The "transaction" here does not refer to `Transaction` objects.
# @return [String, nil]
def transaction_name
@transaction_names.last
end
# Returns the associated Transaction object.
# @return [Transaction, nil]
def get_transaction
span.transaction if span
end
# Returns the associated Span object.
# @return [Span, nil]
def get_span
span
end
# Sets the scope's fingerprint attribute.
# @param fingerprint [Array]
# @return [Array]
def set_fingerprint(fingerprint)
check_argument_type!(fingerprint, Array)
@fingerprint = fingerprint
end
# Adds a new event processor [Proc] to the scope.
# @param block [Proc]
# @return [void]
def add_event_processor(&block)
@event_processors << block
end
protected
# for duplicating scopes internally
attr_writer(*ATTRIBUTES)
private
def set_default_value
@contexts = { :os => self.class.os_context, :runtime => self.class.runtime_context }
@extra = {}
@tags = {}
@user = {}
@level = :error
@fingerprint = []
@transaction_names = []
@event_processors = []
@rack_env = {}
@span = nil
@session = nil
set_new_breadcrumb_buffer
end
def set_new_breadcrumb_buffer
@breadcrumbs = BreadcrumbBuffer.new(@max_breadcrumbs)
end
class << self
# @return [Hash]
def os_context
@os_context ||=
begin
uname = Etc.uname
{
name: uname[:sysname] || RbConfig::CONFIG["host_os"],
version: uname[:version],
build: uname[:release],
kernel_version: uname[:version]
}
end
end
# @return [Hash]
def runtime_context
@runtime_context ||= {
name: RbConfig::CONFIG["ruby_install_name"],
version: RUBY_DESCRIPTION || Sentry.sys_command("ruby -v")
}
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/span.rb 0000644 0000041 0000041 00000013452 14232765714 020400 0 ustar www-data www-data # frozen_string_literal: true
require "securerandom"
module Sentry
class Span
STATUS_MAP = {
400 => "invalid_argument",
401 => "unauthenticated",
403 => "permission_denied",
404 => "not_found",
409 => "already_exists",
429 => "resource_exhausted",
499 => "cancelled",
500 => "internal_error",
501 => "unimplemented",
503 => "unavailable",
504 => "deadline_exceeded"
}
# An uuid that can be used to identify a trace.
# @return [String]
attr_reader :trace_id
# An uuid that can be used to identify the span.
# @return [String]
attr_reader :span_id
# Span parent's span_id.
# @return [String]
attr_reader :parent_span_id
# Sampling result of the span.
# @return [Boolean, nil]
attr_reader :sampled
# Starting timestamp of the span.
# @return [Float]
attr_reader :start_timestamp
# Finishing timestamp of the span.
# @return [Float]
attr_reader :timestamp
# Span description
# @return [String]
attr_reader :description
# Span operation
# @return [String]
attr_reader :op
# Span status
# @return [String]
attr_reader :status
# Span tags
# @return [Hash]
attr_reader :tags
# Span data
# @return [Hash]
attr_reader :data
# The SpanRecorder the current span belongs to.
# SpanRecorder holds all spans under the same Transaction object (including the Transaction itself).
# @return [SpanRecorder]
attr_accessor :span_recorder
# The Transaction object the Span belongs to.
# Every span needs to be attached to a Transaction and their child spans will also inherit the same transaction.
# @return [Transaction]
attr_accessor :transaction
def initialize(
description: nil,
op: nil,
status: nil,
trace_id: nil,
parent_span_id: nil,
sampled: nil,
start_timestamp: nil,
timestamp: nil
)
@trace_id = trace_id || SecureRandom.uuid.delete("-")
@span_id = SecureRandom.hex(8)
@parent_span_id = parent_span_id
@sampled = sampled
@start_timestamp = start_timestamp || Sentry.utc_now.to_f
@timestamp = timestamp
@description = description
@op = op
@status = status
@data = {}
@tags = {}
end
# Finishes the span by adding a timestamp.
# @return [self]
def finish
# already finished
return if @timestamp
@timestamp = Sentry.utc_now.to_f
self
end
# Generates a trace string that can be used to connect other transactions.
# @return [String]
def to_sentry_trace
sampled_flag = ""
sampled_flag = @sampled ? 1 : 0 unless @sampled.nil?
"#{@trace_id}-#{@span_id}-#{sampled_flag}"
end
# @return [Hash]
def to_hash
{
trace_id: @trace_id,
span_id: @span_id,
parent_span_id: @parent_span_id,
start_timestamp: @start_timestamp,
timestamp: @timestamp,
description: @description,
op: @op,
status: @status,
tags: @tags,
data: @data
}
end
# Returns the span's context that can be used to embed in an Event.
# @return [Hash]
def get_trace_context
{
trace_id: @trace_id,
span_id: @span_id,
parent_span_id: @parent_span_id,
description: @description,
op: @op,
status: @status
}
end
# Starts a child span with given attributes.
# @param attributes [Hash] the attributes for the child span.
def start_child(**attributes)
attributes = attributes.dup.merge(trace_id: @trace_id, parent_span_id: @span_id, sampled: @sampled)
new_span = Span.new(**attributes)
new_span.transaction = transaction
new_span.span_recorder = span_recorder
if span_recorder
span_recorder.add(new_span)
end
new_span
end
# Starts a child span, yield it to the given block, and then finish the span after the block is executed.
# @example
# span.with_child_span do |child_span|
# # things happen here will be recorded in a child span
# end
#
# @param attributes [Hash] the attributes for the child span.
# @param block [Proc] the action to be recorded in the child span.
# @yieldparam child_span [Span]
def with_child_span(**attributes, &block)
child_span = start_child(**attributes)
yield(child_span)
child_span.finish
end
def deep_dup
dup
end
# Sets the span's operation.
# @param op [String] operation of the span.
def set_op(op)
@op = op
end
# Sets the span's description.
# @param description [String] description of the span.
def set_description(description)
@description = description
end
# Sets the span's status.
# @param satus [String] status of the span.
def set_status(status)
@status = status
end
# Sets the span's finish timestamp.
# @param timestamp [Float] finished time in float format (most precise).
def set_timestamp(timestamp)
@timestamp = timestamp
end
# Sets the span's status with given http status code.
# @param status_code [String] example: "500".
def set_http_status(status_code)
status_code = status_code.to_i
set_data("status_code", status_code)
status =
if status_code >= 200 && status_code < 299
"ok"
else
STATUS_MAP[status_code]
end
set_status(status)
end
# Inserts a key-value pair to the span's data payload.
# @param key [String, Symbol]
# @param value [Object]
def set_data(key, value)
@data[key] = value
end
# Sets a tag to the span.
# @param key [String, Symbol]
# @param value [String]
def set_tag(key, value)
@tags[key] = value
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/linecache.rb 0000644 0000041 0000041 00000002145 14232765714 021347 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
# @api private
class LineCache
def initialize
@cache = {}
end
# Any linecache you provide to Sentry must implement this method.
# Returns an Array of Strings representing the lines in the source
# file. The number of lines retrieved is (2 * context) + 1, the middle
# line should be the line requested by lineno. See specs for more information.
def get_file_context(filename, lineno, context)
return nil, nil, nil unless valid_path?(filename)
lines = Array.new(2 * context + 1) do |i|
getline(filename, lineno - context + i)
end
[lines[0..(context - 1)], lines[context], lines[(context + 1)..-1]]
end
private
def valid_path?(path)
lines = getlines(path)
!lines.nil?
end
def getlines(path)
@cache[path] ||= begin
IO.readlines(path)
rescue
nil
end
end
def getline(path, n)
return nil if n < 1
lines = getlines(path)
return nil if lines.nil?
lines[n - 1]
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/rack.rb 0000644 0000041 0000041 00000000130 14232765714 020344 0 ustar www-data www-data # frozen_string_literal: true
require 'rack'
require 'sentry/rack/capture_exceptions'
sentry-ruby-core-5.3.0/lib/sentry/redis.rb 0000644 0000041 0000041 00000004165 14232765714 020546 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
# @api private
class Redis
OP_NAME = "db.redis.command"
LOGGER_NAME = :redis_logger
def initialize(commands, host, port, db)
@commands, @host, @port, @db = commands, host, port, db
end
def instrument
return yield unless Sentry.initialized?
record_span do
yield.tap do
record_breadcrumb
end
end
end
private
attr_reader :commands, :host, :port, :db
def record_span
return yield unless (transaction = Sentry.get_current_scope.get_transaction) && transaction.sampled
sentry_span = transaction.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
yield.tap do
sentry_span.set_description(commands_description)
sentry_span.set_data(:server, server_description)
sentry_span.set_timestamp(Sentry.utc_now.to_f)
end
end
def record_breadcrumb
return unless Sentry.configuration.breadcrumbs_logger.include?(LOGGER_NAME)
Sentry.add_breadcrumb(
Sentry::Breadcrumb.new(
level: :info,
category: OP_NAME,
type: :info,
data: {
commands: parsed_commands,
server: server_description
}
)
)
end
def commands_description
parsed_commands.map do |statement|
statement.values.join(" ").strip
end.join(", ")
end
def parsed_commands
commands.map do |statement|
command, key, *arguments = statement
{ command: command.to_s.upcase, key: key }.tap do |command_set|
command_set[:arguments] = arguments.join(" ") if Sentry.configuration.send_default_pii
end
end
end
def server_description
"#{host}:#{port}/#{db}"
end
module Client
def logging(commands, &block)
Sentry::Redis.new(commands, host, port, db).instrument do
super
end
end
end
end
end
if defined?(::Redis::Client)
Sentry.register_patch do
patch = Sentry::Redis::Client
Redis::Client.prepend(patch) unless Redis::Client.ancestors.include?(patch)
end
end
sentry-ruby-core-5.3.0/lib/sentry/integrable.rb 0000644 0000041 0000041 00000001243 14232765714 021546 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
module Integrable
def register_integration(name:, version:)
Sentry.register_integration(name, version)
@integration_name = name
end
def integration_name
@integration_name
end
def capture_exception(exception, **options, &block)
options[:hint] ||= {}
options[:hint][:integration] = integration_name
Sentry.capture_exception(exception, **options, &block)
end
def capture_message(message, **options, &block)
options[:hint] ||= {}
options[:hint][:integration] = integration_name
Sentry.capture_message(message, **options, &block)
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/transaction.rb 0000644 0000041 0000041 00000014141 14232765714 021760 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
class Transaction < Span
SENTRY_TRACE_REGEXP = Regexp.new(
"^[ \t]*" + # whitespace
"([0-9a-f]{32})?" + # trace_id
"-?([0-9a-f]{16})?" + # span_id
"-?([01])?" + # sampled
"[ \t]*$" # whitespace
)
UNLABELD_NAME = "".freeze
MESSAGE_PREFIX = "[Tracing]"
include LoggingHelper
# The name of the transaction.
# @return [String]
attr_reader :name
# The sampling decision of the parent transaction, which will be considered when making the current transaction's sampling decision.
# @return [String]
attr_reader :parent_sampled
# @deprecated Use Sentry.get_current_hub instead.
attr_reader :hub
# @deprecated Use Sentry.configuration instead.
attr_reader :configuration
# @deprecated Use Sentry.logger instead.
attr_reader :logger
def initialize(name: nil, parent_sampled: nil, hub:, **options)
super(**options)
@name = name
@parent_sampled = parent_sampled
@transaction = self
@hub = hub
@configuration = hub.configuration # to be removed
@tracing_enabled = hub.configuration.tracing_enabled?
@traces_sampler = hub.configuration.traces_sampler
@traces_sample_rate = hub.configuration.traces_sample_rate
@logger = hub.configuration.logger
init_span_recorder
end
# Initalizes a Transaction instance with a Sentry trace string from another transaction (usually from an external request).
#
# The original transaction will become the parent of the new Transaction instance. And they will share the same `trace_id`.
#
# The child transaction will also store the parent's sampling decision in its `parent_sampled` attribute.
# @param sentry_trace [String] the trace string from the previous transaction.
# @param hub [Hub] the hub that'll be responsible for sending this transaction when it's finished.
# @param options [Hash] the options you want to use to initialize a Transaction instance.
# @return [Transaction, nil]
def self.from_sentry_trace(sentry_trace, hub: Sentry.get_current_hub, **options)
return unless hub.configuration.tracing_enabled?
return unless sentry_trace
match = SENTRY_TRACE_REGEXP.match(sentry_trace)
return if match.nil?
trace_id, parent_span_id, sampled_flag = match[1..3]
parent_sampled =
if sampled_flag.nil?
nil
else
sampled_flag != "0"
end
new(trace_id: trace_id, parent_span_id: parent_span_id, parent_sampled: parent_sampled, hub: hub, **options)
end
# @return [Hash]
def to_hash
hash = super
hash.merge!(name: @name, sampled: @sampled, parent_sampled: @parent_sampled)
hash
end
# @return [Transaction]
def deep_dup
copy = super
copy.init_span_recorder(@span_recorder.max_length)
@span_recorder.spans.each do |span|
# span_recorder's first span is the current span, which should not be added to the copy's spans
next if span == self
copy.span_recorder.add(span.dup)
end
copy
end
# Sets initial sampling decision of the transaction.
# @param sampling_context [Hash] a context Hash that'll be passed to `traces_sampler` (if provided).
# @return [void]
def set_initial_sample_decision(sampling_context:)
unless @tracing_enabled
@sampled = false
return
end
return unless @sampled.nil?
sample_rate =
if @traces_sampler.is_a?(Proc)
@traces_sampler.call(sampling_context)
elsif !sampling_context[:parent_sampled].nil?
sampling_context[:parent_sampled]
else
@traces_sample_rate
end
transaction_description = generate_transaction_description
unless [true, false].include?(sample_rate) || (sample_rate.is_a?(Numeric) && sample_rate >= 0.0 && sample_rate <= 1.0)
@sampled = false
log_warn("#{MESSAGE_PREFIX} Discarding #{transaction_description} because of invalid sample_rate: #{sample_rate}")
return
end
if sample_rate == 0.0 || sample_rate == false
@sampled = false
log_debug("#{MESSAGE_PREFIX} Discarding #{transaction_description} because traces_sampler returned 0 or false")
return
end
if sample_rate == true
@sampled = true
else
@sampled = Random.rand < sample_rate
end
if @sampled
log_debug("#{MESSAGE_PREFIX} Starting #{transaction_description}")
else
log_debug(
"#{MESSAGE_PREFIX} Discarding #{transaction_description} because it's not included in the random sample (sampling rate = #{sample_rate})"
)
end
end
# Finishes the transaction's recording and send it to Sentry.
# @param hub [Hub] the hub that'll send this transaction. (Deprecated)
# @return [TransactionEvent]
def finish(hub: nil)
if hub
log_warn(
<<~MSG
Specifying a different hub in `Transaction#finish` will be deprecated in version 5.0.
Please use `Hub#start_transaction` with the designated hub.
MSG
)
end
hub ||= @hub
super() # Span#finish doesn't take arguments
if @name.nil?
@name = UNLABELD_NAME
end
if @sampled
event = hub.current_client.event_from_transaction(self)
hub.capture_event(event)
else
hub.current_client.transport.record_lost_event(:sample_rate, 'transaction')
end
end
protected
def init_span_recorder(limit = 1000)
@span_recorder = SpanRecorder.new(limit)
@span_recorder.add(self)
end
private
def generate_transaction_description
result = op.nil? ? "" : "<#{@op}> "
result += "transaction"
result += " <#{@name}>" if @name
result
end
class SpanRecorder
attr_reader :max_length, :spans
def initialize(max_length)
@max_length = max_length
@spans = []
end
def add(span)
if @spans.count < @max_length
@spans << span
end
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/backtrace.rb 0000644 0000041 0000041 00000006233 14232765714 021355 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
# @api private
class Backtrace
# Handles backtrace parsing line by line
class Line
RB_EXTENSION = ".rb"
# regexp (optional leading X: on windows, or JRuby9000 class-prefix)
RUBY_INPUT_FORMAT = /
^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>):
(\d+)
(?: :in \s `([^']+)')?$
/x.freeze
# org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:170)
JAVA_INPUT_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/.freeze
# The file portion of the line (such as app/models/user.rb)
attr_reader :file
# The line number portion of the line
attr_reader :number
# The method of the line (such as index)
attr_reader :method
# The module name (JRuby)
attr_reader :module_name
attr_reader :in_app_pattern
# Parses a single line of a given backtrace
# @param [String] unparsed_line The raw line from +caller+ or some backtrace
# @return [Line] The parsed backtrace line
def self.parse(unparsed_line, in_app_pattern)
ruby_match = unparsed_line.match(RUBY_INPUT_FORMAT)
if ruby_match
_, file, number, method = ruby_match.to_a
file.sub!(/\.class$/, RB_EXTENSION)
module_name = nil
else
java_match = unparsed_line.match(JAVA_INPUT_FORMAT)
_, module_name, method, file, number = java_match.to_a
end
new(file, number, method, module_name, in_app_pattern)
end
def initialize(file, number, method, module_name, in_app_pattern)
@file = file
@module_name = module_name
@number = number.to_i
@method = method
@in_app_pattern = in_app_pattern
end
def in_app
if file =~ in_app_pattern
true
else
false
end
end
# Reconstructs the line in a readable fashion
def to_s
"#{file}:#{number}:in `#{method}'"
end
def ==(other)
to_s == other.to_s
end
def inspect
""
end
end
APP_DIRS_PATTERN = /(bin|exe|app|config|lib|test)/.freeze
# holder for an Array of Backtrace::Line instances
attr_reader :lines
def self.parse(backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback)
ruby_lines = backtrace.is_a?(Array) ? backtrace : backtrace.split(/\n\s*/)
ruby_lines = backtrace_cleanup_callback.call(ruby_lines) if backtrace_cleanup_callback
in_app_pattern ||= begin
Regexp.new("^(#{project_root}/)?#{app_dirs_pattern || APP_DIRS_PATTERN}")
end
lines = ruby_lines.to_a.map do |unparsed_line|
Line.parse(unparsed_line, in_app_pattern)
end
new(lines)
end
def initialize(lines)
@lines = lines
end
def inspect
""
end
def to_s
content = []
lines.each do |line|
content << line
end
content.join("\n")
end
def ==(other)
if other.respond_to?(:lines)
lines == other.lines
else
false
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/logger.rb 0000644 0000041 0000041 00000000722 14232765714 020712 0 ustar www-data www-data # frozen_string_literal: true
require 'logger'
module Sentry
class Logger < ::Logger
LOG_PREFIX = "** [Sentry] "
PROGNAME = "sentry"
def initialize(*)
super
@level = ::Logger::INFO
original_formatter = ::Logger::Formatter.new
@default_formatter = proc do |severity, datetime, _progname, msg|
msg = "#{LOG_PREFIX}#{msg}"
original_formatter.call(severity, datetime, PROGNAME, msg)
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/session_flusher.rb 0000644 0000041 0000041 00000003430 14232765714 022645 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
class SessionFlusher
include LoggingHelper
FLUSH_INTERVAL = 60
def initialize(configuration, client)
@thread = nil
@client = client
@pending_aggregates = {}
@release = configuration.release
@environment = configuration.environment
@logger = configuration.logger
log_debug("[Sessions] Sessions won't be captured without a valid release") unless @release
end
def flush
return if @pending_aggregates.empty?
envelope = pending_envelope
Sentry.background_worker.perform do
@client.transport.send_envelope(envelope)
end
@pending_aggregates = {}
end
def add_session(session)
return unless @release
ensure_thread
return unless Session::AGGREGATE_STATUSES.include?(session.status)
@pending_aggregates[session.aggregation_key] ||= init_aggregates(session.aggregation_key)
@pending_aggregates[session.aggregation_key][session.status] += 1
end
def kill
log_debug("Killing session flusher")
@thread&.kill
end
private
def init_aggregates(aggregation_key)
aggregates = { started: aggregation_key.iso8601 }
Session::AGGREGATE_STATUSES.each { |k| aggregates[k] = 0 }
aggregates
end
def pending_envelope
envelope = Envelope.new
header = { type: 'sessions' }
payload = { attrs: attrs, aggregates: @pending_aggregates.values }
envelope.add_item(header, payload)
envelope
end
def attrs
{ release: @release, environment: @environment }
end
def ensure_thread
return if @thread&.alive?
@thread = Thread.new do
loop do
sleep(FLUSH_INTERVAL)
flush
end
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/hub.rb 0000644 0000041 0000041 00000012044 14232765714 020211 0 ustar www-data www-data # frozen_string_literal: true
require "sentry/scope"
require "sentry/client"
require "sentry/session"
module Sentry
class Hub
include ArgumentCheckingHelper
attr_reader :last_event_id
def initialize(client, scope)
first_layer = Layer.new(client, scope)
@stack = [first_layer]
@last_event_id = nil
end
def new_from_top
Hub.new(current_client, current_scope)
end
def current_client
current_layer&.client
end
def configuration
current_client.configuration
end
def current_scope
current_layer&.scope
end
def clone
layer = current_layer
if layer
scope = layer.scope&.dup
Hub.new(layer.client, scope)
end
end
def bind_client(client)
layer = current_layer
if layer
layer.client = client
end
end
def configure_scope(&block)
block.call(current_scope)
end
def with_scope(&block)
push_scope
yield(current_scope)
ensure
pop_scope
end
def push_scope
new_scope =
if current_scope
current_scope.dup
else
Scope.new
end
@stack << Layer.new(current_client, new_scope)
end
def pop_scope
@stack.pop
end
def start_transaction(transaction: nil, custom_sampling_context: {}, **options)
return unless configuration.tracing_enabled?
transaction ||= Transaction.new(**options.merge(hub: self))
sampling_context = {
transaction_context: transaction.to_hash,
parent_sampled: transaction.parent_sampled
}
sampling_context.merge!(custom_sampling_context)
transaction.set_initial_sample_decision(sampling_context: sampling_context)
transaction
end
def capture_exception(exception, **options, &block)
check_argument_type!(exception, ::Exception)
return if Sentry.exception_captured?(exception)
return unless current_client
options[:hint] ||= {}
options[:hint][:exception] = exception
event = current_client.event_from_exception(exception, options[:hint])
return unless event
current_scope.session&.update_from_exception(event.exception)
capture_event(event, **options, &block).tap do
# mark the exception as captured so we can use this information to avoid duplicated capturing
exception.instance_variable_set(Sentry::CAPTURED_SIGNATURE, true)
end
end
def capture_message(message, **options, &block)
check_argument_type!(message, ::String)
return unless current_client
options[:hint] ||= {}
options[:hint][:message] = message
backtrace = options.delete(:backtrace)
event = current_client.event_from_message(message, options[:hint], backtrace: backtrace)
return unless event
capture_event(event, **options, &block)
end
def capture_event(event, **options, &block)
check_argument_type!(event, Sentry::Event)
return unless current_client
hint = options.delete(:hint) || {}
scope = current_scope.dup
if block
block.call(scope)
elsif custom_scope = options[:scope]
scope.update_from_scope(custom_scope)
elsif !options.empty?
scope.update_from_options(**options)
end
event = current_client.capture_event(event, scope, hint)
if event && configuration.debug
configuration.log_debug(event.to_json_compatible)
end
@last_event_id = event&.event_id unless event.is_a?(Sentry::TransactionEvent)
event
end
def add_breadcrumb(breadcrumb, hint: {})
return unless configuration.enabled_in_current_env?
if before_breadcrumb = current_client.configuration.before_breadcrumb
breadcrumb = before_breadcrumb.call(breadcrumb, hint)
end
return unless breadcrumb
current_scope.add_breadcrumb(breadcrumb)
end
# this doesn't do anything to the already initialized background worker
# but it temporarily disables dispatching events to it
def with_background_worker_disabled(&block)
original_background_worker_threads = configuration.background_worker_threads
configuration.background_worker_threads = 0
block.call
ensure
configuration.background_worker_threads = original_background_worker_threads
end
def start_session
return unless current_scope
current_scope.set_session(Session.new)
end
def end_session
return unless current_scope
session = current_scope.session
current_scope.set_session(nil)
return unless session
session.close
Sentry.session_flusher.add_session(session)
end
def with_session_tracking(&block)
return yield unless configuration.auto_session_tracking
start_session
yield
ensure
end_session
end
private
def current_layer
@stack.last
end
class Layer
attr_accessor :client
attr_reader :scope
def initialize(client, scope)
@client = client
@scope = scope
end
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/envelope.rb 0000644 0000041 0000041 00000001366 14232765714 021255 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
# @api private
class Envelope
class Item
attr_accessor :headers, :payload
def initialize(headers, payload)
@headers = headers
@payload = payload
end
def type
@headers[:type] || 'event'
end
def to_s
<<~ITEM
#{JSON.generate(@headers)}
#{JSON.generate(@payload)}
ITEM
end
end
attr_accessor :headers, :items
def initialize(headers = {})
@headers = headers
@items = []
end
def add_item(headers, payload)
@items << Item.new(headers, payload)
end
def item_types
@items.map(&:type)
end
def event_id
@headers[:event_id]
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/breadcrumb_buffer.rb 0000644 0000041 0000041 00000002347 14232765714 023077 0 ustar www-data www-data # frozen_string_literal: true
require "sentry/breadcrumb"
module Sentry
class BreadcrumbBuffer
DEFAULT_SIZE = 100
include Enumerable
# @return [Array]
attr_accessor :buffer
# @param size [Integer, nil] If it's not provided, it'll fallback to DEFAULT_SIZE
def initialize(size = nil)
@buffer = Array.new(size || DEFAULT_SIZE)
end
# @param crumb [Breadcrumb]
# @return [void]
def record(crumb)
yield(crumb) if block_given?
@buffer.slice!(0)
@buffer << crumb
end
# @return [Array]
def members
@buffer.compact
end
# Returns the last breadcrumb stored in the buffer. If the buffer it's empty, it returns nil.
# @return [Breadcrumb, nil]
def peek
members.last
end
# Iterates through all breadcrumbs.
# @param block [Proc]
# @yieldparam crumb [Breadcrumb]
# @return [Array]
def each(&block)
members.each(&block)
end
# @return [Boolean]
def empty?
members.none?
end
# @return [Hash]
def to_hash
{
values: members.map(&:to_hash)
}
end
# @return [BreadcrumbBuffer]
def dup
copy = super
copy.buffer = buffer.deep_dup
copy
end
end
end
sentry-ruby-core-5.3.0/lib/sentry/transaction_event.rb 0000644 0000041 0000041 00000001263 14232765714 023162 0 ustar www-data www-data # frozen_string_literal: true
module Sentry
# TransactionEvent represents events that carry transaction data (type: "transaction").
class TransactionEvent < Event
TYPE = "transaction"
# @return []
attr_accessor :spans
# @return [Float, nil]
attr_reader :start_timestamp
# Sets the event's start_timestamp.
# @param time [Time, Float]
# @return [void]
def start_timestamp=(time)
@start_timestamp = time.is_a?(Time) ? time.to_f : time
end
# @return [Hash]
def to_hash
data = super
data[:spans] = @spans.map(&:to_hash) if @spans
data[:start_timestamp] = @start_timestamp
data
end
end
end
sentry-ruby-core-5.3.0/lib/sentry-ruby.rb 0000644 0000041 0000041 00000031643 14232765714 020420 0 ustar www-data www-data # frozen_string_literal: true
require "English"
require "forwardable"
require "time"
require "sentry/version"
require "sentry/exceptions"
require "sentry/core_ext/object/deep_dup"
require "sentry/utils/argument_checking_helper"
require "sentry/utils/logging_helper"
require "sentry/configuration"
require "sentry/logger"
require "sentry/event"
require "sentry/error_event"
require "sentry/transaction_event"
require "sentry/span"
require "sentry/transaction"
require "sentry/hub"
require "sentry/background_worker"
require "sentry/session_flusher"
[
"sentry/rake",
"sentry/rack",
].each do |lib|
begin
require lib
rescue LoadError
end
end
module Sentry
META = { "name" => "sentry.ruby", "version" => Sentry::VERSION }.freeze
CAPTURED_SIGNATURE = :@__sentry_captured
LOGGER_PROGNAME = "sentry".freeze
SENTRY_TRACE_HEADER_NAME = "sentry-trace".freeze
THREAD_LOCAL = :sentry_hub
class << self
# @!visibility private
def exception_locals_tp
@exception_locals_tp ||= TracePoint.new(:raise) do |tp|
exception = tp.raised_exception
# don't collect locals again if the exception is re-raised
next if exception.instance_variable_get(:@sentry_locals)
next unless tp.binding
locals = tp.binding.local_variables.each_with_object({}) do |local, result|
result[local] = tp.binding.local_variable_get(local)
end
exception.instance_variable_set(:@sentry_locals, locals)
end
end
# @!attribute [rw] background_worker
# @return [BackgroundWorker]
attr_accessor :background_worker
# @!attribute [r] session_flusher
# @return [SessionFlusher]
attr_reader :session_flusher
##### Patch Registration #####
# @!visibility private
def register_patch(&block)
registered_patches << block
end
# @!visibility private
def apply_patches(config)
registered_patches.each do |patch|
patch.call(config)
end
end
# @!visibility private
def registered_patches
@registered_patches ||= []
end
##### Integrations #####
# Returns a hash that contains all the integrations that have been registered to the main SDK.
#
# @return [Hash{String=>Hash}]
def integrations
@integrations ||= {}
end
# Registers the SDK integration with its name and version.
#
# @param name [String] name of the integration
# @param version [String] version of the integration
def register_integration(name, version)
if initialized?
logger.warn(LOGGER_PROGNAME) do
<<~MSG
Integration '#{name}' is loaded after the SDK is initialized, which can cause unexpected behavior. Please make sure all integrations are loaded before SDK initialization.
MSG
end
end
meta = { name: "sentry.ruby.#{name}", version: version }.freeze
integrations[name.to_s] = meta
end
##### Method Delegation #####
extend Forwardable
# @!macro [new] configuration
# The Configuration object that's used for configuring the client and its transport.
# @return [Configuration]
# @!macro [new] send_event
# Sends the event to Sentry.
# @param event [Event] the event to be sent.
# @param hint [Hash] the hint data that'll be passed to `before_send` callback.
# @return [Event]
# @!method configuration
# @!macro configuration
def configuration
return unless initialized?
get_current_client.configuration
end
# @!method send_event
# @!macro send_event
def send_event(*args)
return unless initialized?
get_current_client.send_event(*args)
end
# @!macro [new] set_extras
# Updates the scope's extras attribute by merging with the old value.
# @param extras [Hash]
# @return [Hash]
# @!macro [new] set_user
# Sets the scope's user attribute.
# @param user [Hash]
# @return [Hash]
# @!macro [new] set_context
# Adds a new key-value pair to current contexts.
# @param key [String, Symbol]
# @param value [Object]
# @return [Hash]
# @!macro [new] set_tags
# Updates the scope's tags attribute by merging with the old value.
# @param tags [Hash]
# @return [Hash]
# @!method set_tags
# @!macro set_tags
def set_tags(*args)
return unless initialized?
get_current_scope.set_tags(*args)
end
# @!method set_extras
# @!macro set_extras
def set_extras(*args)
return unless initialized?
get_current_scope.set_extras(*args)
end
# @!method set_user
# @!macro set_user
def set_user(*args)
return unless initialized?
get_current_scope.set_user(*args)
end
# @!method set_context
# @!macro set_context
def set_context(*args)
return unless initialized?
get_current_scope.set_context(*args)
end
##### Main APIs #####
# Initializes the SDK with given configuration.
#
# @yieldparam config [Configuration]
# @return [void]
def init(&block)
config = Configuration.new
yield(config) if block_given?
config.detect_release
apply_patches(config)
client = Client.new(config)
scope = Scope.new(max_breadcrumbs: config.max_breadcrumbs)
hub = Hub.new(client, scope)
Thread.current.thread_variable_set(THREAD_LOCAL, hub)
@main_hub = hub
@background_worker = Sentry::BackgroundWorker.new(config)
@session_flusher = if config.auto_session_tracking
Sentry::SessionFlusher.new(config, client)
else
nil
end
if config.capture_exception_frame_locals
exception_locals_tp.enable
end
at_exit do
@session_flusher&.kill
@background_worker.shutdown
end
end
# Returns true if the SDK is initialized.
#
# @return [Boolean]
def initialized?
!!get_main_hub
end
# Returns an uri for security policy reporting that's generated from the given DSN
# (To learn more about security policy reporting: https://docs.sentry.io/product/security-policy-reporting/)
#
# It returns nil if
# - The SDK is not initialized yet.
# - The DSN is not provided or is invalid.
#
# @return [String, nil]
def csp_report_uri
return unless initialized?
configuration.csp_report_uri
end
# Returns the main thread's active hub.
#
# @return [Hub]
def get_main_hub
@main_hub
end
# Takes an instance of Sentry::Breadcrumb and stores it to the current active scope.
#
# @return [Breadcrumb, nil]
def add_breadcrumb(breadcrumb, **options)
return unless initialized?
get_current_hub.add_breadcrumb(breadcrumb, **options)
end
# Returns the current active hub.
# If the current thread doesn't have an active hub, it will clone the main thread's active hub,
# stores it in the current thread, and then returns it.
#
# @return [Hub]
def get_current_hub
# we need to assign a hub to the current thread if it doesn't have one yet
#
# ideally, we should do this proactively whenever a new thread is created
# but it's impossible for the SDK to keep track every new thread
# so we need to use this rather passive way to make sure the app doesn't crash
Thread.current.thread_variable_get(THREAD_LOCAL) || clone_hub_to_current_thread
end
# Returns the current active client.
# @return [Client, nil]
def get_current_client
return unless initialized?
get_current_hub.current_client
end
# Returns the current active scope.
#
# @return [Scope, nil]
def get_current_scope
return unless initialized?
get_current_hub.current_scope
end
# Clones the main thread's active hub and stores it to the current thread.
#
# @return [void]
def clone_hub_to_current_thread
Thread.current.thread_variable_set(THREAD_LOCAL, get_main_hub.clone)
end
# Takes a block and yields the current active scope.
#
# @example
# Sentry.configure_scope do |scope|
# scope.set_tags(foo: "bar")
# end
#
# Sentry.capture_message("test message") # this event will have tags { foo: "bar" }
#
# @yieldparam scope [Scope]
# @return [void]
def configure_scope(&block)
return unless initialized?
get_current_hub.configure_scope(&block)
end
# Takes a block and yields a temporary scope.
# The temporary scope will inherit all the attributes from the current active scope and replace it to be the active
# scope inside the block.
#
# @example
# Sentry.configure_scope do |scope|
# scope.set_tags(foo: "bar")
# end
#
# Sentry.capture_message("test message") # this event will have tags { foo: "bar" }
#
# Sentry.with_scope do |temp_scope|
# temp_scope.set_tags(foo: "baz")
# Sentry.capture_message("test message 2") # this event will have tags { foo: "baz" }
# end
#
# Sentry.capture_message("test message 3") # this event will have tags { foo: "bar" }
#
# @yieldparam scope [Scope]
# @return [void]
def with_scope(&block)
return unless initialized?
get_current_hub.with_scope(&block)
end
# Wrap a given block with session tracking.
# Aggregate sessions in minutely buckets will be recorded
# around this block and flushed every minute.
#
# @example
# Sentry.with_session_tracking do
# a = 1 + 1 # new session recorded with :exited status
# end
#
# Sentry.with_session_tracking do
# 1 / 0
# rescue => e
# Sentry.capture_exception(e) # new session recorded with :errored status
# end
# @return [void]
def with_session_tracking(&block)
return yield unless initialized?
get_current_hub.with_session_tracking(&block)
end
# Takes an exception and reports it to Sentry via the currently active hub.
#
# @yieldparam scope [Scope]
# @return [Event, nil]
def capture_exception(exception, **options, &block)
return unless initialized?
get_current_hub.capture_exception(exception, **options, &block)
end
# Takes a message string and reports it to Sentry via the currently active hub.
#
# @yieldparam scope [Scope]
# @return [Event, nil]
def capture_message(message, **options, &block)
return unless initialized?
get_current_hub.capture_message(message, **options, &block)
end
# Takes an instance of Sentry::Event and dispatches it to the currently active hub.
#
# @return [Event, nil]
def capture_event(event)
return unless initialized?
get_current_hub.capture_event(event)
end
# Takes or initializes a new Sentry::Transaction and makes a sampling decision for it.
#
# @return [Transaction, nil]
def start_transaction(**options)
return unless initialized?
get_current_hub.start_transaction(**options)
end
# Records the block's execution as a child of the current span.
# If the current scope doesn't have a span, the block would still be executed but the yield param will be nil.
# @param attributes [Hash] attributes for the child span.
# @yieldparam child_span [Span, nil]
# @return yield result
#
# @example
# Sentry.with_child_span(op: "my operation") do |child_span|
# child_span.set_data(operation_data)
# child_span.set_description(operation_detail)
# # result will be returned
# end
#
def with_child_span(**attributes, &block)
current_span = get_current_scope.get_span
if current_span
result = nil
begin
current_span.with_child_span(**attributes) do |child_span|
get_current_scope.set_span(child_span)
result = yield(child_span)
end
ensure
get_current_scope.set_span(current_span)
end
result
else
yield(nil)
end
end
# Returns the id of the lastly reported Sentry::Event.
#
# @return [String, nil]
def last_event_id
return unless initialized?
get_current_hub.last_event_id
end
# Checks if the exception object has been captured by the SDK.
#
# @return [Boolean]
def exception_captured?(exc)
return false unless initialized?
!!exc.instance_variable_get(CAPTURED_SIGNATURE)
end
##### Helpers #####
# @!visibility private
def sys_command(command)
result = `#{command} 2>&1` rescue nil
return if result.nil? || result.empty? || ($CHILD_STATUS && $CHILD_STATUS.exitstatus != 0)
result.strip
end
# @!visibility private
def logger
configuration.logger
end
# @!visibility private
def sdk_meta
META
end
# @!visibility private
def utc_now
Time.now.utc
end
end
end
# patches
require "sentry/net/http"
require "sentry/redis"
sentry-ruby-core-5.3.0/.yardopts 0000644 0000041 0000041 00000000072 14232765714 016660 0 ustar www-data www-data --exclude lib/sentry/utils/
--exclude lib/sentry/core_ext
sentry-ruby-core-5.3.0/Gemfile 0000644 0000041 0000041 00000001130 14232765714 016301 0 ustar www-data www-data source "https://rubygems.org"
git_source(:github) { |name| "https://github.com/#{name}.git" }
gem "sentry-ruby-core", path: "./"
gem "sentry-ruby", path: "./"
gem "rack" unless ENV["WITHOUT_RACK"] == "1"
gem "rake", "~> 12.0"
gem "rspec", "~> 3.0"
gem "rspec-retry"
gem "webmock"
gem "fakeredis"
gem "timecop"
gem 'simplecov'
gem "simplecov-cobertura", "~> 1.4"
gem "rexml"
gem "object_tracer"
gem "debug", github: "ruby/debug", platform: :ruby if RUBY_VERSION.to_f >= 2.6
gem "pry"
gem "benchmark-ips"
gem "benchmark_driver"
gem "benchmark-ipsa"
gem "benchmark-memory"
gem "yard", "~> 0.9.27"
sentry-ruby-core-5.3.0/LICENSE.txt 0000644 0000041 0000041 00000002061 14232765714 016635 0 ustar www-data www-data The MIT License (MIT)
Copyright (c) 2020 Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
sentry-ruby-core-5.3.0/sentry-ruby.gemspec 0000644 0000041 0000041 00000001514 14232765714 020664 0 ustar www-data www-data require_relative "lib/sentry/version"
Gem::Specification.new do |spec|
spec.name = "sentry-ruby"
spec.version = Sentry::VERSION
spec.authors = ["Sentry Team"]
spec.description = spec.summary = "A gem that provides a client interface for the Sentry error logger"
spec.email = "accounts@sentry.io"
spec.license = 'MIT'
spec.homepage = "https://github.com/getsentry/sentry-ruby"
spec.platform = Gem::Platform::RUBY
spec.required_ruby_version = '>= 2.4'
spec.extra_rdoc_files = ["README.md", "LICENSE.txt"]
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
spec.add_dependency "sentry-ruby-core", Sentry::VERSION
spec.add_dependency "concurrent-ruby", '~> 1.0', '>= 1.0.2'
end