Skip to content

Grape support #562

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

Merged
merged 29 commits into from
Oct 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4f00e21
Foundations for Grape support
estolfo Oct 11, 2019
b12f0a5
Only instrument endpoint_run and pass app to Grape.start
estolfo Oct 14, 2019
4a998c0
Test Grape.start
estolfo Oct 14, 2019
6ca2a13
Remove Grape app constant after test
estolfo Oct 14, 2019
b19206d
Add documentation for app parameter to Grape.start
estolfo Oct 14, 2019
aff2492
Add documentation for getting started with Grape
estolfo Oct 14, 2019
da49860
Add documentation for configuring Grape
estolfo Oct 14, 2019
3d729c6
Test passing config values to Grape.start
estolfo Oct 14, 2019
6dd0feb
Resolve rubocop complaints
estolfo Oct 14, 2019
8ae18f6
Add Grape.start documentation to API docs
estolfo Oct 14, 2019
86c38ad
Make method name clearer
estolfo Oct 15, 2019
6d1458d
Grape should be tested as a framework in Jenkins
estolfo Oct 15, 2019
4a3206d
Fix spacing
estolfo Oct 15, 2019
f4cebca
Correct documentation of config options order
estolfo Oct 15, 2019
11ddddb
Support installing and testing against multiple frameworks
estolfo Oct 16, 2019
bcb5df5
Update framework info in transaction context for Grape
estolfo Oct 16, 2019
a6cb7c2
Add rails and grape spec
estolfo Oct 16, 2019
70df966
Grape github repo name has a different pattern than the other frameworks
estolfo Oct 16, 2019
b2f820c
Only test grape master on newest ruby
estolfo Oct 16, 2019
2b38625
Add message that grape test is skipped
estolfo Oct 16, 2019
807203c
Test Grape and Rails together
estolfo Oct 16, 2019
f1c9c81
No need for SpecLogger in rails-grape spec
estolfo Oct 16, 2019
d9e86bd
Fix formatting
estolfo Oct 16, 2019
27ce114
Remove unncessary test settings
estolfo Oct 18, 2019
523cd2c
Move requiring gemspec in Gemfile so framework modules are required a…
estolfo Oct 18, 2019
2a9996e
Remove one more unnecessary require
estolfo Oct 18, 2019
6666074
Revert docs change about order that config options are applied
estolfo Oct 18, 2019
bad8409
Move changelog entry to asciidoc file
estolfo Oct 22, 2019
42d30e4
Be explicit that scenario is grape and rack without rails
estolfo Oct 22, 2019
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
37 changes: 37 additions & 0 deletions .ci/.jenkins_exclude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,40 @@ exclude:
- RUBY_VERSION: docker.elastic.co/observability-ci/jruby:9.1-7-jdk
FRAMEWORK: sinatra-master

- RUBY_VERSION: ruby:2.5
FRAMEWORK: grape-master
- RUBY_VERSION: ruby:2.4
FRAMEWORK: grape-master
- RUBY_VERSION: ruby:2.3
FRAMEWORK: grape-master
- RUBY_VERSION: jruby:9.2
FRAMEWORK: grape-master
- RUBY_VERSION: jruby:9.1
FRAMEWORK: grape-master
- RUBY_VERSION: docker.elastic.co/observability-ci/jruby:9.2-12-jdk
FRAMEWORK: grape-master
- RUBY_VERSION: docker.elastic.co/observability-ci/jruby:9.2-11-jdk
FRAMEWORK: grape-master
- RUBY_VERSION: docker.elastic.co/observability-ci/jruby:9.2-8-jdk
FRAMEWORK: grape-master
- RUBY_VERSION: docker.elastic.co/observability-ci/jruby:9.1-7-jdk
FRAMEWORK: grape-master

