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,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