newrelic-telemetry_sdk 0.1.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.
@@ -0,0 +1,43 @@
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_relative 'client'
7
+
8
+ module NewRelic
9
+ module TelemetrySdk
10
+ # The {TraceClient} sends {Span} data to the Trace API host endpoint.
11
+ #
12
+ # @example
13
+ # trace_client = NewRelic::TelemetrySdk::TraceClient.new
14
+ #
15
+ # span = NewRelic::TelemetrySdk::Span.new(
16
+ # id: random_id(16),
17
+ # trace_id: random_id(32),
18
+ # start_time: Time.now,
19
+ # name: "Net::HTTP#get"
20
+ # )
21
+ # trace_client.report span
22
+ #
23
+ class TraceClient < Client
24
+ def initialize host: trace_api_host
25
+ super host: host,
26
+ path: '/trace/v1',
27
+ headers: {
28
+ :'Content-Type' => 'application/json',
29
+ :'Api-Key' => api_insert_key,
30
+ :'Data-Format' => 'newrelic',
31
+ :'Data-Format-Version' => '1'
32
+ },
33
+ payload_type: :spans
34
+ end
35
+
36
+ private
37
+
38
+ def trace_api_host
39
+ TelemetrySdk.config.trace_api_host
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,97 @@
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
+
9
+ # This class allows setting configuration options for the Telemetry SDK
10
+ # via NewRelic::TelemetrySdk.configure.
11
+ #
12
+ # @example Setting the API Key
13
+ # NewRelic::TelemetrySdk.configure do |config|
14
+ # config.api_insert_key = ENV["API_KEY"]
15
+ # end
16
+ #
17
+ # @api public
18
+ class Config
19
+
20
+ # Default host for the Trace Client
21
+ DEFAULT_TRACE_API_HOST = "https://trace-api.newrelic.com"
22
+
23
+ # Default harvest interval in seconds
24
+ DEFAULT_HARVEST_INTERVAL = 5
25
+
26
+ # Default backoff strategy factorial
27
+ DEFAULT_BACKOFF_FACTOR = 5
28
+
29
+ # Default maximum backoff wait time in seconds
30
+ DEFAULT_BACKOFF_MAX = 80
31
+
32
+ # Default number of retries to send same data
33
+ DEFAULT_MAX_RETRIES = 8
34
+
35
+ # @!attribute api_insert_key [optional, String]
36
+ # A New Relic Insert API key. Necessary for sending data to
37
+ # New Relic via the Telemetry SDK. Defaults to +API_INSERT_KEY+
38
+ # environment variable.
39
+ # @see https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys#event-insert-key Types of New Relic API keys: Insert API key
40
+ attr_accessor :api_insert_key
41
+
42
+ # @!attribute logger [optional, Logger]
43
+ # A Logger object customized with your preferred log output
44
+ # destination (if any) and log level.
45
+ attr_accessor :logger
46
+
47
+ # @!attribute audit_logging_enabled [optional, Boolean]
48
+ # If audit logging is enabled, the contents of every payload
49
+ # sent to New Relic will be recorded in logs.
50
+ # This is a very verbose log level for debugging purposes.
51
+ attr_accessor :audit_logging_enabled
52
+
53
+ # @!attribute harvest_interval [optional, Integer]
54
+ # The frequency of automatic harvest (in seconds) if sending data
55
+ # with a harvester.
56
+ # Defaults to {DEFAULT_HARVEST_INTERVAL} seconds.
57
+ attr_accessor :harvest_interval
58
+
59
+ # @!attribute backoff_factor [optional, Integer]
60
+ # The amount of time (in seconds) to wait after sending data
61
+ # to New Relic fails before attempting to send again.
62
+ # Defaults to {DEFAULT_HARVEST_INTERVAL} seconds.
63
+ attr_accessor :backoff_factor
64
+
65
+ # @!attribute backoff_max [optional, Integer]
66
+ # If data cannot be sent to New Relic intermittently, the SDK will
67
+ # retry the request at increasing intervals, but will stop increasing
68
+ # the retry intervals when they have reached +backoff_max+ seconds.
69
+ # Defaults to {DEFAULT_BACKOFF_MAX} seconds.
70
+ # @see https://github.com/newrelic/newrelic-telemetry-sdk-specs/blob/master/communication.md#graceful-degradation Graceful Degradation
71
+ attr_accessor :backoff_max
72
+
73
+ # @!attribute max_retries [optional, Integer]
74
+ # The maximum number of times to retry sending data to New Relic.
75
+ # Defaults to {DEFAULT_MAX_RETRIES}.
76
+ attr_accessor :max_retries
77
+
78
+ # @!attribute trace_api_host [optional, String]
79
+ # An alternative New Relic host URL where spans can be sent.
80
+ # Defaults to {DEFAULT_TRACE_API_HOST}
81
+ attr_accessor :trace_api_host
82
+
83
+ # @api private
84
+ def initialize
85
+ @api_insert_key = ENV[API_INSERT_KEY]
86
+ @logger = Logger.logger
87
+ @audit_logging_enabled = false
88
+
89
+ @harvest_interval = DEFAULT_HARVEST_INTERVAL
90
+ @backoff_factor = DEFAULT_BACKOFF_FACTOR
91
+ @backoff_max = DEFAULT_BACKOFF_MAX
92
+ @max_retries = DEFAULT_MAX_RETRIES
93
+ @trace_api_host = DEFAULT_TRACE_API_HOST
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,95 @@
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
+ # The {Configurator} provides the mechanism through which the SDK is configured.
9
+ #
10
+ # @example
11
+ # NewRelic::TelemetrySdk.configure do |config|
12
+ # config.api_insert_key = ENV["API_KEY"]
13
+ # end
14
+ #
15
+ # See {Config} for details on what properties can be configured.
16
+ #
17
+ # @api public
18
+ class Configurator
19
+ def self.config
20
+ @config ||= Config.new
21
+ end
22
+
23
+ # Removes any configurations that were previously customized, effectively
24
+ # resetting the {Config} state to defaults
25
+ #
26
+ # @api private
27
+ def self.reset
28
+ @config = nil
29
+ end
30
+
31
+ # Allows direct access to the config state. The primary purpose of this method is
32
+ # to access config properties throughout the SDK.
33
+ #
34
+ # @note Unlike configuring with # {#self.configure}, setting config properties here
35
+ # may, or may not become immediately active. Use with care.
36
+ #
37
+ # @api public
38
+ def config
39
+ Configurator.config
40
+ end
41
+
42
+ # Set Telemetry SDK configuration with a block.
43
+ # See {Config} for options.
44
+ #
45
+ # @example Setting the API Key
46
+ # NewRelic::TelemetrySdk.configure do |config|
47
+ # config.api_insert_key = ENV["API_KEY"]
48
+ # end
49
+ #
50
+ # @api public
51
+ def configure
52
+ Logger.logger = config.logger
53
+ end
54
+
55
+ private
56
+
57
+ # Delegates any setter methods to the Config object if it responds to such.
58
+ # All other missing methods are propagated up the chain.
59
+ def method_missing method, *args, &block
60
+ if method.to_s =~ /\=$/ && config.respond_to?(method)
61
+ config.send method, *args, &block
62
+ else
63
+ super
64
+ end
65
+ end
66
+ end
67
+
68
+ # Set Telemetry SDK configuration with a block.
69
+ # See {Config} for options.
70
+ #
71
+ #
72
+ # @example Setting the API Key
73
+ # NewRelic::TelemetrySdk.configure do |config|
74
+ # config.api_insert_key = ENV["API_KEY"]
75
+ # end
76
+ #
77
+ # @api public
78
+ def self.configure
79
+ configurator = Configurator.new
80
+ yield configurator if block_given?
81
+ configurator.configure
82
+ end
83
+
84
+ # Allows direct access to the config state. The primary purpose of this method is
85
+ # to access config properties throughout the SDK.
86
+ #
87
+ # @note Unlike configuring with # {#self.configure}, setting config properties here
88
+ # may, or may not become immediately active. Use with care.
89
+ #
90
+ # @api public
91
+ def self.config
92
+ Configurator.config
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,14 @@
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
+ # A {RetriableServerResponseException} is used to signal that the SDK
9
+ # should attempt to resend the data that received a response error
10
+ # from the server on the previous attempt.
11
+ # @private
12
+ class RetriableServerResponseException < StandardError; end
13
+ end
14
+ end
@@ -0,0 +1,117 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/newrelic-telemetry-sdk-ruby/blob/main/LICENSE for complete details.
4
+
5
+ module NewRelic
6
+ module TelemetrySdk
7
+ # This class handles sending data to New Relic automatically at configured
8
+ # intervals.
9
+ #
10
+ # @example
11
+ # harvester = NewRelic::TelemetrySdk::Harvester.new
12
+ # trace_client = NewRelic::TelemetrySdk::TraceClient.new
13
+ # buffer = NewRelic::TelemetrySdk::Buffer.new
14
+ # harvester.register 'external_spans', buffer, trace_client
15
+ # harvester.start
16
+ #
17
+ # @api public
18
+ class Harvester
19
+ include NewRelic::TelemetrySdk::Logger
20
+
21
+ def initialize
22
+ @pipelines = {}
23
+ @shutdown = false
24
+ @running = false
25
+ @lock = Mutex.new
26
+ end
27
+
28
+ # Register a pipeline (i.e. a buffer from which data can be harvested
29
+ # via a +flush+ method and a client that can be used to send that data).
30
+ # @param name [String]
31
+ # A unique name for the type of data associated with this pipeline.
32
+ # Examples: 'spans', 'external_spans'
33
+ # @param buffer [Buffer]
34
+ # An instance of NewRelic::TelemetrySdk::Buffer in which data can be
35
+ # stored for harvest.
36
+ # @param client [Client]
37
+ # An instance of a NewRelic::TelemetrySdk::Client subclass which will
38
+ # send harvested data to the correct New Relic backend (e.g. TraceClient
39
+ # for spans).
40
+ #
41
+ # @api public
42
+ def register name, buffer, client
43
+ logger.info "Registering pipeline #{name}"
44
+ @lock.synchronize do
45
+ @pipelines[name] = {
46
+ buffer: buffer,
47
+ client: client
48
+ }
49
+ end
50
+ rescue => e
51
+ log_error "Encountered error while registering buffer #{name}.", e
52
+ end
53
+
54
+ def [] name
55
+ @pipelines[name]
56
+ end
57
+
58
+ def interval
59
+ TelemetrySdk.config.harvest_interval
60
+ end
61
+
62
+ def running?
63
+ @running
64
+ end
65
+
66
+ # Start scheduled harvests via this harvester.
67
+ #
68
+ # @api public
69
+ def start
70
+ logger.info "Harvesting every #{interval} seconds"
71
+ @running = true
72
+ @harvest_thread = Thread.new do
73
+ begin
74
+ while !@shutdown do
75
+ sleep interval
76
+ harvest
77
+ end
78
+ harvest
79
+ @running = false
80
+ rescue => e
81
+ log_error "Encountered error in harvester", e
82
+ end
83
+ end
84
+ end
85
+
86
+ # Stop scheduled harvests via this harvester. Any remaining
87
+ # buffered data will be sent before the harvest thread is stopped.
88
+ #
89
+ # @api public
90
+ def stop
91
+ logger.info "Stopping harvester"
92
+ @shutdown = true
93
+ @harvest_thread.join if @running
94
+ rescue => e
95
+ log_error "Encountered error stopping harvester", e
96
+ end
97
+
98
+ private
99
+
100
+ def harvest
101
+ @lock.synchronize do
102
+ @pipelines.values.each do |pipeline|
103
+ send_data_via pipeline
104
+ end
105
+ end
106
+ end
107
+
108
+ def send_data_via pipeline
109
+ batch = pipeline[:buffer].flush
110
+ if !batch.nil? && batch[0].respond_to?(:any?) && batch[0].any?
111
+ pipeline[:client].report_batch batch
112
+ end
113
+ end
114
+
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,90 @@
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
+ # The Logger Singleton object manages the logger for the SDK and is fully configurable by the user.
9
+ # Any Ruby class that responds to the common methods of the Ruby standard {::Logger} class can
10
+ # be configured for the SDk.
11
+ #
12
+ # The logger may be configured like this:
13
+ # @example with configure block
14
+ # require 'logger'
15
+ #
16
+ # logger = ::Logger.new(STDOUT)
17
+ # logger.level = Logger::WARN
18
+ #
19
+ # NewRelic::TelemetrySdk.configure do |config|
20
+ # config.logger = logger
21
+ # end
22
+ #
23
+ # @example without configure block
24
+ # require 'logger'
25
+ #
26
+ # NewRelic::TelemetrySdk.logger = ::Logger.new("/dev/null")
27
+ #
28
+ # @api public
29
+ module Logger
30
+ LOG_LEVELS = %w{debug info warn error fatal}
31
+ private_constant :LOG_LEVELS
32
+
33
+ LOG_LEVELS.each do |level|
34
+ define_method "log_#{level}_once" do |key, *msgs|
35
+ log_once level, key, *msgs
36
+ end
37
+ end
38
+
39
+ def self.logger= logger
40
+ @logger = logger
41
+ end
42
+
43
+ def self.logger
44
+ @logger ||= ::Logger.new(STDOUT)
45
+ end
46
+
47
+ def log_once(level, key, *msgs)
48
+ logger_mutex.synchronize do
49
+ return if already_logged.include?(key)
50
+ already_logged[key] = true
51
+ end
52
+
53
+ logger.send(level, *msgs)
54
+ end
55
+
56
+ def clear_already_logged
57
+ logger_mutex.synchronize do
58
+ @already_logged = {}
59
+ end
60
+ end
61
+
62
+ def log_error(message, exception = nil)
63
+ logger.error message
64
+ logger.error exception if exception
65
+ end
66
+
67
+ def logger
68
+ Logger.logger
69
+ end
70
+
71
+ def logger= logger
72
+ Logger.logger = logger
73
+ end
74
+
75
+ private
76
+
77
+ def logger_mutex
78
+ @logger_mutex ||= Mutex.new
79
+ end
80
+
81
+ def already_logged
82
+ @already_logged ||= {}
83
+ end
84
+ end
85
+
86
+ def self.logger
87
+ Logger.logger
88
+ end
89
+ end
90
+ end