- RUBY_VERSION: ruby:2.5
FRAMEWORK: grape-1.2,rails-6.0
- RUBY_VERSION: ruby:2.4
FRAMEWORK: grape-1.2,rails-6.0
- RUBY_VERSION: ruby:2.3
FRAMEWORK: grape-1.2,rails-6.0
- RUBY_VERSION: jruby:9.2
FRAMEWORK: grape-1.2,rails-6.0
- RUBY_VERSION: jruby:9.1
FRAMEWORK: grape-1.2,rails-6.0
- RUBY_VERSION: docker.elastic.co/observability-ci/jruby:9.2-12-jdk
FRAMEWORK: grape-1.2,rails-6.0
- RUBY_VERSION: docker.elastic.co/observability-ci/jruby:9.2-11-jdk
FRAMEWORK: grape-1.2,rails-6.0
- RUBY_VERSION: docker.elastic.co/observability-ci/jruby:9.2-8-jdk
FRAMEWORK: grape-1.2,rails-6.0
- RUBY_VERSION: docker.elastic.co/observability-ci/jruby:9.1-7-jdk
FRAMEWORK: grape-1.2,rails-6.0
4 changes: 4 additions & 0 deletions .ci/.jenkins_framework.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ FRAMEWORK:

- sinatra-2.0
- sinatra-1.4

- grape-1.2

- grape-1.2,rails-6.0
1 change: 1 addition & 0 deletions .ci/.jenkins_master_framework.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
FRAMEWORK:
- rails-master
- sinatra-master
- grape-master
2 changes: 1 addition & 1 deletion CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ endif::[]

