newrelic-telemetry_sdk 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,95 @@
1
+ [![Community Project header](https://github.com/newrelic/opensource-website/raw/master/src/images/categories/Community_Project.png)](https://opensource.newrelic.com/oss-category/#community-project)
2
+
3
+ # New Relic Ruby Telemetry SDK
4
+ The New Relic Ruby Telemetry SDK is an easy way to send data to New Relic. The SDK currently supports sending span/trace data via New Relic's [Trace API](https://docs.newrelic.com/docs/understand-dependencies/distributed-tracing/trace-api/introduction-trace-api).
5
+
6
+ Why is this cool?
7
+
8
+ Send data to New Relic! No agent required.
9
+
10
+ Our [Telemetry SDK](https://docs.newrelic.com/docs/data-ingest-apis/get-data-new-relic/new-relic-sdks/telemetry-sdks-send-custom-telemetry-data-new-relic) makes it easier for you to send your telemetry data to New Relic. We've covered all of the basics for you so you can focus on writing feature code directly related to your business need or interest.
11
+
12
+ ## Installation
13
+
14
+ ### With Bundler
15
+
16
+ For using with Bundler, add the New Relic Telemetry SDK gem to your project's Gemfile.
17
+
18
+ ```ruby
19
+ gem 'newrelic-telemetry_sdk'
20
+ ```
21
+
22
+ and run `bundle install` to activate the new gem.
23
+
24
+ ### Without Bundler
25
+
26
+ If you are not using Bundler, install the gem with:
27
+
28
+ ```bash
29
+ gem install newrelic-telemetry_sdk
30
+ ```
31
+
32
+ and then require the New Relic Ruby Telemetry SDK in your Ruby start-up sequence:
33
+
34
+ ```ruby
35
+ require 'newrelic-telemetry_sdk'
36
+ ```
37
+
38
+ ## Getting Started
39
+
40
+ ### Sending your first span
41
+
42
+ The example code assumes you've set the following environment variables:
43
+
44
+ * NEW_RELIC_INSERT_KEY
45
+
46
+ ```
47
+ NewRelic::TelemetrySdk.configure do |config|
48
+ config.api_insert_key = ENV["NEW_RELIC_INSERT_KEY"]
49
+ end
50
+
51
+ span = NewRelic::TelemetrySdk::Span.new
52
+ sleep 1
53
+ span.finish
54
+ client = NewRelic::TelemetrySdk::TraceClient.new
55
+ client.report span
56
+ ```
57
+
58
+ For more detailed examples please see [our examples directory](./examples).
59
+
60
+ ## Testing
61
+
62
+ Running the test suite is simple. Just invoke:
63
+
64
+ bundle
65
+ bundle exec rake
66
+
67
+ ## Support
68
+
69
+ New Relic hosts and moderates an online forum where customers can interact with New Relic employees as well as other customers to get help and share best practices. Like all official New Relic open source projects, there's a related Community topic in the New Relic Explorers Hub. You can find this project's topic/threads here:
70
+
71
+ https://discuss.newrelic.com/t/new-relic-telemetry-sdk-for-ruby/114266
72
+
73
+ ## Contributing
74
+ We encourage your contributions to improve newrelic-telemetry-sdk-ruby! Keep in mind when you submit your pull request, you'll need to sign the CLA via the click-through using CLA-Assistant. You only have to sign the CLA one time per project.
75
+ If you have any questions, or to execute our corporate CLA, required if your contribution is on behalf of a company, please drop us an email at opensource@newrelic.com.
76
+
77
+ ## License
78
+ newrelic-telemetry-sdk-ruby is licensed under the [Apache 2.0](http://apache.org/licenses/LICENSE-2.0.txt) License.
79
+
80
+ ## Find and use data
81
+
82
+ Tips on how to find and query your data in New Relic:
83
+
84
+ * [Find metric data](https://docs.newrelic.com/docs/data-ingest-apis/get-data-new-relic/metric-api/introduction-metric-api#find-data)
85
+ * [Find event data](https://docs.newrelic.com/docs/insights/insights-data-sources/custom-data/introduction-event-api#find-data)
86
+ * [Find trace/span data](https://docs.newrelic.com/docs/understand-dependencies/distributed-tracing/trace-api/introduction-trace-api#view-data>)
87
+
88
+ For general querying information, see:
89
+
90
+ * [Query New Relic data](https://docs.newrelic.com/docs/using-new-relic/data/understand-data/query-new-relic-data)
91
+ * [Intro to NRQL](https://docs.newrelic.com/docs/query-data/nrql-new-relic-query-language/getting-started/introduction-nrql)
92
+
93
+ ## Limitations
94
+
95
+ The New Relic Telemetry APIs are rate limited. Please reference the documentation for [New Relic Metrics API](https://docs.newrelic.com/docs/introduction-new-relic-metric-api) and [New Relic Trace API requirements and limits](https://docs.newrelic.com/docs/apm/distributed-tracing/trace-api/trace-api-general-requirements-limits) on the specifics of the rate limits.
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/cla.md ADDED
@@ -0,0 +1,16 @@
1
+ # NEW RELIC, INC.
2
+ ## INDIVIDUAL CONTRIBUTOR LICENSE AGREEMENT
3
+ Thank you for your interest in contributing to the open source projects of New Relic, Inc. (“New Relic”). In order to clarify the intellectual property license granted with Contributions from any person or entity, New Relic must have a Contributor License Agreement ("Agreement") on file that has been signed by each Contributor, indicating agreement to the license terms below. This Agreement is for your protection as a Contributor as well as the protection of New Relic; it does not change your rights to use your own Contributions for any other purpose.
4
+
5
+ You accept and agree to the following terms and conditions for Your present and future Contributions submitted to New Relic. Except for the licenses granted herein to New Relic and recipients of software distributed by New Relic, You reserve all right, title, and interest in and to Your Contributions.
6
+
7
+ ## Definitions.
8
+ 1. "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is entering into this Agreement with New Relic. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
9
+ 2. "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to New Relic for inclusion in, or documentation of, any of the products managed or maintained by New Relic (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to New Relic or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, New Relic for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
10
+ 3. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to New Relic and to recipients of software distributed by New Relic a perpetual, worldwide, non-exclusive, no-charge, royalty-free, transferable, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
11
+ 4. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to New Relic and to recipients of software distributed by New Relic a perpetual, worldwide, non-exclusive, no-charge, royalty-free, transferable, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contributions alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that Your Contribution, or the Work to which You have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
12
+ 5. You represent that You are legally entitled to grant the above licenses. If Your employer(s) has rights to intellectual property that You create that includes Your Contributions, You represent that You have received permission to make Contributions on behalf of that employer, that Your employer has waived such rights for Your Contributions to New Relic, or that Your employer has executed a separate Agreement with New Relic.
13
+ 6. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which You are personally aware and which are associated with any part of Your Contributions.
14
+ 7. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
15
+ 8. Should You wish to submit work that is not Your original creation, You may submit it to New Relic separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which You are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
16
+ 9. You agree to notify New Relic of any facts or circumstances of which You become aware that would make these representations inaccurate in any respect.
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'newrelic-telemetry_sdk', path: "../../"
@@ -0,0 +1,80 @@
1
+ # This example is one of the simplest examples demonstrating sending one span
2
+ # at at time to the Trace API endpoint. It will time how long it takes to retrive
3
+ # an external URL's content and construct a span and send it to New Relic's server.
4
+ #
5
+ # Usage: `API_KEY=<YOUR_API_KEY> bundle exec ruby simple.rb`
6
+ require 'net/http'
7
+ require 'bundler'
8
+ Bundler.require
9
+
10
+ # Fail fast if an API_KEY wasn't exported to the environment.
11
+ unless ENV["API_KEY"]
12
+ raise "No API Key supplied. Export API_KEY environment variable!"
13
+ end
14
+
15
+ # Just a random ID generator -- A string of 16 (by default) hexadecimal digits.
16
+ def random_id length=16
17
+ length.times.map{rand(16).to_s(16)}.join
18
+ end
19
+
20
+ def configure_sdk
21
+ NewRelic::TelemetrySdk.configure do |config|
22
+ config.api_insert_key = ENV["API_KEY"]
23
+ end
24
+ end
25
+
26
+ # Instantiates and memoizes the SDK client for sending Spans
27
+ def trace_client
28
+ return @trace_client if @trace_client
29
+ configure_sdk
30
+ @trace_client = NewRelic::TelemetrySdk::TraceClient.new
31
+ end
32
+
33
+ # Wraps the given block by recording time to retrieve URL's resource, then
34
+ # constructs the span object and sends it to the server.
35
+ # The response from the given block is returned as the method's result
36
+ def record_external_request
37
+ # Capture time elapsed to make the external request (given block)
38
+ start_time = Time.now
39
+ response = yield
40
+ finish_time = Time.now
41
+ duration = finish_time - start_time
42
+
43
+ # Set some custom attributes for the Span (some static, some dynamic)
44
+ custom_attributes = {
45
+ "name": "Net::HTTP#get",
46
+ "http.method": "GET",
47
+ "http.status": response.code,
48
+ "size": response.body.bytesize,
49
+ }
50
+
51
+ # Construct the span and send it.
52
+ span = NewRelic::TelemetrySdk::Span.new(
53
+ id: random_id(16),
54
+ trace_id: random_id(32),
55
+ start_time: start_time,
56
+ duration_ms: (duration * 1000).to_i,
57
+ name: "Net::HTTP#get",
58
+ custom_attributes: custom_attributes
59
+ )
60
+ trace_client.report span
61
+
62
+ # return the results from the given block/proc
63
+ response
64
+ end
65
+
66
+ # Retrieve the resource from given URL, recording a span
67
+ # for the operation.
68
+ def get_page url
69
+ uri = URI::parse(url)
70
+ response = record_external_request do
71
+ conn = Net::HTTP.new uri.host, uri.port
72
+ conn.use_ssl = true
73
+ conn.request_get("/")
74
+ end
75
+ puts "Fetched: #{uri}"
76
+ puts "Status: #{response.code}"
77
+ puts "Size: #{response.body.bytesize}"
78
+ end
79
+
80
+ get_page "https://google.com"
@@ -0,0 +1,89 @@
1
+ require 'net/http'
2
+ require 'bundler'
3
+ Bundler.require
4
+
5
+ unless ENV["API_KEY"]
6
+ raise "No API Key supplied. Export API_KEY environment variable!"
7
+ end
8
+
9
+ def configure_sdk
10
+ NewRelic::TelemetrySdk.configure do |config|
11
+ config.api_insert_key = ENV["API_KEY"]
12
+ end
13
+ end
14
+
15
+ def setup_buffer_harvesting common_attributes = {host: 'fake_host'}
16
+ configure_sdk
17
+ @harvester = NewRelic::TelemetrySdk::Harvester.new
18
+ @trace_client = NewRelic::TelemetrySdk::TraceClient.new
19
+ # Creates a buffer with common attributes that will be added to all spans in the buffer
20
+ @buffer = NewRelic::TelemetrySdk::Buffer.new common_attributes
21
+ # Register the buffer with a name and the associated client
22
+ @harvester.register 'external_spans', @buffer, @trace_client
23
+ # Begins the harvester running in the background
24
+ @harvester.start
25
+ end
26
+
27
+ def stop_harvester
28
+ @harvester.stop
29
+ end
30
+
31
+ def random_id length=16
32
+ length.times.map{rand(16).to_s(16)}.join
33
+ end
34
+
35
+ def record_external_request
36
+ start_time = Time.now
37
+ response = yield
38
+ finish_time = Time.now
39
+ duration = finish_time - start_time
40
+
41
+ custom_attributes = {
42
+ "http.status": response.code,
43
+ "size": response.body.bytesize,
44
+ }
45
+
46
+ span = NewRelic::TelemetrySdk::Span.new(
47
+ id: random_id(16),
48
+ trace_id: random_id(32),
49
+ start_time: start_time,
50
+ duration_ms: (duration * 1000).to_i,
51
+ name: "Net::HTTP#get",
52
+ custom_attributes: custom_attributes
53
+ )
54
+ @buffer.record span
55
+
56
+ response
57
+ end
58
+
59
+ def get_page url
60
+ uri = URI::parse(url)
61
+ response = record_external_request do
62
+ conn = Net::HTTP.new uri.host, uri.port
63
+ conn.use_ssl = true
64
+ conn.request_get("/")
65
+ end
66
+ puts "Fetched: #{uri}"
67
+ puts "Status: #{response.code}"
68
+ puts "Size: #{response.body.bytesize}"
69
+ end
70
+
71
+ # Creates the buffer, client, and harvester
72
+ common_attributes = {"name": "Net::HTTP#get", "http.method": "GET"}
73
+ setup_buffer_harvesting common_attributes
74
+
75
+ # Buffer holds multiple spans, spans are added to the buffer in the record_external_request_method
76
+ get_page "https://google.com"
77
+ get_page "https://google.com"
78
+ get_page "https://google.com"
79
+
80
+ # allow harvester time to send data, default interval is 5 seconds
81
+ sleep 6
82
+ # buffer is now empty
83
+
84
+ get_page "https://google.com"
85
+ get_page "https://google.com"
86
+ get_page "https://google.com"
87
+
88
+ # harvester will flush remaining data in buffer when stopped
89
+ stop_harvester
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ # This file is distributed under New Relic's license terms.
4
+ # See https://github.com/newrelic/newrelic-telemetry-sdk-ruby/blob/main/LICENSE for complete details.
5
+
6
+ require 'net/http'
7
+ require 'logger'
8
+ require 'json'
9
+ require 'zlib'
10
+ require 'securerandom'
11
+ require 'logger'
12
+
13
+ module NewRelic
14
+ module TelemetrySdk
15
+ private
16
+
17
+ USER_AGENT_NAME = "NewRelic-Ruby-TelemetrySDK"
18
+ RFC7230_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/
19
+
20
+ NEW_RELIC_PREFIX = "NEW_RELIC"
21
+ API_INSERT_KEY = "#{NEW_RELIC_PREFIX}_API_INSERT_KEY"
22
+ end
23
+ end
24
+
25
+ require 'newrelic/telemetry_sdk/version'
26
+ require 'newrelic/telemetry_sdk/util'
27
+ require 'newrelic/telemetry_sdk/exception'
28
+
29
+ require 'newrelic/telemetry_sdk/config'
30
+ require 'newrelic/telemetry_sdk/configurator'
31
+ require 'newrelic/telemetry_sdk/logger'
32
+
33
+ require 'newrelic/telemetry_sdk/span'
34
+ require 'newrelic/telemetry_sdk/harvester'
35
+ require 'newrelic/telemetry_sdk/buffer'
36
+
37
+ require 'newrelic/telemetry_sdk/clients/client'
38
+ require 'newrelic/telemetry_sdk/clients/trace_client'
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ # This file is distributed under New Relic's license terms.
4
+ # See https://github.com/newrelic/newrelic-telemetry-sdk-ruby/blob/main/LICENSE for complete details.
5
+
6
+ module NewRelic
7
+ module TelemetrySdk
8
+ # Buffers store discrete pieces of data (e.g. {Span Spans}) until they are
9
+ # sent via a timed {Harvester}. Batches of data may also be flushed
10
+ # from a buffer and sent directly through the client.
11
+ #
12
+ # @api public
13
+ class Buffer
14
+
15
+ include NewRelic::TelemetrySdk::Logger
16
+
17
+ # @!attribute common_attributes [optional, Hash]
18
+ # Attributes that should be added to every item in the batch
19
+ # e.g. +{host: 'my_host'}+
20
+ attr_accessor :common_attributes
21
+
22
+ # Record a discrete piece of data (e.g. a Span) into the buffer
23
+ # for batching purposes.
24
+ # @param common_attributes [optional, Hash]
25
+ # Attributes that should be added to every item in the batch
26
+ # e.g. +{host: 'my_host'}+
27
+ #
28
+ # @api public
29
+ def initialize common_attributes=nil
30
+ @items = []
31
+ @common_attributes = common_attributes
32
+ @lock = Mutex.new
33
+ end
34
+
35
+ # Record a discrete piece of data (e.g. a {Span}) into the {Buffer buffer}.
36
+ # @param item [Span, etc.]
37
+ # A piece of data to record into the buffer. Must have a to_h method
38
+ # for transformation.
39
+ #
40
+ # @api public
41
+ def record item
42
+ @lock.synchronize { @items << item }
43
+ rescue => e
44
+ log_error "Encountered error while recording in buffer", e
45
+ end
46
+
47
+ # Return a batch of data that has been collected in this buffer
48
+ # as an Array of Hashes. Also returns a Hash of any common attributes
49
+ # that have been set on the buffer to be attached to each individual data item.
50
+ #
51
+ # @api public
52
+ def flush
53
+ data = nil
54
+ @lock.synchronize do
55
+ data = @items
56
+ @items = []
57
+ end
58
+ data.map!(&:to_h)
59
+ return data, @common_attributes
60
+ rescue => e
61
+ log_error "Encountered error while flushing buffer", e
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,262 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ # This file is distributed under New Relic's license terms.
4
+ # See https://github.com/newrelic/newrelic-telemetry-sdk-ruby/blob/main/LICENSE for complete details.
5
+
6
+ module NewRelic
7
+ module TelemetrySdk
8
+ # This class is a parent class for clients used to send data to the New Relic data
9
+ # ingest endpoints over HTTP (e.g. TraceClient for span data). Clients will automatically
10
+ # resend data if a recoverable error occurs. They will also automatically handle
11
+ # connection issues and New Relic errors.
12
+ #
13
+ # @api public
14
+ class Client
15
+ include NewRelic::TelemetrySdk::Logger
16
+
17
+ def initialize host:,
18
+ path:,
19
+ headers: {},
20
+ use_gzip: true,
21
+ payload_type:
22
+ @connection = set_up_connection host
23
+ @path = path
24
+ @headers = headers
25
+ @gzip_request = use_gzip
26
+ @payload_type = payload_type
27
+ @user_agent_products = nil
28
+ add_user_agent_header @headers
29
+ add_content_encoding_header @headers if @gzip_request
30
+ @connection_attempts = 0
31
+ end
32
+
33
+ # Reports a single item to a New Relic data ingest endpoint.
34
+ #
35
+ # @param item
36
+ # a single point of data to send to New Relic (e.g. a Span). The item
37
+ # should respond to the +#to_h+ method to return a Hash which is then serialized
38
+ # and sent to the data ingest endpoint.
39
+ #
40
+ # @api public
41
+ def report item
42
+ # Report a batch of one pre-transformed item with no common attributes
43
+ report_batch [[item.to_h], nil]
44
+ rescue => e
45
+ log_error "Encountered error reporting item in client. Dropping data: 1 point of data", e
46
+ end
47
+
48
+ # Reports a batch of one or more items to a New Relic data ingest endpoint.
49
+ #
50
+ # @param batch_data [Array]
51
+ # a two-part array contianing a Array of Hashes paired with a Hash of
52
+ # common attributes.
53
+ #
54
+ # @api public
55
+ def report_batch batch_data
56
+ # We need to generate a version 4 uuid that will
57
+ # be used for each unique batch, including on retries.
58
+ # If a batch is split due to a 413 response,
59
+ # each smaller batch should have its own.
60
+
61
+ data, common_attributes = batch_data
62
+
63
+ @headers[:'x-request-id'] = SecureRandom.uuid
64
+
65
+ post_body = format_payload data, common_attributes
66
+ send_with_response_handling post_body, data, common_attributes
67
+ rescue => e
68
+ log_error "Encountered error reporting batch in client. Dropping data: #{data.size} points of data", e
69
+ end
70
+
71
+ # Allows creators of exporters and other product built on this SDK to provide information about
72
+ # their product for analytic purposes. It may be called multiple times and is idempotent.
73
+ #
74
+ # @param product [String]
75
+ # The name of the exporter or other product, e.g. NewRelic-Ruby-OpenTelemetry.
76
+ # @param version [optional, String]
77
+ # The version number of the exporter or other product.
78
+ #
79
+ # Both product and version must conform to RFC 7230.
80
+ # @see https://github.com/newrelic/newrelic-telemetry-sdk-specs/blob/master/communication.md#extending-user-agent-with-exporter-product Communication with New Relic
81
+ #
82
+ # @api public
83
+ def add_user_agent_product product, version=nil
84
+ # The product token must be valid to add to the headers
85
+ if product !~ RFC7230_TOKEN
86
+ log_once :warn, "Product is not a valid RFC 7230 token"
87
+ return
88
+ end
89
+
90
+ # The version is ignored if invalid
91
+ if version && version !~ RFC7230_TOKEN
92
+ log_once :warn, "Product version is not a valid RFC 7230 token"
93
+ version = nil
94
+ end
95
+
96
+ entry = [product, version].compact.join("/")
97
+
98
+ # adds the product entry and updates the combined user agent
99
+ # header, ignoring duplicate product entries.
100
+ @user_agent_products ||= []
101
+ unless @user_agent_products.include? entry
102
+ @user_agent_products << entry
103
+ add_user_agent_header @headers
104
+ end
105
+ rescue => e
106
+ log_error "Encountered error adding user agent product", e
107
+ end
108
+
109
+ private
110
+
111
+ def api_insert_key
112
+ TelemetrySdk.config.api_insert_key
113
+ end
114
+
115
+ def max_retries
116
+ TelemetrySdk.config.max_retries
117
+ end
118
+
119
+ def backoff_factor
120
+ TelemetrySdk.config.backoff_factor
121
+ end
122
+
123
+ def backoff_max
124
+ TelemetrySdk.config.backoff_max
125
+ end
126
+
127
+ def send_with_response_handling post_body, data, common_attributes
128
+ response = send_request post_body
129
+
130
+ case response
131
+ when Net::HTTPSuccess # 200 - 299
132
+ @connection_attempts = 0 # reset count after sucessful connection
133
+ logger.debug "Successfully sent data to New Relic with response: #{response.code}"
134
+
135
+ when Net::HTTPBadRequest, # 400
136
+ Net::HTTPUnauthorized, # 401
137
+ Net::HTTPForbidden, # 403
138
+ Net::HTTPNotFound, # 404
139
+ Net::HTTPMethodNotAllowed, # 405
140
+ Net::HTTPConflict, # 409
141
+ Net::HTTPGone, # 410
142
+ Net::HTTPLengthRequired # 411
143
+ log_once_and_drop_data response, data
144
+
145
+ when Net::HTTPRequestTimeOut # 408
146
+ log_and_retry response
147
+
148
+ when Net::HTTPRequestEntityTooLarge # 413
149
+ log_and_split_payload response, data, common_attributes
150
+
151
+ when Net::HTTPTooManyRequests # 429
152
+ log_and_retry_later response
153
+
154
+ else
155
+ log_and_retry_with_backoff response, data
156
+ end
157
+
158
+ rescue NewRelic::TelemetrySdk::RetriableServerResponseException
159
+ retry
160
+ end
161
+
162
+ def audit_logging_enabled?
163
+ TelemetrySdk.config.audit_logging_enabled
164
+ end
165
+
166
+ def send_request body
167
+ body = serialize body
168
+ log_json_payload body if audit_logging_enabled?
169
+ body = gzip_data body if @gzip_request
170
+ @connection.post @path, body, @headers
171
+ end
172
+
173
+ def log_json_payload payload
174
+ logger.debug "Sent payload: #{payload}"
175
+ end
176
+
177
+ def log_and_retry response
178
+ log_error response.message
179
+ raise NewRelic::TelemetrySdk::RetriableServerResponseException
180
+ end
181
+
182
+ def log_and_retry_later response
183
+ wait_time = response['Retry-After'].to_i
184
+ log_error "Connection error. Retrying in #{wait_time} seconds", response.message
185
+ sleep wait_time
186
+ raise NewRelic::TelemetrySdk::RetriableServerResponseException
187
+ end
188
+
189
+ def log_once_and_drop_data response, data
190
+ log_error_once response.class, response.message
191
+ log_error "Connection error. Dropping data: #{data.size} points of data"
192
+ end
193
+
194
+ def log_and_split_payload response, data, common_attributes
195
+ log_error "Payload too large. Splitting payload in half and attempting to resend.", response.message
196
+ if data.size > 1
197
+ # splits the data in half and calls report_batch for each half
198
+ midpoint = data.size/2.0
199
+ report_batch [data.first(midpoint.ceil), common_attributes]
200
+ report_batch [data.last(midpoint.floor), common_attributes]
201
+ else
202
+ # payload cannot be split, drop data
203
+ log_error "Unable to split payload. Dropping data: #{data.size} points of data"
204
+ end
205
+ end
206
+
207
+ def log_and_retry_with_backoff response, data
208
+ if @connection_attempts < max_retries
209
+ wait = backoff_strategy
210
+ log_error "Connection error. Retrying in #{wait} seconds.", response.message
211
+ sleep wait
212
+ raise NewRelic::TelemetrySdk::RetriableServerResponseException
213
+ else
214
+ log_error "Maximum retries reached. Dropping data: #{data.size} points of data"
215
+ end
216
+ end
217
+
218
+ def calculate_backoff_strategy
219
+ [backoff_max, (backoff_factor * (2**(@connection_attempts-1)).to_i)].min
220
+ end
221
+
222
+ def backoff_strategy
223
+ calculate_backoff_strategy.tap { @connection_attempts += 1 }
224
+ end
225
+
226
+ def format_payload data, common_attributes
227
+ post_body = { @payload_type => data }
228
+
229
+ if common_attributes
230
+ post_body[:common] = {}
231
+ post_body[:common][:attributes] = common_attributes
232
+ end
233
+
234
+ [post_body]
235
+ end
236
+
237
+ def add_user_agent_header headers
238
+ sdk_id = "#{USER_AGENT_NAME}/#{VERSION}"
239
+ headers[:'User-Agent'] = ([sdk_id] + Array(@user_agent_products)).join(" ")
240
+ end
241
+
242
+ def add_content_encoding_header headers
243
+ headers.merge!(:'content-encoding' => 'gzip')
244
+ end
245
+
246
+ def set_up_connection host
247
+ uri = URI(host)
248
+ conn = Net::HTTP.new uri.host, uri.port
249
+ conn.use_ssl = true
250
+ conn
251
+ end
252
+
253
+ def serialize data
254
+ JSON.generate data
255
+ end
256
+
257
+ def gzip_data data
258
+ Zlib.gzip data
259
+ end
260
+ end
261
+ end
262
+ end