elastic-apm 4.1.0 → 4.5.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/Jenkinsfile +1 -1
  3. data/.ci/docker/jruby/11-jdk/Dockerfile +1 -1
  4. data/.ci/docker/jruby/12-jdk/Dockerfile +1 -1
  5. data/.ci/docker/jruby/13-jdk/Dockerfile +1 -1
  6. data/.ci/docker/jruby/7-jdk/Dockerfile +1 -1
  7. data/.ci/docker/jruby/8-jdk/Dockerfile +1 -1
  8. data/.ci/jobs/apm-agent-ruby-mbp.yml +1 -0
  9. data/CHANGELOG.asciidoc +51 -0
  10. data/Gemfile +5 -4
  11. data/SECURITY.md +7 -0
  12. data/docker-compose.yml +1 -1
  13. data/docs/configuration.asciidoc +26 -2
  14. data/docs/metrics.asciidoc +92 -0
  15. data/elastic-apm.gemspec +8 -8
  16. data/lib/elastic_apm/agent.rb +4 -4
  17. data/lib/elastic_apm/central_config.rb +2 -2
  18. data/lib/elastic_apm/config.rb +20 -0
  19. data/lib/elastic_apm/context/request/socket.rb +1 -2
  20. data/lib/elastic_apm/fields.rb +98 -0
  21. data/lib/elastic_apm/instrumenter.rb +21 -19
  22. data/lib/elastic_apm/metadata/service_info.rb +1 -1
  23. data/lib/elastic_apm/metadata/system_info.rb +2 -1
  24. data/lib/elastic_apm/metrics/cpu_mem_set.rb +9 -6
  25. data/lib/elastic_apm/metrics/jvm_set.rb +88 -0
  26. data/lib/elastic_apm/metrics/metric.rb +2 -0
  27. data/lib/elastic_apm/metrics.rb +22 -8
  28. data/lib/elastic_apm/span/context/destination.rb +39 -58
  29. data/lib/elastic_apm/span.rb +11 -1
  30. data/lib/elastic_apm/spies/azure_storage_table.rb +148 -0
  31. data/lib/elastic_apm/spies/dynamo_db.rb +8 -9
  32. data/lib/elastic_apm/spies/faraday.rb +25 -11
  33. data/lib/elastic_apm/spies/http.rb +3 -4
  34. data/lib/elastic_apm/spies/mongo.rb +11 -3
  35. data/lib/elastic_apm/spies/net_http.rb +21 -14
  36. data/lib/elastic_apm/spies/s3.rb +11 -5
  37. data/lib/elastic_apm/spies/sequel.rb +1 -1
  38. data/lib/elastic_apm/spies/sns.rb +3 -9
  39. data/lib/elastic_apm/spies/sqs.rb +3 -11
  40. data/lib/elastic_apm/spies.rb +20 -0
  41. data/lib/elastic_apm/subscriber.rb +1 -0
  42. data/lib/elastic_apm/transport/connection/http.rb +3 -1
  43. data/lib/elastic_apm/transport/serializers/context_serializer.rb +1 -2
  44. data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +6 -2
  45. data/lib/elastic_apm/transport/serializers/span_serializer.rb +4 -8
  46. data/lib/elastic_apm/transport/user_agent.rb +12 -6
  47. data/lib/elastic_apm/version.rb +1 -1
  48. data/lib/elastic_apm.rb +6 -2
  49. metadata +7 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9c57d8f754de9ab6d024f95f2822b0eae82f17a00cd3edae13d290c9b850393
4
- data.tar.gz: 4d32a175d46dac86cabdf97de023a49e7e5eb155a21be11eb227bf61afdb8ffc
3
+ metadata.gz: c396bc981217532f2d6775de89e9a2aa7a1bcd501100588bbd700d0f4f2eb590
4
+ data.tar.gz: 94f859585a09fc99f02a4bbd261193e0ed1f74bdb9463968692a16f277b1e4f0
5
5
  SHA512:
