elastic-apm 3.7.0 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/Jenkinsfile +139 -96
  3. data/.ci/packer_cache.sh +12 -10
  4. data/.rspec +0 -1
  5. data/CHANGELOG.asciidoc +80 -0
  6. data/Gemfile +4 -3
  7. data/bin/run-tests +4 -1
  8. data/docker-compose.yml +2 -0
  9. data/docs/configuration.asciidoc +38 -0
  10. data/docs/debugging.asciidoc +14 -0
  11. data/docs/supported-technologies.asciidoc +2 -1
  12. data/lib/elastic_apm/config.rb +36 -13
  13. data/lib/elastic_apm/config/options.rb +2 -1
  14. data/lib/elastic_apm/config/round_float.rb +31 -0
  15. data/lib/elastic_apm/config/wildcard_pattern_list.rb +13 -1
  16. data/lib/elastic_apm/context_builder.rb +1 -1
  17. data/lib/elastic_apm/grpc.rb +2 -2
  18. data/lib/elastic_apm/instrumenter.rb +10 -3
  19. data/lib/elastic_apm/metadata.rb +3 -1
  20. data/lib/elastic_apm/metadata/cloud_info.rb +128 -0
  21. data/lib/elastic_apm/metadata/service_info.rb +5 -2
  22. data/lib/elastic_apm/metadata/system_info.rb +5 -3
  23. data/lib/elastic_apm/metadata/system_info/container_info.rb +28 -4
  24. data/lib/elastic_apm/middleware.rb +8 -2
  25. data/lib/elastic_apm/opentracing.rb +47 -23
  26. data/lib/elastic_apm/span.rb +7 -3
  27. data/lib/elastic_apm/spies.rb +16 -14
  28. data/lib/elastic_apm/spies/delayed_job.rb +4 -2
  29. data/lib/elastic_apm/spies/dynamo_db.rb +58 -0
  30. data/lib/elastic_apm/spies/elasticsearch.rb +26 -2
  31. data/lib/elastic_apm/spies/mongo.rb +1 -1
  32. data/lib/elastic_apm/spies/net_http.rb +6 -2
  33. data/lib/elastic_apm/spies/sequel.rb +1 -1
  34. data/lib/elastic_apm/trace_context.rb +1 -1
  35. data/lib/elastic_apm/trace_context/traceparent.rb +2 -4
  36. data/lib/elastic_apm/trace_context/tracestate.rb +112 -9
  37. data/lib/elastic_apm/transaction.rb +26 -5
  38. data/lib/elastic_apm/transport/connection.rb +1 -0
  39. data/lib/elastic_apm/transport/filters/hash_sanitizer.rb +70 -0
  40. data/lib/elastic_apm/transport/filters/secrets_filter.rb +14 -56
  41. data/lib/elastic_apm/transport/serializers.rb +8 -6
  42. data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +56 -23
  43. data/lib/elastic_apm/transport/serializers/span_serializer.rb +2 -1
  44. data/lib/elastic_apm/transport/serializers/transaction_serializer.rb +1 -0
  45. data/lib/elastic_apm/transport/user_agent.rb +3 -3
  46. data/lib/elastic_apm/transport/worker.rb +5 -0
  47. data/lib/elastic_apm/util.rb +2 -0
  48. data/lib/elastic_apm/util/precision_validator.rb +46 -0
  49. data/lib/elastic_apm/version.rb +1 -1
  50. metadata +12 -8
  51. data/.ci/downstreamTests.groovy +0 -192
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86463e0604357bc3c88e3507de76b0a2e92ba2a1311aab220b6b092df9523319
4
- data.tar.gz: 97b3fcbc28f6b46de4e72f536b314380884f6dd192747258f861bf060aa588ab
3
+ metadata.gz: bae79ddc57bfa8955a50f062b2305db04ed9618fa40011ec78341f62083487a3
4
+ data.tar.gz: 176267b31455a0f491aaff4326e1274db3684aa878bf811b60b7f5195613a228
5
5
  SHA512:
