scout_apm_logging 0.0.12 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -4
  3. data/.rubocop.yml +1 -1
  4. data/CHANGELOG.md +8 -0
  5. data/NOTICE +4 -0
  6. data/lib/scout_apm/logging/config.rb +5 -131
  7. data/lib/scout_apm/logging/loggers/capture.rb +8 -6
  8. data/lib/scout_apm/logging/loggers/formatter.rb +21 -2
  9. data/lib/scout_apm/logging/loggers/opentelemetry/LICENSE +201 -0
  10. data/lib/scout_apm/logging/loggers/opentelemetry/NOTICE +9 -0
  11. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/log_record.rb +18 -0
  12. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/logger.rb +64 -0
  13. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/logger_provider.rb +31 -0
  14. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/severity_number.rb +43 -0
  15. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/version.rb +18 -0
  16. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs.rb +28 -0
  17. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/exporter/otlp/logs_exporter.rb +389 -0
  18. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/exporter/otlp/version.rb +20 -0
  19. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/collector/logs/v1/logs_service_pb.rb +43 -0
  20. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/common/v1/common_pb.rb +58 -0
  21. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/logs/v1/logs_pb.rb +91 -0
  22. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/resource/v1/resource_pb.rb +33 -0
  23. data/lib/scout_apm/logging/loggers/opentelemetry/opentelemetry.rb +62 -0
  24. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/batch_log_record_processor.rb +225 -0
  25. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/log_record_exporter.rb +64 -0
  26. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export.rb +34 -0
  27. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record.rb +115 -0
  28. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_data.rb +31 -0
  29. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_processor.rb +53 -0
  30. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger.rb +94 -0
  31. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger_provider.rb +158 -0
  32. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/version.rb +20 -0
  33. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs.rb +28 -0
  34. data/lib/scout_apm/logging/loggers/patches/rails_logger.rb +17 -0
  35. data/lib/scout_apm/logging/loggers/proxy.rb +22 -5
  36. data/lib/scout_apm/logging/loggers/swaps/rails.rb +4 -12
  37. data/lib/scout_apm/logging/loggers/swaps/scout.rb +2 -10
  38. data/lib/scout_apm/logging/loggers/swaps/sidekiq.rb +2 -6
  39. data/lib/scout_apm/logging/utils.rb +0 -69
  40. data/lib/scout_apm/logging/version.rb +1 -1
  41. data/lib/scout_apm_logging.rb +3 -12
  42. data/scout_apm_logging.gemspec +7 -0
  43. data/spec/data/config_test_1.yml +0 -1
  44. data/spec/data/mock_config.yml +0 -3
  45. data/spec/integration/rails/lifecycle_spec.rb +57 -23
  46. data/spec/spec_helper.rb +0 -12
  47. data/spec/unit/config_spec.rb +0 -12
  48. data/spec/unit/loggers/capture_spec.rb +0 -6
  49. metadata +127 -39
  50. data/bin/scout_apm_logging_monitor +0 -6
  51. data/lib/scout_apm/logging/monitor/_rails.rb +0 -22
  52. data/lib/scout_apm/logging/monitor/collector/checksum.rb +0 -51
  53. data/lib/scout_apm/logging/monitor/collector/configuration.rb +0 -150
  54. data/lib/scout_apm/logging/monitor/collector/downloader.rb +0 -78
  55. data/lib/scout_apm/logging/monitor/collector/extractor.rb +0 -37
  56. data/lib/scout_apm/logging/monitor/collector/manager.rb +0 -57
  57. data/lib/scout_apm/logging/monitor/monitor.rb +0 -216
  58. data/lib/scout_apm/logging/monitor_manager/manager.rb +0 -162
  59. data/lib/scout_apm/logging/state.rb +0 -69
  60. data/spec/data/empty_logs_config.yml +0 -0
  61. data/spec/data/logs_config.yml +0 -3
  62. data/spec/data/state_file.json +0 -3
  63. data/spec/integration/loggers/capture_spec.rb +0 -68
  64. data/spec/integration/monitor/collector/downloader/will_verify_checksum.rb +0 -49
  65. data/spec/integration/monitor/collector_healthcheck_spec.rb +0 -29
  66. data/spec/integration/monitor/continuous_state_collector_spec.rb +0 -31
  67. data/spec/integration/monitor/previous_collector_setup_spec.rb +0 -45
  68. data/spec/integration/monitor_manager/disable_agent_spec.rb +0 -30
  69. data/spec/integration/monitor_manager/monitor_pid_file_spec.rb +0 -38
  70. data/spec/integration/monitor_manager/single_monitor_spec.rb +0 -53
  71. data/spec/unit/monitor/collector/configuration_spec.rb +0 -64
  72. data/spec/unit/state_spec.rb +0 -20
  73. data/tooling/checksums.rb +0 -106
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
4
+ # source: opentelemetry/proto/logs/v1/logs.proto
5
+
6
+ require 'google/protobuf'
7
+
8
+ require_relative '../../common/v1/common_pb'
9
+ require_relative '../../resource/v1/resource_pb'
10
+
11
+ Google::Protobuf::DescriptorPool.generated_pool.build do
12
+ add_file("scout/opentelemetry/proto/logs/v1/logs.proto", :syntax => :proto3) do
13
+ add_message "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.LogsData" do
14
+ repeated :resource_logs, :message, 1, "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.ResourceLogs"
15
+ end
16
+ add_message "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.ResourceLogs" do
17
+ optional :resource, :message, 1, "scout_apm.logging.loggers.opentelemetry.proto.resource.v1.Resource"
18
+ repeated :scope_logs, :message, 2, "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.ScopeLogs"
19
+ optional :schema_url, :string, 3
20
+ end
21
+ add_message "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.ScopeLogs" do
22
+ optional :scope, :message, 1, "scout_apm.logging.loggers.opentelemetry.proto.common.v1.InstrumentationScope"
23
+ repeated :log_records, :message, 2, "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.LogRecord"
24
+ optional :schema_url, :string, 3
25
+ end
26
+ add_message "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.LogRecord" do
27
+ optional :time_unix_nano, :fixed64, 1
28
+ optional :observed_time_unix_nano, :fixed64, 11
29
+ optional :severity_number, :enum, 2, "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.SeverityNumber"
30
+ optional :severity_text, :string, 3
31
+ optional :body, :message, 5, "scout_apm.logging.loggers.opentelemetry.proto.common.v1.AnyValue"
32
+ repeated :attributes, :message, 6, "scout_apm.logging.loggers.opentelemetry.proto.common.v1.KeyValue"
33
+ optional :dropped_attributes_count, :uint32, 7
34
+ optional :flags, :fixed32, 8
35
+ optional :trace_id, :bytes, 9
36
+ optional :span_id, :bytes, 10
37
+ end
38
+ add_enum "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.SeverityNumber" do
39
+ value :SEVERITY_NUMBER_UNSPECIFIED, 0
40
+ value :SEVERITY_NUMBER_TRACE, 1
41
+ value :SEVERITY_NUMBER_TRACE2, 2
42
+ value :SEVERITY_NUMBER_TRACE3, 3
43
+ value :SEVERITY_NUMBER_TRACE4, 4
44
+ value :SEVERITY_NUMBER_DEBUG, 5
45
+ value :SEVERITY_NUMBER_DEBUG2, 6
46
+ value :SEVERITY_NUMBER_DEBUG3, 7
47
+ value :SEVERITY_NUMBER_DEBUG4, 8
48
+ value :SEVERITY_NUMBER_INFO, 9
49
+ value :SEVERITY_NUMBER_INFO2, 10
50
+ value :SEVERITY_NUMBER_INFO3, 11
51
+ value :SEVERITY_NUMBER_INFO4, 12
52
+ value :SEVERITY_NUMBER_WARN, 13
53
+ value :SEVERITY_NUMBER_WARN2, 14
54
+ value :SEVERITY_NUMBER_WARN3, 15
55
+ value :SEVERITY_NUMBER_WARN4, 16
56
+ value :SEVERITY_NUMBER_ERROR, 17
57
+ value :SEVERITY_NUMBER_ERROR2, 18
58
+ value :SEVERITY_NUMBER_ERROR3, 19
59
+ value :SEVERITY_NUMBER_ERROR4, 20
60
+ value :SEVERITY_NUMBER_FATAL, 21
61
+ value :SEVERITY_NUMBER_FATAL2, 22
62
+ value :SEVERITY_NUMBER_FATAL3, 23
63
+ value :SEVERITY_NUMBER_FATAL4, 24
64
+ end
65
+ add_enum "scout_apm.logging.loggers.opentelemetry.proto.logs.v1.LogRecordFlags" do
66
+ value :LOG_RECORD_FLAGS_DO_NOT_USE, 0
67
+ value :LOG_RECORD_FLAGS_TRACE_FLAGS_MASK, 255
68
+ end
69
+ end
70
+ end
71
+
72
+ module ScoutApm
73
+ module Logging
74
+ module Loggers
75
+ module Opentelemetry
76
+ module Proto
77
+ module Logs
78
+ module V1
79
+ LogsData = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.logs.v1.LogsData").msgclass
80
+ ResourceLogs = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.logs.v1.ResourceLogs").msgclass
81
+ ScopeLogs = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.logs.v1.ScopeLogs").msgclass
82
+ LogRecord = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.logs.v1.LogRecord").msgclass
83
+ SeverityNumber = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.logs.v1.SeverityNumber").enummodule
84
+ LogRecordFlags = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.logs.v1.LogRecordFlags").enummodule
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
4
+ # source: opentelemetry/proto/resource/v1/resource.proto
5
+
6
+ require 'google/protobuf'
7
+
8
+ require_relative '../../common/v1/common_pb'
9
+
10
+ Google::Protobuf::DescriptorPool.generated_pool.build do
11
+ add_file("scout/opentelemetry/proto/resource/v1/resource.proto", :syntax => :proto3) do
12
+ add_message "scout_apm.logging.loggers.opentelemetry.proto.resource.v1.Resource" do
13
+ repeated :attributes, :message, 1, "scout_apm.logging.loggers.opentelemetry.proto.common.v1.KeyValue"
14
+ optional :dropped_attributes_count, :uint32, 2
15
+ end
16
+ end
17
+ end
18
+
19
+ module ScoutApm
20
+ module Logging
21
+ module Loggers
22
+ module Opentelemetry
23
+ module Proto
24
+ module Resource
25
+ module V1
26
+ Resource = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("scout_apm.logging.loggers.opentelemetry.proto.resource.v1.Resource").msgclass
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+ require 'logger'
3
+ require 'opentelemetry'
4
+ require 'opentelemetry/sdk'
5
+
6
+ require_relative 'api/logs'
7
+ require_relative 'sdk/logs'
8
+ require_relative 'exporter/exporter/otlp/version'
9
+ require_relative 'exporter/exporter/otlp/logs_exporter'
10
+
11
+ module ScoutApm
12
+ module Logging
13
+ module Loggers
14
+ module OpenTelemetry
15
+ class << self
16
+ # Overwritten on setup to be the internal logger.
17
+ # @return [Object, Logger] configured Logger or a default STDOUT Logger.
18
+ def logger
19
+ @logger ||= ::Logger.new($stdout, level: ENV['OTEL_LOG_LEVEL'] || ::Logger::INFO)
20
+ end
21
+
22
+ # @return [Callable] configured error handler or a default that logs the
23
+ # exception and message at ERROR level.
24
+ def error_handler
25
+ @error_handler ||= ->(exception: nil, message: nil) { logger.error("OpenTelemetry error: #{[message, exception&.message, exception&.backtrace&.first].compact.join(' - ')}") }
26
+ end
27
+
28
+ # Handles an error by calling the configured error_handler.
29
+ #
30
+ # @param [optional Exception] exception The exception to be handled
31
+ # @param [optional String] message An error message.
32
+ def handle_error(exception: nil, message: nil)
33
+ error_handler.call(exception: exception, message: message)
34
+ end
35
+
36
+ def logger_provider=(logger_provider)
37
+ @logger_provider = logger_provider
38
+ end
39
+
40
+ def logger_provider
41
+ @logger_provider
42
+ end
43
+ end
44
+
45
+ def self.setup(context)
46
+ @logger = context.logger
47
+
48
+ exporter = OpenTelemetry::Exporter::OTLP::LogsExporter.new(endpoint: context.config.value('logs_reporting_endpoint_http'))
49
+ processor = OpenTelemetry::SDK::Logs::Export::BatchLogRecordProcessor.new(exporter)
50
+ ScoutApm::Logging::Loggers::OpenTelemetry.logger_provider = OpenTelemetry::SDK::Logs::LoggerProvider.new(resource: scout_resource(context))
51
+ ScoutApm::Logging::Loggers::OpenTelemetry.logger_provider.add_log_record_processor(processor)
52
+ end
53
+
54
+ def self.scout_resource(context)
55
+ our_resources = ::OpenTelemetry::SDK::Resources::Resource.create({'telemetryhub.key' => context.config.value('logs_ingest_key')})
56
+ default_resources = ::OpenTelemetry::SDK::Resources::Resource.default
57
+ default_resources.merge(our_resources)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module ScoutApm
8
+ module Logging
9
+ module Loggers
10
+ module OpenTelemetry
11
+ module SDK
12
+ module Logs
13
+ module Export
14
+ # WARNING - The spec has some differences from the LogRecord version of this processor
15
+ # Implementation of the duck type LogRecordProcessor that batches
16
+ # log records exported by the SDK then pushes them to the exporter
17
+ # pipeline.
18
+ #
19
+ # Typically, the BatchLogRecordProcessor will be more suitable for
20
+ # production environments than the SimpleLogRecordProcessor.
21
+ class BatchLogRecordProcessor < LogRecordProcessor # rubocop:disable Metrics/ClassLength
22
+ # Returns a new instance of the {BatchLogRecordProcessor}.
23
+ #
24
+ # @param [LogRecordExporter] exporter The (duck type) LogRecordExporter to where the
25
+ # recorded LogRecords are pushed after batching.
26
+ # @param [Numeric] exporter_timeout The maximum allowed time to export data.
27
+ # Defaults to the value of the OTEL_BLRP_EXPORT_TIMEOUT
28
+ # environment variable, if set, or 30,000 (30 seconds).
29
+ # @param [Numeric] schedule_delay the delay interval between two consecutive exports.
30
+ # Defaults to the value of the OTEL_BLRP_SCHEDULE_DELAY environment
31
+ # variable, if set, or 1,000 (1 second).
32
+ # @param [Integer] max_queue_size the maximum queue size in log records.
33
+ # Defaults to the value of the OTEL_BLRP_MAX_QUEUE_SIZE environment
34
+ # variable, if set, or 2048.
35
+ # @param [Integer] max_export_batch_size the maximum batch size in log records.
36
+ # Defaults to the value of the OTEL_BLRP_MAX_EXPORT_BATCH_SIZE environment
37
+ # variable, if set, or 512.
38
+ #
39
+ # @return a new instance of the {BatchLogRecordProcessor}.
40
+ def initialize(exporter,
41
+ exporter_timeout: Float(ENV.fetch('OTEL_BLRP_EXPORT_TIMEOUT', 30_000)),
42
+ schedule_delay: Float(ENV.fetch('OTEL_BLRP_SCHEDULE_DELAY', 1000)),
43
+ max_queue_size: Integer(ENV.fetch('OTEL_BLRP_MAX_QUEUE_SIZE', 2048)),
44
+ max_export_batch_size: Integer(ENV.fetch('OTEL_BLRP_MAX_EXPORT_BATCH_SIZE', 256)),
45
+ start_thread_on_boot: String(ENV['OTEL_RUBY_BLRP_START_THREAD_ON_BOOT']) !~ /false/i)
46
+
47
+ unless max_export_batch_size <= max_queue_size
48
+ raise ArgumentError,
49
+ 'max_export_batch_size much be less than or equal to max_queue_size'
50
+ end
51
+
52
+ unless ::OpenTelemetry::Common::Utilities.valid_exporter?(exporter)
53
+ raise ArgumentError,
54
+ "exporter #{exporter.inspect} does not appear to be a valid exporter"
55
+ end
56
+
57
+ @exporter = exporter
58
+ @exporter_timeout_seconds = exporter_timeout / 1000.0
59
+ @mutex = Mutex.new
60
+ @export_mutex = Mutex.new
61
+ @condition = ConditionVariable.new
62
+ @keep_running = true
63
+ @stopped = false
64
+ @delay_seconds = schedule_delay / 1000.0
65
+ @max_queue_size = max_queue_size
66
+ @batch_size = max_export_batch_size
67
+ @log_records = []
68
+ @pid = nil
69
+ @thread = nil
70
+ reset_on_fork(restart_thread: start_thread_on_boot)
71
+ end
72
+
73
+ # Adds a log record to the batch. Thread-safe; may block on lock.
74
+ def on_emit(log_record, _context)
75
+ return if @stopped
76
+
77
+ lock do
78
+ reset_on_fork
79
+ n = log_records.size + 1 - max_queue_size
80
+ if n.positive?
81
+ log_records.shift(n)
82
+ report_dropped_log_records(n, reason: 'buffer-full')
83
+ end
84
+ log_records << log_record
85
+ @condition.signal if log_records.size > batch_size
86
+ end
87
+ end
88
+
89
+ # Export all emitted log records that have not yet been exported to
90
+ # the configured `Exporter`.
91
+ #
92
+ # This method should only be called in cases where it is absolutely
93
+ # necessary, such as when using some FaaS providers that may suspend
94
+ # the process after an invocation, but before the `Processor` exports
95
+ # the completed log records.
96
+ #
97
+ # @param [optional Numeric] timeout An optional timeout in seconds.
98
+ # @return [Integer] SUCCESS if no error occurred, FAILURE if a
99
+ # non-specific failure occurred, TIMEOUT if a timeout occurred.
100
+ def force_flush(timeout: nil)
101
+ start_time = ::OpenTelemetry::Common::Utilities.timeout_timestamp
102
+
103
+ snapshot = lock do
104
+ reset_on_fork if @keep_running
105
+ log_records.shift(log_records.size)
106
+ end
107
+
108
+ until snapshot.empty?
109
+ remaining_timeout = ::OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time)
110
+ return TIMEOUT if remaining_timeout&.zero?
111
+
112
+ batch = snapshot.shift(batch_size).map!(&:to_log_record_data)
113
+ result_code = export_batch(batch, timeout: remaining_timeout)
114
+ return result_code unless result_code == SUCCESS
115
+ end
116
+
117
+ @exporter.force_flush(timeout: ::OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time))
118
+ ensure
119
+ # Unshift the remaining log records if we timed out. We drop excess
120
+ # log records from the snapshot because they're older than any
121
+ # records in the buffer.
122
+ lock do
123
+ n = log_records.size + snapshot.size - max_queue_size
124
+
125
+ if n.positive?
126
+ snapshot.shift(n)
127
+ report_dropped_log_records(n, reason: 'buffer-full')
128
+ end
129
+
130
+ log_records.unshift(*snapshot) unless snapshot.empty?
131
+ @condition.signal if log_records.size > max_queue_size / 2
132
+ end
133
+ end
134
+
135
+ # Shuts the consumer thread down and flushes the current accumulated
136
+ # buffer will block until the thread is finished.
137
+ #
138
+ # @param [optional Numeric] timeout An optional timeout in seconds.
139
+ # @return [Integer] SUCCESS if no error occurred, FAILURE if a
140
+ # non-specific failure occurred, TIMEOUT if a timeout occurred.
141
+ def shutdown(timeout: nil)
142
+ return if @stopped
143
+
144
+ start_time = ::OpenTelemetry::Common::Utilities.timeout_timestamp
145
+ thread = lock do
146
+ @keep_running = false
147
+ @stopped = true
148
+ @condition.signal
149
+ @thread
150
+ end
151
+
152
+ thread&.join(timeout)
153
+ force_flush(timeout: ::OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time))
154
+ dropped_log_records = lock { log_records.size }
155
+ report_dropped_log_records(dropped_log_records, reason: 'terminating') if dropped_log_records.positive?
156
+
157
+ @exporter.shutdown(timeout: ::OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time))
158
+ end
159
+
160
+ private
161
+
162
+ attr_reader :log_records, :max_queue_size, :batch_size
163
+
164
+ def work
165
+ loop do
166
+ batch = lock do
167
+ @condition.wait(@mutex, @delay_seconds) if log_records.size < batch_size && @keep_running
168
+ @condition.wait(@mutex, @delay_seconds) while log_records.empty? && @keep_running
169
+ return unless @keep_running
170
+
171
+ fetch_batch
172
+ end
173
+
174
+ export_batch(batch)
175
+ end
176
+ end
177
+
178
+ def reset_on_fork(restart_thread: true)
179
+ pid = Process.pid
180
+ return if @pid == pid
181
+
182
+ @pid = pid
183
+ log_records.clear
184
+ @thread = restart_thread ? Thread.new { work } : nil
185
+ rescue ThreadError => e
186
+ OpenTelemetry.handle_error(exception: e, message: 'unexpected error in BatchLogRecordProcessor#reset_on_fork')
187
+ end
188
+
189
+ def export_batch(batch, timeout: @exporter_timeout_seconds)
190
+ result_code = @export_mutex.synchronize { @exporter.export(batch, timeout: timeout) }
191
+ report_result(result_code, batch)
192
+ result_code
193
+ rescue StandardError => e
194
+ report_result(FAILURE, batch)
195
+ OpenTelemetry.handle_error(exception: e, message: 'unexpected error in BatchLogRecordProcessor#export_batch')
196
+ end
197
+
198
+ def report_result(result_code, batch)
199
+ if result_code == SUCCESS
200
+ OpenTelemetry.logger.debug("Successfully exported #{batch.size} log records")
201
+ else
202
+ OpenTelemetry.handle_error(exception: ExportError.new("Unable to export #{batch.size} log records"))
203
+ OpenTelemetry.logger.error("Result code: #{result_code}")
204
+ end
205
+ end
206
+
207
+ def report_dropped_log_records(count, reason:)
208
+ OpenTelemetry.logger.warn("#{count} log record(s) dropped. Reason: #{reason}")
209
+ end
210
+
211
+ def fetch_batch
212
+ log_records.shift(@batch_size).map!(&:to_log_record_data)
213
+ end
214
+
215
+ def lock(&block)
216
+ @mutex.synchronize(&block)
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module ScoutApm
8
+ module Logging
9
+ module Loggers
10
+ module OpenTelemetry
11
+ module SDK
12
+ module Logs
13
+ module Export
14
+ # LogRecordExporter describes a duck type. It is not required to
15
+ # subclass this class to provide an implementation of LogRecordExporter,
16
+ # provided the interface is satisfied. LogRecordExporter allows
17
+ # different tracing services to export log record data in their own format.
18
+ #
19
+ # To export data an exporter MUST be registered to the {LoggerProvider}
20
+ # using a {LogRecordProcessor} implementation.
21
+ class LogRecordExporter
22
+ def initialize
23
+ @stopped = false
24
+ end
25
+
26
+ # Called to export {LogRecordData}s.
27
+ #
28
+ # @param [Enumerable<LogRecordData>] log_record_data the list of
29
+ # {LogRecordData} to be exported.
30
+ # @param [optional Numeric] timeout An optional timeout in seconds.
31
+ #
32
+ # @return [Integer] the result of the export.
33
+ def export(log_record_data, timeout: nil)
34
+ return SUCCESS unless @stopped
35
+
36
+ FAILURE
37
+ end
38
+
39
+ # Called when {LoggerProvider#force_flush} is called, if this exporter is
40
+ # registered to a {LoggerProvider} object.
41
+ #
42
+ # @param [optional Numeric] timeout An optional timeout in seconds.
43
+ # @return [Integer] SUCCESS if no error occurred, FAILURE if a
44
+ # non-specific failure occurred, TIMEOUT if a timeout occurred.
45
+ def force_flush(timeout: nil)
46
+ SUCCESS
47
+ end
48
+
49
+ # Called when {LoggerProvider#shutdown} is called, if this exporter is
50
+ # registered to a {LoggerProvider} object.
51
+ #
52
+ # @param [optional Numeric] timeout An optional timeout in seconds.
53
+ def shutdown(timeout: nil)
54
+ @stopped = true
55
+ SUCCESS
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module ScoutApm
8
+ module Logging
9
+ module Loggers
10
+ module OpenTelemetry
11
+ module SDK
12
+ module Logs
13
+ # The Export module contains the built-in exporters and log record
14
+ # processors for the OpenTelemetry reference implementation.
15
+ module Export
16
+ ExportError = Class.new(::OpenTelemetry::Error)
17
+ # The operation finished successfully.
18
+ SUCCESS = 0
19
+
20
+ # The operation finished with an error.
21
+ FAILURE = 1
22
+
23
+ # The operation timed out.
24
+ TIMEOUT = 2
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ require_relative 'export/log_record_exporter'
34
+ require_relative 'export/batch_log_record_processor'
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module ScoutApm
8
+ module Logging
9
+ module Loggers
10
+ module OpenTelemetry
11
+ module SDK
12
+ module Logs
13
+ # Implementation of OpenTelemetry::Logs::LogRecord that records log events.
14
+ class LogRecord < OpenTelemetry::Logs::LogRecord
15
+ attr_accessor :timestamp,
16
+ :observed_timestamp,
17
+ :severity_text,
18
+ :severity_number,
19
+ :body,
20
+ :attributes,
21
+ :trace_id,
22
+ :span_id,
23
+ :trace_flags,
24
+ :resource,
25
+ :instrumentation_scope
26
+
27
+ # Creates a new {LogRecord}.
28
+ #
29
+ # @param [optional Time] timestamp Time when the event occurred.
30
+ # @param [optional Time] observed_timestamp Time when the event
31
+ # was observed by the collection system. If nil, will first attempt
32
+ # to set to `timestamp`. If `timestamp` is nil, will set to Time.now.
33
+ # @param [optional OpenTelemetry::Trace::SpanContext] span_context The
34
+ # OpenTelemetry::Trace::SpanContext to associate with the LogRecord.
35
+ # @param [optional String] severity_text The log severity, also known as
36
+ # log level.
37
+ # @param [optional Integer] severity_number The numerical value of the
38
+ # log severity.
39
+ # @param [optional String, Numeric, Boolean, Array<String, Numeric,
40
+ # Boolean>, Hash{String => String, Numeric, Boolean, Array<String,
41
+ # Numeric, Boolean>}] body The body of the {LogRecord}.
42
+ # @param [optional Hash{String => String, Numeric, Boolean,
43
+ # Array<String, Numeric, Boolean>}] attributes Attributes to associate
44
+ # with the {LogRecord}.
45
+ # @param [optional String] trace_id The trace ID associated with the
46
+ # current context.
47
+ # @param [optional String] span_id The span ID associated with the
48
+ # current context.
49
+ # @param [optional OpenTelemetry::Trace::TraceFlags] trace_flags The
50
+ # trace flags associated with the current context.
51
+ # @param [optional OpenTelemetry::SDK::Resources::Resource] resource The
52
+ # source of the log, desrived from the LoggerProvider.
53
+ # @param [optional OpenTelemetry::SDK::InstrumentationScope] instrumentation_scope
54
+ # The instrumentation scope, derived from the emitting Logger
55
+ #
56
+ #
57
+ # @return [LogRecord]
58
+ def initialize(
59
+ timestamp: nil,
60
+ observed_timestamp: nil,
61
+ severity_text: nil,
62
+ severity_number: nil,
63
+ body: nil,
64
+ attributes: nil,
65
+ trace_id: nil,
66
+ span_id: nil,
67
+ trace_flags: nil,
68
+ resource: nil,
69
+ instrumentation_scope: nil
70
+ )
71
+ @timestamp = timestamp
72
+ @observed_timestamp = observed_timestamp || timestamp || Time.now
73
+ @severity_text = severity_text
74
+ @severity_number = severity_number
75
+ @body = body
76
+ @attributes = attributes.nil? ? nil : Hash[attributes] # We need a mutable copy of attributes
77
+ @trace_id = trace_id
78
+ @span_id = span_id
79
+ @trace_flags = trace_flags
80
+ @resource = resource
81
+ @instrumentation_scope = instrumentation_scope
82
+ @total_recorded_attributes = @attributes&.size || 0
83
+ end
84
+
85
+ def to_log_record_data
86
+ LogRecordData.new(
87
+ to_integer_nanoseconds(@timestamp),
88
+ to_integer_nanoseconds(@observed_timestamp),
89
+ @severity_text,
90
+ @severity_number,
91
+ @body,
92
+ @attributes,
93
+ @trace_id,
94
+ @span_id,
95
+ @trace_flags,
96
+ @resource,
97
+ @instrumentation_scope,
98
+ @total_recorded_attributes
99
+ )
100
+ end
101
+
102
+ private
103
+
104
+ def to_integer_nanoseconds(timestamp)
105
+ return unless timestamp.is_a?(Time)
106
+
107
+ (timestamp.to_r * 10**9).to_i
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module ScoutApm
8
+ module Logging
9
+ module Loggers
10
+ module OpenTelemetry
11
+ module SDK
12
+ module Logs
13
+ # LogRecordData is a Struct containing {LogRecord} data for export.
14
+ LogRecordData = Struct.new(:timestamp, # optional Integer nanoseconds since Epoch
15
+ :observed_timestamp, # Integer nanoseconds since Epoch
16
+ :severity_text, # optional String
17
+ :severity_number, # optional Integer
18
+ :body, # optional String, Numeric, Boolean, Array<String, Numeric, Boolean>, Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}
19
+ :attributes, # optional Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}
20
+ :trace_id, # optional String (16-byte binary)
21
+ :span_id, # optional String (8-byte binary)
22
+ :trace_flags, # optional Integer (8-bit byte of bit flags)
23
+ :resource, # optional OpenTelemetry::SDK::Resources::Resource
24
+ :instrumentation_scope, # optional OpenTelemetry::SDK::InstrumentationScope
25
+ :total_recorded_attributes) # Integer
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end