Skip to content

Commit

Permalink
Add Rack instrumentation (#166)
Browse files Browse the repository at this point in the history
* rack: Add Gemfile, gemspec, version

* rack-test: "simple testing API for Rack apps"
* fix: "Could not find rake-12.3.3 in any of the sources"

* Add TracerMiddleware

* use dup._call env pattern
* RE: thread safety: "... easiest and most efficient way to do this is to
  dup the middleware object that is created in runtime."
* do we need 'quantization' now?

* Add Adapters::Rack::Adapter

* retain middleware names (feature exists in dd-trace-rb)

* tests: Add test_helper

* Add example: trace_demonstration

e.g.,
$ docker-compose run --rm ex-adapter-rack
bash-5.0$ ruby trace_demonstration.rb

* Add Rakefile

e.g.,
$ bundle exec rake test

* Add QueueTime

* from dd-trace-rb
* translate spec to minitest

* Add Adapters::Rack

* Update to 2020 copyright

* Initialize 'now' later

* Adapt to Instrumentation Auto Installer interface

* PR #164

* Fix example to use updated Instrumentation Auto Installer

* verified by running via, e.g.,
  $ docker-compose run --rm ex-adapter-rack
  bash-5.0$ bundle
  bash-5.0$ ruby trace_demonstration.rb

Expected: console output of span data

* Handle errors by setting span.status, leave a TODO and rescue clause

* Allow config[:quantization]

* to allow for lower cardinality span names

* Remove optional parent context extraction

* defeats purpose of opentelemetry, which *is* distributed tracing

* Resolve 'http.base_url' TODO

* add 'http.target'

* Resolve 'resource name' TODO

* it seems that dd-trace-rb's 'span.resource' is the same as 'span.name',
  at least in this case

* Resolve 'http.route' TODO

* in sinatra, data is available via env['sinatra.route'].split.last,
  but otherwise, doesn't seem to be readily available

* Note: missing 'span.set_error()' for now

* Resolve FrontendSpan TODOs

* resolve 'http_server.queue' TODO
* span kind of 'proxy' is not defined, yet

* Optimize allowed_request_headers

* reduce string allocations
* TIL: a nested 'before' block doesn't run before *each* test,
  but the top/first 'before' block does. (weird)

* Optimize allowed_response_headers()

* prevent unneeded string and object allocation
* once a header key is found, add it to the list of response headers
  to search for

* Optimize return of EMPTY_HASH frozen constant

* Refactor to avoid using dup._call(env)

* avoid using instance variables

* Add Appraisals, integrate into circleci

* Integrate rubocop, fix violations, add adapters to top-level rake task

* per work in #179

* Update example to use new config

* per #171, #177

* Rewrite examples

* one that is simple, no config options
  * demonstrate integration via 'Rack::Builder#use'
  * this is how ddtrace is currently working

* demonstrate integration using config[:application]

* ultimate goal: 0 config (automagic integration)

* Automatically patch Rack::Builder

* in tests, keep an unpatched ('untainted') version of Rack::Builder,
  restore before and after each test
* fix: ruby2.4: private method `define_method' called

* Port ddtrace Quantization::HTTP

* Integrate Util::Quantization

* Revert "Automatically patch Rack::Builder"

This reverts commit de91025.

# Conflicts:
#	adapters/rack/lib/opentelemetry/adapters/rack/adapter.rb

* Add missing files needed for Bundler.require

* Update Rakefile

* Avoid patching config[:application] during installation

* config[:application] is patched in retain_middleware_names, in which
  case it is required if config[:retain_middleware_names] is truthy
* goal: leave .use call to the user

* Refine/optimize allowed_request_headers

* Refine/optimize allowed_response_headers

* Avoid circular require

* Use SDK.configure to streamline test_helper.rb setup

* Revert "Integrate Util::Quantization"

This reverts commit 99f44e8.

Discussed in SIG:
* avoid eager-quantization (heavy, potentially unwanted)
* defer to user preferences (via config)
* probably better to err on the side of 'too-specific' vs. 'too-general',
  since 'too-specific' can be made more general, but maybe not vice-versa

# Conflicts:
#	adapters/rack/lib/opentelemetry/adapters/rack/adapter.rb

* Revert "Port ddtrace Quantization::HTTP"

This reverts commit e9c021b.

* cleanup remnants that aren't used for now

* Fix example/trace_demonstration2.rb to integrate explicitly, with 'use'

* Update example/trace_demonstration2.rb documentation

* Optimize allowed_response_headers to avoid using Hash#detect

* Simplify allowed_{rack,request}_header_names to inline config

* Optimize to return EMPTY_HASH if allowed_{response,rack_request}_headers.empty?

* Adjust to context prop changes

* Remove unused variables

* Use kind: :server for both frontend and request span

* Make request_span parented by frontend_span

* explicitly manage frontend_span parent context, and prevent
  automatic span activation
* manage frontend_span life-cycle explicitly via a new context, using
  it as the request_span's parent, if it's available

* Implement using helpers to that in_span doesn't have to record and re-raise error

* Cleanup some URL wrapper methods

* goal: eliminate need for Rack::Request allocation

* Optimize: return without assigning local variable

* Just use http.{scheme,host,target} (remove url, base_url)

* Inline Rack::Request#fullpath

* Fix .circleci/config.yml after conflict

* Adjust error handling according to #184

* Rewrite to utilize in_span

* note that order of finished spans is now swapped

* Reduce comments that were more useful in development/review

* Update http.host to use HTTP_HOST or 'unknown'

Co-Authored-By: Matthew Wear <[email protected]>

* Update request_start_time to be number, not timestamp

Co-Authored-By: Matthew Wear <[email protected]>

* Remove request_span comment

* Remove 'service' attribute when creating frontend span

* value is potentially nil, which is not a valid attribute value
* also 'service' is not an official semantic convention and will
  probably come from the application resource in the future

* Change frontend_span to 'http_server.proxy', make request_span :internal

* request_span.kind is :internal if frontend_span is present
* future: change request_span :kind to ':proxy' if/when it gets added to spec

Co-authored-by: Matthew Wear <[email protected]>
  • Loading branch information
fbogsany and mwear authored Mar 5, 2020
1 parent b9df39e commit c53e44d
Show file tree
Hide file tree
Showing 27 changed files with 1,004 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ commands:
gem install --no-document bundler -v '~> 2.0.2'
bundle install --jobs=3 --retry=3
bundle exec rake test
- run:
name: Bundle + CI (Adapters - Rack)
command: |
cd adapters/rack
gem uninstall -aIx bundler
gem install --no-document bundler -v '~> 2.0.2'
bundle install --jobs=3 --retry=3
bundle exec appraisal install
bundle exec appraisal rake test
- run:
name: Bundle + CI (Adapters - Redis)
command: |
Expand Down
6 changes: 6 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ GEM_INFO = {
OpenTelemetry::Adapters::Net::HTTP::VERSION
}
},
"opentelemetry-adapters-rack" => {
version_getter: ->() {
require './lib/opentelemetry/adapters/rack/version.rb'
OpenTelemetry::Adapters::Rack::VERSION
}
},
"opentelemetry-adapters-redis" => {
version_getter: ->() {
require './lib/opentelemetry/adapters/redis/version.rb'
Expand Down
27 changes: 27 additions & 0 deletions adapters/rack/.rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
AllCops:
TargetRubyVersion: '2.4.0'

Bundler/OrderedGems:
Exclude:
- gemfiles/**/*
Lint/UnusedMethodArgument:
Enabled: false
Metrics/AbcSize:
Max: 18
Metrics/LineLength:
Enabled: false
Metrics/MethodLength:
Max: 20
Metrics/ParameterLists:
Enabled: false
Naming/FileName:
Exclude:
- "lib/opentelemetry-adapters-rack.rb"
Style/FrozenStringLiteralComment:
Exclude:
- gemfiles/**/*
Style/ModuleFunction:
Enabled: false
Style/StringLiterals:
Exclude:
- gemfiles/**/*
13 changes: 13 additions & 0 deletions adapters/rack/Appraisals
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

# Copyright 2020 OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

appraise 'rack-2.1' do
gem 'rack', '~> 2.1.2'
end

appraise 'rack-2.0' do
gem 'rack', '2.0.8'
end
15 changes: 15 additions & 0 deletions adapters/rack/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

# Copyright 2020 OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

source 'https://rubygems.org'

gemspec

gem 'opentelemetry-api', path: '../../api'

group :test do
gem 'opentelemetry-sdk', path: '../../sdk'
end
28 changes: 28 additions & 0 deletions adapters/rack/Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

# Copyright 2020 OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

require 'bundler/gem_tasks'
require 'rake/testtask'
require 'yard'
require 'rubocop/rake_task'

RuboCop::RakeTask.new

Rake::TestTask.new :test do |t|
t.libs << 'test'
t.libs << 'lib'
t.test_files = FileList['test/**/*_test.rb']
end

YARD::Rake::YardocTask.new do |t|
t.stats_options = ['--list-undoc']
end

if RUBY_ENGINE == 'truffleruby'
task default: %i[test]
else
task default: %i[test rubocop yard]
end
9 changes: 9 additions & 0 deletions adapters/rack/example/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

source 'https://rubygems.org'

gem 'opentelemetry-adapters-rack', path: '../../../adapters/rack'
gem 'opentelemetry-api', path: '../../../api'
gem 'opentelemetry-sdk', path: '../../../sdk'
gem 'rack'
gem 'rack-test'
27 changes: 27 additions & 0 deletions adapters/rack/example/trace_demonstration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

# Copyright 2020 OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

require 'rubygems'
require 'bundler/setup'

Bundler.require

OpenTelemetry::SDK.configure do |c|
c.use 'OpenTelemetry::Adapters::Rack'
end

# setup fake rack application:
builder = Rack::Builder.app do
# integration should be automatic in web frameworks (like rails),
# but for a plain Rack application, enable it in your config.ru, e.g.,
use OpenTelemetry::Adapters::Rack::Middlewares::TracerMiddleware

app = ->(_env) { [200, { 'Content-Type' => 'text/plain' }, ['All responses are OK']] }
run app
end

# demonstrate tracing (span output to console):
puts Rack::MockRequest.new(builder).get('/')
28 changes: 28 additions & 0 deletions adapters/rack/example/trace_demonstration2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

# Copyright 2020 OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

require 'rubygems'
require 'bundler/setup'

Bundler.require

# setup fake rack application:
builder = Rack::Builder.new
app = ->(_env) { [200, { 'Content-Type' => 'text/plain' }, ['All responses are OK']] }
builder.run app

# demonstrate integration using 'retain_middlware_names' and 'application':
OpenTelemetry::SDK.configure do |c|
c.use 'OpenTelemetry::Adapters::Rack', retain_middleware_names: true,
application: builder,
record_frontend_span: true
end

# integrate instrumentation explicitly:
builder.use OpenTelemetry::Adapters::Rack::Middlewares::TracerMiddleware

# demonstrate tracing (span output to console):
puts Rack::MockRequest.new(builder).get('/')
12 changes: 12 additions & 0 deletions adapters/rack/gemfiles/rack_2.0.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# This file was generated by Appraisal

source "https://rubygems.org"

gem "opentelemetry-api", path: "../../../api"
gem "rack", "2.0.8"

group :test do
gem "opentelemetry-sdk", path: "../../../sdk"
end

gemspec path: "../"
12 changes: 12 additions & 0 deletions adapters/rack/gemfiles/rack_2.1.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# This file was generated by Appraisal

source "https://rubygems.org"

gem "opentelemetry-api", path: "../../../api"
gem "rack", "~> 2.1.2"

group :test do
gem "opentelemetry-sdk", path: "../../../sdk"
end

gemspec path: "../"
7 changes: 7 additions & 0 deletions adapters/rack/lib/opentelemetry-adapters-rack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

# Copyright 2020 OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

require_relative './opentelemetry/adapters'
13 changes: 13 additions & 0 deletions adapters/rack/lib/opentelemetry/adapters.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

# Copyright 2020 OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

module OpenTelemetry
# Adapters should be able to handle the case when the library is not installed on a user's system.
module Adapters
end
end

require_relative './adapters/rack'
16 changes: 16 additions & 0 deletions adapters/rack/lib/opentelemetry/adapters/adapters.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

# Copyright 2019 OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

module OpenTelemetry
# "Instrumentation adapters" are specified by
# https://github.com/open-telemetry/opentelemetry-specification/blob/57714f7547fe4dcb342ad0ad10a80d86118431c7/specification/overview.md#instrumentation-adapters
#
# Adapters should be able to handle the case when the library is not installed on a user's system.
module Adapters
end
end

require_relative './adapters/rack'
18 changes: 18 additions & 0 deletions adapters/rack/lib/opentelemetry/adapters/rack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

# Copyright 2020 OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

require 'opentelemetry'

module OpenTelemetry
module Adapters
# Contains the OpenTelemetry adapter for the Rack gem
module Rack
end
end
end

require_relative './rack/adapter'
require_relative './rack/version'
57 changes: 57 additions & 0 deletions adapters/rack/lib/opentelemetry/adapters/rack/adapter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

# Copyright 2020 OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

require 'opentelemetry'

module OpenTelemetry
module Adapters
module Rack
# The Adapter class contains logic to detect and install the Rack
# instrumentation adapter
class Adapter < OpenTelemetry::Instrumentation::Adapter
install do |config|
require_dependencies

retain_middleware_names if config[:retain_middleware_names]
end

present do
defined?(::Rack)
end

private

def require_dependencies
require_relative 'middlewares/tracer_middleware'
end

MissingApplicationError = Class.new(StandardError)

# intercept all middleware-compatible calls, retain class name
def retain_middleware_names
next_middleware = config[:application]
raise MissingApplicationError unless next_middleware

while next_middleware
if next_middleware.respond_to?(:call)
next_middleware.singleton_class.class_eval do
alias_method :__call, :call

def call(env)
env['RESPONSE_MIDDLEWARE'] = self.class.to_s
__call(env)
end
end
end

next_middleware = next_middleware.instance_variable_defined?('@app') &&
next_middleware.instance_variable_get('@app')
end
end
end
end
end
end
Loading

0 comments on commit c53e44d

Please sign in to comment.