6
- metadata.gz: 56a3b688bf4646a11888677fad884937b9f83b04b2cb47d5740f9cb0ce3e1c754abbe861e4888418f6ec168695f940d8ac24c4de3461c61a2d4f7360953d3f30
7
- data.tar.gz: 9743b2bc369dfedf3ff28893062171bb1af0a2af201f845701966409ab4c0063a4584257f445fabc6c14eba8a170ddbac9e5fe391554be53652f801cf2d482d5
6
+ metadata.gz: '089b563ba5cba6732bf75f6b29218e1b2cdf29f251c21c4c0abd577b4492fcea2c7ee24d5d616c6e8cec92f051cbbf551219e580846db48689fcf5c77c857b71'
7
+ data.tar.gz: 28c4160d29e5af26bd388dfda7feacdbb0d484f522080e57b5aafc10041ed07b8c62b6c799a8a6676bb59fca3db643a9319fcb7afbe661788c37558454bd924c
@@ -5,14 +5,10 @@ import co.elastic.matrix.*
5
5
  import groovy.transform.Field
6
6
 
7
7
  /**
8
- This is required to know if there any failures when running the downstream jobs.
8
+ This is the parallel tasks generator,
9
+ it is need as field to store the results of the tests.
9
10
  */
10
- @Field def rubyTasksFailed = false
11
-
12
- /**
13
- This is required to store the build status for the downstream jobs.
14
- */
15
- @Field def rubyDownstreamJobs = [:]
11
+ @Field def rubyTasksGen
16
12
 