[float]
===== Added
- Feature {pull}2526[#2526]
- Add Grape support ([#562](https://github.com/elastic/apm-agent-ruby/pull/562))

[float]
===== Changed
Expand Down
38 changes: 24 additions & 14 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ source 'https://rubygems.org'

git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

gemspec

gem 'rack-test'
gem 'rspec'
gem 'rspec-its'
Expand Down Expand Up @@ -33,25 +31,37 @@ else
gem 'sqlite3'
end

framework, *version = ENV.fetch('FRAMEWORK', 'rails').split('-')
version = version.join('-')
## Install Framework
GITHUB_REPOS = { 'grape' => 'ruby-grape/grape'}

case version
when 'master'
gem framework, github: "#{framework}/#{framework}"
when /.+/
gem framework, "~> #{version}.0"
else
gem framework
frameworks = ENV.fetch('FRAMEWORK', 'rails').split(',')
frameworks_versions = frameworks.inject({}) do |frameworks, str|
framework, *version = str.split('-')
frameworks.merge(framework => version.join('-'))
end

gem 'activerecord-jdbcsqlite3-adapter', platform: :jruby
frameworks_versions.each do |framework, version|
case version
when 'master'
gem framework, github: (GITHUB_REPOS[framework] || "#{framework}/#{framework}")
when /.+/
gem framework, "~> #{version}.0"
else
gem framework
end
end

unless version =~ /^(master|6)/
gem 'delayed_job', require: nil
if frameworks_versions.key?('rails')
unless frameworks_versions['rails'] =~ /^(master|6)/
gem 'delayed_job', require: nil
end
end

gem 'activerecord-jdbcsqlite3-adapter', platform: :jruby

group :bench do
gem 'ruby-prof', require: nil, platforms: %i[ruby]
gem 'stackprof', require: nil, platforms: %i[ruby]
end

gemspec
14 changes: 12 additions & 2 deletions docs/api.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,9 @@ Returns the built context.

[float]
[[rails-start]]
=== Manually hooking into Rails
=== Rails

Start the agent and hook into Rails explicitly. This is useful if you skip requiring
Start the agent and hook into Rails manually. This is useful if you skip requiring
the gem and using the `Railtie`.

[source,ruby]
Expand All @@ -212,6 +212,16 @@ Start the agent and hook into Sinatra.
[source,ruby]
----
ElasticAPM::Sinatra.start(MySinatraApp, server_url: 'http://localhost:8200')

[float]
[[grape-start]]
=== Grape

Start the agent and hook into Grape.

[source,ruby]
----
ElasticAPM::Grape.start(MyGrapeApp, server_url: 'http://localhost:8200')
----

[float]
Expand Down
17 changes: 17 additions & 0 deletions docs/configuration.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,23 @@ ElasticAPM::Sinatra.start(

See <<getting-started-rack>>.

[float]
=== Grape and Rack

When using APM with Grape and Rack (without Rails), you can configure it when starting
the agent:

[source,ruby]
----
# config.ru or similar
ElasticAPM::Grape.start(
MyApp,
service_name: 'SomeOtherName'
)
----

See <<getting-started-rack>>.

[float]
=== Options

Expand Down
24 changes: 24 additions & 0 deletions docs/getting-started-rack.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,27 @@ run MySinatraApp
at_exit { ElasticAPM.stop }
----

[float]
[[getting-started-grape]]
==== Grape example

[source,ruby]
----
# Example config.ru

require 'grape'

module Twitter
class API < Grape::API
use ElasticAPM::Middleware

# ...
end
end

# Start the agent and hook in your app
ElasticAPM::Grape.start(Twitter::API, config)

run Twitter::API

----
1 change: 1 addition & 0 deletions lib/elastic_apm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

require 'elastic_apm/railtie' if defined?(::Rails::Railtie)
require 'elastic_apm/sinatra' if defined?(::Sinatra)
require 'elastic_apm/grape' if defined?(::Grape)

# ElasticAPM
module ElasticAPM # rubocop:disable Metrics/ModuleLength
Expand Down
46 changes: 46 additions & 0 deletions lib/elastic_apm/grape.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# frozen_string_literal: true

require 'elastic_apm/subscriber'
require 'elastic_apm/normalizers/grape'

module ElasticAPM
# Module for starting the ElasticAPM agent and hooking into Grape.
module Grape
extend self
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
# Start the ElasticAPM agent and hook into Grape.
#
# @param app [Grape::API] A Grape app.
# @param config [Config, Hash] An instance of Config or a Hash config.
# @return [true, nil] true if the agent was started, nil otherwise.
def start(app, config = {})
config = Config.new(config) unless config.is_a?(Config)
configure_app(app, config)

ElasticAPM.start(config).tap do |agent|
attach_subscriber(agent)
end
ElasticAPM.running?
rescue StandardError => e
config.logger.error format('Failed to start: %s', e.message)
config.logger.debug "Backtrace:\n" + e.backtrace.join("\n")
end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize

private

def configure_app(app, config)
config.service_name ||= app.name
config.framework_name ||= 'Grape'
config.framework_version ||= ::Grape::VERSION
config.logger ||= app.logger
config.__root_path ||= Dir.pwd
end

def attach_subscriber(agent)
return unless agent

agent.instrumenter.subscriber = ElasticAPM::Subscriber.new(agent)
end
end
end
2 changes: 1 addition & 1 deletion lib/elastic_apm/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def initialize(app)
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
def call(env)
begin
if running? && !path_ignored?(env)
if running? && !path_ignored?(env) && !ElasticAPM.current_transaction
transaction = start_transaction(env)
end

Expand Down
5 changes: 5 additions & 0 deletions lib/elastic_apm/normalizers/grape.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

%w[endpoint_run].each do |lib|
require "elastic_apm/normalizers/grape/#{lib}"
end
34 changes: 34 additions & 0 deletions lib/elastic_apm/normalizers/grape/endpoint_run.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module ElasticAPM
module Normalizers
module Grape
# @api private
class EndpointRun < Normalizer
register 'endpoint_run.grape'

TYPE = 'app'
SUBTYPE = 'resource'

FRAMEWORK_NAME = 'Grape'

def normalize(transaction, _name, payload)
transaction.name = endpoint(payload[:env])
unless transaction.config.framework_name == FRAMEWORK_NAME
transaction.context.set_service(framework_name: FRAMEWORK_NAME,
framework_version: ::Grape::VERSION)
end
[transaction.name, TYPE, SUBTYPE, nil, nil]
end

private

def endpoint(env)
route_name = env['api.endpoint']&.routes&.first&.pattern&.origin ||
env['REQUEST_PATH']
[env['REQUEST_METHOD'], route_name].join(' ')
end
end
end
end
end
35 changes: 35 additions & 0 deletions spec/elastic_apm/grape_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

if defined?(Grape)
RSpec.describe Grape do
describe '.start' do
before do
class GrapeTestApp < ::Grape::API
use ElasticAPM::Middleware
end
ElasticAPM::Grape.start(GrapeTestApp, config)
end

after do
ElasticAPM.stop
Object.send(:remove_const, :GrapeTestApp)
end

context 'with no overridden config settings' do
let(:config) { {} }
it 'starts the agent' do
expect(ElasticAPM::Agent).to be_running
end
end

context 'a config with settings' do
let(:config) { { service_name: 'Other Name' } }

it 'sets the options' do
expect(ElasticAPM.agent.config.options[:service_name].value)
.to eq('Other Name')
end
end
end
end
end
Loading