6
- metadata.gz: '09cddb89d0d5265c7c12e8838702cde336754e197d8f4e6647f3885ccde3f6fc806f91b22ab054cf8c5c465373e34bf0f46580a2ab01513da5246d9ea443bb2c'
7
- data.tar.gz: 10732912f09d131204363dd0191e59f689d9560935af8df0b195cd4a145e9402c4a5f3cc3ed136399471f1a406efd7202a7d8384772bc6356b4a64752333cd03
6
+ metadata.gz: ae66a68c4651f695361b2028bf76132e8bc257fc141d3ad2872786bbab10b8e19bc3609a84478de53e56ad0c15695ad7debbcf62c4af8997833d0cf7cc7f6d23
7
+ data.tar.gz: c6b9fd6f80de4139cac0dd040e45d56f869eb2ff8b3db38df376e898490b8c2a5523a9bb3f1ba73a8583356ae5647ec67835b994597dc3db8d72c62ee506794f
data/.ci/Jenkinsfile CHANGED
@@ -39,7 +39,7 @@ pipeline {
39
39
  quietPeriod(10)
40
40
  }
41
41
  triggers {
42
- issueCommentTrigger('(?i).*(?:jenkins\\W+)?run\\W+(?:the\\W+)?(?:benchmark\\W+)?tests(?:\\W+please)?.*')
42
+ issueCommentTrigger("(${obltGitHubComments()}|^run benchmark tests)")
43
43
  }
