elastic-apm 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9c57d8f754de9ab6d024f95f2822b0eae82f17a00cd3edae13d290c9b850393
4
- data.tar.gz: 4d32a175d46dac86cabdf97de023a49e7e5eb155a21be11eb227bf61afdb8ffc
3
+ metadata.gz: 81641cee86d90236fac69836ed6b6e4e858e0650d099c7cf0ab2fbc58e9d8550
4
+ data.tar.gz: a8f13b6c5b4b430c4cba21f92167b784634bc151ef802472e9a4fc1af6f95c1b
5
5
  SHA512:
6
- metadata.gz: '09cddb89d0d5265c7c12e8838702cde336754e197d8f4e6647f3885ccde3f6fc806f91b22ab054cf8c5c465373e34bf0f46580a2ab01513da5246d9ea443bb2c'
7
- data.tar.gz: 10732912f09d131204363dd0191e59f689d9560935af8df0b195cd4a145e9402c4a5f3cc3ed136399471f1a406efd7202a7d8384772bc6356b4a64752333cd03
6
+ metadata.gz: 3db0a577687ac11af79c29de68c3935cb0df9572bed2583c5d9666dd15aefabb97a0ab0a081b79fd41ee7e53e1e3319700d0ebd74a30883eaa55b45684ea9862
7
+ data.tar.gz: a191284ee54fd8be1511927ff1af81f13bb129c6f282d2078099cf8a79fe124ec5f995243260d66a23920f6d82fdb1955614cdbe260b58413bb9863ee0226a3a
data/CHANGELOG.asciidoc CHANGED
@@ -35,6 +35,21 @@ endif::[]
35
35
  [[release-notes-4.x]]
36
36
  === Ruby Agent version 4.x
37
37
 
