opentelemetry-logs-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,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Logs
10
+ module Export
11
+ # LogRecordExporter describes a duck type. It is not required to
12
+ # subclass this class to provide an implementation of LogRecordExporter,
13
+ # provided the interface is satisfied. LogRecordExporter allows
14
+ # different tracing services to export log record data in their own format.
15
+ #
16
+ # To export data an exporter MUST be registered to the {LoggerProvider}
17
+ # using a {LogRecordProcessor} implementation.
18
+ class LogRecordExporter
19
+ def initialize
20
+ @stopped = false
21
+ end
22
+
23
+ # Called to export {LogRecordData}s.
24
+ #
25
+ # @param [Enumerable<LogRecordData>] log_record_data the list of
26
+ # {LogRecordData} to be exported.
27
+ # @param [optional Numeric] timeout An optional timeout in seconds.
28
+ #
29
+ # @return [Integer] the result of the export.
30
+ def export(log_record_data, timeout: nil)
31
+ return SUCCESS unless @stopped
32
+
33
+ FAILURE
34
+ end
35
+
36
+ # Called when {LoggerProvider#force_flush} is called, if this exporter is
37
+ # registered to a {LoggerProvider} object.
38
+ #
39
+ # @param [optional Numeric] timeout An optional timeout in seconds.
40
+ # @return [Integer] SUCCESS if no error occurred, FAILURE if a
41
+ # non-specific failure occurred, TIMEOUT if a timeout occurred.
42
+ def force_flush(timeout: nil)
43
+ SUCCESS
44
+ end
45
+
46
+ # Called when {LoggerProvider#shutdown} is called, if this exporter is
47
+ # registered to a {LoggerProvider} object.
48
+ #
49
+ # @param [optional Numeric] timeout An optional timeout in seconds.
50
+ def shutdown(timeout: nil)
51
+ @stopped = true
52
+ SUCCESS
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Logs
10
+ module Export
11
+ # An implementation of {LogRecordProcessor} that converts the LogRecord
12
+ # into a ReadableLogRecord and passes it to the configured exporter
13
+ # on emit.
14
+ #
15
+ # Typically, the SimpleLogRecordProcessor will be most suitable for use
16
+ # in testing; it should be used with caution in production. It may be
17
+ # appropriate for production use in scenarios where creating multiple
18
+ # threads is not desirable as well as scenarios where different custom
19
+ # attributes should be added to individual log records based on code
20
+ # scopes.
21
+ class SimpleLogRecordProcessor < OpenTelemetry::SDK::Logs::LogRecordProcessor
22
+ # Returns a new {SimpleLogRecordProcessor} that converts log records
23
+ # to {ReadableLogRecords} and forwards them to the given
24
+ # log_record_exporter.
25
+ #
26
+ # @param log_record_exporter the LogRecordExporter to push the
27
+ # recorded log records.
28
+ # @return [SimpleLogRecordProcessor]
29
+ # @raise ArgumentError if the log_record_exporter is invalid or nil.
30
+ def initialize(log_record_exporter)
31
+ raise ArgumentError, "exporter #{log_record_exporter.inspect} does not appear to be a valid exporter" unless Common::Utilities.valid_exporter?(log_record_exporter)
32
+
33
+ @log_record_exporter = log_record_exporter
34
+ @stopped = false
35
+ end
36
+
37
+ # Called when a LogRecord is emitted.
38
+ #
39
+ # This method is called synchronously on the execution thread. It
40
+ # should not throw or block the execution thread. It may not be called
41
+ # after shutdown.
42
+ #
43
+ # @param [LogRecord] log_record The emitted {LogRecord}
44
+ # @param [Context] _context The current {Context}
45
+ def on_emit(log_record, _context)
46
+ return if @stopped
47
+
48
+ @log_record_exporter&.export([log_record.to_log_record_data])
49
+ rescue => e # rubocop:disable Style/RescueStandardError
50
+ OpenTelemetry.handle_error(exception: e, message: 'Unexpected error in Logger#on_emit')
51
+ end
52
+
53
+ # Export all log records to the configured `Exporter` that have not
54
+ # yet been exported, then call {Exporter#force_flush}.
55
+ #
56
+ # This method should only be called in cases where it is absolutely
57
+ # necessary, such as when using some FaaS providers that may suspend
58
+ # the process after an invocation, but before the `Processor` exports
59
+ # the completed log records.
60
+ #
61
+ # @param [optional Numeric] timeout An optional timeout in seconds.
62
+ # @return [Integer] SUCCESS if no error occurred, FAILURE if a
63
+ # non-specific failure occurred, TIMEOUT if a timeout occurred.
64
+ # TODO: Should a rescue/handle error be added here for non-specific failures?
65
+ def force_flush(timeout: nil)
66
+ return if @stopped
67
+
68
+ @log_record_exporter&.force_flush(timeout: timeout) || SUCCESS
69
+ end
70
+
71
+ # Called when {LoggerProvider#shutdown} is called.
72
+ #
73
+ # @param [optional Numeric] timeout An optional timeout in seconds.
74
+ # @return [Integer] SUCCESS if no error occurred, FAILURE if a
75
+ # non-specific failure occurred, TIMEOUT if a timeout occurred.
76
+ # TODO: Should a rescue/handle error be added here for non-specific failures?
77
+ def shutdown(timeout: nil)
78
+ return if @stopped
79
+
80
+ @log_record_exporter&.shutdown(timeout: timeout) || SUCCESS
81
+ ensure
82
+ @stopped = true
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ 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 OpenTelemetry
8
+ module SDK
9
+ module Logs
10
+ # The Export module contains the built-in exporters and log record
11
+ # processors for the OpenTelemetry reference implementation.
12
+ module Export
13
+ ExportError = Class.new(OpenTelemetry::Error)
14
+ # The operation finished successfully.
15
+ SUCCESS = 0
16
+
17
+ # The operation finished with an error.
18
+ FAILURE = 1
19
+
20
+ # The operation timed out.
21
+ TIMEOUT = 2
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ require_relative 'export/console_log_record_exporter'
28
+ require_relative 'export/in_memory_log_record_exporter'
29
+ require_relative 'export/log_record_exporter'
30
+ require_relative 'export/simple_log_record_processor'
31
+ require_relative 'export/batch_log_record_processor'
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Logs
10
+ # Implementation of OpenTelemetry::Logs::LogRecord that records log events.
11
+ class LogRecord < OpenTelemetry::Logs::LogRecord
12
+ EMPTY_ATTRIBUTES = {}.freeze
13
+
14
+ private_constant :EMPTY_ATTRIBUTES
15
+
16
+ attr_accessor :timestamp,
17
+ :observed_timestamp,
18
+ :severity_text,
19
+ :severity_number,
20
+ :body,
21
+ :attributes,
22
+ :trace_id,
23
+ :span_id,
24
+ :trace_flags,
25
+ :resource,
26
+ :instrumentation_scope
27
+
28
+ # Creates a new {LogRecord}.
29
+ #
30
+ # @param [optional Time] timestamp Time when the event occurred.
31
+ # @param [optional Time] observed_timestamp Time when the event
32
+ # was observed by the collection system. If nil, will first attempt
33
+ # to set to `timestamp`. If `timestamp` is nil, will set to Time.now.
34
+ # @param [optional OpenTelemetry::Trace::SpanContext] span_context The
35
+ # OpenTelemetry::Trace::SpanContext to associate with the LogRecord.
36
+ # @param [optional String] severity_text The log severity, also known as
37
+ # log level.
38
+ # @param [optional Integer] severity_number The numerical value of the
39
+ # log severity.
40
+ # @param [optional String, Numeric, Boolean, Array<String, Numeric,
41
+ # Boolean>, Hash{String => String, Numeric, Boolean, Array<String,
42
+ # Numeric, Boolean>}] body The body of the {LogRecord}.
43
+ # @param [optional Hash{String => String, Numeric, Boolean,
44
+ # Array<String, Numeric, Boolean>}] attributes Attributes to associate
45
+ # with the {LogRecord}.
46
+ # @param [optional String] trace_id The trace ID associated with the
47
+ # current context.
48
+ # @param [optional String] span_id The span ID associated with the
49
+ # current context.
50
+ # @param [optional OpenTelemetry::Trace::TraceFlags] trace_flags The
51
+ # trace flags associated with the current context.
52
+ # @param [optional OpenTelemetry::SDK::Resources::Resource] resource The
53
+ # source of the log, desrived from the LoggerProvider.
54
+ # @param [optional OpenTelemetry::SDK::InstrumentationScope] instrumentation_scope
55
+ # The instrumentation scope, derived from the emitting Logger
56
+ # @param [optional] OpenTelemetry::SDK::LogRecordLimits] log_record_limits
57
+ # Attribute limits
58
+ #
59
+ #
60
+ # @return [LogRecord]
61
+ def initialize(
62
+ timestamp: nil,
63
+ observed_timestamp: nil,
64
+ severity_text: nil,
65
+ severity_number: nil,
66
+ body: nil,
67
+ attributes: nil,
68
+ trace_id: nil,
69
+ span_id: nil,
70
+ trace_flags: nil,
71
+ resource: nil,
72
+ instrumentation_scope: nil,
73
+ log_record_limits: nil
74
+ )
75
+ @timestamp = timestamp
76
+ @observed_timestamp = observed_timestamp || timestamp || Time.now
77
+ @severity_text = severity_text
78
+ @severity_number = severity_number
79
+ @body = body
80
+ @attributes = attributes.nil? ? nil : Hash[attributes] # We need a mutable copy of attributes
81
+ @trace_id = trace_id
82
+ @span_id = span_id
83
+ @trace_flags = trace_flags
84
+ @resource = resource
85
+ @instrumentation_scope = instrumentation_scope
86
+ @log_record_limits = log_record_limits || LogRecordLimits::DEFAULT
87
+ @total_recorded_attributes = @attributes&.size || 0
88
+
89
+ trim_attributes(@attributes)
90
+ end
91
+
92
+ def to_log_record_data
93
+ LogRecordData.new(
94
+ to_integer_nanoseconds(@timestamp),
95
+ to_integer_nanoseconds(@observed_timestamp),
96
+ @severity_text,
97
+ @severity_number,
98
+ @body,
99
+ @attributes,
100
+ @trace_id,
101
+ @span_id,
102
+ @trace_flags,
103
+ @resource,
104
+ @instrumentation_scope,
105
+ @total_recorded_attributes
106
+ )
107
+ end
108
+
109
+ private
110
+
111
+ def to_integer_nanoseconds(timestamp)
112
+ return unless timestamp.is_a?(Time)
113
+
114
+ (timestamp.to_r * 10**9).to_i
115
+ end
116
+
117
+ def trim_attributes(attributes)
118
+ return if attributes.nil?
119
+
120
+ # truncate total attributes
121
+ truncate_attributes(attributes, @log_record_limits.attribute_count_limit)
122
+
123
+ # truncate attribute values
124
+ truncate_attribute_values(attributes, @log_record_limits.attribute_length_limit)
125
+
126
+ # validate attributes
127
+ validate_attributes(attributes)
128
+
129
+ nil
130
+ end
131
+
132
+ def truncate_attributes(attributes, attribute_limit)
133
+ excess = attributes.size - attribute_limit
134
+ excess.times { attributes.shift } if excess.positive?
135
+ end
136
+
137
+ def validate_attributes(attrs)
138
+ # Similar to Internal.valid_attributes?, but with different messages
139
+ # Future refactor opportunity: https://github.com/open-telemetry/opentelemetry-ruby/issues/1739
140
+ attrs.keep_if do |k, v|
141
+ if !Internal.valid_key?(k)
142
+ OpenTelemetry.handle_error(message: "invalid log record attribute key type #{k.class} on record: '#{body}'")
143
+ return false
144
+ elsif !Internal.valid_value?(v)
145
+ OpenTelemetry.handle_error(message: "invalid log record attribute value type #{v.class} for key '#{k}' on record: '#{body}'")
146
+ return false
147
+ end
148
+
149
+ true
150
+ end
151
+ end
152
+
153
+ def truncate_attribute_values(attributes, attribute_length_limit)
154
+ return EMPTY_ATTRIBUTES if attributes.nil?
155
+ return attributes if attribute_length_limit.nil?
156
+
157
+ attributes.transform_values! { |value| OpenTelemetry::Common::Utilities.truncate_attribute_value(value, attribute_length_limit) }
158
+
159
+ attributes
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Logs
10
+ # LogRecordData is a Struct containing {LogRecord} data for export.
11
+ LogRecordData = Struct.new(:timestamp, # optional Integer nanoseconds since Epoch
12
+ :observed_timestamp, # Integer nanoseconds since Epoch
13
+ :severity_text, # optional String
14
+ :severity_number, # optional Integer
15
+ :body, # optional String, Numeric, Boolean, Array<String, Numeric, Boolean>, Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}
16
+ :attributes, # optional Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}
17
+ :trace_id, # optional String (16-byte binary)
18
+ :span_id, # optional String (8-byte binary)
19
+ :trace_flags, # optional Integer (8-bit byte of bit flags)
20
+ :resource, # optional OpenTelemetry::SDK::Resources::Resource
21
+ :instrumentation_scope, # optional OpenTelemetry::SDK::InstrumentationScope
22
+ :total_recorded_attributes) # Integer
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Logs
10
+ # Class that holds log record attribute limit parameters.
11
+ class LogRecordLimits
12
+ # The global default max number of attributes per {LogRecord}.
13
+ attr_reader :attribute_count_limit
14
+
15
+ # The global default max length of attribute value per {LogRecord}.
16
+ attr_reader :attribute_length_limit
17
+
18
+ # Returns a {LogRecordLimits} with the desired values.
19
+ #
20
+ # @return [LogRecordLimits] with the desired values.
21
+ # @raise [ArgumentError] if any of the max numbers are not positive.
22
+ def initialize(attribute_count_limit: Integer(OpenTelemetry::Common::Utilities.config_opt(
23
+ 'OTEL_LOG_RECORD_ATTRIBUTE_COUNT_LIMIT',
24
+ 'OTEL_ATTRIBUTE_COUNT_LIMIT',
25
+ default: 128
26
+ )),
27
+ attribute_length_limit: OpenTelemetry::Common::Utilities.config_opt(
28
+ 'OTEL_LOG_RECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT',
29
+ 'OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT'
30
+ ))
31
+ raise ArgumentError, 'attribute_count_limit must be positive' unless attribute_count_limit.positive?
32
+ raise ArgumentError, 'attribute_length_limit must not be less than 32' unless attribute_length_limit.nil? || Integer(attribute_length_limit) >= 32
33
+
34
+ @attribute_count_limit = attribute_count_limit
35
+ @attribute_length_limit = attribute_length_limit.nil? ? nil : Integer(attribute_length_limit)
36
+ end
37
+
38
+ # The default {LogRecordLimits}.
39
+ DEFAULT = new
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Logs
10
+ # LogRecordProcessor describes a duck type and provides a synchronous no-op hook for when a
11
+ # {LogRecord} is emitted. It is not required to subclass this
12
+ # class to provide an implementation of LogRecordProcessor, provided the interface is
13
+ # satisfied.
14
+ class LogRecordProcessor
15
+ # Called when a {LogRecord} is emitted. Subsequent calls are not
16
+ # permitted after shutdown is called.
17
+ # @param [LogRecord] log_record The emitted {LogRecord}
18
+ # @param [Context] context The {Context}
19
+ def on_emit(log_record, context); end
20
+
21
+ # Export all log records to the configured `Exporter` that have not yet
22
+ # been exported.
23
+ #
24
+ # This method should only be called in cases where it is absolutely
25
+ # necessary, such as when using some FaaS providers that may suspend
26
+ # the process after an invocation, but before the `Processor` exports
27
+ # the completed spans.
28
+ #
29
+ # @param [optional Numeric] timeout An optional timeout in seconds.
30
+ # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
31
+ # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
32
+ def force_flush(timeout: nil)
33
+ Export::SUCCESS
34
+ end
35
+
36
+ # Called when {LoggerProvider#shutdown} is called.
37
+ #
38
+ # @param [optional Numeric] timeout An optional timeout in seconds.
39
+ # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
40
+ # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
41
+ def shutdown(timeout: nil)
42
+ Export::SUCCESS
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module SDK
9
+ module Logs
10
+ # The SDK implementation of OpenTelemetry::Logs::Logger
11
+ class Logger < OpenTelemetry::Logs::Logger
12
+ # @api private
13
+ #
14
+ # Returns a new {OpenTelemetry::SDK::Logs::Logger} instance. This should
15
+ # not be called directly. New loggers should be created using
16
+ # {LoggerProvider#logger}.
17
+ #
18
+ # @param [String] name Instrumentation package name
19
+ # @param [String] version Instrumentation package version
20
+ # @param [LoggerProvider] logger_provider The {LoggerProvider} that
21
+ # initialized the logger
22
+ #
23
+ # @return [OpenTelemetry::SDK::Logs::Logger]
24
+ def initialize(name, version, logger_provider)
25
+ @instrumentation_scope = InstrumentationScope.new(name, version)
26
+ @logger_provider = logger_provider
27
+ end
28
+
29
+ # Emit a {LogRecord} to the processing pipeline.
30
+ #
31
+ # @param [optional Time] timestamp Time when the event occurred.
32
+ # @param [optional Time] observed_timestamp Time when the event was
33
+ # observed by the collection system.
34
+ # @param [optional OpenTelemetry::Trace::SpanContext] span_context The
35
+ # OpenTelemetry::Trace::SpanContext to associate with the
36
+ # {LogRecord}.
37
+ # @param [optional String] severity_text Original string representation of
38
+ # the severity as it is known at the source. Also known as log level.
39
+ # @param severity_number [optional Integer] Numerical value of the
40
+ # severity. Smaller numerical values correspond to less severe events
41
+ # (such as debug events), larger numerical values correspond to more
42
+ # severe events (such as errors and critical events).
43
+ # @param [optional String, Numeric, Boolean, Array<String, Numeric,
44
+ # Boolean>, Hash{String => String, Numeric, Boolean, Array<String,
45
+ # Numeric, Boolean>}] body A value containing the body of the log record.
46
+ # @param [optional Hash{String => String, Numeric, Boolean,
47
+ # Array<String, Numeric, Boolean>}] attributes Additional information
48
+ # about the event.
49
+ # @param [optional String (16-byte binary)] trace_id Request trace id as
50
+ # defined in {https://www.w3.org/TR/trace-context/#trace-id W3C Trace Context}.
51
+ # Can be set for logs that are part of request processing and have an
52
+ # assigned trace id.
53
+ # @param [optional String (8-byte binary)] span_id Span id. Can be set
54
+ # for logs that are part of a particular processing span. If span_id
55
+ # is present trace_id should also be present.
56
+ # @param [optional Integer (8-bit byte of bit flags)] trace_flags Trace
57
+ # flag as defined in {https://www.w3.org/TR/trace-context/#trace-flags W3C Trace Context}
58
+ # specification. At the time of writing the specification defines one
59
+ # flag - the SAMPLED flag.
60
+ # @param [optional OpenTelemetry::Context] context The OpenTelemetry::Context
61
+ # to associate with the {LogRecord}.
62
+ #
63
+ # @api public
64
+ def on_emit(timestamp: nil,
65
+ observed_timestamp: Time.now,
66
+ severity_text: nil,
67
+ severity_number: nil,
68
+ body: nil,
69
+ attributes: nil,
70
+ trace_id: nil,
71
+ span_id: nil,
72
+ trace_flags: nil,
73
+ context: OpenTelemetry::Context.current)
74
+ current_span = OpenTelemetry::Trace.current_span(context)
75
+ span_context = current_span.context unless current_span == OpenTelemetry::Trace::Span::INVALID
76
+
77
+ @logger_provider.on_emit(timestamp: timestamp,
78
+ observed_timestamp: observed_timestamp,
79
+ severity_text: severity_text,
80
+ severity_number: severity_number,
81
+ body: body,
82
+ attributes: attributes,
83
+ trace_id: trace_id || span_context&.trace_id,
84
+ span_id: span_id || span_context&.span_id,
85
+ trace_flags: trace_flags || span_context&.trace_flags,
86
+ instrumentation_scope: @instrumentation_scope,
87
+ context: context)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end