elastic-apm 4.1.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []