Skip to content

Commit

Permalink
Add Azure Storage Table/CosmosDB support (#999)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mikkel Malmberg authored Jul 6, 2021
1 parent 6a11c0f commit a96aea3
Show file tree
Hide file tree
Showing 34 changed files with 839 additions and 299 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ endif::[]
[[unreleased]]
==== Unreleased
[float]
===== Added
- Add support for AWS Storage Table/CosmosDB {pull}999[#999]
[float]
===== Fixed
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ gem 'aws-sdk-dynamodb', require: nil
gem 'aws-sdk-s3', require: nil
gem 'aws-sdk-sqs', require: nil
gem 'aws-sdk-sns', require: nil
gem 'azure-storage-table', require: nil if RUBY_VERSION < '3.0'
gem 'elasticsearch', require: nil
gem 'fakeredis', require: nil
gem 'faraday', require: nil
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ services:
build:
context: .
args:
BUNDLER_VERSION: '2.0.2'
BUNDLER_VERSION: '2.2.21'
image: '$IMAGE_NAME'
environment:
HOME: '/tmp'
Expand Down
2 changes: 1 addition & 1 deletion features/azure_app_service_metadata.feature
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,4 @@ Feature: Extracting Metadata for Azure App Service
| WEBSITE_RESOURCE_GROUP | resource_group |
| WEBSITE_SITE_NAME | site_name |
When cloud metadata is collected
Then cloud metadata is null
Then cloud metadata is null
1 change: 1 addition & 0 deletions lib/elastic_apm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
require 'elastic_apm/logging'

# Core
require 'elastic_apm/fields'
require 'elastic_apm/agent'
require 'elastic_apm/config'
require 'elastic_apm/context'
Expand Down
1 change: 1 addition & 0 deletions lib/elastic_apm/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def assign(update)
def available_instrumentations
%w[
action_dispatch
azure_storage_table
delayed_job
dynamo_db
elasticsearch
Expand Down
88 changes: 88 additions & 0 deletions lib/elastic_apm/fields.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# frozen_string_literal: true

module ElasticAPM
# An interface for creating simple, value holding objects that correspond to
# object fields in the API.
#
# Example:
# class MyThing
# include Fields
# field :name
# field :address, optional: true
# end
#
# MyThing.new(name: 'AJ').to_h
# # => { name: 'AJ' }
# MyThing.new().empty?
# # => true
module Fields
module InstanceMethods
def initialize(**attrs)
attrs.each do |key, value|
self.send(:"#{key}=", value)
end

super()
end

def empty?
self.class.fields.each do |key|
next if send(key)
next if optionals.include?(key)

return true
end

false
end

def to_h
self.class.fields.each_with_object({}) do |key, fields|
fields[key] = send(key)
end
end

private

def optionals
self.class.optionals
end
end

module ClassMethods
def field(key, optional: false)
attr_accessor(key)

fields.push(key)
optionals.push(key) if optional
end

attr_reader :fields, :optionals
end

def self.included(cls)
cls.extend(ClassMethods)
cls.include(InstanceMethods)

cls.instance_variable_set(:@fields, [])
cls.instance_variable_set(:@optionals, [])
end
end
end
16 changes: 16 additions & 0 deletions lib/elastic_apm/span.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,18 @@ def stop(clock_end = Util.monotonic_micros)
@duration ||= (clock_end - @clock_start)
@parent.child_stopped
@self_time = @duration - child_durations.duration

if exit_span?
context.destination ||= Context::Destination.new
context.destination.service ||= Context::Destination::Service.new
context.destination.service.resource ||= (subtype || type)

# Deprecated fields but required by some versions of APM Server, so
# we auto-infer them from existing fields
context.destination.service.name ||= (subtype || type)
context.destination.service.type ||= type
end

self
end

Expand Down Expand Up @@ -166,5 +178,9 @@ def long_enough_for_stacktrace?

duration >= min_duration
end

def exit_span?
context.destination || context.db || context.message || context.http
end
end
end
97 changes: 39 additions & 58 deletions lib/elastic_apm/span/context/destination.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,108 +14,89 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

#
# frozen_string_literal: true

module ElasticAPM
class Span
class Context
# @api private
class Destination
include Fields

field :address
field :port
field :service
field :cloud

# @api private
class Service
class MissingValues < StandardError; end

def initialize(name: nil, type: nil, resource: nil)
@name = name
@type = type
@resource = resource
include Fields

# APM Server expects all values for service
raise MissingValues unless @name && @type && resource
end

attr_accessor :name, :type, :resource
field :name
field :type
field :resource
end

# @api private
class Cloud
def initialize(region: nil)
@region = region
end
include Fields

attr_accessor :region
field :region
end

def initialize(
address: nil,
port: nil,
service: nil,
cloud: nil
)
@address = address
@port = port
@service = build_service(service)
@cloud = build_cloud(cloud)
end
def initialize(service: nil, cloud: nil, **attrs)
super(**attrs)

attr_reader(
:address,
:port,
:service,
:cloud
)
self.service = build_service(service)
self.cloud = build_cloud(cloud)
end

def self.from_uri(uri_or_str, type: 'external', port: nil)
def self.from_uri(uri_or_str, type: nil, **attrs)
uri = normalize(uri_or_str)

service = Service.new(
name: only_scheme_and_host(uri),
resource: "#{uri.host}:#{uri.port}",
type: type
)
service =
case type
when 'http' then http_service(uri)
else nil
end

new(
address: uri.hostname,
port: port || uri.port,
service: service
port: uri.port,
service: service,
**attrs
)
end

def self.only_scheme_and_host(uri_or_str)
uri = normalize(uri_or_str)
uri.path = ''
uri.password = uri.query = uri.fragment = nil
uri.to_s
end

class << self
private

def normalize(uri_or_str)
return uri_or_str.dup if uri_or_str.is_a?(URI)
URI(uri_or_str)
end

def http_service(uri)
Service.new(resource: "#{uri.host}:#{uri.port}")
end
end

private

def build_service(service = nil)
return unless service
return service if service.is_a?(Service)

Service.new(**service)
rescue Service::MissingValues
nil # If we are missing any service value, return nothing
end

def build_cloud(cloud = nil)
return unless cloud
return Cloud.new unless cloud
return cloud if cloud.is_a?(Cloud)

Cloud.new(**cloud)
end

def build_service(service = nil)
return Service.new unless service
return service if service.is_a?(Service)

Service.new(**service)
end
end
end
end
Expand Down
20 changes: 20 additions & 0 deletions lib/elastic_apm/spies.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,26 @@ def self.register(*args)
end
end

def self.without_faraday
return yield unless defined?(FaradaySpy)

# rubocop:disable Style/ExplicitBlockArgument
ElasticAPM::Spies::FaradaySpy.disable_in do
yield
end
# rubocop:enable Style/ExplicitBlockArgument
end

def self.without_net_http
return yield unless defined?(NetHTTPSpy)

# rubocop:disable Style/ExplicitBlockArgument
ElasticAPM::Spies::NetHTTPSpy.disable_in do
yield
end
# rubocop:enable Style/ExplicitBlockArgument
end

def self.register_require_hook(registration)
registration.require_paths.each do |path|
require_hooks[path] = registration
Expand Down
Loading

0 comments on commit a96aea3

Please sign in to comment.