44
44
  parameters {
45
45
  booleanParam(name: 'Run_As_Master_Branch', defaultValue: false, description: 'Allow to run any steps on a PR, some steps normally only run on master branch.')
@@ -5,7 +5,7 @@ RUN apt-get update \
5
5
  && apt-get install -y gcc \
6
6
  && rm -rf /var/lib/apt/lists/*
7
7
 
8
- ENV JRUBY_VERSION 9.2.10.0
8
+ ENV JRUBY_VERSION 9.2.16.0
9
9
  ENV JRUBY_SHA256 9199707712c683c525252ccb1de5cb8e75f53b790c5b57a18f6367039ec79553
10
10
 
11
11
  RUN mkdir -p /opt/jruby \
@@ -5,7 +5,7 @@ RUN apt-get update \
5
5
  && apt-get install -y gcc \
6
6
  && rm -rf /var/lib/apt/lists/*
7
7
 
8
- ENV JRUBY_VERSION 9.2.10.0
8
+ ENV JRUBY_VERSION 9.3.1.0
9
9
  ENV JRUBY_SHA256 9199707712c683c525252ccb1de5cb8e75f53b790c5b57a18f6367039ec79553
10
10
 
11
11
  RUN mkdir -p /opt/jruby \
@@ -5,7 +5,7 @@ RUN apt-get update \
5
5
  && apt-get install -y gcc \
6
6
  && rm -rf /var/lib/apt/lists/*
7
7
 
8
- ENV JRUBY_VERSION 9.2.10.0
8
+ ENV JRUBY_VERSION 9.3.1.0
9
9
  ENV JRUBY_SHA256 9199707712c683c525252ccb1de5cb8e75f53b790c5b57a18f6367039ec79553
10
10
 
11
11
  RUN mkdir -p /opt/jruby \
@@ -5,7 +5,7 @@ RUN apt-get update \
5
5
  && apt-get install -y gcc \
6
6
  && rm -rf /var/lib/apt/lists/*
7
7
 
8
- ENV JRUBY_VERSION 9.1.17.0
8
+ ENV JRUBY_VERSION 9.3.1.0
9
9
  ENV JRUBY_SHA256 6a22f7bf8fef1a52530a9c9781a9d374ad07bbbef0d3d8e2af0ff5cbead0dfd5
10
10
 
11
11
  RUN mkdir -p /opt/jruby \
@@ -5,7 +5,7 @@ RUN apt-get update \
5
5
  && apt-get install -y gcc \
6
6
  && rm -rf /var/lib/apt/lists/*
7
7
 
8
- ENV JRUBY_VERSION 9.2.10.0
8
+ ENV JRUBY_VERSION 9.2.14.0
9
9
  ENV JRUBY_SHA256 9199707712c683c525252ccb1de5cb8e75f53b790c5b57a18f6367039ec79553
10
10
 
11
11
  RUN mkdir -p /opt/jruby \
@@ -15,6 +15,7 @@
15
15
  repo: apm-agent-ruby
16
16
  repo-owner: elastic
17
17
  credentials-id: 2a9602aa-ab9f-4e52-baf3-b71ca88469c7-UserAndToken
18
+ head-filter-regex: '^(master|PR-.*|[3-4]\.x)$'
18
19
  ssh-checkout:
19
20
  credentials: f6c7695a-671e-4f4f-a331-acdce44ff9ba
20
21
  build-strategies:
data/CHANGELOG.asciidoc CHANGED
@@ -35,6 +35,57 @@ endif::[]
35
35
  [[release-notes-4.x]]
36
36
  === Ruby Agent version 4.x
37
37
 
38
+ [[release-notes-4.5.0]]
39
+ ==== 4.5.0
40
+
41
+ [float]
42
+ ===== Changed
43
+
44
+ - Stop collecting the field `http.request.socket.encrypted` {pull}1181[#1181]
45
+
46
+ [float]
47
+ ===== Fixed
48
+
49
+ - Fixed MongoDB spy thread safety {pull}1202[#1202]
50
+ - Fixed span context fields for DynamoDB instrumentation {pull}1178[#1178]
51
+ - Fixed span context fields for S3 instrumentation {pull}1179[#1179]
52
+ - Update user agent info to match spec {pull}1182[#1182]
53
+
54
+ [[release-notes-4.4.0]]
55
+ ==== 4.4.0
56
+
57
+ [float]
58
+ ===== Added
59
+ - Optional span to be ended instead of current span {pull}1039[#1039]
60
+ - Config option `log_ecs_formatting` {pull}1053[#1053]
61
+
62
+ [float]
63
+ ===== Fixed
64
+ - Fixed detecting Linux on Alpine for CPU/MEM metrics {pull}1057[#1057]
65
+
66
+ [[release-notes-4.3.0]]
67
+ ==== 4.3.0
68
+
69
+ [float]
70
+ ===== Added
71
+
72
+ - Add JVM memory metrics {pull}1040[#1040]
73
+
74
+ [[release-notes-4.2.0]]
75
+ ==== 4.2.0
76
+
77
+ [float]
78
+ ===== Added
79
+
80
+ - Add support for AWS Storage Table/CosmosDB {pull}999[#999]
81
+
82
+ [float]
83
+ ===== Fixed
84
+
85
+ - Align HTTP span types/subtypes with spec {pull}1014[#1014]
86
+ - Passing a full URL as a path to `Net::HTTP` {pull}1029[#1029]
87
+ - Fix growing number of open file descriptors {pull}1033[#1033]
88
+
38
89
  [[release-notes-4.1.0]]
39
90
  ==== 4.1.0
40
91
 
data/Gemfile CHANGED
@@ -36,6 +36,8 @@ 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'
40
+ gem 'ecs-logging', require: 'ecs_logging/logger'
39
41
  gem 'elasticsearch', require: nil
40
42
  gem 'fakeredis', require: nil
41
43
  gem 'faraday', require: nil
@@ -60,10 +62,6 @@ gem 'sucker_punch', '~> 2.0', require: nil
60
62
  gem 'yard', require: nil
61
63
  gem 'yarjuf'
62
64
 
63
- # See issue #6547 in the JRuby repo. When that bug is fixed,
64
- # we can use the latest version of the i18n gem.
65
- gem 'i18n', '< 1.8.8'
66
-
67
65
  ## Install Framework
68
66
  GITHUB_REPOS = {
69
67
  'grape' => 'ruby-grape/grape',
@@ -99,6 +97,9 @@ if frameworks_versions.key?('rails')
99
97
  end
100
98
 
101
99
  if RUBY_PLATFORM == 'java'
100
+ # See issue #6547 in the JRuby repo. It is fixed in JRuby 9.3
101
+ gem 'i18n', '< 1.8.8' if JRUBY_VERSION < '9.3'
102
+
102
103
  case rails = frameworks_versions['rails']
103
104
  when 'main'
104
105
  gem 'activerecord-jdbcsqlite3-adapter', git: 'https://github.com/jruby/activerecord-jdbc-adapter', glob: 'activerecord-jdbcsqlite3-adapter/*.gemspec'
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'
@@ -240,7 +240,7 @@ NOTE: This feature requires APM Server and Kibana >= 7.3.
240
240
  <<dynamic-configuration, image:./images/dynamic-config.svg[] >>
241
241
 
242
242
  |============
243
- | Environment | `Config` key | Default | Example |
243
+ | Environment | `Config` key | Default | Example
244
244
  | `ELASTIC_APM_CAPTURE_BODY` | `capture_body` | `"off"` | `"all"`
245
245
  |============
246
246
 
@@ -502,7 +502,31 @@ Use this option to ignore certain URL patterns such as healthchecks or admin sec
502
502
  | `ELASTIC_APM_INSTRUMENTED_RAKE_TASKS` | `instrumented_rake_tasks` | `[]` | `['my_task']`
503
503
  |============
504
504
 
505
- Elastic APM can instrument your Rake tasks. Theis is an opt-in field, as they are used are for a multitude of things.
505
+ Elastic APM can instrument your Rake tasks. This is an opt-in field, as they are used are for a multitude of things.
506
+
507
+ [float]
508
+ [[config-log-ecs-formatting]]
509
+ ==== `log_ecs_formatting`
510
+
511
+ [options="header"]
512
+ |============
513
+ | Environment | `Config` key | Default
514
+ | `ELASTIC_APM_LOG_ECS_FORMATTING` | `log_ecs_formatting` | `off`
515
+ |============
516
+
517
+ This is an experimental option that configures the agent to use the logger from the `ecs-logging` gem. The two
518
+ valid options are `off` and `override`.
519
+
520
+ Setting this option to `override` will set the agent logger to a `EcsLogging::Logger` instance and all logged output
521
+ will be in ECS-compatible json.
522
+
523
+ The `ecs-logging` gem must be installed before the agent is started. If `log_ecs_formatting` is set to `override`,
524
+ the agent will attempt to require the gem and if it cannot be loaded, it will fall back to using the standard Ruby
525
+ `::Logger` and log the load error.
526
+
527
+ Note that if you're using Rails, the agent will ignore this option and use the Rails logger. If you want to use a
528
+ `EcsLogging::Logger` when using Rails, set the agent's logger config option explicitly to a `EcsLogging::Logger`
529
+ instance.
506
530
 
507
531
  [float]
508
532
  [[config-log-level]]
@@ -141,3 +141,95 @@ The total time spent in garbage collection.
141
141
 
142
142
  **NB:** You need to enable Ruby's GC Profiler for this to get reported.
143
143
  You can do this at any time when your application boots by calling `GC::Profiler.enable`.
144
+
145
+ [float]
146
+ [[metrics-jvm-metrics]]
147
+ === JVM Metrics
148
+
149
+ The following metrics are available when using JRuby. They use the ruby java API to gather metrics via MXBean.
150
+
151
+ [float]
152
+ [[metric-jvm.memory.heap.used]]
153
+ ==== `jvm.memory.heap.used`
154
+
155
+ * *Type:* Long
156
+ * *Format:* Bytes
157
+
158
+ The amount of used heap memory in bytes.
159
+
160
+ [float]
161
+ [[metric-jvm.memory.heap.committed]]
162
+ ==== `jvm.memory.heap.committed`
163
+
164
+ * *Type:* Long
165
+ * *Format:* Bytes
166
+
167
+ The amount of heap memory in bytes that is committed for the Java virtual machine to use. This amount of memory is
168
+ guaranteed for the Java virtual machine to use.
169
+
170
+ [float]
171
+ [[metric-jvm.memory.heap.max]]
172
+ ==== `jvm.memory.heap.max`
173
+
174
+ * *Type:* Long
175
+ * *Format:* Bytes
176
+
177
+ The amount of heap memory in bytes that is committed for the Java virtual machine to use. This amount of memory is
178
+ guaranteed for the Java virtual machine to use.
179
+
180
+ [float]
181
+ [[metric-jvm.memory.non_heap.used]]
182
+ ==== `jvm.memory.non_heap.used`
183
+
184
+ * *Type:* Long
185
+ * *Format:* Bytes
186
+
187
+ The amount of used non-heap memory in bytes.
188
+
189
+ [float]
190
+ [[metric-jvm.memory.non_heap.committed]]
191
+ ==== `jvm.memory.non_heap.committed`
192
+
193
+ * *Type:* Long
194
+ * *Format:* Bytes
195
+
196
+ The amount of non-heap memory in bytes that is committed for the Java virtual machine to use. This amount of memory is
197
+ guaranteed for the Java virtual machine to use.
198
+
199
+ [float]
200
+ [[metric-jvm.memory.non_heap.max]]
201
+ ==== `jvm.memory.non_heap.max`
202
+
203
+ * *Type:* Long
204
+ * *Format:* Bytes
205
+
206
+ The maximum amount of non-heap memory in bytes that can be used for memory management. If the maximum memory size is
207
+ undefined, the value is -1.
208
+
209
+ [float]
210
+ [[metric-jvm.memory.heap.pool.used]]
211
+ ==== `jvm.memory.heap.pool.used`
212
+
213
+ * *Type:* Long
214
+ * *Format:* Bytes
215
+
216
+ The amount of used memory in bytes of the memory pool.
217
+
218
+ [float]
219
+ [[metric-jvm.memory.heap.pool.committed]]
220
+ ==== `jvm.memory.heap.pool.committed`
221
+
222
+ * *Type:* Long
223
+ * *Format:* Bytes
224
+
225
+ The amount of memory in bytes that is committed for the memory pool. This amount of memory is guaranteed for this
226
+ specific pool.
227
+
228
+ [float]
229
+ [[metric-jvm.memory.heap.pool.max]]
230
+ ==== `jvm.memory.heap.pool.max`
231
+
232
+ * *Type:* Long
233
+ * *Format:* Bytes
234
+
235
+ The maximum amount of memory in bytes that can be used for the memory pool.
data/elastic-apm.gemspec CHANGED
@@ -20,15 +20,15 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
20
20
  require 'elastic_apm/version'
21
21
 
22
22
  Gem::Specification.new do |spec|
23
- spec.name = 'elastic-apm'
24
- spec.version = ElasticAPM::VERSION
25
- spec.authors = ['Mikkel Malmberg']
26
- spec.email = ['mikkel@elastic.co']
23
+ spec.name = 'elastic-apm'
24
+ spec.version = ElasticAPM::VERSION
25
+ spec.authors = ['Mikkel Malmberg']
26
+ spec.email = ['mikkel@elastic.co']
27
27
 
28
- spec.summary = 'The official Elastic APM agent for Ruby'
29
- spec.homepage = 'https://github.com/elastic/apm-agent-ruby'
30
- spec.metadata = { 'source_code_uri' => 'https://github.com/elastic/apm-agent-ruby' }
31
- spec.license = 'Apache-2.0'
28
+ spec.summary = 'The official Elastic APM agent for Ruby'
29
+ spec.homepage = 'https://github.com/elastic/apm-agent-ruby'
30
+ spec.metadata = { 'source_code_uri' => 'https://github.com/elastic/apm-agent-ruby' }
31
+ spec.license = 'Apache-2.0'
32
32
  spec.required_ruby_version = ">= 2.3.0"
33
33
 
34
34
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
@@ -211,8 +211,8 @@ module ElasticAPM
211
211
  end
212
212
  # rubocop:enable Metrics/ParameterLists
213
213
 
214
- def end_span
215
- instrumenter.end_span
214
+ def end_span(span = nil)
215
+ instrumenter.end_span(span)
216
216
  end
217
217
 
218
218
  def set_label(key, value)
@@ -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)
@@ -70,6 +70,7 @@ module ElasticAPM
70
70
  option :ignore_url_patterns, type: :list, default: [], converter: RegexpList.new
71
71
  option :instrument, type: :bool, default: true
72
72
  option :instrumented_rake_tasks, type: :list, default: []
73
+ option :log_ecs_formatting, type: :string, default: 'off'
73
74
  option :log_level, type: :int, default: Logger::INFO, converter: LogLevelMap.new
74
75
  option :log_path, type: :string
75
76
  option :metrics_interval, type: :int, default: '30s', converter: Duration.new
@@ -133,6 +134,7 @@ module ElasticAPM
133
134
  def available_instrumentations
134
135
  %w[
135
136
  action_dispatch
137
+ azure_storage_table
136
138
  delayed_job
137
139
  dynamo_db
138
140
  elasticsearch
@@ -245,11 +247,29 @@ module ElasticAPM
245
247
  end
246
248
 
247
249
  def build_logger
250
+ if self.log_ecs_formatting == 'override'
251
+ begin
252
+ return build_ecs_logger
253
+ rescue LoadError
254
+ logger.info "Attempted to use EcsLogging::Logger but the gem couldn't be " \
255
+ "loaded so a ::Logger was created instead. Check if you have the `ecs-logging` " \
256
+ "gem installed and attempt to start the agent again."
257
+ end
258
+ end
259
+
248
260
  Logger.new(log_path == '-' ? $stdout : log_path).tap do |logger|
249
261
  logger.level = log_level
250
262
  end
251
263
  end
252
264
 
265
+ def build_ecs_logger
266
+ require 'ecs_logging/logger'
267
+
268
+ ::EcsLogging::Logger.new(log_path == '-' ? $stdout : log_path).tap do |logger|
269
+ logger.level = log_level
270
+ end
271
+ end
272
+
253
273
  def app_type?(app)
254
274
  if defined?(::Rails::Application) && app.is_a?(::Rails::Application)
255
275
  return :rails
@@ -26,10 +26,9 @@ module ElasticAPM
26
26
  class Socket
27
27
  def initialize(req)
28
28
  @remote_addr = req.env['REMOTE_ADDR']
29
- @encrypted = req.scheme == 'https'
30
29
  end
31
30
 
32
- attr_reader :remote_addr, :encrypted
31
+ attr_reader :remote_addr
33
32
  end
34
33
  end
35
34
  end
@@ -0,0 +1,98 @@
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, default: 'There'
29
+ # end
30
+ #
31
+ # MyThing.new(name: 'AJ').to_h
32
+ # # => { name: 'AJ' }
33
+ # MyThing.new().empty?
34
+ # # => true
35
+ module Fields
36
+ class Field
37
+ def initialize(key, default: nil)
38
+ @key = key
39
+ @default = default
40
+ end
41
+
42
+ attr_reader :key, :default
43
+ end
44
+
45
+ module InstanceMethods
46
+ def initialize(**attrs)
47
+ schema.each do |key, field|
48
+ send(:"#{key}=", field.default)
49
+ end
50
+
51
+ attrs.each do |key, value|
52
+ send(:"#{key}=", value)
53
+ end
54
+
55
+ super()
56
+ end
57
+
58
+ def empty?
59
+ self.class.schema.each do |key, field|
60
+ next if send(key).nil?
61
+ return false
62
+ end
63
+
64
+ true
65
+ end
66
+
67
+ def to_h
68
+ schema.each_with_object({}) do |(key, field), hsh|
69
+ hsh[key] = send(key)
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def schema
76
+ self.class.schema
77
+ end
78
+ end
79
+
80
+ module ClassMethods
81
+ def field(key, default: nil)
82
+ field = Field.new(key, default: default)
83
+ schema[key] = field
84
+
85
+ attr_accessor(key)
86
+ end
87
+
88
+ attr_reader :schema
89
+ end
90
+
91
+ def self.included(cls)
92
+ cls.extend(ClassMethods)
93
+ cls.include(InstanceMethods)
94
+
95
+ cls.instance_variable_set(:@schema, {})
96
+ end
97
+ end
98
+ end
@@ -179,7 +179,8 @@ module ElasticAPM
179
179
  context: nil,
180
180
  trace_context: nil,
181
181
  parent: nil,
182
- sync: nil
182
+ sync: nil,
183
+ exit_span: nil
183
184
  )
184
185
 
185
186
  transaction =
@@ -197,6 +198,15 @@ module ElasticAPM
197
198
 
198
199
  parent ||= (current_span || current_transaction)
199
200
 
201
+ # To not mess with breakdown metric stats, exit spans MUST not add
202
+ # sub-spans unless they share the same type and subtype.
203
+ if parent && parent.is_a?(Span) && parent.exit_span?
204
+ if parent.type != type || parent.subtype != subtype
205
+ debug "Skipping new span '#{name}' as its parent is an exit_span"
206
+ return
207
+ end
208
+ end
209
+
200
210
  span = Span.new(
201
211
  name: name,
202
212
  subtype: subtype,
@@ -207,7 +217,8 @@ module ElasticAPM
207
217
  type: type,
208
218
  context: context,
209
219
  stacktrace_builder: stacktrace_builder,
210
- sync: sync
220
+ sync: sync,
221
+ exit_span: exit_span
211
222
  )
212
223
 
213
224
  if backtrace && transaction.span_frames_min_duration
@@ -222,8 +233,14 @@ module ElasticAPM
222
233
  # rubocop:enable Metrics/CyclomaticComplexity
223
234
  # rubocop:enable Metrics/PerceivedComplexity
224
235
 
225
- def end_span
226
- return unless (span = current_spans.pop)
236
+ def end_span(span = nil)
237
+ if span
238
+ current_spans.delete(span)
239
+ else
240
+ span = current_spans.pop
241
+ end
242
+
243
+ return unless span
227
244
 
228
245
  span.done
229
246
 
@@ -273,24 +290,9 @@ module ElasticAPM
273
290
  'transaction.type': transaction.type
274
291
  }
275
292
 
276
- @metrics.get(:transaction).timer(
277
- :'transaction.duration.sum.us',
278
- tags: tags, reset_on_collect: true
279
- ).update(transaction.duration)
280
-
281
- @metrics.get(:transaction).counter(
282
- :'transaction.duration.count',
283
- tags: tags, reset_on_collect: true
284
- ).inc!
285
-
286
293
  return unless transaction.sampled?
287
294
  return unless transaction.breakdown_metrics
288
295
 
289
- @metrics.get(:breakdown).counter(
290
- :'transaction.breakdown.count',
291
- tags: tags, reset_on_collect: true
292
- ).inc!
293
-
294
296
  span_tags = tags.merge('span.type': 'app')
295
297
 
296
298
  @metrics.get(:breakdown).timer(
@@ -49,7 +49,7 @@ module ElasticAPM
49
49
  )
50
50
  @language = Language.new(name: 'ruby', version: RUBY_VERSION)
51
51
  @runtime = lookup_runtime
52
- @version = @config.service_version || Util.git_sha
52
+ @version = @config.service_version
53
53
  end
54
54
 
55
55
  attr_reader :name, :node_name, :environment, :agent, :framework,
@@ -50,7 +50,8 @@ module ElasticAPM
50
50
  private
51
51
 
52
52
  def detect_hostname
53
- `hostname`.chomp
53
+ Socket.gethostname.chomp
54
+ rescue
54
55
  end
55
56
  end
56
57
  end