38
+ [[release-notes-4.2.0]]
39
+ ==== 4.2.0
40
+
41
+ [float]
42
+ ===== Added
43
+
44
+ - Add support for AWS Storage Table/CosmosDB {pull}999[#999]
45
+
46
+ [float]
47
+ ===== Fixed
48
+
49
+ - Align HTTP span types/subtypes with spec {pull}1014[#1014]
50
+ - Passing a full URL as a path to `Net::HTTP` {pull}1029[#1029]
51
+ - Fix growing number of open file descriptors {pull}1033[#1033]
52
+
38
53
  [[release-notes-4.1.0]]
39
54
  ==== 4.1.0
40
55
 
data/Gemfile CHANGED
@@ -36,6 +36,7 @@ gem 'aws-sdk-dynamodb', require: nil
36
36
  gem 'aws-sdk-s3', require: nil
37
37
  gem 'aws-sdk-sqs', require: nil
38
38
  gem 'aws-sdk-sns', require: nil
39
+ gem 'azure-storage-table', require: nil if RUBY_VERSION < '3.0'
39
40
  gem 'elasticsearch', require: nil
40
41
  gem 'fakeredis', require: nil
41
42
  gem 'faraday', require: nil
data/SECURITY.md ADDED
@@ -0,0 +1,7 @@
1
+ # Security Policy
2
+
3
+ Thanks for your interest in the security of our products.
4
+ Our security policy can be found at [https://www.elastic.co/community/security](https://www.elastic.co/community/security).
5
+
6
+ ## Reporting a Vulnerability
7
+ Please send security vulnerability reports to security@elastic.co.
data/docker-compose.yml CHANGED
@@ -11,7 +11,7 @@ services:
11
11
  build:
12
12
  context: .
13
13
  args:
14
- BUNDLER_VERSION: '2.0.2'
14
+ BUNDLER_VERSION: '2.2.21'
15
15
  image: '$IMAGE_NAME'
16
16
  environment:
17
17
  HOME: '/tmp'
data/lib/elastic_apm.rb CHANGED
@@ -32,6 +32,7 @@ require 'elastic_apm/internal_error'
32
32
  require 'elastic_apm/logging'
33
33
 
34
34
  # Core
35
+ require 'elastic_apm/fields'
35
36
  require 'elastic_apm/agent'
36
37
  require 'elastic_apm/config'
37
38
  require 'elastic_apm/context'
@@ -280,8 +280,8 @@ module ElasticAPM
280
280
  def detect_forking!
281
281
  return if @pid == Process.pid
282
282
 
283
- config.logger.debug "Forked process detected,
284
- restarting threads in process [PID:#{Process.pid}]"
283
+ config.logger.debug(
284
+ "Forked process detected, restarting threads in process [PID:#{Process.pid}]")
285
285
 
286
286
  central_config.handle_forking!
287
287
  transport.handle_forking!
@@ -119,7 +119,7 @@ module ElasticAPM
119
119
  end
120
120
 
121
121
  if resp.status == 304
122
- info 'Received 304 Not Modified'
122
+ debug 'Received 304 Not Modified'
123
123
  else
124
124
  if resp.body && !resp.body.empty?
125
125
  update = JSON.parse(resp.body.to_s)
@@ -169,7 +169,7 @@ module ElasticAPM
169
169
  end
170
170
 
171
171
  def headers
172
- { 'Etag': @etag }
172
+ { 'If-None-Match': @etag }
173
173
  end
174
174
 
175
175
  def schedule_next_fetch(resp = nil)
@@ -133,6 +133,7 @@ module ElasticAPM
133
133
  def available_instrumentations
134
134
  %w[
135
135
  action_dispatch
136
+ azure_storage_table
136
137
  delayed_job
137
138
  dynamo_db
138
139
  elasticsearch
@@ -0,0 +1,88 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+ #
18
+ # frozen_string_literal: true
19
+
20
+ module ElasticAPM
21
+ # An interface for creating simple, value holding objects that correspond to
22
+ # object fields in the API.
23
+ #
24
+ # Example:
25
+ # class MyThing
26
+ # include Fields
27
+ # field :name
28
+ # field :address, optional: true
29
+ # end
30
+ #
31
+ # MyThing.new(name: 'AJ').to_h
32
+ # # => { name: 'AJ' }
33
+ # MyThing.new().empty?
34
+ # # => true
35
+ module Fields
36
+ module InstanceMethods
37
+ def initialize(**attrs)
38
+ attrs.each do |key, value|
39
+ self.send(:"#{key}=", value)
40
+ end
41
+
42
+ super()
43
+ end
44
+
45
+ def empty?
46
+ self.class.fields.each do |key|
47
+ next if send(key)
48
+ next if optionals.include?(key)
49
+
50
+ return true
51
+ end
52
+
53
+ false
54
+ end
55
+
56
+ def to_h
57
+ self.class.fields.each_with_object({}) do |key, fields|
58
+ fields[key] = send(key)
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def optionals
65
+ self.class.optionals
66
+ end
67
+ end
68
+
69
+ module ClassMethods
70
+ def field(key, optional: false)
71
+ attr_accessor(key)
72
+
73
+ fields.push(key)
74
+ optionals.push(key) if optional
75
+ end
76
+
77
+ attr_reader :fields, :optionals
78
+ end
79
+
80
+ def self.included(cls)
81
+ cls.extend(ClassMethods)
82
+ cls.include(InstanceMethods)
83
+
84
+ cls.instance_variable_set(:@fields, [])
85
+ cls.instance_variable_set(:@optionals, [])
86
+ end
87
+ end
88
+ end
@@ -106,6 +106,18 @@ module ElasticAPM
106
106
  @duration ||= (clock_end - @clock_start)
107
107
  @parent.child_stopped
108
108
  @self_time = @duration - child_durations.duration
109
+
110
+ if exit_span?
111
+ context.destination ||= Context::Destination.new
112
+ context.destination.service ||= Context::Destination::Service.new
113
+ context.destination.service.resource ||= (subtype || type)
114
+
115
+ # Deprecated fields but required by some versions of APM Server, so
116
+ # we auto-infer them from existing fields
117
+ context.destination.service.name ||= (subtype || type)
118
+ context.destination.service.type ||= type
119
+ end
120
+
109
121
  self
110
122
  end
111
123
 
@@ -144,6 +156,8 @@ module ElasticAPM
144
156
  "<ElasticAPM::Span id:#{trace_context&.id}" \
145
157
  " name:#{name.inspect}" \
146
158
  " type:#{type.inspect}" \
159
+ " subtype:#{subtype.inspect}" \
160
+ " action:#{action.inspect}" \
147
161
  '>'
148
162
  end
149
163
 
@@ -166,5 +180,9 @@ module ElasticAPM
166
180
 
167
181
  duration >= min_duration
168
182
  end
183
+
184
+ def exit_span?
185
+ context.destination || context.db || context.message || context.http
186
+ end
169
187
  end
170
188
  end
@@ -14,7 +14,7 @@
14
14
  # KIND, either express or implied. See the License for the
15
15
  # specific language governing permissions and limitations
16
16
  # under the License.
17
-
17
+ #
18
18
  # frozen_string_literal: true
19
19
 
20
20
  module ElasticAPM
@@ -22,74 +22,53 @@ module ElasticAPM
22
22
  class Context
23
23
  # @api private
24
24
  class Destination
25
+ include Fields
26
+
27
+ field :address
28
+ field :port
29
+ field :service
30
+ field :cloud
25
31
 
26
32
  # @api private
27
33
  class Service
28
- class MissingValues < StandardError; end
29
-
30
- def initialize(name: nil, type: nil, resource: nil)
31
- @name = name
32
- @type = type
33
- @resource = resource
34
+ include Fields
34
35
 
35
- # APM Server expects all values for service
36
- raise MissingValues unless @name && @type && resource
37
- end
38
-
39
- attr_accessor :name, :type, :resource
36
+ field :name
37
+ field :type
38
+ field :resource
40
39
  end
41
40
 
42
41
  # @api private
43
42
  class Cloud
44
- def initialize(region: nil)
45
- @region = region
46
- end
43
+ include Fields
47
44
 
48
- attr_accessor :region
45
+ field :region
49
46
  end
50
47
 
51
- def initialize(
52
- address: nil,
53
- port: nil,
54
- service: nil,
55
- cloud: nil
56
- )
57
- @address = address
58
- @port = port
59
- @service = build_service(service)
60
- @cloud = build_cloud(cloud)
61
- end
48
+ def initialize(service: nil, cloud: nil, **attrs)
49
+ super(**attrs)
62
50
 
63
- attr_reader(
64
- :address,
65
- :port,
66
- :service,
67
- :cloud
68
- )
51
+ self.service = build_service(service)
52
+ self.cloud = build_cloud(cloud)
53
+ end
69
54
 
70
- def self.from_uri(uri_or_str, type: 'external', port: nil)
55
+ def self.from_uri(uri_or_str, type: nil, **attrs)
71
56
  uri = normalize(uri_or_str)
72
57
 
73
- service = Service.new(
74
- name: only_scheme_and_host(uri),
75
- resource: "#{uri.host}:#{uri.port}",
76
- type: type
77
- )
58
+ service =
59
+ case type
60
+ when 'http' then http_service(uri)
61
+ else nil
62
+ end
78
63
 
79
64
  new(
80
65
  address: uri.hostname,
81
- port: port || uri.port,
82
- service: service
66
+ port: uri.port,
67
+ service: service,
68
+ **attrs
83
69
  )
84
70
  end
85
71
 
86
- def self.only_scheme_and_host(uri_or_str)
87
- uri = normalize(uri_or_str)
88
- uri.path = ''
89
- uri.password = uri.query = uri.fragment = nil
90
- uri.to_s
91
- end
92
-
93
72
  class << self
94
73
  private
95
74
 
@@ -97,25 +76,27 @@ module ElasticAPM
97
76
  return uri_or_str.dup if uri_or_str.is_a?(URI)
98
77
  URI(uri_or_str)
99
78
  end
79
+
80
+ def http_service(uri)
81
+ Service.new(resource: "#{uri.host}:#{uri.port}")
82
+ end
100
83
  end
101
84
 
102
85
  private
103
86
 
104
- def build_service(service = nil)
105
- return unless service
106
- return service if service.is_a?(Service)
107
-
108
- Service.new(**service)
109
- rescue Service::MissingValues
110
- nil # If we are missing any service value, return nothing
111
- end
112
-
113
87
  def build_cloud(cloud = nil)
114
- return unless cloud
88
+ return Cloud.new unless cloud
115
89
  return cloud if cloud.is_a?(Cloud)
116
90
 
117
91
  Cloud.new(**cloud)
118
92
  end
93
+
94
+ def build_service(service = nil)
95
+ return Service.new unless service
96
+ return service if service.is_a?(Service)
97
+
98
+ Service.new(**service)
99
+ end
119
100
  end
120
101
  end
121
102
  end
@@ -56,6 +56,26 @@ module ElasticAPM
56
56
  end
57
57
  end
58
58
 
59
+ def self.without_faraday
60
+ return yield unless defined?(FaradaySpy)
61
+
62
+ # rubocop:disable Style/ExplicitBlockArgument
63
+ ElasticAPM::Spies::FaradaySpy.disable_in do
64
+ yield
65
+ end
66
+ # rubocop:enable Style/ExplicitBlockArgument
67
+ end
68
+
69
+ def self.without_net_http
70
+ return yield unless defined?(NetHTTPSpy)
71
+
72
+ # rubocop:disable Style/ExplicitBlockArgument
73
+ ElasticAPM::Spies::NetHTTPSpy.disable_in do
74
+ yield
75
+ end
76
+ # rubocop:enable Style/ExplicitBlockArgument
77
+ end
78
+
59
79
  def self.register_require_hook(registration)
60
80
  registration.require_paths.each do |path|
61
81
  require_hooks[path] = registration
@@ -0,0 +1,148 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+ #
18
+ # frozen_string_literal: true
19
+
20
+ module ElasticAPM
21
+ # @api private
22
+ module Spies
23
+ # @api private
24
+ class AzureStorageTableSpy
25
+ TYPE = "storage"
26
+ SUBTYPE = "azuretable"
27
+
28
+ module Helpers
29
+ class << self
30
+ def instrument(operation_name, table_name = nil, service:)
31
+ span_name = span_name(operation_name, table_name)
32
+ action = formatted_op_name(operation_name)
33
+ account_name = account_name_from_storage_table_host(service.storage_service_host[:primary])
34
+
35
+ destination = ElasticAPM::Span::Context::Destination.from_uri(service.storage_service_host[:primary])
36
+ destination.service.resource = "#{SUBTYPE}/#{account_name}"
37
+
38
+ context = ElasticAPM::Span::Context.new(destination: destination)
39
+
40
+ ElasticAPM.with_span(span_name, TYPE, subtype: SUBTYPE, action: action, context: context) do
41
+ ElasticAPM::Spies.without_faraday do
42
+ ElasticAPM::Spies.without_net_http do
43
+ yield
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ DEFAULT_OP_NAMES = {
52
+ "create_table" => "Create",
53
+ "delete_table" => "Delete",
54
+ "get_table_acl" => "GetAcl",
55
+ "set_table_acl" => "SetAcl",
56
+ "insert_entity" => "Insert",
57
+ "query_entities" => "Query",
58
+ "update_entity" => "Update",
59
+ "merge_entity" => "Merge",
60
+ "delete_entity" => "Delete"
61
+ }.freeze
62
+
63
+ def formatted_op_names
64
+ @formatted_op_names ||= Concurrent::Map.new
65
+ end
66
+
67
+ def account_names
68
+ @account_names ||= Concurrent::Map.new
69
+ end
70
+
71
+ def span_name(operation_name, table_name = nil)
72
+ base = "AzureTable #{formatted_op_name(operation_name)}"
73
+
74
+ return base unless table_name
75
+
76
+ "#{base} #{table_name}"
77
+ end
78
+
79
+ def formatted_op_name(operation_name)
80
+ formatted_op_names.compute_if_absent(operation_name) do
81
+ DEFAULT_OP_NAMES.fetch(operation_name) do
82
+ operation_name.to_s.split("_").collect(&:capitalize).join
83
+ end
84
+ end
85
+ end
86
+
87
+ def account_name_from_storage_table_host(host)
88
+ account_names.compute_if_absent(host) do
89
+ URI(host).host.split(".").first || "unknown"
90
+ end
91
+ rescue Exception
92
+ "unknown"
93
+ end
94
+ end
95
+ end
96
+
97
+ # @api private
98
+ module Ext
99
+ # Methods with table_name as first parameter
100
+ %i[
101
+ create_table
102
+ delete_table
103
+ get_table
104
+ get_table_acl
105
+ set_table_acl
106
+ insert_entity
107
+ query_entities
108
+ update_entity
109
+ merge_entity
110
+ delete_entity
111
+ ].each do |method_name|
112
+ define_method(method_name) do |table_name, *args|
113
+ unless (transaction = ElasticAPM.current_transaction)
114
+ return super(table_name, *args)
115
+ end
116
+
117
+ ElasticAPM::Spies::AzureStorageTableSpy::Helpers.instrument(
118
+ method_name.to_s, table_name, service: self
119
+ ) do
120
+ super(table_name, *args)
121
+ end
122
+ end
123
+ end
124
+
125
+ # Methods WITHOUT table_name as first parameter
126
+ def query_tables(*args)
127
+ unless (transaction = ElasticAPM.current_transaction)
128
+ return super(*args)
129
+ end
130
+
131
+ ElasticAPM::Spies::AzureStorageTableSpy::Helpers.instrument("query_tables", service: self) do
132
+ super(*args)
133
+ end
134
+ end
135
+ end
136
+
137
+ def install
138
+ ::Azure::Storage::Table::TableService.prepend(Ext)
139
+ end
140
+ end
141
+
142
+ register(
143
+ "Azure::Storage::Table::TableService",
144
+ "azure/storage/table",
145
+ AzureStorageTableSpy.new
146
+ )
147
+ end
148
+ end
@@ -56,8 +56,6 @@ module ElasticAPM
56
56
  # Alias all available operations
57
57
  mod.api.operation_names.each do |operation_name|
58
58
  define_method(operation_name) do |params = {}, options = {}|
59
- cloud = ElasticAPM::Span::Context::Destination::Cloud.new(region: config.region)
60
-
61
59
  context = ElasticAPM::Span::Context.new(
62
60
  db: {
63
61
  instance: config.region,
@@ -65,12 +63,8 @@ module ElasticAPM
65
63
  statement: params[:key_condition_expression]
66
64
  },
67
65
  destination: {
68
- cloud: cloud,
69
- service: {
70
- name: NAME,
71
- resource: SUBTYPE,
72
- type: TYPE
73
- }
66
+ service: { resource: "#{SUBTYPE}/#{config.region}" },
67
+ cloud: { region: config.region }
74
68
  }
75
69
  )
76
70
 
@@ -22,17 +22,28 @@ module ElasticAPM
22
22
  module Spies
23
23
  # @api private
24
24
  class FaradaySpy
25
- TYPE = 'ext'
26
- SUBTYPE = 'faraday'
25
+ DISABLE_KEY = :__elastic_apm_faraday_disabled
26
+ TYPE = 'external'
27
+ SUBTYPE = 'http'
27
28
 
28
- def self.without_net_http
29
- return yield unless defined?(NetHTTPSpy)
29
+ class << self
30
+ def disabled=(disabled)
31
+ Thread.current[DISABLE_KEY] = disabled
32
+ end
33
+
34
+ def disabled?
35
+ Thread.current[DISABLE_KEY] ||= false
36
+ end
37
+
38
+ def disable_in
39
+ self.disabled = true
30
40
 
31
- # rubocop:disable Style/ExplicitBlockArgument
32
- ElasticAPM::Spies::NetHTTPSpy.disable_in do
33
- yield
41
+ begin
42
+ yield
43
+ ensure
44
+ self.disabled = false
45
+ end
34
46
  end
35
- # rubocop:enable Style/ExplicitBlockArgument
36
47
  end
37
48
 
38
49
  # @api private
@@ -43,6 +54,10 @@ module ElasticAPM
43
54
  return super(method, url, body, headers, &block)
44
55
  end
45
56
 
57
+ if ElasticAPM::Spies::FaradaySpy.disabled?
58
+ return super(method, url, body, headers, &block)
59
+ end
60
+
46
61
  uri = URI(build_url(url))
47
62
 
48
63
  # If url is set inside block it isn't available until yield,
@@ -61,7 +76,7 @@ module ElasticAPM
61
76
  upcased_method = method.to_s.upcase
62
77
 
63
78
  if uri
64
- destination = ElasticAPM::Span::Context::Destination.from_uri(uri)
79
+ destination = ElasticAPM::Span::Context::Destination.from_uri(uri, type: SUBTYPE)
65
80
 
66
81
  context =
67
82
  ElasticAPM::Span::Context.new(
@@ -83,10 +98,9 @@ module ElasticAPM
83
98
  "#{upcased_method} #{host}",
84
99
  TYPE,
85
100
  subtype: SUBTYPE,
86
- action: upcased_method,
87
101
  context: context
88
102
  ) do |span|
89
- ElasticAPM::Spies::FaradaySpy.without_net_http do
103
+ ElasticAPM::Spies.without_net_http do
90
104
  trace_context = span&.trace_context || transaction.trace_context
91
105
 
92
106
  result = super(method, url, body, headers) do |req|
@@ -22,8 +22,8 @@ module ElasticAPM
22
22
  module Spies
23
23
  # @api private
24
24
  class HTTPSpy
25
- TYPE = 'ext'
26
- SUBTYPE = 'http_rb'
25
+ TYPE = 'external'
26
+ SUBTYPE = 'http'
27
27
 
28
28
  # @api private
29
29
  module Ext
@@ -37,7 +37,7 @@ module ElasticAPM
37
37
 
38
38
  context = ElasticAPM::Span::Context.new(
39
39
  http: { url: req.uri, method: method },
40
- destination: ElasticAPM::Span::Context::Destination.from_uri(req.uri)
40
+ destination: ElasticAPM::Span::Context::Destination.from_uri(req.uri, type: SUBTYPE)
41
41
  )
42
42
 
43
43
  name = "#{method} #{host}"
@@ -46,7 +46,6 @@ module ElasticAPM
46
46
  name,
47
47
  TYPE,
48
48
  subtype: SUBTYPE,
49
- action: method,
50
49
  context: context
51
50
  ) do |span|
52
51
  trace_context = span&.trace_context || transaction.trace_context
@@ -22,17 +22,17 @@ module ElasticAPM
22
22
  module Spies
23
23
  # @api private
24
24
  class NetHTTPSpy
25
- KEY = :__elastic_apm_net_http_disabled
26
- TYPE = 'ext'
27
- SUBTYPE = 'net_http'
25
+ DISABLE_KEY = :__elastic_apm_net_http_disabled
26
+ TYPE = 'external'
27
+ SUBTYPE = 'http'
28
28
 
29
29
  class << self
30
30
  def disabled=(disabled)
31
- Thread.current[KEY] = disabled
31
+ Thread.current[DISABLE_KEY] = disabled
32
32
  end
33
33
 
34
34
  def disabled?
35
- Thread.current[KEY] ||= false
35
+ Thread.current[DISABLE_KEY] ||= false
36
36
  end
37
37
 
38
38
  def disable_in
@@ -60,26 +60,33 @@ module ElasticAPM
60
60
 
61
61
  host = req['host']&.split(':')&.first || address || 'localhost'
62
62
  method = req.method.to_s.upcase
63
- path, query = req.path.split('?')
64
63
 
65
- url = use_ssl? ? +'https://' : +'http://'
66
- url << host
67
- url << ":#{port}" if port
68
- url << path
69
- url << "?#{query}" if query
70
- uri = URI(url)
64
+ uri_or_path = URI(req.path)
65
+
66
+ # Support the case where a whole url is passed as a path to a nil host
67
+ uri =
68
+ if uri_or_path.host
69
+ uri_or_path
70
+ else
71
+ path, query = req.path.split('?')
72
+ url = use_ssl? ? +'https://' : +'http://'
73
+ url << host
74
+ url << ":#{port}" if port
75
+ url << path
76
+ url << "?#{query}" if query
77
+ URI(url)
78
+ end
71
79
 
72
80
  context =
73
81
  ElasticAPM::Span::Context.new(
74
82
  http: { url: uri, method: method },
75
- destination: ElasticAPM::Span::Context::Destination.from_uri(uri)
83
+ destination: ElasticAPM::Span::Context::Destination.from_uri(uri, type: SUBTYPE)
76
84
  )
77
85
 
78
86
  ElasticAPM.with_span(
79
87
  "#{method} #{host}",
80
88
  TYPE,
81
89
  subtype: SUBTYPE,
82
- action: method,
83
90
  context: context
84
91
  ) do |span|
85
92
  trace_context = span&.trace_context || transaction.trace_context
@@ -14,7 +14,7 @@
14
14
  # KIND, either express or implied. See the License for the
15
15
  # specific language governing permissions and limitations
16
16
  # under the License.
17
-
17
+ #
18
18
  # frozen_string_literal: true
19
19
 
20
20
  module ElasticAPM
@@ -85,14 +85,11 @@ module ElasticAPM
85
85
  bucket_name = ElasticAPM::Spies::S3Spy.bucket_name(params)
86
86
  region = ElasticAPM::Spies::S3Spy.accesspoint_region(params) || config.region
87
87
 
88
+ resource = "#{SUBTYPE}/#{bucket_name || 'unknown-bucket'}"
88
89
  context = ElasticAPM::Span::Context.new(
89
90
  destination: {
90
- cloud: { region: region },
91
- service: {
92
- resource: bucket_name,
93
- type: TYPE,
94
- name: SUBTYPE
95
- }
91
+ service: { resource: resource },
92
+ cloud: { region: region }
96
93
  }
97
94
  )
98
95
 
@@ -46,7 +46,7 @@ module ElasticAPM
46
46
 
47
47
  context = ElasticAPM::Span::Context.new(
48
48
  db: { statement: sql, type: 'sql', user: opts[:user] },
49
- destination: { service: { name: subtype, resource: subtype, type: TYPE } }
49
+ destination: { service: { resource: subtype } }
50
50
  )
51
51
 
52
52
  span = ElasticAPM.start_span(
@@ -65,16 +65,10 @@ module ElasticAPM
65
65
 
66
66
  def self.span_context(topic, region)
67
67
  ElasticAPM::Span::Context.new(
68
- message: {
69
- queue_name: topic
70
- },
68
+ message: { queue_name: topic },
71
69
  destination: {
72
- service: {
73
- resource: [SUBTYPE, topic].compact.join('/'),
74
- type: TYPE,
75
- name: SUBTYPE,
76
- },
77
- cloud: ElasticAPM::Span::Context::Destination::Cloud.new(region: region)
70
+ service: { resource: "#{SUBTYPE}/#{topic}" },
71
+ cloud: { region: region }
78
72
  }
79
73
  )
80
74
  end
@@ -50,19 +50,11 @@ module ElasticAPM
50
50
  end
51
51
 
52
52
  def self.span_context(queue_name, region)
53
- cloud = ElasticAPM::Span::Context::Destination::Cloud.new(region: region)
54
-
55
53
  ElasticAPM::Span::Context.new(
56
- message: {
57
- queue_name: queue_name
58
- },
54
+ message: { queue_name: queue_name },
59
55
  destination: {
60
- service: {
61
- resource: [SUBTYPE, queue_name].compact.join('/'),
62
- type: TYPE,
63
- name: SUBTYPE
64
- },
65
- cloud: cloud
56
+ service: { resource: "#{SUBTYPE}/#{queue_name}" },
57
+ cloud: { region: region }
66
58
  }
67
59
  )
68
60
  end
@@ -45,6 +45,7 @@ module ElasticAPM
45
45
  # AS::Notifications API
46
46
 
47
47
  Notification = Struct.new(:id, :span)
48
+
48
49
  def start(name, id, payload)
49
50
  return unless (transaction = @agent.current_transaction)
50
51
 
@@ -85,7 +85,7 @@ module ElasticAPM
85
85
  end
86
86
 
87
87
  def closed?
88
- @closed.true?
88
+ @rd.closed? && @closed.true?
89
89
  end
90
90
 
91
91
  def inspect
@@ -117,6 +117,8 @@ module ElasticAPM
117
117
  error(
118
118
  "Couldn't establish connection to APM Server:\n%p", e.inspect
119
119
  )
120
+ ensure
121
+ @rd&.close
120
122
  end
121
123
  end
122
124
  end
@@ -100,16 +100,12 @@ module ElasticAPM
100
100
  port: destination.port
101
101
  }
102
102
 
103
- if service = destination.service
104
- base[:service] = {
105
- name: keyword_field(destination.service.name),
106
- resource: keyword_field(destination.service.resource),
107
- type: keyword_field(destination.service.type)
108
- }
103
+ unless destination.service&.empty?
104
+ base[:service] = destination.service.to_h
109
105
  end
110
106
 
111
- if cloud = destination.cloud
112
- base[:cloud] = { region: cloud.region }
107
+ unless destination.cloud&.empty?
108
+ base[:cloud] = destination.cloud.to_h
113
109
  end
114
110
 
115
111
  base
@@ -18,5 +18,5 @@
18
18
  # frozen_string_literal: true
19
19
 
20
20
  module ElasticAPM
21
- VERSION = '4.1.0'
21
+ VERSION = '4.2.0'
22
22
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elastic-apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikkel Malmberg
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-27 00:00:00.000000000 Z
11
+ date: 2021-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -38,7 +38,7 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.0'
41
- description:
41
+ description:
42
42
  email:
43
43
  - mikkel@elastic.co
44
44
  executables: []
@@ -85,6 +85,7 @@ files:
85
85
  - LICENSE
86
86
  - README.md
87
87
  - Rakefile
88
+ - SECURITY.md
88
89
  - bench/.gitignore
89
90
  - bench/app.rb
90
91
  - bench/benchmark.rb
@@ -148,6 +149,7 @@ files:
148
149
  - lib/elastic_apm/error/exception.rb
149
150
  - lib/elastic_apm/error/log.rb
150
151
  - lib/elastic_apm/error_builder.rb
152
+ - lib/elastic_apm/fields.rb
151
153
  - lib/elastic_apm/grape.rb
152
154
  - lib/elastic_apm/graphql.rb
153
155
  - lib/elastic_apm/grpc.rb
@@ -193,6 +195,7 @@ files:
193
195
  - lib/elastic_apm/span_helpers.rb
194
196
  - lib/elastic_apm/spies.rb
195
197
  - lib/elastic_apm/spies/action_dispatch.rb
198
+ - lib/elastic_apm/spies/azure_storage_table.rb
196
199
  - lib/elastic_apm/spies/delayed_job.rb
197
200
  - lib/elastic_apm/spies/dynamo_db.rb
198
201
  - lib/elastic_apm/spies/elasticsearch.rb
@@ -254,7 +257,7 @@ licenses:
254
257
  - Apache-2.0
255
258
  metadata:
256
259
  source_code_uri: https://github.com/elastic/apm-agent-ruby
257
- post_install_message:
260
+ post_install_message:
258
261
  rdoc_options: []
259
262
  require_paths:
260
263
  - lib
@@ -269,8 +272,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
269
272
  - !ruby/object:Gem::Version
270
273
  version: '0'
271
274
  requirements: []
272
- rubygems_version: 3.1.6
273
- signing_key:
275
+ rubygems_version: 3.0.3.1
276
+ signing_key:
274
277
  specification_version: 4
275
278
  summary: The official Elastic APM agent for Ruby
276
279
  test_files: []