elastic-apm 2.3.1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of elastic-apm might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b8d2dcb34f97fa80f6dd200bbb5d6a02a0219d054a0f2ca0df5a186ff530c8a
4
- data.tar.gz: 4ad68c7e8c7fcac5d82c7b621090091dd9816e90584115b0482523b7ca458942
3
+ metadata.gz: e3fb435d6ad1fd7a4e60cadedf93d9058da43310444e2fe4751d340a8a0eb7ed
4
+ data.tar.gz: 7e4a452a0ef34cd1d91bb5b9e1e74cd4c14a2d66ed4fe85ee5317682077c193b
5
5
  SHA512:
6
- metadata.gz: f1203dad667452f7080e67d7509f8046b327989c88da82e7019ecac3488c6a990d974e631a2ebd284c0e7b0896c8d533912bb8228bf728f7244c4a6da10edbd6
7
- data.tar.gz: be5499f9a2bbb5803879d8fde02c8aa6b4b6c7585b150cb9f7baebd7d52b86df79db664aaa3b9e330458e2f29c60dc53b4f0a96ffeb999a8615215a7b8de2289
6
+ metadata.gz: ad0a3ea9273288644640513a82db7baf1ed5c89f5ff4c1e57246d08d6baa8796d854263170afa887edbc5ab1ce2d73a5d73b8e6d367c58f56efd4d830e4c9be4
7
+ data.tar.gz: 0410ba02cd8c06b473fcb428900a5ed4d1eeae16134f187cde4525139edd669e75122cbe7b98d0a98a1639df4863d07d429dd6a3b27696604385e4703edaacb4
@@ -4,7 +4,23 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## Changed (2019-01-31)
7
+ ## 2.4.0 (2019-02-27)
8
+
9
+ ### Added
10
+
11
+ - Added option to specify a custom server CA certificate ([#315](https://github.com/elastic/apm-agent-ruby/pull/315))
12
+
13
+ ### Changed
14
+
15
+ - **NB:** Default value of option `capture_body` flipped to `false` to align with other agents. Set `capture_body: true` in your configuration to get them back. ([#324](https://github.com/elastic/apm-agent-ruby/pull/324))
16
+
17
+ ### Fixed
18
+
19
+ - Reading CPU stats from `/proc/stat` on RHEL ([#312](https://github.com/elastic/apm-agent-ruby/pull/312))
20
+ - Change TraceContext to differentiate between `id` and `parent_id` ([#326](https://github.com/elastic/apm-agent-ruby/pull/326))
21
+ - `capture_body` will now force encode text bodies to utf-8 when possible ([#332](https://github.com/elastic/apm-agent-ruby/pull/332))
22
+
23
+ ## 2.3.1 (2019-01-31)
8
24
 
9
25
  ### Added
10
26
 
@@ -0,0 +1,93 @@
1
+ # Contributing to the APM Agent
2
+
3
+ The APM Agent is open source and we love to receive contributions from our community — you!
4
+
5
+ There are many ways to contribute,
6
+ from writing tutorials or blog posts,
7
+ improving the documentation,
8
+ submitting bug reports and feature requests or writing code.
9
+
10
+ You can get in touch with us through [Discuss](https://discuss.elastic.co/c/apm),
11
+ feedback and ideas are always welcome.
12
+
13
+ ## Code contributions
14
+
15
+ If you have a bugfix or new feature that you would like to contribute,
16
+ please find or open an issue about it first.
17
+ Talk about what you would like to do.
18
+ It may be that somebody is already working on it,
19
+ or that there are particular issues that you should know about before implementing the change.
20
+
21
+ ### Submitting your changes
22
+
23
+ Generally, we require that you test any code you are adding or modifying.
24
+ Once your changes are ready to submit for review:
25
+
26
+ 1. Sign the Contributor License Agreement
27
+
28
+ Please make sure you have signed our [Contributor License Agreement](https://www.elastic.co/contributor-agreement/).
29
+ We are not asking you to assign copyright to us,
30
+ but to give us the right to distribute your code without restriction.
31
+ We ask this of all contributors in order to assure our users of the origin and continuing existence of the code.
32
+ You only need to sign the CLA once.
33
+
34
+ 2. Test your changes
35
+
36
+ Run the test suite to make sure that nothing is broken.
37
+ See [testing](#testing) for details.
38
+
39
+ 3. Rebase your changes
40
+
41
+ Update your local repository with the most recent code from the main repo,
42
+ and rebase your branch on top of the latest master branch.
43
+ We prefer your initial changes to be squashed into a single commit.
44
+ Later,
45
+ if we ask you to make changes,
46
+ add them as separate commits.
47
+ This makes them easier to review.
48
+ As a final step before merging we will either ask you to squash all commits yourself or we'll do it for you.
49
+
50
+ 4. Submit a pull request
51
+
52
+ Push your local changes to your forked copy of the repository and [submit a pull request](https://help.github.com/articles/using-pull-requests).
53
+ In the pull request,
54
+ choose a title which sums up the changes that you have made,
55
+ and in the body provide more details about what your changes do.
56
+ Also mention the number of the issue where discussion has taken place,
57
+ eg "Closes #123".
58
+
59
+ 5. Be patient
60
+
61
+ We might not be able to review your code as fast as we would like to,
62
+ but we'll do our best to dedicate it the attention it deserves.
63
+ Your effort is much appreciated!
64
+
65
+ ### Workflow
66
+
67
+ All feature development and most bug fixes hit the master branch first.
68
+ Pull requests should be reviewed by someone with commit access.
69
+ Once approved, the author of the pull request,
70
+ or reviewer if the author does not have commit access,
71
+ should "Squash and merge".
72
+
73
+ ### Testing
74
+
75
+ To do a full test run, use either `bundle exec rspec` or `rake spec`. Individual specs should also run as expected. The Mongo test needs a Mongo instance running, but will start one itself if Docker is installed.
76
+
77
+ To test other platform, use the Docker setup and scripts like `spec.sh RUBY FRAMEWORK`.
78
+
79
+ ```sh
80
+ $ spec/scripts/spec.sh ruby-2.6 rails-5.2
81
+ ```
82
+
83
+ ### Releasing
84
+
85
+ To release a new version:
86
+
87
+ 1. Update `VERSION` in `lib/elastic_apm/version.rb` according to the changes (major, minor, patch).
88
+ 2. Update `CHANGELOG.md` to reflect the new version -- change _Unreleased_ section to _Version (release date)_.
89
+ 3. Run `rake release`. This will...
90
+ 1. Tag the current commit as new version.
91
+ 2. Push the tag to GitHub.
92
+ 3. Build the gem and upload to Rubygems (local user needs to be signed in and authorized.)
93
+ 4. Update `2.x` branch to be at released commit and push it to GitHub.
data/Gemfile CHANGED
@@ -22,6 +22,8 @@ gem 'opentracing', require: nil
22
22
  gem 'rake', require: nil
23
23
  gem 'sequel', require: nil
24
24
  gem 'sidekiq', require: nil
25
+ gem 'simplecov', require: false, group: :test
26
+ gem 'simplecov-cobertura', require: false, group: :test
25
27
  gem 'yard', require: nil
26
28
  gem 'yarjuf'
27
29
 
@@ -43,7 +45,7 @@ else
43
45
  gem framework
44
46
  end
45
47
 
46
- unless version == 'master'
48
+ unless version =~ /^(master|6)/
47
49
  gem 'delayed_job', require: nil
48
50
  end
49
51
 
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env groovy
2
+ @Library('apm@v1.0.7') _
3
+
4
+ import co.elastic.matrix.*
5
+ import groovy.transform.Field
6
+
7
+ /**
8
+ This is the parallel tasks generator,
9
+ it is need as field to store the results of the tests.
10
+ */
11
+ @Field def rubyTasksGen
12
+
13
+ pipeline {
14
+ agent any
15
+ environment {
16
+ BASE_DIR="src/github.com/elastic/apm-agent-ruby"
17
+ PIPELINE_LOG_LEVEL='INFO'
18
+ NOTIFY_TO = credentials('notify-to')
19
+ JOB_GCS_BUCKET = credentials('gcs-bucket')
20
+ }
21
+ options {
22
+ timeout(time: 2, unit: 'HOURS')
23
+ buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20', daysToKeepStr: '30'))
24
+ timestamps()
25
+ ansiColor('xterm')
26
+ disableResume()
27
+ durabilityHint('PERFORMANCE_OPTIMIZED')
28
+ }
29
+ triggers {
30
+ issueCommentTrigger('.*(?:jenkins\\W+)?run\\W+(?:the\\W+)?tests(?:\\W+please)?.*')
31
+ }
32
+ parameters {
33
+ 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.')
34
+ booleanParam(name: 'doc_ci', defaultValue: true, description: 'Enable build docs.')
35
+ booleanParam(name: 'bench_ci', defaultValue: true, description: 'Enable run benchmarks.')
36
+ }
37
+ stages {
38
+ /**
39
+ Checkout the code and stash it, to use it on other stages.
40
+ */
41
+ stage('Checkout') {
42
+ agent { label 'flyweight' }
43
+ options { skipDefaultCheckout() }
44
+ steps {
45
+ deleteDir()
46
+ gitCheckout(basedir: "${BASE_DIR}")
47
+ stash allowEmpty: true, name: 'source', useDefaultExcludes: false
48
+ }
49
+ }
50
+ /**
51
+ Execute unit tests.
52
+ */
53
+ stage('Test') {
54
+ agent { label 'flyweight' }
55
+ options { skipDefaultCheckout() }
56
+ steps {
57
+ deleteDir()
58
+ unstash "source"
59
+ dir("${BASE_DIR}"){
60
+ script {
61
+ rubyTasksGen = new RubyParallelTaskGenerator(
62
+ xKey: 'RUBY_VERSION',
63
+ yKey: 'FRAMEWORK',
64
+ xFile: "./spec/.jenkins_ruby.yml",
65
+ yFile: "./spec/.jenkins_framework.yml",
66
+ exclusionFile: "./spec/.jenkins_exclude.yml",
67
+ tag: "Ruby",
68
+ name: "Ruby",
69
+ steps: this
70
+ )
71
+ def mapPatallelTasks = rubyTasksGen.generateParallelTests()
72
+ parallel(mapPatallelTasks)
73
+ }
74
+ }
75
+ }
76
+ }
77
+ stage('Benchmarks') {
78
+ options { skipDefaultCheckout() }
79
+ when {
80
+ beforeAgent true
81
+ allOf {
82
+ anyOf {
83
+ not {
84
+ changeRequest()
85
+ }
86
+ branch 'master'
87
+ branch "\\d+\\.\\d+"
88
+ branch "v\\d?"
89
+ tag "v\\d+\\.\\d+\\.\\d+*"
90
+ expression { return params.Run_As_Master_Branch }
91
+ }
92
+ expression { return params.bench_ci }
93
+ }
94
+ }
95
+ stages {
96
+ stage('Clean Workspace') {
97
+ agent { label 'metal' }
98
+ steps {
99
+ echo "Cleaning Workspace"
100
+ }
101
+ post {
102
+ always {
103
+ cleanWs()
104
+ }
105
+ }
106
+ }
107
+ /**
108
+ Run the benchmarks and store the results on ES.
109
+ The result JSON files are also archive into Jenkins.
110
+ */
111
+ stage('Run Benchmarks') {
112
+ agent { label 'linux && immutable' }
113
+ steps {
114
+ deleteDir()
115
+ unstash 'source'
116
+ dir("${BASE_DIR}"){
117
+ script {
118
+ def versions = readYaml(file: "./spec/.jenkins_ruby.yml")
119
+ def benchmarkTask = [:]
120
+ versions['RUBY_VERSION'].each{ v ->
121
+ benchmarkTask[v] = runBenchmark(v)
122
+ }
123
+ parallel(benchmarkTask)
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+ /**
131
+ Build the documentation.
132
+ */
133
+ stage('Documentation') {
134
+ agent { label 'linux && immutable' }
135
+ options { skipDefaultCheckout() }
136
+ when {
137
+ beforeAgent true
138
+ allOf {
139
+ anyOf {
140
+ not {
141
+ changeRequest()
142
+ }
143
+ branch 'master'
144
+ branch "\\d+\\.\\d+"
145
+ branch "v\\d?"
146
+ tag "v\\d+\\.\\d+\\.\\d+*"
147
+ expression { return params.Run_As_Master_Branch }
148
+ }
149
+ expression { return params.doc_ci }
150
+ }
151
+ }
152
+ steps {
153
+ deleteDir()
154
+ unstash 'source'
155
+ buildDocs(docsDir: "${BASE_DIR}/docs", archive: true)
156
+ }
157
+ }
158
+ }
159
+ post {
160
+ always{
161
+ script{
162
+ if(rubyTasksGen?.results){
163
+ writeJSON(file: 'results.json', json: toJSON(rubyTasksGen.results), pretty: 2)
164
+ def mapResults = ["Ruby": rubyTasksGen.results]
165
+ def processor = new ResultsProcessor()
166
+ processor.processResults(mapResults)
167
+ archiveArtifacts allowEmptyArchive: true, artifacts: 'results.json,results.html', defaultExcludes: false
168
+ }
169
+ }
170
+ }
171
+ success {
172
+ echoColor(text: '[SUCCESS]', colorfg: 'green', colorbg: 'default')
173
+ }
174
+ aborted {
175
+ echoColor(text: '[ABORTED]', colorfg: 'magenta', colorbg: 'default')
176
+ }
177
+ failure {
178
+ echoColor(text: '[FAILURE]', colorfg: 'red', colorbg: 'default')
179
+ step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: "${NOTIFY_TO}", sendToIndividuals: false])
180
+ }
181
+ unstable {
182
+ echoColor(text: '[UNSTABLE]', colorfg: 'yellow', colorbg: 'default')
183
+ }
184
+ }
185
+ }
186
+
187
+ /**
188
+ Parallel task generator for the integration tests.
189
+ */
190
+ class RubyParallelTaskGenerator extends DefaultParallelTaskGenerator {
191
+
192
+ public RubyParallelTaskGenerator(Map params){
193
+ super(params)
194
+ }
195
+
196
+ /**
197
+ build a clousure that launch and agent and execute the corresponding test script,
198
+ then store the results.
199
+ */
200
+ public Closure generateStep(x, y){
201
+ return {
202
+ steps.node('linux && immutable'){
203
+ def label = "${tag}:${x}#${y}"
204
+ try {
205
+ steps.runScript(label: label, ruby: x, framework: y)
206
+ saveResult(x, y, 1)
207
+ } catch(e){
208
+ saveResult(x, y, 0)
209
+ error("${label} tests failed : ${e.toString()}\n")
210
+ } finally {
211
+ steps.junit(allowEmptyResults: false,
212
+ keepLongStdio: true,
213
+ testResults: "**/spec/ruby-agent-junit.xml")
214
+ steps.codecov(repo: 'apm-agent-ruby', basedir: "${steps.env.BASE_DIR}")
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+
221
+ /**
222
+ Run tests for a Ruby version and framework version.
223
+ */
224
+ def runScript(Map params = [:]){
225
+ def label = params.label
226
+ def ruby = params.ruby
227
+ def framework = params.framework
228
+ log(level: 'INFO', text: "${label}")
229
+ env.HOME = "${env.WORKSPACE}"
230
+ env.PATH = "${env.PATH}:${env.WORKSPACE}/bin"
231
+ deleteDir()
232
+ unstash 'source'
233
+ dir("${BASE_DIR}"){
234
+ retry(2){
235
+ sleep randomNumber(min:10, max: 30)
236
+ sh("./spec/scripts/spec.sh ${ruby} ${framework}")
237
+ }
238
+ }
239
+ }
240
+
241
+ /**
242
+ Run benchmarks for a Ruby version, then report the results to the Elasticsearch server.
243
+ */
244
+ def runBenchmark(version){
245
+ return {
246
+ node('metal'){
247
+ env.HOME = "${env.WORKSPACE}/${version}"
248
+ dir("${version}"){
249
+ deleteDir()
250
+ unstash 'source'
251
+ dir("${BASE_DIR}"){
252
+ try{
253
+ sh "./spec/scripts/benchmarks.sh ${version}"
254
+ } catch(e){
255
+ throw e
256
+ } finally {
257
+ sendBenchmarks(file: "benchmark-${version}.bulk",
258
+ index: "benchmark-ruby", archive: true)
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # elastic-apm
2
2
  ## Elastic APM agent for ♦️Ruby
3
3
 
4
- [![Jenkins](https://img.shields.io/jenkins/s/https/apm-ci.elastic.co/job/elastic+apm-agent-ruby+master.svg)](https://apm-ci.elastic.co/job/elastic+apm-agent-ruby+master/) [![Gem](https://img.shields.io/gem/v/elastic-apm.svg)](https://rubygems.org/gems/elastic-apm)
4
+ [![Jenkins](https://img.shields.io/jenkins/s/https/apm-ci.elastic.co/job/elastic+apm-agent-ruby+master.svg)](https://apm-ci.elastic.co/job/elastic+apm-agent-ruby+master/) [![Gem](https://img.shields.io/gem/v/elastic-apm.svg)](https://rubygems.org/gems/elastic-apm) [![codecov](https://codecov.io/gh/elastic/apm-agent-ruby/branch/master/graph/badge.svg)](https://codecov.io/gh/elastic/apm-agent-ruby)
5
5
 
6
6
  The official Rubygem for [Elastic][] [APM][].
7
7
 
@@ -5,33 +5,10 @@ Encoding.default_external = 'utf-8'
5
5
 
6
6
  require 'time'
7
7
  require 'bundler/setup'
8
- require 'faraday'
9
8
  require 'json'
10
9
 
11
- ELASTICSEARCH_URL = ENV.fetch('CLOUD_ADDR') { '' }.chomp
12
- if ELASTICSEARCH_URL == ''
13
- puts 'ELASTICSEARCH_URL missing, exiting ...'
14
- exit 1
15
- else
16
- # DEBUG
17
- # puts ELASTICSEARCH_URL.gsub(/:[^\/]+(.*)@/) do |m|
18
- # ":#{Array.new(m.length - 2).map { '*' }.join}@"
19
- # end
20
- end
21
-
22
- CONN = Faraday.new(url: ELASTICSEARCH_URL) do |f|
23
- # f.response :logger
24
- f.adapter Faraday.default_adapter
25
- end
26
-
27
- healthcheck = CONN.get('/microbenchmark*/_search')
28
- if healthcheck.status != 200
29
- puts healthcheck.body.to_s
30
- exit 1
31
- end
32
-
33
10
  input = STDIN.read.split("\n")
34
- puts input
11
+ STDERR.puts input
35
12
 
36
13
  titles = input.grep(/^===/).map { |t| t.gsub(/^=== /, '') }
37
14
  counts = input.grep(/^Count: /).map { |a| a.gsub(/^Count: /, '').to_i }
@@ -60,14 +37,10 @@ payloads = titles.zip(averages, counts).map do |(title, avg, count)|
60
37
  }
61
38
  end.compact
62
39
 
63
- puts '=== Reporting to ES'
64
- puts payloads.inspect
40
+ STDERR.puts '=== Reporting to ES'
41
+ STDERR.puts payloads.inspect
65
42
 
66
43
  payloads.each do |payload|
67
- result = CONN.post('/benchmark-ruby/_doc') do |req|
68
- req.headers['Content-Type'] = 'application/json'
69
- req.body = payload.to_json
70
- end
71
-
72
- puts result.body unless (200...300).include?(result.status)
44
+ puts '{ "index" : { "_index" : "benchmark-ruby", "_type" : "_doc" } }'
45
+ puts payload.to_json
73
46
  end
@@ -129,7 +129,7 @@ WARNING: Secret tokens only provide any real security if your APM server use TLS
129
129
 
130
130
  Maximum amount of objects kept in queue, before sending to APM Server.
131
131
 
132
- If you hit this limit you either have to increase the agent's
132
+ If you hit this limit you either have to increase the agent's
133
133
  <<config-pool-size,worker pool size>> or it could mean the agent has trouble
134
134
  connecting to APM Server. The <<config-log-path,logs>> should tell you what
135
135
  went wrong.
@@ -512,6 +512,18 @@ Set it to `0` to disable stack trace collection for all spans.
512
512
 
513
513
  It has to be provided in *<<config-format-duration, duration format>>*.
514
514
 
515
+ [float]
516
+ [[config-ssl-ca-cert]]
517
+ ==== `server_ca_cert`
518
+
519
+ [options="header"]
520
+ |============
521
+ | Environment | `Config` key | Default | Example
522
+ | `ELASTIC_APM_SERVER_CA_CERT` | `server_ca_cert` | `nil` | `'/path/to/ca.pem'`
523
+ |============
524
+
525
+ The path to a custom CA certificate for connecting to APM Server.
526
+
515
527
  [float]
516
528
  [[config-transaction-max-spans]]
517
529
  ==== `transaction_max_spans`
@@ -21,7 +21,7 @@ method.
21
21
  [source,ruby]
22
22
  ----
23
23
  class Thing
24
- include SpanHelpers
24
+ include ElasticAPM::SpanHelpers
25
25
 
26
26
  def do_the_work
27
27
  # ...
@@ -53,11 +53,11 @@ See <<getting-started-rack>>.
53
53
 
54
54
  We automatically instrument database actions using:
55
55
 
56
- - ActiveRecord
57
- - Elasticsearch
58
- - Mongo
59
- - Redis
60
- - Sequel
56
+ - ActiveRecord (v4.2+)
57
+ - Elasticsearch (v0.9+)
58
+ - Mongo (v2.1+)
59
+ - Redis (v3.1+)
60
+ - Sequel (v4.35+)
61
61
 
62
62
  [float]
63
63
  [[supported-technologies-http]]
@@ -67,4 +67,5 @@ We automatically instrument and add support for distributed tracing to external
67
67
  requests using these libraries:
68
68
 
69
69
  - `net/http`
70
- - Http.rb
70
+ - Http.rb (v0.6+)
71
+ - Faraday (v0.2.1+)
@@ -22,7 +22,7 @@ module ElasticAPM
22
22
  api_buffer_size: 256,
23
23
  api_request_size: '750kb',
24
24
  api_request_time: '10s',
25
- capture_body: true,
25
+ capture_body: false,
26
26
  capture_headers: true,
27
27
  capture_env: true,
28
28
  current_user_email_method: :email,
@@ -81,6 +81,7 @@ module ElasticAPM
81
81
  'ELASTIC_APM_LOG_PATH' => 'log_path',
82
82
  'ELASTIC_APM_METRICS_INTERVAL' => [:int, 'metrics_interval'],
83
83
  'ELASTIC_APM_POOL_SIZE' => [:int, 'pool_size'],
84
+ 'ELASTIC_APM_SERVER_CA_CERT' => 'server_ca_cert',
84
85
  'ELASTIC_APM_SERVICE_NAME' => 'service_name',
85
86
  'ELASTIC_APM_SERVICE_VERSION' => 'service_version',
86
87
  'ELASTIC_APM_SOURCE_LINES_ERROR_APP_FRAMES' =>
@@ -156,6 +157,7 @@ module ElasticAPM
156
157
  attr_accessor :logger
157
158
  attr_accessor :metrics_interval
158
159
  attr_accessor :pool_size
160
+ attr_accessor :server_ca_cert
159
161
  attr_accessor :service_name
160
162
  attr_accessor :service_version
161
163
  attr_accessor :source_lines_error_app_frames
@@ -3,6 +3,9 @@
3
3
  module ElasticAPM
4
4
  # @api private
5
5
  class ContextBuilder
6
+ MAX_BODY_LENGTH = 2048
7
+ SKIPPED = '[SKIPPED]'
8
+
6
9
  def initialize(config)
7
10
  @config = config
8
11
  end
@@ -29,7 +32,7 @@ module ElasticAPM
29
32
  request.method = req.request_method
30
33
  request.url = Context::Request::Url.new(req)
31
34
 
32
- request.body = get_body(req) if config.capture_body?
35
+ request.body = config.capture_body? ? get_body(req) : SKIPPED
33
36
 
34
37
  headers, env = get_headers_and_env(rack_env)
35
38
  request.headers = headers if config.capture_headers?
@@ -40,11 +43,16 @@ module ElasticAPM
40
43
  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
41
44
 
42
45
  def get_body(req)
43
- return req.POST if req.form_data?
44
-
45
- body = req.body.read
46
- req.body.rewind
47
- body
46
+ case req.media_type
47
+ when 'application/x-www-form-urlencoded'
48
+ req.POST
49
+ when 'multipart/form-data'
50
+ req.POST
51
+ else
52
+ body = req.body.read
53
+ req.body.rewind
54
+ body.byteslice(0, MAX_BODY_LENGTH).force_encoding('utf-8')
55
+ end
48
56
  end
49
57
 
50
58
  def rails_req?(env)
@@ -4,14 +4,17 @@ module ElasticAPM
4
4
  # @api private
5
5
  module Deprecations
6
6
  def deprecate(name, replacement = nil)
7
+ alias_name = "#{name.to_s.chomp('=')}__deprecated_"
8
+ alias_name += '=' if name.to_s.end_with?('=')
9
+
7
10
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
8
- alias :"#{name}__deprecated" :"#{name}"
11
+ alias :"#{alias_name}" :"#{name}"
9
12
 
10
13
  def #{name}(*args, &block)
11
14
  warn "[ElasticAPM] [DEPRECATED] `#{name}' is being removed. " \
12
- "#{replacement && "See `#{replacement}'."}" \
13
- "\nCalled from \#{caller.first}"
14
- #{name}__deprecated(*args, &block)
15
+ "#{replacement && "See `#{replacement}'."}" \
16
+ "\nCalled from \#{caller.first}"
17
+ send("#{alias_name}", *args, &block)
15
18
  end
16
19
  RUBY
17
20
  end
@@ -152,13 +152,12 @@ module ElasticAPM
152
152
  parent = current_span || transaction
153
153
 
154
154
  span = Span.new(
155
- name,
156
- type,
155
+ name: name,
157
156
  transaction_id: transaction.id,
158
- parent_id: parent.id,
157
+ trace_context: trace_context || parent.trace_context.child,
158
+ type: type,
159
159
  context: context,
160
- stacktrace_builder: stacktrace_builder,
161
- trace_context: trace_context || parent.trace_context.child
160
+ stacktrace_builder: stacktrace_builder
162
161
  )
163
162
 
164
163
  if backtrace && config.span_frames_min_duration?
@@ -124,6 +124,19 @@ module ElasticAPM
124
124
  class ProcStat
125
125
  attr_reader :total, :usage
126
126
 
127
+ CPU_FIELDS = %i[
128
+ user
129
+ nice
130
+ system
131
+ idle
132
+ iowait
133
+ irq
134
+ softirq
135
+ steal
136
+ guest
137
+ guest_nice
138
+ ].freeze
139
+
127
140
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
128
141
  def read!
129
142
  stat =
@@ -133,20 +146,22 @@ module ElasticAPM
133
146
  .split
134
147
  .map(&:to_i)[1..-1]
135
148
 
136
- user, nice, system, idle, iowait, irq, softirq, steal,
137
- _guest, _guest_nice = stat
149
+ values =
150
+ CPU_FIELDS.each_with_index.each_with_object({}) do |(key, i), v|
151
+ v[key] = stat[i] || 0
152
+ end
138
153
 
139
154
  @total =
140
- user +
141
- nice +
142
- system +
143
- idle +
144
- iowait +
145
- irq +
146
- softirq +
147
- steal
148
-
149
- @usage = @total - (idle + iowait)
155
+ values[:user] +
156
+ values[:nice] +
157
+ values[:system] +
158
+ values[:idle] +
159
+ values[:iowait] +
160
+ values[:irq] +
161
+ values[:softirq] +
162
+ values[:steal]
163
+
164
+ @usage = @total - (values[:idle] + values[:iowait])
150
165
 
151
166
  self
152
167
  end
@@ -93,28 +93,20 @@ module ElasticAPM
93
93
 
94
94
  # @api private
95
95
  class SpanContext
96
- def initialize(id:, trace_id:, baggage: nil)
96
+ def initialize(trace_context:, baggage: nil)
97
97
  if baggage
98
98
  ElasticAPM.agent.config.logger.warn(
99
99
  'Baggage is not supported by ElasticAPM'
100
100
  )
101
101
  end
102
102
 
103
- @id = id
104
- @trace_id = trace_id
105
- @trace_context =
106
- ElasticAPM::TraceContext.new(trace_id: trace_id, span_id: id)
103
+ @trace_context = trace_context
107
104
  end
108
105
 
109
- attr_accessor :id, :trace_id, :trace_context
106
+ attr_accessor :trace_context
110
107
 
111
108
  def self.from_trace_context(trace_context)
112
- new(
113
- trace_id: trace_context.trace_id,
114
- id: trace_context.span_id
115
- ).tap do |span_context|
116
- span_context.trace_context = trace_context
117
- end
109
+ new(trace_context: trace_context)
118
110
  end
119
111
  end
120
112
 
@@ -1,48 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'securerandom'
4
+ require 'forwardable'
4
5
 
5
6
  require 'elastic_apm/span/context'
6
7
 
7
8
  module ElasticAPM
8
9
  # @api private
9
10
  class Span
11
+ extend Forwardable
12
+
13
+ def_delegators :@trace_context, :trace_id, :parent_id, :id
14
+
10
15
  DEFAULT_TYPE = 'custom'
11
16
 
12
17
  # rubocop:disable Metrics/ParameterLists
13
18
  def initialize(
14
- name,
15
- type = nil,
16
- transaction_id: nil,
17
- parent_id: nil,
19
+ name:,
20
+ transaction_id:,
21
+ trace_context:,
22
+ type: nil,
18
23
  context: nil,
19
- stacktrace_builder: nil,
20
- trace_context: nil
24
+ stacktrace_builder: nil
21
25
  )
22
26
  @name = name
23
27
  @type = type || DEFAULT_TYPE
24
28
 
25
29
  @transaction_id = transaction_id
26
-
27
- @parent_id = parent_id
28
- @trace_context = trace_context || TraceContext.for_span
30
+ @trace_context = trace_context
29
31
 
30
32
  @context = context || Span::Context.new
31
33
  @stacktrace_builder = stacktrace_builder
32
34
  end
33
35
  # rubocop:enable Metrics/ParameterLists
34
36
 
35
- attr_accessor :name, :type, :original_backtrace, :parent_id, :trace_context
37
+ attr_accessor :name, :type, :original_backtrace, :trace_context
36
38
  attr_reader :context, :stacktrace, :duration, :timestamp, :transaction_id
37
39
 
38
- def id
39
- trace_context&.span_id
40
- end
41
-
42
- def trace_id
43
- trace_context&.trace_id
44
- end
45
-
46
40
  # life cycle
47
41
 
48
42
  def start(timestamp = Util.micros)
@@ -3,56 +3,47 @@
3
3
  module ElasticAPM
4
4
  # @api private
5
5
  class TraceContext
6
+ extend Deprecations
6
7
  class InvalidTraceparentHeader < StandardError; end
7
8
 
8
9
  VERSION = '00'
9
10
  HEX_REGEX = /[^[:xdigit:]]/.freeze
10
11
 
12
+ TRACE_ID_LENGTH = 16
13
+ ID_LENGTH = 8
14
+
11
15
  def initialize(
12
16
  version: VERSION,
13
17
  trace_id: nil,
14
18
  span_id: nil,
19
+ id: nil,
15
20
  recorded: true
16
21
  )
17
22
  @version = version
18
- @trace_id = trace_id
19
- @span_id = span_id
23
+ @trace_id = trace_id || hex(TRACE_ID_LENGTH)
24
+ # TODO: rename to parent_id with next major version bump
25
+ @parent_id = span_id
26
+ @id = id || hex(ID_LENGTH)
20
27
  @recorded = recorded
21
28
  end
22
29
 
23
- attr_accessor :version, :trace_id, :span_id, :recorded
30
+ attr_accessor :version, :id, :trace_id, :parent_id, :recorded
24
31
 
25
32
  alias :recorded? :recorded
26
33
 
27
- def self.for_transaction(sampled: true)
28
- new.tap do |t|
29
- t.trace_id = SecureRandom.hex(16)
30
- t.span_id = SecureRandom.hex(8)
31
- t.recorded = sampled
32
- end
33
- end
34
-
35
- def self.for_span
36
- new.tap do |t|
37
- t.trace_id = SecureRandom.hex(16)
38
- t.span_id = SecureRandom.hex(8)
39
- t.recorded = true
40
- end
41
- end
42
-
43
34
  # rubocop:disable Metrics/AbcSize
44
35
  def self.parse(header)
45
36
  raise InvalidTraceparentHeader unless header.length == 55
46
37
  raise InvalidTraceparentHeader unless header[0..1] == VERSION
47
38
 
48
39
  new.tap do |t|
49
- t.version, t.trace_id, t.span_id, t.flags =
40
+ t.version, t.trace_id, t.parent_id, t.flags =
50
41
  header.split('-').tap do |values|
51
42
  values[-1] = Util.hex_to_bits(values[-1])
52
43
  end
53
44
 
54
45
  raise InvalidTraceparentHeader if HEX_REGEX =~ t.trace_id
55
- raise InvalidTraceparentHeader if HEX_REGEX =~ t.span_id
46
+ raise InvalidTraceparentHeader if HEX_REGEX =~ t.parent_id
56
47
  end
57
48
  end
58
49
  # rubocop:enable Metrics/AbcSize
@@ -71,12 +62,38 @@ module ElasticAPM
71
62
  format('%02x', flags.to_i(2))
72
63
  end
73
64
 
65
+ def ensure_parent_id
66
+ @parent_id ||= hex(ID_LENGTH)
67
+ end
68
+
74
69
  def child
75
- dup.tap { |tc| tc.span_id = SecureRandom.hex(8) }
70
+ dup.tap do |tc|
71
+ tc.parent_id = tc.id
72
+ tc.id = hex(ID_LENGTH)
73
+ end
76
74
  end
77
75
 
78
76
  def to_header
79
- format('%s-%s-%s-%s', version, trace_id, span_id, hex_flags)
77
+ format('%s-%s-%s-%s', version, trace_id, id, hex_flags)
78
+ end
79
+
80
+ # @deprecated Use parent_id instead
81
+ def span_id
82
+ @parent_id
83
+ end
84
+
85
+ # @deprecated Use parent_id instead
86
+ def span_id=(span_id)
87
+ @parent_id = span_id
88
+ end
89
+
90
+ deprecate :span_id, :parent_id
91
+ deprecate :span_id=, :parent_id=
92
+
93
+ private
94
+
95
+ def hex(len)
96
+ SecureRandom.hex(len)
80
97
  end
81
98
  end
82
99
  end
@@ -1,15 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'securerandom'
4
+ require 'forwardable'
4
5
 
5
6
  module ElasticAPM
6
7
  # @api private
7
8
  class Transaction
8
9
  extend Deprecations
10
+ extend Forwardable
11
+
12
+ def_delegators :@trace_context,
13
+ :trace_id, :parent_id, :id, :ensure_parent_id
9
14
 
10
15
  DEFAULT_TYPE = 'custom'
11
16
 
12
- # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
17
+ # rubocop:disable Metrics/ParameterLists
13
18
  def initialize(
14
19
  name = nil,
15
20
  type = nil,
@@ -26,28 +31,19 @@ module ElasticAPM
26
31
  @context = context || Context.new # TODO: Lazy generate this?
27
32
  Util.reverse_merge!(@context.tags, tags) if tags
28
33
 
29
- if trace_context
30
- @parent_id = trace_context.span_id
31
- @trace_context = trace_context
32
- else
33
- @trace_context = TraceContext.for_transaction(sampled: sampled)
34
- end
34
+ @trace_context = trace_context || TraceContext.new(recorded: sampled)
35
35
 
36
36
  @started_spans = 0
37
37
  @dropped_spans = 0
38
38
 
39
39
  @notifications = [] # for AS::Notifications
40
40
  end
41
- # rubocop:enable Metrics/ParameterLists, Metrics/MethodLength
41
+ # rubocop:enable Metrics/ParameterLists
42
42
 
43
43
  attr_accessor :name, :type, :result
44
44
 
45
45
  attr_reader :context, :duration, :started_spans, :dropped_spans,
46
- :timestamp, :trace_context, :notifications, :parent_id
47
-
48
- def id
49
- trace_context.span_id
50
- end
46
+ :timestamp, :trace_context, :notifications
51
47
 
52
48
  def sampled?
53
49
  @sampled
@@ -63,10 +59,6 @@ module ElasticAPM
63
59
 
64
60
  deprecate :done?, :stopped?
65
61
 
66
- def trace_id
67
- trace_context&.trace_id
68
- end
69
-
70
62
  # life cycle
71
63
 
72
64
  def start(timestamp = Util.micros)
@@ -86,11 +78,6 @@ module ElasticAPM
86
78
  self
87
79
  end
88
80
 
89
- def ensure_parent_id
90
- @parent_id ||= SecureRandom.hex(8)
91
- @parent_id
92
- end
93
-
94
81
  # spans
95
82
 
96
83
  def inc_started_spans!
@@ -28,6 +28,7 @@ module ElasticAPM
28
28
  }.freeze
29
29
  GZIP_HEADERS = HEADERS.merge('Content-Encoding' => 'gzip').freeze
30
30
 
31
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
31
32
  def initialize(config, metadata)
32
33
  @config = config
33
34
  @metadata = metadata.to_json
@@ -41,10 +42,16 @@ module ElasticAPM
41
42
  headers['Authorization'] = "Bearer #{token}"
42
43
  end
43
44
 
45
+ if config.use_ssl? && config.server_ca_cert
46
+ @ssl_context = OpenSSL::SSL::SSLContext.new
47
+ @ssl_context.ca_file = config.server_ca_cert
48
+ end
49
+
44
50
  @client = HTTP.headers(headers).persistent(@url)
45
51
 
46
52
  @mutex = Mutex.new
47
53
  end
54
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
48
55
 
49
56
  def write(str)
50
57
  return if @config.disable_send
@@ -109,7 +116,12 @@ module ElasticAPM
109
116
  @conn_thread = Thread.new do
110
117
  begin
111
118
  @connected = true
112
- resp = @client.post(@url, body: @rd).flush
119
+
120
+ resp = @client.post(
121
+ @url,
122
+ body: @rd,
123
+ ssl_context: @ssl_context
124
+ ).flush
113
125
  rescue Exception => e
114
126
  @connection_error = e
115
127
  ensure
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'elastic_apm/transport/filters/request_body_filter'
4
3
  require 'elastic_apm/transport/filters/secrets_filter'
5
4
 
6
5
  module ElasticAPM
@@ -14,10 +13,7 @@ module ElasticAPM
14
13
  # @api private
15
14
  class Container
16
15
  def initialize(config)
17
- @filters = {
18
- request_body: RequestBodyFilter.new(config),
19
- secrets: SecretsFilter.new(config)
20
- }
16
+ @filters = { secrets: SecretsFilter.new(config) }
21
17
  end
22
18
 
23
19
  def add(key, filter)
@@ -28,21 +28,21 @@ module ElasticAPM
28
28
  end
29
29
 
30
30
  def call(payload)
31
- strip_from payload[:transaction], :context, :request, :headers
32
- strip_from payload[:transaction], :context, :response, :headers
33
- strip_from payload[:error], :context, :request, :headers
34
- strip_from payload[:error], :context, :response, :headers
31
+ strip_from! payload.dig(:transaction, :context, :request, :headers)
32
+ strip_from! payload.dig(:transaction, :context, :response, :headers)
33
+ strip_from! payload.dig(:error, :context, :request, :headers)
34
+ strip_from! payload.dig(:error, :context, :response, :headers)
35
+ strip_from! payload.dig(:transaction, :context, :request, :body)
35
36
 
36
37
  payload
37
38
  end
38
39
 
39
- def strip_from(event, *path)
40
- return unless event
41
- return unless (headers = event.dig(*path))
40
+ def strip_from!(obj)
41
+ return unless obj && obj.is_a?(Hash)
42
42
 
43
- headers.each do |k, v|
43
+ obj.each do |k, v|
44
44
  if filter_key?(k) || filter_value?(v)
45
- headers[k] = FILTERED
45
+ obj[k] = FILTERED
46
46
  end
47
47
  end
48
48
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ElasticAPM
4
- VERSION = '2.3.1'
4
+ VERSION = '2.4.0'
5
5
  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: 2.3.1
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikkel Malmberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-31 00:00:00.000000000 Z
11
+ date: 2019-02-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -51,7 +51,9 @@ files:
51
51
  - ".rubocop.yml"
52
52
  - CHANGELOG.md
53
53
  - CODE_OF_CONDUCT.md
54
+ - CONTRIBUTING.md
54
55
  - Gemfile
56
+ - Jenkinsfile
55
57
  - LICENSE
56
58
  - README.md
57
59
  - Rakefile
@@ -145,7 +147,6 @@ files:
145
147
  - lib/elastic_apm/transport/base.rb
146
148
  - lib/elastic_apm/transport/connection.rb
147
149
  - lib/elastic_apm/transport/filters.rb
148
- - lib/elastic_apm/transport/filters/request_body_filter.rb
149
150
  - lib/elastic_apm/transport/filters/secrets_filter.rb
150
151
  - lib/elastic_apm/transport/serializers.rb
151
152
  - lib/elastic_apm/transport/serializers/context_serializer.rb
@@ -181,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
181
182
  - !ruby/object:Gem::Version
182
183
  version: '0'
183
184
  requirements: []
184
- rubygems_version: 3.0.2
185
+ rubygems_version: 3.0.1
185
186
  signing_key:
186
187
  specification_version: 4
187
188
  summary: The official Elastic APM agent for Ruby
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ElasticAPM
4
- module Transport
5
- module Filters
6
- # @api private
7
- class RequestBodyFilter
8
- FILTERED = '[FILTERED]'
9
-
10
- def initialize(config)
11
- @config = config
12
- end
13
-
14
- def call(payload)
15
- strip_body_from payload[:transaction]
16
- strip_body_from payload[:error]
17
-
18
- payload
19
- end
20
-
21
- private
22
-
23
- def strip_body_from(payload)
24
- return unless payload
25
- return unless (request = payload.dig(:context, :request))
26
- request[:body] = FILTERED
27
- end
28
- end
29
- end
30
- end
31
- end