Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Roda Integration #2368

Merged
merged 47 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
de8db34
Initial integration of Roda.
randy-girard Mar 13, 2019
3d03eda
Add roda to Appraisals and Rakefile. Start writing tests for Roda int…
wantsui Jul 9, 2019
910d7fe
Add and refactor instrumentation module to Roda instead of Base Insta…
wantsui Jul 10, 2019
9114586
Update default tagging for http url and method. Add tests for various…
wantsui Jul 10, 2019
403831a
Start adding tags for trace search and analytics and add unit tests.
wantsui Jul 11, 2019
c8953fd
Update span type to reference from updated EXT file, add additional t…
wantsui Jul 11, 2019
352d1cb
Fix the tagging for the analytics option and add additional tests for…
wantsui Jul 12, 2019
68d3b5d
Refactor tests. Add distributed tracing to Roda integration. Update u…
wantsui Jul 15, 2019
72553ad
Start to refactor unit tests, remove unused variables and constants f…
wantsui Jul 16, 2019
ab7d7fe
Continue to refactor unit tests and add private method for distribute…
wantsui Jul 16, 2019
6103e1f
Add unit tests for analytics.
wantsui Jul 16, 2019
fef14ff
Move analytics examples to unit tests and add logic to check for Ruby…
wantsui Jul 17, 2019
76c4a38
Update formatting for linting
wantsui Jul 17, 2019
586fd57
Update instrumentation to handle new dispatch api. Refactor unit test…
wantsui Jul 19, 2019
5016c7b
Refactor code for formatting.
wantsui Jul 19, 2019
4c74c4b
Remove duplicated server error example and add http status code tag.
wantsui Jul 19, 2019
f8c0c32
Rewrite Roda integration for 1.x by replacing pin with prepend in the…
wantsui Nov 8, 2022
1462d1e
Rewrite unit tests to work with 1.x updates.
wantsui Nov 15, 2022
aa1db97
Update instrumentation to account for errors where Roda does not have…
wantsui Dec 13, 2022
d16dbd5
Add Roda to the GettingStarted Docs
wantsui Dec 13, 2022
a9db21c
Merge branch 'master' into add-roda-rebase
marcotc Dec 20, 2022
2342811
Update minimum and maximum versions, add integration spec tests, and …
wantsui Dec 20, 2022
41be369
remove content header
wantsui Dec 20, 2022
585b862
WIP
TonyCTHsu Dec 20, 2022
affbc7a
Add missing end block.
wantsui Dec 20, 2022
4f107a0
Revert error logic back to previous assumption and update patcher spe…
wantsui Dec 27, 2022
3061065
Add changes for linter.
wantsui Dec 27, 2022
fe099ac
Update roda files for sorbet type checks.
wantsui Dec 27, 2022
af40cb6
Run install_appraisal_gemfiles with the latest Appraisal file.
wantsui Jan 3, 2023
c3804ba
Merge latest changes.
wantsui Jan 3, 2023
edb4f2b
Merge branch 'master' into add-roda-rebase
wantsui Jan 17, 2023
4555d3d
Add frozen literal comment for rubocop
wantsui Jan 17, 2023
0aff08c
Remove frozen string and relace with typed ignore instead.
wantsui Jan 17, 2023
901297b
Update for rubocop frozen literal and sorbet type.
wantsui Jan 17, 2023
8510779
Import gemfiles from master branch to avoid merge conflict
ivoanjo Jan 24, 2023
426f6cf
Merge branch 'master' into add-roda-rebase
ivoanjo Jan 24, 2023
78237cc
Minor: Tweak spacing in docs table
ivoanjo Jan 24, 2023
c66542d
Merge branch 'master' into add-roda-rebase
wantsui Feb 21, 2023
b3081e9
Remove default service name in favor of global configuration, remove …
wantsui Feb 28, 2023
9323c6c
Merge branch 'master' into add-roda-rebase
wantsui Feb 28, 2023
e18b260
Merge branch 'master' into add-roda-rebase
wantsui Mar 10, 2023
034eef6
Add roda files to Steepfile and remove empty line.
wantsui Mar 10, 2023
c3a1e2c
Catch only standard error during errors in instrumentation for roda.
wantsui Mar 10, 2023
5aa0e94
Merge branch 'master' into add-roda-rebase
wantsui Mar 10, 2023
29a9aa8
Remove roda from Steepfile.
wantsui Mar 10, 2023
0b0fe25
Merge branch 'master' into add-roda-rebase
wantsui Mar 14, 2023
3682891
Remove Rack TraceMiddleware from being loaded by default. Instead, ap…
wantsui Mar 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ namespace :spec do
:rake,
:redis,
:resque,
:roda,
:rest_client,
:semantic_logger,
:sequel,
Expand Down Expand Up @@ -314,6 +315,7 @@ task :ci do
declare '✅ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:rake'
declare '✅ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:resque'
declare '✅ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:rest_client'
declare '✅ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:roda'
declare '✅ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:rspec'
declare '✅ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:semantic_logger'
declare '✅ 2.1 / ✅ 2.2 / ✅ 2.3 / ✅ 2.4 / ✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ jruby' => 'bundle exec appraisal contrib rake spec:sequel'
Expand Down
35 changes: 35 additions & 0 deletions docs/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ To contribute, check out the [contribution guidelines][contribution docs] and [d
- [Redis](#redis)
- [Resque](#resque)
- [Rest Client](#rest-client)
- [Roda](#roda)
- [RSpec](#rspec)
- [Sequel](#sequel)
- [Shoryuken](#shoryuken)
Expand Down Expand Up @@ -515,6 +516,7 @@ For a list of available integrations, and their configuration options, please re
| Redis | `redis` | `>= 3.2` | `>= 3.2` | *[Link](#redis)* | *[Link](https://github.com/redis/redis-rb)* |
| Resque | `resque` | `>= 1.0` | `>= 1.0` | *[Link](#resque)* | *[Link](https://github.com/resque/resque)* |
| Rest Client | `rest-client` | `>= 1.8` | `>= 1.8` | *[Link](#rest-client)* | *[Link](https://github.com/rest-client/rest-client)* |
| Roda | `roda` | `>= 2.1, <4` | `>= 2.1, <4` | *[Link](#roda)* | *[Link](https://github.com/jeremyevans/roda)* |
| Sequel | `sequel` | `>= 3.41` | `>= 3.41` | *[Link](#sequel)* | *[Link](https://github.com/jeremyevans/sequel)* |
| Shoryuken | `shoryuken` | `>= 3.2` | `>= 3.2` | *[Link](#shoryuken)* | *[Link](https://github.com/phstc/shoryuken)* |
| Sidekiq | `sidekiq` | `>= 3.5.4` | `>= 3.5.4` | *[Link](#sidekiq)* | *[Link](https://github.com/mperham/sidekiq)* |
Expand Down Expand Up @@ -1873,6 +1875,39 @@ end
| `service_name` | Service name for `rest_client` instrumentation. | `'rest_client'` |
| `split_by_domain` | Uses the request domain as the service name when set to `true`. | `false` |

### Roda

The Roda integration traces requests.

The **Roda** integration can be enabled through `Datadog.configure`. It is recommended to use this integration with **Rack** through `use Datadog::Tracing::Contrib::Rack::TraceMiddleware` for distributed tracing.

```ruby
require "roda"
require "ddtrace"

class SampleApp < Roda
use Datadog::Tracing::Contrib::Rack::TraceMiddleware

Datadog.configure do |c|
c.tracing.instrument :roda, **options
end

route do |r|
r.root do
r.get do
'Hello World!'
end
end
end
end
```

`options` are the following keyword arguments:

| Key | Description | Default |
| --- | ----------- | ------- |
| `service_name` | Service name for `roda` instrumentation. | `'nil'` |

### RSpec

RSpec integration will trace all executions of example groups and examples when using `rspec` test framework.
Expand Down
1 change: 1 addition & 0 deletions lib/datadog/tracing/contrib.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ module Contrib
require_relative 'contrib/redis/integration'
require_relative 'contrib/resque/integration'
require_relative 'contrib/rest_client/integration'
require_relative 'contrib/roda/integration'
require_relative 'contrib/semantic_logger/integration'
require_relative 'contrib/sequel/integration'
require_relative 'contrib/shoryuken/integration'
Expand Down
34 changes: 34 additions & 0 deletions lib/datadog/tracing/contrib/roda/configuration/settings.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

require_relative '../../configuration/settings'
require_relative '../ext'

module Datadog
module Tracing
module Contrib
module Roda
module Configuration
# Custom settings for the Roda integration
class Settings < Contrib::Configuration::Settings
option :enabled do |o|
o.default { env_to_bool(Ext::ENV_ENABLED, true) }
o.lazy
end

option :analytics_enabled do |o|
o.default { env_to_bool(Ext::ENV_ANALYTICS_ENABLED, false) }
o.lazy
end

option :analytics_sample_rate do |o|
o.default { env_to_float(Ext::ENV_ANALYTICS_SAMPLE_RATE, 1.0) }
o.lazy
end

option :service_name
end
end
end
end
end
end
18 changes: 18 additions & 0 deletions lib/datadog/tracing/contrib/roda/ext.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Datadog
module Tracing
module Contrib
module Roda
# Roda integration constants
module Ext
APP = 'roda'
ENV_ENABLED = 'DD_TRACE_RODA_ENABLED'
ENV_ANALYTICS_ENABLED = 'DD_RODA_ANALYTICS_ENABLED'
ENV_ANALYTICS_SAMPLE_RATE = 'DD_RODA_ANALYTICS_SAMPLE_RATE'
SPAN_REQUEST = 'roda.request'
end
end
end
end
end
76 changes: 76 additions & 0 deletions lib/datadog/tracing/contrib/roda/instrumentation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# frozen_string_literal: true

require_relative '../../metadata/ext'
require_relative 'ext'
require_relative '../analytics'

module Datadog
module Tracing
module Contrib
module Roda
# Instrumentation for Roda
module Instrumentation
def _roda_handle_main_route
instrument(Ext::SPAN_REQUEST) { super }
end

def call
instrument(Ext::SPAN_REQUEST) { super }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can move the error handling logic to the caller

          def call
            instrument(Ext::SPAN_REQUEST) do |span|
              super
            rescue => e
              # ... Handle error
            end
          end

So you could be confident yielding in the block without nested begin/rescue.

response = yield span

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like in the Roda code, dispatch can land on one of these methods (seen in this conditional)

Would I need to duplicate the rescue block and handle the error in both since either one of these can handle the dispatch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an update to this, I wasn't able to move the error handling logic anywhere else because it interfered with the errors produced by the underlying app.

end

private

def instrument(span_name, &block)
Tracing.trace(span_name) do |span|
begin
request_method = request.request_method.to_s.upcase

span.service = configuration[:service_name] if configuration[:service_name]

span.span_type = Tracing::Metadata::Ext::HTTP::TYPE_INBOUND

# Using the http method as a resource, since the URL/path can trigger
# a possibly infinite number of resources.
span.resource = request_method

span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, request.path)
span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, request_method)

# Add analytics tag to the span
if Contrib::Analytics.enabled?(configuration[:analytics_enabled])
Contrib::Analytics.set_sample_rate(span, configuration[:analytics_sample_rate])
end

# Measure service stats
Contrib::Analytics.set_measured(span)
ensure
begin
response = yield
rescue StandardError
# The status code is unknown to Roda and decided by the upstream web runner.
# In this case, spans default to status code 500 rather than a blank status code.
default_error_status = '500'
span.resource = "#{request_method} #{default_error_status}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly strange edge case here where request.request_method.to_s.upcase raised an error, hit the ensure block, request raised an error, caught by this rescue block, then request_method will likely be undefined, possibly raising another error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how this error can be thrown because all routes should be defined with a type from the start. Do you have a suggestion for handling this scenario?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I paired with Wan and it's not possible for request.request_method.to_s.upcase to fail, as it comes straight from Rack and it's part of the Rack contract. This is the same behaviour of our other Rack-ralated integrations (Sinatra, Rails, Hanami, Grace)

span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_STATUS_CODE, default_error_status)
raise
end
end

status_code = response[0]

# Adds status code to the resource name once the resource comes back
span.resource = "#{request_method} #{status_code}"
marcotc marked this conversation as resolved.
Show resolved Hide resolved
span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_STATUS_CODE, status_code)
span.status = 1 if status_code.to_s.start_with?('5')
response
end
end

def configuration
Datadog.configuration.tracing[:roda]
end
end
end
end
end
end
45 changes: 45 additions & 0 deletions lib/datadog/tracing/contrib/roda/integration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

# typed: false

require_relative '../integration'
require_relative 'configuration/settings'
require_relative 'patcher'

module Datadog
module Tracing
module Contrib
module Roda
# Description of Roda integration
class Integration
include Contrib::Integration

MINIMUM_VERSION = Gem::Version.new('2.0.0')
MAXIMUM_VERSION = Gem::Version.new('4.0.0')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know this maximum. Do we know we don't support Roda 4.0.0? If so, why don't we?

Also "maximum" version as a name is slightly misleading in that its exclusive (we don't actually support 4.0.0.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the time of this comment: Roda 4.x isn't out. The latest version is 3.65.0.

Given the changes that occurred between 1.x and 2.x, I'm just being cautious by setting a maximum of 4.x for now. That way, in our public docs, we can be clear that this logic is intended for 2.x and 3.x.

Whenever 4.x comes out, it'll require visiting to make sure it still functions as expected.


register_as :roda

def self.version
Gem.loaded_specs['roda'] && Gem.loaded_specs['roda'].version
end

def self.loaded?
!defined?(::Roda).nil?
end

def self.compatible?
super && version >= MINIMUM_VERSION && version < MAXIMUM_VERSION
end

def new_configuration
Configuration::Settings.new
end

def patcher
Patcher
end
end
end
end
end
end
30 changes: 30 additions & 0 deletions lib/datadog/tracing/contrib/roda/patcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

# typed: ignore

require_relative 'patcher'
require_relative 'ext'
require_relative 'instrumentation'

module Datadog
module Tracing
module Contrib
module Roda
# Patcher enables patching of 'roda'
module Patcher
include Contrib::Patcher

module_function

def target_version
Integration.version
end

def patch
::Roda.prepend(Instrumentation)
end
end
end
end
end
end
61 changes: 61 additions & 0 deletions spec/datadog/tracing/contrib/roda/instrumentation_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# typed: false

require_relative './shared_examples'
require 'roda'
require 'ddtrace'
require 'datadog/tracing/contrib/roda/instrumentation'
require 'datadog/tracing/contrib/roda/ext'
require 'datadog/tracing/contrib/support/spec_helper'
require 'datadog/tracing/contrib/support/tracer_helpers'

RSpec.describe Datadog::Tracing::Contrib::Roda::Instrumentation do
describe 'when implemented in Roda' do
let(:configuration_options) { {} }

before do
Datadog.configure do |c|
c.tracing.instrument :roda, configuration_options
end
end

after do
Datadog.registry[:roda].reset_configuration!
end

describe 'When using automatic instrumentation' do
let(:env) { { REQUEST_METHOD: 'GET' } }
context 'configuring roda' do
context 'with default settings' do
it 'enables the tracer' do
expect(Datadog.configuration.tracing.enabled).to eq(true)
end

it 'does not have a default service name (left up to global configuration)' do
expect(Datadog.configuration.tracing[:roda].service_name).to eq(nil)
expect(Datadog.configuration.service).to eq('rspec')
end

context 'with a custom service name' do
let(:custom_service_name) { 'custom_service_name' }
let(:configuration_options) { { service_name: custom_service_name } }

it 'sets a custom service name' do
expect(Datadog.configuration.service).to eq('rspec')
expect(Datadog.configuration.tracing[:roda].service_name).to eq(custom_service_name)
end
end
end
end
end

describe 'when application calls on the instrumented method' do
context 'using #call' do
it_behaves_like 'shared examples for roda', :call
end

context 'using #_roda_handle_main_route' do
it_behaves_like 'shared examples for roda', :_roda_handle_main_route
end
end
end
end
Loading