17
13
  pipeline {
18
14
  agent { label 'linux && immutable' }
@@ -42,7 +38,7 @@ pipeline {
42
38
  quietPeriod(10)
43
39
  }
44
40
  triggers {
45
- issueCommentTrigger('(?i).*(?:jenkins\\W+)?run\\W+(?:the\\W+)?tests(?:\\W+please)?.*')
41
+ issueCommentTrigger('(?i).*(?:jenkins\\W+)?run\\W+(?:the\\W+)?(?:benchmark\\W+)?tests(?:\\W+please)?.*')
46
42
  }
47
43
  parameters {
48
44
  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.')
@@ -98,50 +94,53 @@ pipeline {
98
94
  /**
99
95
  Execute unit tests.
100
96
  */
101
- stage('Test') {
102
- options { skipDefaultCheckout() }
97
+ stage('Tests') {
103
98
  when {
104
99
  beforeAgent true
105
100
  expression { return env.ONLY_DOCS == "false" }
106
101
  }
107
- steps {
108
- withGithubNotify(context: 'Tests', tab: 'tests') {
109
- deleteDir()
110
- unstash "source"
111
- dir("${BASE_DIR}"){
112
- script {
113
- def ruby = readYaml(file: '.ci/.jenkins_ruby.yml')
114
- def testTasks = [:]
115
- def i = 0
116
- ruby['RUBY_VERSION'].each{ rubyVersion ->
117
- testTasks[rubyVersion] = { runJob(rubyVersion, i++) }
118
- }
119
- parallel(testTasks)
102
+ failFast false
103
+ parallel {
104
+ stage('Tests') {
105
+ options { skipDefaultCheckout() }
106
+ steps {
107
+ withGithubNotify(context: 'Tests', tab: 'tests') {
108
+ runTests('.ci/.jenkins_framework.yml')
109
+ }
110
+ }
111
+ post {
112
+ always {
113
+ convergeCoverage()
120
114
  }
121
115
  }
122
116
  }
123
- }
124
- }
125
- stage('Integration Tests') {
126
- agent none
127
- when {
128
- beforeAgent true
129
- allOf {
130
- expression { return env.ONLY_DOCS == "false" }
131
- anyOf {
132
- changeRequest()
133
- expression { return !params.Run_As_Master_Branch }
117
+ stage('Master Tests frameworks') {
118
+ options { skipDefaultCheckout() }
119
+ steps {
120
+ catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE', message: "The tests for the master framework have failed. Let's warn instead.") {
121
+ runTests('.ci/.jenkins_master_framework.yml')
122
+ }
123
+ }
124
+ }
125
+ stage('Integration Tests') {
126
+ agent none
127
+ when {
128
+ beforeAgent true
129
+ anyOf {
130
+ changeRequest()
131
+ expression { return !params.Run_As_Master_Branch }
132
+ }
133
+ }
134
+ steps {
135
+ githubNotify(context: "${env.GITHUB_CHECK_ITS_NAME}", description: "${env.GITHUB_CHECK_ITS_NAME} ...", status: 'PENDING', targetUrl: "${env.JENKINS_URL}search/?q=${env.ITS_PIPELINE.replaceAll('/','+')}")
136
+ build(job: env.ITS_PIPELINE, propagate: false, wait: true,
137
+ parameters: [ string(name: 'INTEGRATION_TEST', value: 'Ruby'),
138
+ string(name: 'BUILD_OPTS', value: "--ruby-agent-version ${env.GIT_BASE_COMMIT} --ruby-agent-version-state ${env.GIT_BUILD_CAUSE} --ruby-agent-repo ${env.CHANGE_FORK?.trim() ?: 'elastic'}/${env.REPO} --opbeans-ruby-agent-branch ${env.GIT_BASE_COMMIT}"),
139
+ string(name: 'GITHUB_CHECK_NAME', value: env.GITHUB_CHECK_ITS_NAME),
140
+ string(name: 'GITHUB_CHECK_REPO', value: env.REPO),
141
+ string(name: 'GITHUB_CHECK_SHA1', value: env.GIT_BASE_COMMIT) ])
134
142
  }
135
143
  }
136
- }
137
- steps {
138
- build(job: env.ITS_PIPELINE, propagate: false, wait: false,
139
- parameters: [string(name: 'INTEGRATION_TEST', value: 'Ruby'),
140
- string(name: 'BUILD_OPTS', value: "--ruby-agent-version ${env.GIT_BASE_COMMIT} --ruby-agent-version-state ${env.GIT_BUILD_CAUSE} --ruby-agent-repo ${env.CHANGE_FORK?.trim() ?: 'elastic'}/${env.REPO} --opbeans-ruby-agent-branch ${env.GIT_BASE_COMMIT}"),
141
- string(name: 'GITHUB_CHECK_NAME', value: env.GITHUB_CHECK_ITS_NAME),
142
- string(name: 'GITHUB_CHECK_REPO', value: env.REPO),
143
- string(name: 'GITHUB_CHECK_SHA1', value: env.GIT_BASE_COMMIT)])
144
- githubNotify(context: "${env.GITHUB_CHECK_ITS_NAME}", description: "${env.GITHUB_CHECK_ITS_NAME} ...", status: 'PENDING', targetUrl: "${env.JENKINS_URL}search/?q=${env.ITS_PIPELINE.replaceAll('/','+')}")
145
144
  }
146
145
  }
147
146
  stage('Benchmarks') {
@@ -155,6 +154,7 @@ pipeline {
155
154
  branch "v\\d?"
156
155
  tag pattern: 'v\\d+.*', comparator: "REGEXP"
157
156
  expression { return params.Run_As_Master_Branch }
157
+ expression { return env.GITHUB_COMMENT?.contains('benchmark tests') }
158
158
  }
159
159
  expression { return params.bench_ci }
160
160
  }
@@ -195,20 +195,6 @@ pipeline {
195
195
  }
196
196
  }
197
197
  }
198
- stage('Populate Downstream failures') {
199
- when {
200
- expression { return rubyTasksFailed }
201
- }
202
- steps {
203
- script {
204
- def message = 'Failures when running the "Test" stage'
205
- if (notifyBuildResult.isAnyDownstreamJobFailedWithTimeout(rubyDownstreamJobs)) {
206
- message += ' related to a timeout issue'
207
- }
208
- error(message)
209
- }
210
- }
211
- }
212
198
  stage('Release') {
213
199
  options { skipDefaultCheckout() }
214
200
  environment {
@@ -272,33 +258,14 @@ pipeline {
272
258
  }
273
259
  }
274
260
  }
275
- stage('Coverage converge') {
276
- when {
277
- beforeAgent true
278
- expression { return env.ONLY_DOCS == "false" }
279
- }
280
- steps{
281
- dir("${BASE_DIR}"){
282
- unpack_artifacts(rubyDownstreamJobs)
283
- sh(script: "./spec/scripts/coverage_converge.sh")
284
- cobertura coberturaReportFile: "coverage/coverage.xml"
285
- }
286
- }
287
- }
288
261
  }
289
262
  post {
290
263
  cleanup {
291
- notifyBuildResult(downstreamJobs: rubyDownstreamJobs)
264
+ notifyBuildResult()
292
265
  }
293
266
  }
294
267
  }
295
268
 
296
- def unpack_artifacts(builds) {
297
- builds.values().each() { build ->
298
- copyArtifacts(projectName: "apm-agent-ruby/apm-agent-ruby-downstream/${env.BRANCH_NAME}", selector: specific(buildNumber: build.number.toString()))
299
- }
300
- }
301
-
302
269
  /**
303
270
  Run benchmarks for a Ruby version, then report the results to the Elasticsearch server.
304
271
  */
@@ -337,26 +304,95 @@ def runBenchmark(version){
337
304
  }
338
305
  }
339
306
 
340
- def runJob(String rubyVersion, int sleep = 1){
341
- def buildObject
342
- def branch = env.CHANGE_ID?.trim() ? env.CHANGE_TARGET : env.BRANCH_NAME
343
- try {
344
- def quiet = (sleep * randomNumber(min: 2, max: 6)) + 5
345
- buildObject = build(job: "apm-agent-ruby/apm-agent-ruby-downstream/${env.BRANCH_NAME}",
346
- parameters: [
347
- string(name: 'RUBY_VERSION', value: "${rubyVersion}"),
348
- string(name: 'BRANCH_SPECIFIER', value: "${env.GIT_BASE_COMMIT}"),
349
- string(name: 'MERGE_TARGET', value: "${branch}")
350
- ],
351
- quietPeriod: quiet)
352
- } catch(e) {
353
- rubyTasksFailed = true
354
- buildObject = e
355
- warnError('Downstream Failed') {
356
- error("Downstream job for '${rubyVersion}' failed")
307
+ class RubyParallelTaskGenerator extends DefaultParallelTaskGenerator {
308
+
309
+ public RubyParallelTaskGenerator(Map params){
310
+ super(params)
311
+ }
312
+
313
+ /**
314
+ build a clousure that launch and agent and execute the corresponding test script,
315
+ then store the results.
316
+ */
317
+ public Closure generateStep(x, y){
318
+ return {
319
+ steps.sleep steps.randomNumber(min:10, max: 30)
320
+ steps.node('linux && immutable'){
321
+ // Label is transformed to avoid using the internal docker registry in the x coordinate
322
+ // TODO: def label = "${tag}:${x?.drop(x?.lastIndexOf('/')+1)}#${y}"
323
+ def label = "${tag}:${x}#${y}"
324
+ try {
325
+ steps.runScript(label: label, ruby: x, framework: y)
326
+ saveResult(x, y, 1)
327
+ } catch(e){
328
+ saveResult(x, y, 0)
329
+ steps.error("${label} tests failed : ${e.toString()}\n")
330
+ } finally {
331
+ steps.junit(allowEmptyResults: false,
332
+ keepLongStdio: true,
333
+ testResults: "**/spec/junit-reports/**/ruby-agent-junit.xml")
334
+ steps.dir("${steps.env.BASE_DIR}"){
335
+ steps.archiveArtifacts(artifacts: 'coverage/matrix_results/', defaultExcludes: false)
336
+ steps.stash(name: steps.normalise("coverage-${x}-${y}"), includes: 'coverage/matrix_results/', allowEmpty: true)
337
+ }
338
+ }
339
+ }
357
340
  }
358
- } finally {
359
- rubyDownstreamJobs["${rubyVersion}"] = buildObject
341
+ }
342
+ }
343
+
344
+ /**
345
+ Run all the tests for the given file with the frameworks to test
346
+ */
347
+ def runTests(frameworkFile) {
348
+ deleteDir()
349
+ unstash "source"
350
+ dir("${BASE_DIR}"){
351
+ rubyTasksGen = new RubyParallelTaskGenerator(
352
+ xKey: 'RUBY_VERSION',
353
+ yKey: 'FRAMEWORK',
354
+ xFile: ".ci/.jenkins_ruby.yml",
355
+ yFile: frameworkFile,
356
+ exclusionFile: ".ci/.jenkins_exclude.yml",
357
+ tag: "Ruby",
358
+ name: "Ruby",
359
+ steps: this
360
+ )
361
+ def testTasks = rubyTasksGen.generateParallelTests()
362
+ parallel(testTasks)
363
+ }
364
+ }
365
+
366
+ /**
367
+ Run tests for a Ruby version and framework version.
368
+ */
369
+ def runScript(Map params = [:]){
370
+ def label = params.label
371
+ def ruby = params.ruby
372
+ def framework = params.framework
373
+ log(level: 'INFO', text: "${label}")
374
+ retry(2){
375
+ withEnv(["HOME=${env.WORKSPACE}", "PATH=${env.PATH}:${env.WORKSPACE}/bin"]) {
376
+ deleteDir()
377
+ unstash 'source'
378
+ dir("${BASE_DIR}"){
379
+ sleep randomNumber(min:10, max: 30)
380
+ dockerLogin(secret: "${DOCKER_SECRET}", registry: "${DOCKER_REGISTRY}")
381
+ sh("./spec/scripts/spec.sh ${ruby} ${framework}")
382
+ }
383
+ }
384
+ }
385
+ }
386
+
387
+ def convergeCoverage() {
388
+ deleteDir()
389
+ unstash('source')
390
+ dir("${BASE_DIR}"){
391
+ rubyTasksGen.dumpMatrix('-')?.each {
392
+ unstash(normalise("coverage-${it}"))
393
+ }
394
+ sh(script: './spec/scripts/coverage_converge.sh')
395
+ cobertura coberturaReportFile: 'coverage/coverage.xml', onlyStable: false
360
396
  }
361
397
  }
362
398
 
@@ -374,3 +410,10 @@ def prepareRelease(Closure body){
374
410
  }
375
411
  }
376
412
  }
413
+
414
+ // Transform the versions like:
415
+ // - docker.elastic.co/observability-ci/jruby:9.2-12-jdk to jruby-9.2-12-jdk
416
+ // - jruby:9.1 to jruby-9.1
417
+ def normalise(def what) {
418
+ return what.replaceAll('.*/', '').replaceAll(':', '-')
419
+ }
@@ -2,13 +2,15 @@
2
2
 
3
3
  source /usr/local/bin/bash_standard_lib.sh
4
4
 
5
- grep "-" .ci/.jenkins_ruby.yml | grep -v 'observability-ci' | cut -d'-' -f2- | \
6
- while read -r version;
7
- do
8
- transformedName=${version/:/-}
9
- transformedVersion=$(echo "${version}" | cut -d":" -f2)
10
- imageName="apm-agent-ruby"
11
- registryImageName="docker.elastic.co/observability-ci/${imageName}:${transformedName}"
12
- (retry 2 docker pull "${registryImageName}")
13
- docker tag "${registryImageName}" "${imageName}:${transformedVersion}"
14
- done
5
+ if [ -x "$(command -v docker)" ]; then
6
+ grep "-" .ci/.jenkins_ruby.yml | grep -v 'observability-ci' | cut -d'-' -f2- | \
7
+ while read -r version;
8
+ do
9
+ transformedName=${version/:/-}
10
+ transformedVersion=$(echo "${version}" | cut -d":" -f2)
11
+ imageName="apm-agent-ruby"
12
+ registryImageName="docker.elastic.co/observability-ci/${imageName}:${transformedName}"
13
+ (retry 2 docker pull "${registryImageName}")
14
+ docker tag "${registryImageName}" "${imageName}:${transformedVersion}"
15
+ done
16
+ fi
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
1
  --color
2
- --require spec_helper
3
2
  --exclude-pattern spec/integration/**/*_spec.rb
@@ -35,7 +35,87 @@ endif::[]
35
35
  [[release-notes-3.x]]
36
36
  === Ruby Agent version 3.x
37
37
 
38
+ [[release-notes-3.11.0]]
39
+ ==== 3.11.0 (2020-10-27)
40
+
41
+ [float]
42
+ ===== Added
43
+
44
+ - Add and read sampling info from Tracestate headers {pull}858[#858]
45
+ - Add information about cloud hosting environment if available {pull}871[#871]
46
+
47
+ [float]
48
+ ===== Changed
49
+
50
+ - Align the default value of `sanitize_field_names` with other agents {pull}867[#867]
51
+ - Ensure max 4 digits of precision for `sample_rate` as per agent spec {pull}880[#880]
52
+
53
+ [float]
54
+ ===== Fixed
55
+
56
+ - Fix Delayed::Job class names when used through ActiveJob {pull}869[#869]
57
+ - Fix Delayed::Job when run without the agent running {pull}874[#874]
58
+ - Fix Kubernetes related metadata {pull}882[#882]
59
+
60
+ [[release-notes-3.10.1]]
61
+ ==== 3.10.1 (2020-08-26)
62
+
63
+ [float]
64
+ ===== Fixed
65
+
66
+ - Remove secrets from cookies in errors {pull}863[#863]
67
+ - Silence deprecation warning when setting `ignore_url_patterns` to default {pull}865[#865]
68
+
69
+ [[release-notes-3.10.0]]
70
+ ==== 3.10.0 (2020-08-26)
71
+
72
+ [float]
73
+ ===== Added
74
+
75
+ - Config option `transaction_ignore_urls` to replace `ignore_url_patterns` {pull}844[#844]
76
+ - Prepend `(?-i)` to patterns to make them case-sensitive {pull}846[#846]
77
+
38
78
  [float]
79
+ ===== Fixed
80
+
81
+ - Reverted {pull}839[#839]
82
+ - Improved Kubernetes pod ID detection {pull}859[#859]
83
+
84
+ [[release-notes-3.9.0]]
85
+ ==== 3.9.0 (2020-08-04)
86
+
87
+ [float]
88
+ ===== Fixed
89
+ - Scrub request body of illegal UTF-8 characters {pull}832[#832]
90
+
91
+ [float]
92
+ ===== Added
93
+
94
+ - Support for DynamoDB {pull}827[#827]
95
+
96
+ [float]
97
+ ===== Fixed
98
+
99
+ - Fix Rails Engine views' paths being reported as absolute {pull}839[#839]
100
+ - Fix an issue when using Elasticsearch spy without a running agent {pull}830[#830]
101
+
102
+ [[release-notes-3.8.0]]
103
+ ==== 3.8.0 (2020-06-18)
104
+
105
+ [float]
106
+ ===== Added
107
+
108
+ - Add the option `capture_elasticsearch_queries` {pull}789[#789]
109
+ - Add option to skip patching Kernal#require {pull}812[#812]
110
+ - Add option `service_node_name` {pull}820[#820]
111
+
112
+ [float]
113
+ ===== Fixed
114
+
115
+ - Allow underscores in hostnames in Net::HTTP spy {pull}804[#804]
116
+ - Don't change log level on logger object via remote config {pull}809[#809]
117
+ - Update and fix the Opentracing bridge {pull}791[#791]
118
+
39
119
  [[release-notes-3.7.0]]
40
120
  ==== 3.7.0 (2020-05-14)
41
121
 
data/Gemfile CHANGED
@@ -28,18 +28,19 @@ gem 'pry'
28
28
  gem 'rack-test'
29
29
  gem 'rspec', '~> 3'
30
30
  gem 'rspec-its'
31
- gem 'rubocop', require: nil
32
- gem 'rubocop-performance', require: nil
33
31
  gem 'timecop'
34
32
  gem 'webmock'
35
33
 
36
34
  # Integrations
35
+ gem 'aws-sdk-dynamodb', require: nil
37
36
  gem 'aws-sdk-sqs', require: nil
38
37
  gem 'elasticsearch', require: nil
39
38
  gem 'fakeredis', require: nil
40
39
  gem 'faraday', require: nil
41
40
  gem 'graphql', require: nil
42
- gem 'grpc' if !defined?(JRUBY_VERSION) && RUBY_VERSION < '2.7'
41
+ gem 'google-protobuf', '< 3.12' if !defined?(JRUBY_VERSION) && RUBY_VERSION < '2.5'
42
+ gem 'grpc' if !defined?(JRUBY_VERSION)
43
+ gem 'json'
43
44
  gem 'json-schema', require: nil
44
45
  gem 'mongo', require: nil
45
46
  gem 'opentracing', require: nil