From dbda356445364b1a73fe882e8282e821ee8a1034 Mon Sep 17 00:00:00 2001 From: yusuf Date: Thu, 16 Jan 2025 16:50:02 +0800 Subject: [PATCH 01/61] feat: LogRecord limits --- lib/src/sdk/common/limits.dart | 19 +++++++++++++++ lib/src/sdk/logs/log_record_limit.dart | 33 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 lib/src/sdk/logs/log_record_limit.dart diff --git a/lib/src/sdk/common/limits.dart b/lib/src/sdk/common/limits.dart index 506c50ff..d46c5ae7 100644 --- a/lib/src/sdk/common/limits.dart +++ b/lib/src/sdk/common/limits.dart @@ -4,6 +4,7 @@ import 'package:meta/meta.dart'; import '../../../api.dart' as api; import '../../../sdk.dart' as sdk; +import '../logs/log_record_limit.dart'; /// Applies given [sdk.SpanLimits] to a list of [api.SpanLink]s. @protected @@ -77,6 +78,24 @@ api.Attribute applyAttributeLimits(api.Attribute attr, sdk.SpanLimits limits) { return attr; } +@protected +api.Attribute applyAttributeLimitsForLog(api.Attribute attr, LogRecordLimits limits) { + // if maxNumAttributeLength is less than zero, then it has unlimited length. + if (limits.attributeValueLengthLimit < 0) return attr; + + if (attr.value is String) { + attr = api.Attribute.fromString( + attr.key, applyAttributeLengthLimit(attr.value as String, limits.attributeValueLengthLimit)); + } else if (attr.value is List) { + final listString = attr.value as List; + for (var j = 0; j < listString.length; j++) { + listString[j] = applyAttributeLengthLimit(listString[j], limits.attributeValueLengthLimit); + } + attr = api.Attribute.fromStringList(attr.key, listString); + } + return attr; +} + /// Truncate just strings which length is longer than configuration. /// Reference: https://github.com/open-telemetry/opentelemetry-java/blob/14ffacd1cdd22f5aa556eeda4a569c7f144eadf2/sdk/common/src/main/java/io/opentelemetry/sdk/internal/AttributeUtil.java#L80 @protected diff --git a/lib/src/sdk/logs/log_record_limit.dart b/lib/src/sdk/logs/log_record_limit.dart new file mode 100644 index 00000000..75f2a484 --- /dev/null +++ b/lib/src/sdk/logs/log_record_limit.dart @@ -0,0 +1,33 @@ +// https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecord-limits +abstract class LogRecordLimits { + // https://opentelemetry.io/docs/specs/otel/common/#configurable-parameters + factory LogRecordLimits({ + int attributeCountLimit = 128, + int attributeValueLengthLimit = -1, + }) => + _LogRecordLimits( + attributeCountLimit: attributeCountLimit, + attributeValueLengthLimit: attributeValueLengthLimit, + ); + + int get attributeCountLimit; + + int get attributeValueLengthLimit; +} + +class _LogRecordLimits implements LogRecordLimits { + final int _attributeCountLimit; + final int _attributeValueLengthLimit; + + _LogRecordLimits({ + int attributeCountLimit = 128, + int attributeValueLengthLimit = -1, + }) : _attributeCountLimit = attributeCountLimit, + _attributeValueLengthLimit = attributeValueLengthLimit; + + @override + int get attributeCountLimit => _attributeCountLimit; + + @override + int get attributeValueLengthLimit => _attributeValueLengthLimit; +} From 630f2cfdaeb281e794bf31b6eb57b9ea5f9bd265 Mon Sep 17 00:00:00 2001 From: yusuf Date: Thu, 16 Jan 2025 16:53:37 +0800 Subject: [PATCH 02/61] feat: LogRecord --- lib/src/experimental_sdk.dart | 4 +- lib/src/sdk/logs/log_record.dart | 189 ++++++++++++++++++++++++ test/unit/sdk/logs/log_record_test.dart | 118 +++++++++++++++ 3 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 lib/src/sdk/logs/log_record.dart create mode 100644 test/unit/sdk/logs/log_record_test.dart diff --git a/lib/src/experimental_sdk.dart b/lib/src/experimental_sdk.dart index 5536a699..bdb060aa 100644 --- a/lib/src/experimental_sdk.dart +++ b/lib/src/experimental_sdk.dart @@ -6,7 +6,9 @@ library experimental_sdk; import 'package:meta/meta.dart'; +export 'sdk/logs/log_record.dart' show ReadableLogRecord, ReadWriteLogRecord, LogRecord; +export 'sdk/logs/log_record_limit.dart' show LogRecordLimits; export 'sdk/metrics/counter.dart' show Counter; -export 'sdk/metrics/meter_provider.dart' show MeterProvider; export 'sdk/metrics/meter.dart' show Meter; +export 'sdk/metrics/meter_provider.dart' show MeterProvider; export 'sdk/resource/resource.dart' show Resource; diff --git a/lib/src/sdk/logs/log_record.dart b/lib/src/sdk/logs/log_record.dart new file mode 100644 index 00000000..5a02577c --- /dev/null +++ b/lib/src/sdk/logs/log_record.dart @@ -0,0 +1,189 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +import 'package:fixnum/fixnum.dart'; +import 'package:meta/meta.dart'; +import 'package:opentelemetry/api.dart' as api; +import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/experimental_api.dart' as api; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; + +import '../common/limits.dart'; + +/// https://opentelemetry.io/docs/specs/otel/logs/sdk/#readwritelogrecord +abstract class ReadableLogRecord { + Int64? get timeStamp; + + Int64? get observedTimestamp; + + String? get severityText; + + api.Severity? get severityNumber; + + dynamic get body; + + sdk.Attributes? get attributes; + + api.SpanContext? get spanContext; + + sdk.Resource? get resource; + + sdk.InstrumentationScope? get instrumentationScope; + + int get droppedAttributesCount; +} + +abstract class ReadWriteLogRecord extends ReadableLogRecord { + set body(dynamic severity); + + set severityText(String? severity); + + set severityNumber(api.Severity? severity); +} + + +class LogRecord implements ReadWriteLogRecord { + @override + final sdk.InstrumentationScope instrumentationScope; + + @override + final sdk.Resource? resource; + + final sdk.TimeProvider _timeProvider; + final api.Context? context; + final api.SpanContext? _spanContext; + final sdk.LogRecordLimits logRecordLimits; + final DateTime? _timeStamp; + final DateTime? _observedTimestamp; + + bool _isReadonly = false; + String? _severityText; + api.Severity? _severityNumber; + dynamic _body; + int _totalAttributesCount = 0; + + final sdk.Attributes _attributes; + + LogRecord({ + required this.instrumentationScope, + required this.logRecordLimits, + api.Severity? severityNumber, + String? severityText, + sdk.Attributes? attributes, + DateTime? timeStamp, + DateTime? observedTimestamp, + dynamic body, + this.resource, + this.context, + sdk.TimeProvider? timeProvider, + }) : _severityText = severityText, + _body = body, + _attributes = sdk.Attributes.empty(), + _severityNumber = severityNumber, + _spanContext = api.spanContextFromContext(context ?? api.Context.current), + _timeStamp = timeStamp, + _observedTimestamp = observedTimestamp, + _timeProvider = timeProvider ?? sdk.DateTimeTimeProvider() { + if (attributes != null) setAttributes(attributes); + } + + @override + sdk.Attributes? get attributes => _attributes; + + @override + dynamic get body => _body; + + @override + set body(dynamic body) { + if (_isReadonly) return; + _body = body; + } + + @override + api.SpanContext? get spanContext => _spanContext; + + @override + int get droppedAttributesCount => _totalAttributesCount - (attributes?.length ?? 0); + + @override + Int64? get timeStamp => _timeStamp != null ? Int64(_timeStamp!.microsecondsSinceEpoch) * 1000 : _timeProvider.now; + + @override + Int64? get observedTimestamp => + _observedTimestamp != null ? Int64(_observedTimestamp!.microsecondsSinceEpoch) * 1000 : _timeProvider.now; + + @override + api.Severity? get severityNumber => _severityNumber; + + @override + set severityNumber(api.Severity? severity) { + if (_isReadonly) return; + _severityNumber = severity; + } + + @override + String? get severityText => _severityText; + + @override + set severityText(String? severity) { + if (_isReadonly) return; + _severityText = severity; + } + + void setAttributes(sdk.Attributes attributes) { + for (final key in attributes.keys) { + setAttribute(key, attributes.get(key)); + } + } + + void setAttribute(String key, Object? value) { + if (value == null) return; + if (_isReadonly) return; + if (key.isEmpty) return; + if (logRecordLimits.attributeCountLimit == 0) return; + _totalAttributesCount += 1; + if (value is String) { + _attributes.add( + applyAttributeLimitsForLog(api.Attribute.fromString(key, value), logRecordLimits), + ); + } + + if (value is bool) { + _attributes.add(api.Attribute.fromBoolean(key, value)); + } + + if (value is double) { + _attributes.add(api.Attribute.fromDouble(key, value)); + } + + if (value is int) { + _attributes.add(api.Attribute.fromInt(key, value)); + } + + if (value is List) { + _attributes.add( + applyAttributeLimitsForLog(api.Attribute.fromStringList(key, value), logRecordLimits), + ); + } + + if (value is List) { + _attributes.add(api.Attribute.fromBooleanList(key, value)); + } + + if (value is List) { + _attributes.add(api.Attribute.fromDoubleList(key, value)); + } + + if (value is List) { + _attributes.add(api.Attribute.fromIntList(key, value)); + } + } + + /// A LogRecordProcessor may freely modify logRecord for the duration of the OnEmit call. + /// If logRecord is needed after OnEmit returns (i.e. for asynchronous processing) only reads are permitted. + @internal + void makeReadonly() { + _isReadonly = true; + } +} + diff --git a/test/unit/sdk/logs/log_record_test.dart b/test/unit/sdk/logs/log_record_test.dart new file mode 100644 index 00000000..f25503e2 --- /dev/null +++ b/test/unit/sdk/logs/log_record_test.dart @@ -0,0 +1,118 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +@TestOn('vm') +import 'package:fixnum/fixnum.dart'; +import 'package:opentelemetry/api.dart' as api; +import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/experimental_api.dart' as api; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; +import 'package:test/test.dart'; + +import '../trace_provider_test.dart'; + +void main() { + test('set readonly will block values from being set', () { + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + logRecordLimits: sdk.LogRecordLimits(), + timeProvider: FakeTimeProvider(now: Int64(123))) + ..makeReadonly() + ..body = 'Log Message' + ..severityNumber = api.Severity.debug + ..severityText = 'DEBUG' + ..setAttributes(sdk.Attributes.empty()..add(api.Attribute.fromString('key', 'value'))) + ..setAttribute('key2', 'value2'); + + expect(logRecord.body, null); + expect(logRecord.severityNumber, null); + expect(logRecord.severityText, null); + expect(logRecord.attributes?.keys, const []); + expect(logRecord.droppedAttributesCount, 0); + expect(logRecord.timeStamp, Int64(123)); + expect(logRecord.observedTimestamp, Int64(123)); + }); + + test('logRecord call setter', () { + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + logRecordLimits: sdk.LogRecordLimits(), + timeProvider: FakeTimeProvider(now: Int64(123))) + ..body = 'Log Message' + ..severityNumber = api.Severity.debug + ..severityText = 'DEBUG' + ..setAttributes(sdk.Attributes.empty()..add(api.Attribute.fromString('key', 'value'))) + ..setAttribute('key2', 'value2'); + + expect(logRecord.body, 'Log Message'); + expect(logRecord.severityNumber, api.Severity.debug); + expect(logRecord.severityText, 'DEBUG'); + expect(logRecord.attributes?.keys, const ['key', 'key2']); + expect(logRecord.droppedAttributesCount, 0); + expect(logRecord.timeStamp, Int64(123)); + expect(logRecord.observedTimestamp, Int64(123)); + }); + + test('logRecord update same attribute will create attributesCount diff', () { + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + logRecordLimits: sdk.LogRecordLimits(), + ) + ..setAttributes(sdk.Attributes.empty()..add(api.Attribute.fromString('key2', 'value'))) + ..setAttribute('key2', 'value2'); + + expect(logRecord.droppedAttributesCount, 1); + }); + + test('logRecord time stamp will be converted to Int64', () { + final now = DateTime.now(); + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + timeStamp: now, + observedTimestamp: now, + logRecordLimits: sdk.LogRecordLimits(), + ) + ..setAttributes(sdk.Attributes.empty()..add(api.Attribute.fromString('key2', 'value'))) + ..setAttribute('key2', 'value2'); + + expect(logRecord.timeStamp, Int64(now.microsecondsSinceEpoch) * 1000); + expect(logRecord.observedTimestamp, Int64(now.microsecondsSinceEpoch) * 1000); + }); + + test('logRecord set attribute', () { + final now = DateTime.now(); + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + timeStamp: now, + observedTimestamp: now, + logRecordLimits: sdk.LogRecordLimits(attributeValueLengthLimit: 2), + ) + ..setAttribute('key', 'value') + ..setAttribute('key2', true) + ..setAttribute('key3', 1) + ..setAttribute('key4', 1.1) + ..setAttribute('key5', ['value2']) + ..setAttribute('key6', [true]) + ..setAttribute('key7', [1]) + ..setAttribute('key8', [1.1]); + + expect(logRecord.droppedAttributesCount, 0); + expect(logRecord.attributes?.keys, const ['key', 'key2', 'key3', 'key4', 'key5', 'key6', 'key7', 'key8']); + expect(logRecord.attributes?.get('key'), 'va'); + }); + + test('logRecord set attribute with limit', () { + final now = DateTime.now(); + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + timeStamp: now, + observedTimestamp: now, + logRecordLimits: sdk.LogRecordLimits(attributeValueLengthLimit: 2), + ) + ..setAttribute('key', 'value') + ..setAttribute('key2', ['value2']); + + expect(logRecord.attributes?.get('key'), 'va'); + expect(logRecord.attributes?.get('key2'), const ['va']); + }); +} From 7119f8e0778c56a253ad98a91821fad8a90e4e90 Mon Sep 17 00:00:00 2001 From: yusuf Date: Thu, 16 Jan 2025 18:13:49 +0800 Subject: [PATCH 03/61] chore: fix comment --- lib/src/api/logs/logger_provider.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/api/logs/logger_provider.dart b/lib/src/api/logs/logger_provider.dart index ae7be6ba..4c1977f6 100644 --- a/lib/src/api/logs/logger_provider.dart +++ b/lib/src/api/logs/logger_provider.dart @@ -7,7 +7,7 @@ import 'package:opentelemetry/src/api/logs/logger.dart'; abstract class LoggerProvider { /// Gets or creates a [Logger] instance. /// - /// The meter is identified by the combination of [name], [version], + /// The logger is identified by the combination of [name], [version], /// [schemaUrl] and [attributes]. The [name] SHOULD uniquely identify the /// instrumentation scope, such as the instrumentation library /// (e.g. io.opentelemetry.contrib.mongodb), package, module or class name. From 6fb8422a6236176b421fe04db802d0ee4ecf57b3 Mon Sep 17 00:00:00 2001 From: yusuf Date: Thu, 16 Jan 2025 18:22:40 +0800 Subject: [PATCH 04/61] chore: log record limit test --- lib/src/sdk/logs/log_record_limit.dart | 12 ++--------- test/unit/sdk/common/limits_test.dart | 27 +++++++++++++++++++++++++ test/unit/sdk/logs/log_record_test.dart | 13 ++++++------ 3 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 test/unit/sdk/common/limits_test.dart diff --git a/lib/src/sdk/logs/log_record_limit.dart b/lib/src/sdk/logs/log_record_limit.dart index 75f2a484..fccd26c8 100644 --- a/lib/src/sdk/logs/log_record_limit.dart +++ b/lib/src/sdk/logs/log_record_limit.dart @@ -1,25 +1,17 @@ // https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecord-limits abstract class LogRecordLimits { // https://opentelemetry.io/docs/specs/otel/common/#configurable-parameters - factory LogRecordLimits({ - int attributeCountLimit = 128, - int attributeValueLengthLimit = -1, - }) => - _LogRecordLimits( - attributeCountLimit: attributeCountLimit, - attributeValueLengthLimit: attributeValueLengthLimit, - ); int get attributeCountLimit; int get attributeValueLengthLimit; } -class _LogRecordLimits implements LogRecordLimits { +class LogRecordLimitsImpl implements LogRecordLimits { final int _attributeCountLimit; final int _attributeValueLengthLimit; - _LogRecordLimits({ + const LogRecordLimitsImpl({ int attributeCountLimit = 128, int attributeValueLengthLimit = -1, }) : _attributeCountLimit = attributeCountLimit, diff --git a/test/unit/sdk/common/limits_test.dart b/test/unit/sdk/common/limits_test.dart new file mode 100644 index 00000000..a61f7b8d --- /dev/null +++ b/test/unit/sdk/common/limits_test.dart @@ -0,0 +1,27 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +import 'package:opentelemetry/api.dart' as api; +import 'package:opentelemetry/src/sdk/common/limits.dart'; +import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart'; +import 'package:test/test.dart'; + +void main() { + test('test log record limit', () { + final logLimit = applyAttributeLimitsForLog( + api.Attribute.fromString('key', 'value'), + LogRecordLimitsImpl(attributeValueLengthLimit: 2), + ); + + expect(logLimit.value, 'va'); + }); + + test('test log record limit list', () { + final logLimit = applyAttributeLimitsForLog( + api.Attribute.fromStringList('key', ['value1', 'value2']), + LogRecordLimitsImpl(attributeValueLengthLimit: 2), + ); + + expect(logLimit.value, ['va', 'va']); + }); +} diff --git a/test/unit/sdk/logs/log_record_test.dart b/test/unit/sdk/logs/log_record_test.dart index f25503e2..64bcc441 100644 --- a/test/unit/sdk/logs/log_record_test.dart +++ b/test/unit/sdk/logs/log_record_test.dart @@ -7,6 +7,7 @@ import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/sdk.dart' as sdk; import 'package:opentelemetry/src/experimental_api.dart' as api; import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; +import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart'; import 'package:test/test.dart'; import '../trace_provider_test.dart'; @@ -15,7 +16,7 @@ void main() { test('set readonly will block values from being set', () { final logRecord = sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), - logRecordLimits: sdk.LogRecordLimits(), + logRecordLimits: LogRecordLimitsImpl(), timeProvider: FakeTimeProvider(now: Int64(123))) ..makeReadonly() ..body = 'Log Message' @@ -36,7 +37,7 @@ void main() { test('logRecord call setter', () { final logRecord = sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), - logRecordLimits: sdk.LogRecordLimits(), + logRecordLimits: LogRecordLimitsImpl(), timeProvider: FakeTimeProvider(now: Int64(123))) ..body = 'Log Message' ..severityNumber = api.Severity.debug @@ -56,7 +57,7 @@ void main() { test('logRecord update same attribute will create attributesCount diff', () { final logRecord = sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), - logRecordLimits: sdk.LogRecordLimits(), + logRecordLimits: LogRecordLimitsImpl(), ) ..setAttributes(sdk.Attributes.empty()..add(api.Attribute.fromString('key2', 'value'))) ..setAttribute('key2', 'value2'); @@ -70,7 +71,7 @@ void main() { instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), timeStamp: now, observedTimestamp: now, - logRecordLimits: sdk.LogRecordLimits(), + logRecordLimits: LogRecordLimitsImpl(), ) ..setAttributes(sdk.Attributes.empty()..add(api.Attribute.fromString('key2', 'value'))) ..setAttribute('key2', 'value2'); @@ -85,7 +86,7 @@ void main() { instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), timeStamp: now, observedTimestamp: now, - logRecordLimits: sdk.LogRecordLimits(attributeValueLengthLimit: 2), + logRecordLimits: LogRecordLimitsImpl(attributeValueLengthLimit: 2), ) ..setAttribute('key', 'value') ..setAttribute('key2', true) @@ -107,7 +108,7 @@ void main() { instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), timeStamp: now, observedTimestamp: now, - logRecordLimits: sdk.LogRecordLimits(attributeValueLengthLimit: 2), + logRecordLimits: LogRecordLimitsImpl(attributeValueLengthLimit: 2), ) ..setAttribute('key', 'value') ..setAttribute('key2', ['value2']); From 69e4e18e90d5d529429bc5e8a72215293b4ec85c Mon Sep 17 00:00:00 2001 From: yusuf Date: Thu, 16 Jan 2025 18:23:12 +0800 Subject: [PATCH 05/61] feat: LogRecordProcessor --- .../sdk/logs/processors/log_record_processor.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 lib/src/sdk/logs/processors/log_record_processor.dart diff --git a/lib/src/sdk/logs/processors/log_record_processor.dart b/lib/src/sdk/logs/processors/log_record_processor.dart new file mode 100644 index 00000000..cde26d50 --- /dev/null +++ b/lib/src/sdk/logs/processors/log_record_processor.dart @@ -0,0 +1,13 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; + +/// https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecordprocessor +abstract class LogRecordProcessor { + void onEmit(sdk.ReadableLogRecord logRecord); + + Future forceFlush(); + + Future shutdown(); +} From 79640fb37a6973c43e846733b667c23ec738616a Mon Sep 17 00:00:00 2001 From: yusuf Date: Thu, 16 Jan 2025 18:24:16 +0800 Subject: [PATCH 06/61] feat: NoopLogRecordProcessor --- .../sdk/logs/processors/noop_log_processor.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 lib/src/sdk/logs/processors/noop_log_processor.dart diff --git a/lib/src/sdk/logs/processors/noop_log_processor.dart b/lib/src/sdk/logs/processors/noop_log_processor.dart new file mode 100644 index 00000000..22893c74 --- /dev/null +++ b/lib/src/sdk/logs/processors/noop_log_processor.dart @@ -0,0 +1,17 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; + +class NoopLogRecordProcessor implements sdk.LogRecordProcessor { + const NoopLogRecordProcessor(); + + @override + Future forceFlush() async {} + + @override + void onEmit(sdk.ReadableLogRecord logRecord) {} + + @override + Future shutdown() async {} +} From 8ff6c713adb2033bb1e95263911ff90953cda136 Mon Sep 17 00:00:00 2001 From: yusuf Date: Thu, 16 Jan 2025 18:24:40 +0800 Subject: [PATCH 07/61] feat: LoggerConfig --- lib/src/sdk/logs/logger_config.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 lib/src/sdk/logs/logger_config.dart diff --git a/lib/src/sdk/logs/logger_config.dart b/lib/src/sdk/logs/logger_config.dart new file mode 100644 index 00000000..597fbbf1 --- /dev/null +++ b/lib/src/sdk/logs/logger_config.dart @@ -0,0 +1,11 @@ +// https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerconfig +class LoggerConfig { + /// If not explicitly set, + /// the disabled parameter SHOULD default to false ( i.e. Loggers are enabled by default). + /// If a Logger is disabled, it MUST behave equivalently to No-op Logger. + final bool disabled; + + const LoggerConfig({ + this.disabled = false, + }); +} From d3d98e6aecaeea931eb1735ca325c8685d88fe10 Mon Sep 17 00:00:00 2001 From: yusuf Date: Thu, 16 Jan 2025 18:25:34 +0800 Subject: [PATCH 08/61] feat: Logger --- lib/src/sdk/logs/logger.dart | 48 ++++++++++++++++++++++++++++ test/unit/sdk/logs/logger_test.dart | 49 +++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 lib/src/sdk/logs/logger.dart create mode 100644 test/unit/sdk/logs/logger_test.dart diff --git a/lib/src/sdk/logs/logger.dart b/lib/src/sdk/logs/logger.dart new file mode 100644 index 00000000..957cfceb --- /dev/null +++ b/lib/src/sdk/logs/logger.dart @@ -0,0 +1,48 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/api/context/context.dart'; +import 'package:opentelemetry/src/experimental_api.dart' as api; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; + +class Logger extends api.Logger { + final sdk.InstrumentationScope instrumentationScope; + final sdk.Resource? resource; + final Function(sdk.ReadWriteLogRecord)? onLogEmit; + final sdk.LogRecordLimits logRecordLimits; + final sdk.TimeProvider? timeProvider; + + Logger({ + required this.instrumentationScope, + required this.logRecordLimits, + this.onLogEmit, + this.resource, + this.timeProvider, + }); + + @override + void emit({ + sdk.Attributes? attributes, + Context? context, + dynamic body, + DateTime? observedTimestamp, + api.Severity? severityNumber, + String? severityText, + DateTime? timeStamp, + }) { + final log = sdk.LogRecord( + logRecordLimits: logRecordLimits, + resource: resource, + instrumentationScope: instrumentationScope, + context: context, + severityText: severityText, + severityNumber: severityNumber, + attributes: attributes, + body: body, + timeProvider: timeProvider, + ); + onLogEmit?.call(log); + log.makeReadonly(); + } +} diff --git a/test/unit/sdk/logs/logger_test.dart b/test/unit/sdk/logs/logger_test.dart new file mode 100644 index 00000000..25875f5b --- /dev/null +++ b/test/unit/sdk/logs/logger_test.dart @@ -0,0 +1,49 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +@TestOn('vm') +import 'package:mocktail/mocktail.dart'; +import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; +import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart'; +import 'package:test/test.dart'; + +class Callback { + const Callback(); + + void call(sdk.ReadWriteLogRecord logRecord) {} +} + +class CallbackMock extends Mock implements Callback {} + +void main() { + setUpAll(() { + registerFallbackValue(sdk.Logger( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + logRecordLimits: LogRecordLimitsImpl(), + )); + }); + + test('emit new log', () { + final callBack = CallbackMock(); + sdk.Logger( + logRecordLimits: LogRecordLimitsImpl(), + instrumentationScope: sdk.InstrumentationScope( + 'library_name', + 'library_version', + 'url://schema', + [], + ), + resource: sdk.Resource([]), + onLogEmit: callBack, + ).emit(body: 'TEST!'); + + verify(() => callBack.call(any(that: predicate((it) { + return it.attributes?.keys.isEmpty == true && + it.instrumentationScope.name == 'library_name' && + it.instrumentationScope.version == 'library_version' && + it.instrumentationScope.schemaUrl == 'url://schema' && + it.resource?.attributes.keys.isEmpty == true; + })))).called(1); + }); +} From f4b05aab6e86032c69a5d4ace82379b2a6ccb53a Mon Sep 17 00:00:00 2001 From: yusuf Date: Thu, 16 Jan 2025 18:25:50 +0800 Subject: [PATCH 09/61] feat: LoggerProvider --- lib/src/sdk/logs/logger_provider.dart | 77 ++++++++++++++++++ test/unit/mocks.dart | 15 +++- test/unit/sdk/logs/logger_provider_test.dart | 83 ++++++++++++++++++++ 3 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 lib/src/sdk/logs/logger_provider.dart create mode 100644 test/unit/sdk/logs/logger_provider_test.dart diff --git a/lib/src/sdk/logs/logger_provider.dart b/lib/src/sdk/logs/logger_provider.dart new file mode 100644 index 00000000..44b607fe --- /dev/null +++ b/lib/src/sdk/logs/logger_provider.dart @@ -0,0 +1,77 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +import 'package:meta/meta.dart'; +import 'package:opentelemetry/api.dart' as api; +import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/experimental_api.dart' as api; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; +import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart'; +import 'package:opentelemetry/src/sdk/logs/logger_config.dart'; +import 'package:quiver/core.dart'; + +const defaultLoggerName = 'unknown'; + +// https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerprovider +class LoggerProvider implements api.LoggerProvider { + @protected + final Map loggers = {}; + + final LoggerConfig config; + + final List processors; + + final sdk.Resource? resource; + final sdk.LogRecordLimits logRecordLimits; + + final sdk.TimeProvider _timeProvider; + + LoggerProvider({ + this.resource, + this.config = const LoggerConfig(), + this.logRecordLimits = const LogRecordLimitsImpl(), + this.processors = const [], + sdk.TimeProvider? timeProvider, + }) : _timeProvider = timeProvider ?? sdk.DateTimeTimeProvider(); + + @override + api.Logger get( + String name, { + String version = '', + String schemaUrl = '', + List attributes = const [], + bool? includeTraceContext, + }) { + final loggerName = name.isNotEmpty ? name : defaultLoggerName; + final key = hash3(loggerName, version, schemaUrl); + if (config.disabled) { + return api.NoopLogger(); + } + return loggers.putIfAbsent( + key, + () => sdk.Logger( + logRecordLimits: logRecordLimits, + resource: resource, + instrumentationScope: sdk.InstrumentationScope(loggerName, version, schemaUrl, attributes), + timeProvider: _timeProvider, + onLogEmit: (log) { + for (final processor in processors) { + processor.onEmit(log); + } + }, + ), + ); + } + + void addLogRecordProcessor(sdk.LogRecordProcessor processor) { + processors.add(processor); + } + + Future forceFlush() async { + await Future.forEach(processors, (e) => e.forceFlush()); + } + + Future shutdown() async { + await Future.forEach(processors, (e) => e.shutdown()); + } +} diff --git a/test/unit/mocks.dart b/test/unit/mocks.dart index f53b4f6c..ce6eb842 100644 --- a/test/unit/mocks.dart +++ b/test/unit/mocks.dart @@ -1,12 +1,13 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information +import 'package:fixnum/fixnum.dart'; import 'package:http/http.dart' as http; import 'package:mocktail/mocktail.dart'; +import 'package:opentelemetry/sdk.dart'; import 'package:opentelemetry/src/api/context/context.dart'; import 'package:opentelemetry/src/api/trace/span.dart'; -import 'package:opentelemetry/src/sdk/trace/read_only_span.dart'; -import 'package:opentelemetry/src/sdk/trace/span_processors/span_processor.dart'; +import 'package:opentelemetry/src/experimental_sdk.dart'; class MockContext extends Mock implements Context {} @@ -17,3 +18,13 @@ class MockSpan extends Mock implements Span {} class MockReadOnlySpan extends Mock implements ReadOnlySpan {} class MockSpanProcessor extends Mock implements SpanProcessor {} + +class MockLogRecordProcessor extends Mock implements LogRecordProcessor {} + +class FakeTimeProvider extends Mock implements TimeProvider { + FakeTimeProvider({required Int64 now}) : _now = now; + final Int64 _now; + + @override + Int64 get now => _now; +} \ No newline at end of file diff --git a/test/unit/sdk/logs/logger_provider_test.dart b/test/unit/sdk/logs/logger_provider_test.dart new file mode 100644 index 00000000..2ababd5a --- /dev/null +++ b/test/unit/sdk/logs/logger_provider_test.dart @@ -0,0 +1,83 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +@TestOn('vm') +import 'package:fixnum/src/int64.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; +import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart'; +import 'package:test/test.dart'; + +import '../../mocks.dart'; + +void main() { + setUpAll(() { + registerFallbackValue(sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + logRecordLimits: LogRecordLimitsImpl(), + )); + }); + + test('getLogger stores tracers by name', () { + final provider = sdk.LoggerProvider(); + final fooTracer = provider.get('foo'); + final barTracer = provider.get('bar'); + final fooWithVersionTracer = provider.get('foo', version: '1.0'); + + expect(fooTracer, allOf([isNot(barTracer), isNot(fooWithVersionTracer), same(provider.get('foo'))])); + + expect(provider.processors, isA>()); + }); + + test('tracerProvider custom span processors', () { + final mockProcessor1 = MockLogRecordProcessor(); + final mockProcessor2 = MockLogRecordProcessor(); + final provider = sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]); + + expect(provider.processors, [mockProcessor1, mockProcessor2]); + }); + + test('traceProvider custom timeProvider', () { + final mockTimeProvider = FakeTimeProvider(now: Int64(123)); + final mockProcessor1 = MockLogRecordProcessor(); + final provider = sdk.LoggerProvider(timeProvider: mockTimeProvider, processors: [mockProcessor1]); + provider.get('foo').emit(); + verify(() => mockProcessor1.onEmit(any( + that: predicate((a) { + if (a is! sdk.ReadWriteLogRecord) return false; + return a.timeStamp == 123 && a.observedTimestamp == 123; + }), + ))).called(1); + }); + + test('loggerProvider force flushes all processors', () async { + final mockProcessor1 = MockLogRecordProcessor(); + final mockProcessor2 = MockLogRecordProcessor(); + when(mockProcessor1.forceFlush).thenAnswer((_) async => Future.value()); + when(mockProcessor2.forceFlush).thenAnswer((_) async => Future.value()); + await sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]).forceFlush(); + + verify(mockProcessor1.forceFlush).called(1); + verify(mockProcessor2.forceFlush).called(1); + }); + + test('loggerProvider shuts down all processors', () async { + final mockProcessor1 = MockLogRecordProcessor(); + final mockProcessor2 = MockLogRecordProcessor(); + when(mockProcessor1.shutdown).thenAnswer((_) async => Future.value()); + when(mockProcessor2.shutdown).thenAnswer((_) async => Future.value()); + await sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]).shutdown(); + + verify(mockProcessor1.shutdown).called(1); + verify(mockProcessor2.shutdown).called(1); + }); + + test('logger provider test add processor', () { + final provider = sdk.LoggerProvider() + ..addLogRecordProcessor(const sdk.NoopLogRecordProcessor()) + ..addLogRecordProcessor(const sdk.NoopLogRecordProcessor()); + + expect(provider.processors.length, 2); + }); +} From ff5d1e3cfa3bc21b767a2de2c23bfaa62a6c01a3 Mon Sep 17 00:00:00 2001 From: yusuf Date: Thu, 16 Jan 2025 18:25:55 +0800 Subject: [PATCH 10/61] feat: exports --- lib/src/experimental_sdk.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/src/experimental_sdk.dart b/lib/src/experimental_sdk.dart index bdb060aa..076ae31d 100644 --- a/lib/src/experimental_sdk.dart +++ b/lib/src/experimental_sdk.dart @@ -7,6 +7,10 @@ library experimental_sdk; import 'package:meta/meta.dart'; export 'sdk/logs/log_record.dart' show ReadableLogRecord, ReadWriteLogRecord, LogRecord; +export 'sdk/logs/logger.dart' show Logger; +export 'sdk/logs/logger_provider.dart' show LoggerProvider; +export 'sdk/logs/processors/log_record_processor.dart' show LogRecordProcessor; +export 'sdk/logs/processors/noop_log_processor.dart' show NoopLogRecordProcessor; export 'sdk/logs/log_record_limit.dart' show LogRecordLimits; export 'sdk/metrics/counter.dart' show Counter; export 'sdk/metrics/meter.dart' show Meter; From 1e050b4bb571fd8f7112cec6ff7b15c10ddaa1af Mon Sep 17 00:00:00 2001 From: yusuf Date: Thu, 16 Jan 2025 18:41:36 +0800 Subject: [PATCH 11/61] wip: change processors initialization from `const` --- lib/src/sdk/logs/logger_provider.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/sdk/logs/logger_provider.dart b/lib/src/sdk/logs/logger_provider.dart index 44b607fe..c4c537d8 100644 --- a/lib/src/sdk/logs/logger_provider.dart +++ b/lib/src/sdk/logs/logger_provider.dart @@ -30,9 +30,10 @@ class LoggerProvider implements api.LoggerProvider { this.resource, this.config = const LoggerConfig(), this.logRecordLimits = const LogRecordLimitsImpl(), - this.processors = const [], + List? processors, sdk.TimeProvider? timeProvider, - }) : _timeProvider = timeProvider ?? sdk.DateTimeTimeProvider(); + }) : processors = processors ?? [], + _timeProvider = timeProvider ?? sdk.DateTimeTimeProvider(); @override api.Logger get( From 5eed62e85ef17de305dd02533e9e7288e52e0097 Mon Sep 17 00:00:00 2001 From: yusuf Date: Thu, 16 Jan 2025 18:42:44 +0800 Subject: [PATCH 12/61] wip: fix test --- test/unit/sdk/logs/logger_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/sdk/logs/logger_test.dart b/test/unit/sdk/logs/logger_test.dart index 25875f5b..12f9a5e4 100644 --- a/test/unit/sdk/logs/logger_test.dart +++ b/test/unit/sdk/logs/logger_test.dart @@ -18,7 +18,7 @@ class CallbackMock extends Mock implements Callback {} void main() { setUpAll(() { - registerFallbackValue(sdk.Logger( + registerFallbackValue(sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), logRecordLimits: LogRecordLimitsImpl(), )); From 99e374fa65fae9d23d6621bcc4d5f84b37fa2377 Mon Sep 17 00:00:00 2001 From: yusuf Date: Tue, 21 Jan 2025 00:51:36 +0800 Subject: [PATCH 13/61] wip: fix import arrange --- lib/src/experimental_api.dart | 12 ++++++------ lib/src/experimental_sdk.dart | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/src/experimental_api.dart b/lib/src/experimental_api.dart index f2933e07..1f24c4c5 100644 --- a/lib/src/experimental_api.dart +++ b/lib/src/experimental_api.dart @@ -10,13 +10,13 @@ export 'api/context/context_manager.dart' show ContextManager; export 'api/context/noop_context_manager.dart' show NoopContextManager; export 'api/context/zone_context.dart' show ZoneContext; export 'api/context/zone_context_manager.dart' show ZoneContextManager; -export 'api/metrics/counter.dart' show Counter; -export 'api/metrics/meter_provider.dart' show MeterProvider; -export 'api/metrics/meter.dart' show Meter; -export 'api/metrics/noop/noop_meter.dart' show NoopMeter; -export 'api/trace/nonrecording_span.dart' show NonRecordingSpan; -export 'api/logs/logger.dart' show Logger; export 'api/logs/log_record.dart' show Severity; +export 'api/logs/logger.dart' show Logger; export 'api/logs/logger_provider.dart' show LoggerProvider; export 'api/logs/noop/noop_logger.dart' show NoopLogger; export 'api/logs/noop/noop_logger_provider.dart' show NoopLoggerProvider; +export 'api/metrics/counter.dart' show Counter; +export 'api/metrics/meter.dart' show Meter; +export 'api/metrics/meter_provider.dart' show MeterProvider; +export 'api/metrics/noop/noop_meter.dart' show NoopMeter; +export 'api/trace/nonrecording_span.dart' show NonRecordingSpan; diff --git a/lib/src/experimental_sdk.dart b/lib/src/experimental_sdk.dart index 076ae31d..1dc5c1b1 100644 --- a/lib/src/experimental_sdk.dart +++ b/lib/src/experimental_sdk.dart @@ -7,11 +7,11 @@ library experimental_sdk; import 'package:meta/meta.dart'; export 'sdk/logs/log_record.dart' show ReadableLogRecord, ReadWriteLogRecord, LogRecord; +export 'sdk/logs/log_record_limit.dart' show LogRecordLimits; export 'sdk/logs/logger.dart' show Logger; export 'sdk/logs/logger_provider.dart' show LoggerProvider; export 'sdk/logs/processors/log_record_processor.dart' show LogRecordProcessor; export 'sdk/logs/processors/noop_log_processor.dart' show NoopLogRecordProcessor; -export 'sdk/logs/log_record_limit.dart' show LogRecordLimits; export 'sdk/metrics/counter.dart' show Counter; export 'sdk/metrics/meter.dart' show Meter; export 'sdk/metrics/meter_provider.dart' show MeterProvider; From 38b213706a4d0fbd2156f56356213db838d1273f Mon Sep 17 00:00:00 2001 From: yusuf Date: Tue, 21 Jan 2025 01:47:10 +0800 Subject: [PATCH 14/61] wip: attach copyright --- lib/src/sdk/logs/log_record_limit.dart | 3 +++ lib/src/sdk/logs/logger_config.dart | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lib/src/sdk/logs/log_record_limit.dart b/lib/src/sdk/logs/log_record_limit.dart index fccd26c8..7e3516a1 100644 --- a/lib/src/sdk/logs/log_record_limit.dart +++ b/lib/src/sdk/logs/log_record_limit.dart @@ -1,3 +1,6 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + // https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecord-limits abstract class LogRecordLimits { // https://opentelemetry.io/docs/specs/otel/common/#configurable-parameters diff --git a/lib/src/sdk/logs/logger_config.dart b/lib/src/sdk/logs/logger_config.dart index 597fbbf1..e54d4f47 100644 --- a/lib/src/sdk/logs/logger_config.dart +++ b/lib/src/sdk/logs/logger_config.dart @@ -1,3 +1,6 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + // https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerconfig class LoggerConfig { /// If not explicitly set, From 88eb916323d037309c227b9f7f2ebb9236cad494 Mon Sep 17 00:00:00 2001 From: yusuf Date: Fri, 31 Jan 2025 11:30:08 +0800 Subject: [PATCH 15/61] wip: change from `api.SpanContext?` and `api.Context?` to non-null private `api.Context` with `api.Context?` as constructor argument. If no `api.Context` is passed into constructor, `api.Context.current` is used instead. --- lib/src/sdk/logs/log_record.dart | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/src/sdk/logs/log_record.dart b/lib/src/sdk/logs/log_record.dart index 5a02577c..9e90b0a3 100644 --- a/lib/src/sdk/logs/log_record.dart +++ b/lib/src/sdk/logs/log_record.dart @@ -41,7 +41,6 @@ abstract class ReadWriteLogRecord extends ReadableLogRecord { set severityNumber(api.Severity? severity); } - class LogRecord implements ReadWriteLogRecord { @override final sdk.InstrumentationScope instrumentationScope; @@ -50,8 +49,7 @@ class LogRecord implements ReadWriteLogRecord { final sdk.Resource? resource; final sdk.TimeProvider _timeProvider; - final api.Context? context; - final api.SpanContext? _spanContext; + final api.Context _context; final sdk.LogRecordLimits logRecordLimits; final DateTime? _timeStamp; final DateTime? _observedTimestamp; @@ -72,15 +70,15 @@ class LogRecord implements ReadWriteLogRecord { sdk.Attributes? attributes, DateTime? timeStamp, DateTime? observedTimestamp, + api.Context? context, dynamic body, this.resource, - this.context, sdk.TimeProvider? timeProvider, }) : _severityText = severityText, + _context = context ?? api.Context.current, _body = body, _attributes = sdk.Attributes.empty(), _severityNumber = severityNumber, - _spanContext = api.spanContextFromContext(context ?? api.Context.current), _timeStamp = timeStamp, _observedTimestamp = observedTimestamp, _timeProvider = timeProvider ?? sdk.DateTimeTimeProvider() { @@ -100,17 +98,21 @@ class LogRecord implements ReadWriteLogRecord { } @override - api.SpanContext? get spanContext => _spanContext; + api.SpanContext? get spanContext => api.spanContextFromContext(_context); @override - int get droppedAttributesCount => _totalAttributesCount - (attributes?.length ?? 0); + int get droppedAttributesCount => + _totalAttributesCount - (attributes?.length ?? 0); @override - Int64? get timeStamp => _timeStamp != null ? Int64(_timeStamp!.microsecondsSinceEpoch) * 1000 : _timeProvider.now; + Int64? get timeStamp => _timeStamp != null + ? Int64(_timeStamp!.microsecondsSinceEpoch) * 1000 + : _timeProvider.now; @override - Int64? get observedTimestamp => - _observedTimestamp != null ? Int64(_observedTimestamp!.microsecondsSinceEpoch) * 1000 : _timeProvider.now; + Int64? get observedTimestamp => _observedTimestamp != null + ? Int64(_observedTimestamp!.microsecondsSinceEpoch) * 1000 + : _timeProvider.now; @override api.Severity? get severityNumber => _severityNumber; @@ -144,7 +146,8 @@ class LogRecord implements ReadWriteLogRecord { _totalAttributesCount += 1; if (value is String) { _attributes.add( - applyAttributeLimitsForLog(api.Attribute.fromString(key, value), logRecordLimits), + applyAttributeLimitsForLog( + api.Attribute.fromString(key, value), logRecordLimits), ); } @@ -162,7 +165,8 @@ class LogRecord implements ReadWriteLogRecord { if (value is List) { _attributes.add( - applyAttributeLimitsForLog(api.Attribute.fromStringList(key, value), logRecordLimits), + applyAttributeLimitsForLog( + api.Attribute.fromStringList(key, value), logRecordLimits), ); } @@ -186,4 +190,3 @@ class LogRecord implements ReadWriteLogRecord { _isReadonly = true; } } - From 014f087a9a2685cbaafb6cc1afde9117165acc98 Mon Sep 17 00:00:00 2001 From: yusuf Date: Fri, 31 Jan 2025 12:28:06 +0800 Subject: [PATCH 16/61] wip: remove unused `includeTraceContext` --- lib/src/sdk/logs/logger_provider.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/sdk/logs/logger_provider.dart b/lib/src/sdk/logs/logger_provider.dart index c4c537d8..e3bc3c9f 100644 --- a/lib/src/sdk/logs/logger_provider.dart +++ b/lib/src/sdk/logs/logger_provider.dart @@ -41,7 +41,6 @@ class LoggerProvider implements api.LoggerProvider { String version = '', String schemaUrl = '', List attributes = const [], - bool? includeTraceContext, }) { final loggerName = name.isNotEmpty ? name : defaultLoggerName; final key = hash3(loggerName, version, schemaUrl); From 96938b85e31f7da233aa39e473e026b210353753 Mon Sep 17 00:00:00 2001 From: yusuf Date: Fri, 31 Jan 2025 12:41:47 +0800 Subject: [PATCH 17/61] wip: opentelemetry as defaultLoggerName --- lib/src/sdk/logs/logger_provider.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/sdk/logs/logger_provider.dart b/lib/src/sdk/logs/logger_provider.dart index e3bc3c9f..7a29a872 100644 --- a/lib/src/sdk/logs/logger_provider.dart +++ b/lib/src/sdk/logs/logger_provider.dart @@ -10,7 +10,7 @@ import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart'; import 'package:opentelemetry/src/sdk/logs/logger_config.dart'; import 'package:quiver/core.dart'; -const defaultLoggerName = 'unknown'; +const defaultLoggerName = 'opentelemetry'; // https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerprovider class LoggerProvider implements api.LoggerProvider { From 2a9a80a49dc8ac122cbf11d337f446f5389cc001 Mon Sep 17 00:00:00 2001 From: yusuf Date: Fri, 31 Jan 2025 15:32:35 +0800 Subject: [PATCH 18/61] wip: make log processors immutable --- lib/src/sdk/logs/logger_provider.dart | 15 ++++--- test/unit/sdk/logs/logger_provider_test.dart | 45 +++++++++++++------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/lib/src/sdk/logs/logger_provider.dart b/lib/src/sdk/logs/logger_provider.dart index 7a29a872..b115a6aa 100644 --- a/lib/src/sdk/logs/logger_provider.dart +++ b/lib/src/sdk/logs/logger_provider.dart @@ -1,6 +1,7 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information +import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/sdk.dart' as sdk; @@ -19,7 +20,7 @@ class LoggerProvider implements api.LoggerProvider { final LoggerConfig config; - final List processors; + final List _processors; final sdk.Resource? resource; final sdk.LogRecordLimits logRecordLimits; @@ -32,9 +33,12 @@ class LoggerProvider implements api.LoggerProvider { this.logRecordLimits = const LogRecordLimitsImpl(), List? processors, sdk.TimeProvider? timeProvider, - }) : processors = processors ?? [], + }) : _processors = processors ?? [], _timeProvider = timeProvider ?? sdk.DateTimeTimeProvider(); + UnmodifiableListView get processors => + UnmodifiableListView(_processors); + @override api.Logger get( String name, { @@ -52,7 +56,8 @@ class LoggerProvider implements api.LoggerProvider { () => sdk.Logger( logRecordLimits: logRecordLimits, resource: resource, - instrumentationScope: sdk.InstrumentationScope(loggerName, version, schemaUrl, attributes), + instrumentationScope: sdk.InstrumentationScope( + loggerName, version, schemaUrl, attributes), timeProvider: _timeProvider, onLogEmit: (log) { for (final processor in processors) { @@ -63,10 +68,6 @@ class LoggerProvider implements api.LoggerProvider { ); } - void addLogRecordProcessor(sdk.LogRecordProcessor processor) { - processors.add(processor); - } - Future forceFlush() async { await Future.forEach(processors, (e) => e.forceFlush()); } diff --git a/test/unit/sdk/logs/logger_provider_test.dart b/test/unit/sdk/logs/logger_provider_test.dart index 2ababd5a..1d79a99b 100644 --- a/test/unit/sdk/logs/logger_provider_test.dart +++ b/test/unit/sdk/logs/logger_provider_test.dart @@ -14,7 +14,8 @@ import '../../mocks.dart'; void main() { setUpAll(() { registerFallbackValue(sdk.LogRecord( - instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + instrumentationScope: sdk.InstrumentationScope( + 'library_name', 'library_version', 'url://schema', []), logRecordLimits: LogRecordLimitsImpl(), )); }); @@ -25,7 +26,13 @@ void main() { final barTracer = provider.get('bar'); final fooWithVersionTracer = provider.get('foo', version: '1.0'); - expect(fooTracer, allOf([isNot(barTracer), isNot(fooWithVersionTracer), same(provider.get('foo'))])); + expect( + fooTracer, + allOf([ + isNot(barTracer), + isNot(fooWithVersionTracer), + same(provider.get('foo')) + ])); expect(provider.processors, isA>()); }); @@ -33,7 +40,8 @@ void main() { test('tracerProvider custom span processors', () { final mockProcessor1 = MockLogRecordProcessor(); final mockProcessor2 = MockLogRecordProcessor(); - final provider = sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]); + final provider = + sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]); expect(provider.processors, [mockProcessor1, mockProcessor2]); }); @@ -41,14 +49,15 @@ void main() { test('traceProvider custom timeProvider', () { final mockTimeProvider = FakeTimeProvider(now: Int64(123)); final mockProcessor1 = MockLogRecordProcessor(); - final provider = sdk.LoggerProvider(timeProvider: mockTimeProvider, processors: [mockProcessor1]); + final provider = sdk.LoggerProvider( + timeProvider: mockTimeProvider, processors: [mockProcessor1]); provider.get('foo').emit(); verify(() => mockProcessor1.onEmit(any( - that: predicate((a) { - if (a is! sdk.ReadWriteLogRecord) return false; - return a.timeStamp == 123 && a.observedTimestamp == 123; - }), - ))).called(1); + that: predicate((a) { + if (a is! sdk.ReadWriteLogRecord) return false; + return a.timeStamp == 123 && a.observedTimestamp == 123; + }), + ))).called(1); }); test('loggerProvider force flushes all processors', () async { @@ -56,7 +65,8 @@ void main() { final mockProcessor2 = MockLogRecordProcessor(); when(mockProcessor1.forceFlush).thenAnswer((_) async => Future.value()); when(mockProcessor2.forceFlush).thenAnswer((_) async => Future.value()); - await sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]).forceFlush(); + await sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]) + .forceFlush(); verify(mockProcessor1.forceFlush).called(1); verify(mockProcessor2.forceFlush).called(1); @@ -67,17 +77,20 @@ void main() { final mockProcessor2 = MockLogRecordProcessor(); when(mockProcessor1.shutdown).thenAnswer((_) async => Future.value()); when(mockProcessor2.shutdown).thenAnswer((_) async => Future.value()); - await sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]).shutdown(); + await sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]) + .shutdown(); verify(mockProcessor1.shutdown).called(1); verify(mockProcessor2.shutdown).called(1); }); - test('logger provider test add processor', () { - final provider = sdk.LoggerProvider() - ..addLogRecordProcessor(const sdk.NoopLogRecordProcessor()) - ..addLogRecordProcessor(const sdk.NoopLogRecordProcessor()); + test('loggerProvider processors is immutable', () async { + final mockProcessor1 = MockLogRecordProcessor(); + final mockProcessor2 = MockLogRecordProcessor(); + when(mockProcessor1.shutdown).thenAnswer((_) async => Future.value()); + when(mockProcessor2.shutdown).thenAnswer((_) async => Future.value()); + final provider = sdk.LoggerProvider(processors: [mockProcessor1]); - expect(provider.processors.length, 2); + expect(() => provider.processors.add(mockProcessor2), throwsUnsupportedError); }); } From f0206c6c4426210bf8bfc60bfe7d8c3c75d74fda Mon Sep 17 00:00:00 2001 From: yusuf Date: Fri, 31 Jan 2025 17:29:48 +0800 Subject: [PATCH 19/61] wip: make all variables private --- lib/src/sdk/logs/logger_provider.dart | 30 ++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/src/sdk/logs/logger_provider.dart b/lib/src/sdk/logs/logger_provider.dart index b115a6aa..adac7dfb 100644 --- a/lib/src/sdk/logs/logger_provider.dart +++ b/lib/src/sdk/logs/logger_provider.dart @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information import 'package:collection/collection.dart'; -import 'package:meta/meta.dart'; import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/sdk.dart' as sdk; import 'package:opentelemetry/src/experimental_api.dart' as api; @@ -15,25 +14,28 @@ const defaultLoggerName = 'opentelemetry'; // https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerprovider class LoggerProvider implements api.LoggerProvider { - @protected - final Map loggers = {}; + final Map _loggers = {}; - final LoggerConfig config; + final LoggerConfig _config; final List _processors; - final sdk.Resource? resource; - final sdk.LogRecordLimits logRecordLimits; + final sdk.Resource _resource; + + final sdk.LogRecordLimits _logRecordLimits; final sdk.TimeProvider _timeProvider; LoggerProvider({ - this.resource, - this.config = const LoggerConfig(), - this.logRecordLimits = const LogRecordLimitsImpl(), + LoggerConfig config = const LoggerConfig(), + sdk.LogRecordLimits logRecordLimits = const LogRecordLimitsImpl(), + sdk.Resource? resource, List? processors, sdk.TimeProvider? timeProvider, - }) : _processors = processors ?? [], + }) : _processors = processors ?? const [], + _config = config, + _logRecordLimits = logRecordLimits, + _resource = resource ?? sdk.Resource([]), _timeProvider = timeProvider ?? sdk.DateTimeTimeProvider(); UnmodifiableListView get processors => @@ -48,14 +50,14 @@ class LoggerProvider implements api.LoggerProvider { }) { final loggerName = name.isNotEmpty ? name : defaultLoggerName; final key = hash3(loggerName, version, schemaUrl); - if (config.disabled) { + if (_config.disabled) { return api.NoopLogger(); } - return loggers.putIfAbsent( + return _loggers.putIfAbsent( key, () => sdk.Logger( - logRecordLimits: logRecordLimits, - resource: resource, + logRecordLimits: _logRecordLimits, + resource: _resource, instrumentationScope: sdk.InstrumentationScope( loggerName, version, schemaUrl, attributes), timeProvider: _timeProvider, From 01636f98533cd55d868299a2222376ec3f73f395 Mon Sep 17 00:00:00 2001 From: yusuf Date: Fri, 31 Jan 2025 18:10:17 +0800 Subject: [PATCH 20/61] wip: update limit to only create if value changes. --- lib/src/sdk/common/limits.dart | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/lib/src/sdk/common/limits.dart b/lib/src/sdk/common/limits.dart index d46c5ae7..52381ac4 100644 --- a/lib/src/sdk/common/limits.dart +++ b/lib/src/sdk/common/limits.dart @@ -1,10 +1,11 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information +import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import '../../../api.dart' as api; import '../../../sdk.dart' as sdk; -import '../logs/log_record_limit.dart'; +import '../../experimental_sdk.dart' as sdk; /// Applies given [sdk.SpanLimits] to a list of [api.SpanLink]s. @protected @@ -79,19 +80,31 @@ api.Attribute applyAttributeLimits(api.Attribute attr, sdk.SpanLimits limits) { } @protected -api.Attribute applyAttributeLimitsForLog(api.Attribute attr, LogRecordLimits limits) { +api.Attribute applyAttributeLimitsForLog( + api.Attribute attr, + sdk.LogRecordLimits limits, +) { // if maxNumAttributeLength is less than zero, then it has unlimited length. if (limits.attributeValueLengthLimit < 0) return attr; if (attr.value is String) { - attr = api.Attribute.fromString( - attr.key, applyAttributeLengthLimit(attr.value as String, limits.attributeValueLengthLimit)); + final truncatedValue = applyAttributeLengthLimit( + attr.value as String, limits.attributeValueLengthLimit); + + if (truncatedValue == attr.value) return attr; + + return api.Attribute.fromString(attr.key, truncatedValue); } else if (attr.value is List) { final listString = attr.value as List; - for (var j = 0; j < listString.length; j++) { - listString[j] = applyAttributeLengthLimit(listString[j], limits.attributeValueLengthLimit); - } - attr = api.Attribute.fromStringList(attr.key, listString); + final truncatedValues = listString + .map((e) => + applyAttributeLengthLimit(e, limits.attributeValueLengthLimit)) + .toList(); + + final equal = const ListEquality().equals(listString, truncatedValues); + if (equal) return attr; + + return api.Attribute.fromStringList(attr.key, truncatedValues); } return attr; } From aae1c3eb3418b7a1bdf73a5c35d5534d1a3ab291 Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 1 Feb 2025 01:31:19 +0800 Subject: [PATCH 21/61] wip: change from sdk.Attributes to List --- lib/src/api/logs/logger.dart | 3 +- lib/src/api/logs/noop/noop_logger.dart | 4 +- lib/src/sdk/logs/log_record.dart | 56 ++++------------------ lib/src/sdk/logs/logger.dart | 3 +- test/unit/sdk/logs/log_record_test.dart | 62 ++++++++++++++----------- 5 files changed, 49 insertions(+), 79 deletions(-) diff --git a/lib/src/api/logs/logger.dart b/lib/src/api/logs/logger.dart index 927338b8..df9e77e7 100644 --- a/lib/src/api/logs/logger.dart +++ b/lib/src/api/logs/logger.dart @@ -2,12 +2,11 @@ // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information import '../../../api.dart' as api; -import '../../../sdk.dart' as sdk; import 'log_record.dart'; abstract class Logger { void emit({ - sdk.Attributes? attributes, + List attributes = const [], api.Context? context, dynamic body, DateTime? observedTimestamp, diff --git a/lib/src/api/logs/noop/noop_logger.dart b/lib/src/api/logs/noop/noop_logger.dart index 4c057448..57c33469 100644 --- a/lib/src/api/logs/noop/noop_logger.dart +++ b/lib/src/api/logs/noop/noop_logger.dart @@ -1,17 +1,17 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information +import 'package:opentelemetry/src/api/common/attribute.dart'; import 'package:opentelemetry/src/api/context/context.dart'; import 'package:opentelemetry/src/api/logs/log_record.dart'; import 'package:opentelemetry/src/api/logs/logger.dart'; -import 'package:opentelemetry/src/sdk/common/attributes.dart'; class NoopLogger implements Logger { const NoopLogger(); @override void emit({ - Attributes? attributes, + List attributes = const [], Context? context, dynamic body, DateTime? observedTimestamp, diff --git a/lib/src/sdk/logs/log_record.dart b/lib/src/sdk/logs/log_record.dart index 9e90b0a3..5bd32de1 100644 --- a/lib/src/sdk/logs/log_record.dart +++ b/lib/src/sdk/logs/log_record.dart @@ -7,8 +7,7 @@ import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/sdk.dart' as sdk; import 'package:opentelemetry/src/experimental_api.dart' as api; import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; - -import '../common/limits.dart'; +import 'package:opentelemetry/src/sdk/common/limits.dart'; /// https://opentelemetry.io/docs/specs/otel/logs/sdk/#readwritelogrecord abstract class ReadableLogRecord { @@ -67,7 +66,7 @@ class LogRecord implements ReadWriteLogRecord { required this.logRecordLimits, api.Severity? severityNumber, String? severityText, - sdk.Attributes? attributes, + List attributes = const [], DateTime? timeStamp, DateTime? observedTimestamp, api.Context? context, @@ -82,7 +81,7 @@ class LogRecord implements ReadWriteLogRecord { _timeStamp = timeStamp, _observedTimestamp = observedTimestamp, _timeProvider = timeProvider ?? sdk.DateTimeTimeProvider() { - if (attributes != null) setAttributes(attributes); + if (attributes.isNotEmpty) setAttributes(attributes); } @override @@ -132,55 +131,16 @@ class LogRecord implements ReadWriteLogRecord { _severityText = severity; } - void setAttributes(sdk.Attributes attributes) { - for (final key in attributes.keys) { - setAttribute(key, attributes.get(key)); - } + void setAttributes(List attributes) { + attributes.forEach(setAttribute); } - void setAttribute(String key, Object? value) { - if (value == null) return; + void setAttribute(api.Attribute attribute) { if (_isReadonly) return; - if (key.isEmpty) return; + if (attribute.key.isEmpty) return; if (logRecordLimits.attributeCountLimit == 0) return; _totalAttributesCount += 1; - if (value is String) { - _attributes.add( - applyAttributeLimitsForLog( - api.Attribute.fromString(key, value), logRecordLimits), - ); - } - - if (value is bool) { - _attributes.add(api.Attribute.fromBoolean(key, value)); - } - - if (value is double) { - _attributes.add(api.Attribute.fromDouble(key, value)); - } - - if (value is int) { - _attributes.add(api.Attribute.fromInt(key, value)); - } - - if (value is List) { - _attributes.add( - applyAttributeLimitsForLog( - api.Attribute.fromStringList(key, value), logRecordLimits), - ); - } - - if (value is List) { - _attributes.add(api.Attribute.fromBooleanList(key, value)); - } - - if (value is List) { - _attributes.add(api.Attribute.fromDoubleList(key, value)); - } - - if (value is List) { - _attributes.add(api.Attribute.fromIntList(key, value)); - } + _attributes.add(applyAttributeLimitsForLog(attribute, logRecordLimits)); } /// A LogRecordProcessor may freely modify logRecord for the duration of the OnEmit call. diff --git a/lib/src/sdk/logs/logger.dart b/lib/src/sdk/logs/logger.dart index 957cfceb..4efe6b5b 100644 --- a/lib/src/sdk/logs/logger.dart +++ b/lib/src/sdk/logs/logger.dart @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/src/api/context/context.dart'; import 'package:opentelemetry/src/experimental_api.dart' as api; import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; @@ -23,7 +24,7 @@ class Logger extends api.Logger { @override void emit({ - sdk.Attributes? attributes, + List attributes = const [], Context? context, dynamic body, DateTime? observedTimestamp, diff --git a/test/unit/sdk/logs/log_record_test.dart b/test/unit/sdk/logs/log_record_test.dart index 64bcc441..38c18705 100644 --- a/test/unit/sdk/logs/log_record_test.dart +++ b/test/unit/sdk/logs/log_record_test.dart @@ -15,15 +15,16 @@ import '../trace_provider_test.dart'; void main() { test('set readonly will block values from being set', () { final logRecord = sdk.LogRecord( - instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + instrumentationScope: sdk.InstrumentationScope( + 'library_name', 'library_version', 'url://schema', []), logRecordLimits: LogRecordLimitsImpl(), timeProvider: FakeTimeProvider(now: Int64(123))) ..makeReadonly() ..body = 'Log Message' ..severityNumber = api.Severity.debug ..severityText = 'DEBUG' - ..setAttributes(sdk.Attributes.empty()..add(api.Attribute.fromString('key', 'value'))) - ..setAttribute('key2', 'value2'); + ..setAttributes([api.Attribute.fromString('key', 'value')]) + ..setAttribute(api.Attribute.fromString('key2', 'value2')); expect(logRecord.body, null); expect(logRecord.severityNumber, null); @@ -36,14 +37,15 @@ void main() { test('logRecord call setter', () { final logRecord = sdk.LogRecord( - instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + instrumentationScope: sdk.InstrumentationScope( + 'library_name', 'library_version', 'url://schema', []), logRecordLimits: LogRecordLimitsImpl(), timeProvider: FakeTimeProvider(now: Int64(123))) ..body = 'Log Message' ..severityNumber = api.Severity.debug ..severityText = 'DEBUG' - ..setAttributes(sdk.Attributes.empty()..add(api.Attribute.fromString('key', 'value'))) - ..setAttribute('key2', 'value2'); + ..setAttributes([api.Attribute.fromString('key', 'value')]) + ..setAttribute(api.Attribute.fromString('key2', 'value2')); expect(logRecord.body, 'Log Message'); expect(logRecord.severityNumber, api.Severity.debug); @@ -56,11 +58,12 @@ void main() { test('logRecord update same attribute will create attributesCount diff', () { final logRecord = sdk.LogRecord( - instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + instrumentationScope: sdk.InstrumentationScope( + 'library_name', 'library_version', 'url://schema', []), logRecordLimits: LogRecordLimitsImpl(), ) - ..setAttributes(sdk.Attributes.empty()..add(api.Attribute.fromString('key2', 'value'))) - ..setAttribute('key2', 'value2'); + ..setAttributes([api.Attribute.fromString('key2', 'value')]) + ..setAttribute(api.Attribute.fromString('key2', 'value2')); expect(logRecord.droppedAttributesCount, 1); }); @@ -68,50 +71,57 @@ void main() { test('logRecord time stamp will be converted to Int64', () { final now = DateTime.now(); final logRecord = sdk.LogRecord( - instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + instrumentationScope: sdk.InstrumentationScope( + 'library_name', 'library_version', 'url://schema', []), timeStamp: now, observedTimestamp: now, logRecordLimits: LogRecordLimitsImpl(), ) - ..setAttributes(sdk.Attributes.empty()..add(api.Attribute.fromString('key2', 'value'))) - ..setAttribute('key2', 'value2'); + ..setAttributes([api.Attribute.fromString('key2', 'value')]) + ..setAttribute(api.Attribute.fromString('key2', 'value2')); expect(logRecord.timeStamp, Int64(now.microsecondsSinceEpoch) * 1000); - expect(logRecord.observedTimestamp, Int64(now.microsecondsSinceEpoch) * 1000); + expect( + logRecord.observedTimestamp, Int64(now.microsecondsSinceEpoch) * 1000); }); test('logRecord set attribute', () { final now = DateTime.now(); final logRecord = sdk.LogRecord( - instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + instrumentationScope: sdk.InstrumentationScope( + 'library_name', 'library_version', 'url://schema', []), timeStamp: now, observedTimestamp: now, logRecordLimits: LogRecordLimitsImpl(attributeValueLengthLimit: 2), ) - ..setAttribute('key', 'value') - ..setAttribute('key2', true) - ..setAttribute('key3', 1) - ..setAttribute('key4', 1.1) - ..setAttribute('key5', ['value2']) - ..setAttribute('key6', [true]) - ..setAttribute('key7', [1]) - ..setAttribute('key8', [1.1]); + ..setAttribute(api.Attribute.fromString('key', 'value')) + ..setAttribute(api.Attribute.fromBoolean('key2', true)) + ..setAttribute(api.Attribute.fromInt('key3', 1)) + ..setAttribute(api.Attribute.fromDouble('key4', 1.1)) + ..setAttribute(api.Attribute.fromStringList('key5', ['value2'])) + ..setAttribute(api.Attribute.fromBooleanList('key6', [true])) + ..setAttribute(api.Attribute.fromIntList('key7', [1])) + ..setAttribute(api.Attribute.fromDoubleList('key8', [1.1])); expect(logRecord.droppedAttributesCount, 0); - expect(logRecord.attributes?.keys, const ['key', 'key2', 'key3', 'key4', 'key5', 'key6', 'key7', 'key8']); + expect( + logRecord.attributes?.keys, + const ['key', 'key2', 'key3', 'key4', 'key5', 'key6', 'key7', 'key8'], + ); expect(logRecord.attributes?.get('key'), 'va'); }); test('logRecord set attribute with limit', () { final now = DateTime.now(); final logRecord = sdk.LogRecord( - instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + instrumentationScope: sdk.InstrumentationScope( + 'library_name', 'library_version', 'url://schema', []), timeStamp: now, observedTimestamp: now, logRecordLimits: LogRecordLimitsImpl(attributeValueLengthLimit: 2), ) - ..setAttribute('key', 'value') - ..setAttribute('key2', ['value2']); + ..setAttribute(api.Attribute.fromString('key', 'value')) + ..setAttribute(api.Attribute.fromStringList('key2', ['value2'])); expect(logRecord.attributes?.get('key'), 'va'); expect(logRecord.attributes?.get('key2'), const ['va']); From 464a636b3cdfc2d98cbae14bbc0f2d14d8cf6084 Mon Sep 17 00:00:00 2001 From: yusuf Date: Mon, 10 Feb 2025 13:28:19 +0800 Subject: [PATCH 22/61] wip: change to `DateTime` --- lib/src/sdk/logs/log_record.dart | 15 +++++---------- test/unit/sdk/logs/log_record_test.dart | 17 ++++++++++------- test/unit/sdk/logs/logger_provider_test.dart | 2 +- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/src/sdk/logs/log_record.dart b/lib/src/sdk/logs/log_record.dart index 5bd32de1..d635f04a 100644 --- a/lib/src/sdk/logs/log_record.dart +++ b/lib/src/sdk/logs/log_record.dart @@ -1,7 +1,6 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information -import 'package:fixnum/fixnum.dart'; import 'package:meta/meta.dart'; import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/sdk.dart' as sdk; @@ -11,9 +10,9 @@ import 'package:opentelemetry/src/sdk/common/limits.dart'; /// https://opentelemetry.io/docs/specs/otel/logs/sdk/#readwritelogrecord abstract class ReadableLogRecord { - Int64? get timeStamp; + DateTime? get timeStamp; - Int64? get observedTimestamp; + DateTime? get observedTimestamp; String? get severityText; @@ -78,7 +77,7 @@ class LogRecord implements ReadWriteLogRecord { _body = body, _attributes = sdk.Attributes.empty(), _severityNumber = severityNumber, - _timeStamp = timeStamp, + _timeStamp = timeStamp ?? DateTime.now(), _observedTimestamp = observedTimestamp, _timeProvider = timeProvider ?? sdk.DateTimeTimeProvider() { if (attributes.isNotEmpty) setAttributes(attributes); @@ -104,14 +103,10 @@ class LogRecord implements ReadWriteLogRecord { _totalAttributesCount - (attributes?.length ?? 0); @override - Int64? get timeStamp => _timeStamp != null - ? Int64(_timeStamp!.microsecondsSinceEpoch) * 1000 - : _timeProvider.now; + DateTime? get timeStamp => _timeStamp ?? DateTime.fromMicrosecondsSinceEpoch((_timeProvider.now ~/ 1000).toInt()); @override - Int64? get observedTimestamp => _observedTimestamp != null - ? Int64(_observedTimestamp!.microsecondsSinceEpoch) * 1000 - : _timeProvider.now; + DateTime? get observedTimestamp => _observedTimestamp ?? DateTime.fromMicrosecondsSinceEpoch((_timeProvider.now ~/ 1000).toInt()); @override api.Severity? get severityNumber => _severityNumber; diff --git a/test/unit/sdk/logs/log_record_test.dart b/test/unit/sdk/logs/log_record_test.dart index 38c18705..c8005640 100644 --- a/test/unit/sdk/logs/log_record_test.dart +++ b/test/unit/sdk/logs/log_record_test.dart @@ -31,8 +31,10 @@ void main() { expect(logRecord.severityText, null); expect(logRecord.attributes?.keys, const []); expect(logRecord.droppedAttributesCount, 0); - expect(logRecord.timeStamp, Int64(123)); - expect(logRecord.observedTimestamp, Int64(123)); + expect(logRecord.timeStamp, + DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt())); + expect(logRecord.observedTimestamp, + DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt())); }); test('logRecord call setter', () { @@ -52,8 +54,10 @@ void main() { expect(logRecord.severityText, 'DEBUG'); expect(logRecord.attributes?.keys, const ['key', 'key2']); expect(logRecord.droppedAttributesCount, 0); - expect(logRecord.timeStamp, Int64(123)); - expect(logRecord.observedTimestamp, Int64(123)); + expect(logRecord.timeStamp, + DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt())); + expect(logRecord.observedTimestamp, + DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt())); }); test('logRecord update same attribute will create attributesCount diff', () { @@ -80,9 +84,8 @@ void main() { ..setAttributes([api.Attribute.fromString('key2', 'value')]) ..setAttribute(api.Attribute.fromString('key2', 'value2')); - expect(logRecord.timeStamp, Int64(now.microsecondsSinceEpoch) * 1000); - expect( - logRecord.observedTimestamp, Int64(now.microsecondsSinceEpoch) * 1000); + expect(logRecord.timeStamp, now); + expect(logRecord.observedTimestamp, now); }); test('logRecord set attribute', () { diff --git a/test/unit/sdk/logs/logger_provider_test.dart b/test/unit/sdk/logs/logger_provider_test.dart index 1d79a99b..cdd20b07 100644 --- a/test/unit/sdk/logs/logger_provider_test.dart +++ b/test/unit/sdk/logs/logger_provider_test.dart @@ -55,7 +55,7 @@ void main() { verify(() => mockProcessor1.onEmit(any( that: predicate((a) { if (a is! sdk.ReadWriteLogRecord) return false; - return a.timeStamp == 123 && a.observedTimestamp == 123; + return a.timeStamp == DateTime.fromMicrosecondsSinceEpoch(123 ~/ 1000) && a.observedTimestamp == DateTime.fromMicrosecondsSinceEpoch(123 ~/ 1000); }), ))).called(1); }); From 2ea0fcf20147d243dba5ef3049d79999ed04426d Mon Sep 17 00:00:00 2001 From: yusuf Date: Mon, 10 Feb 2025 13:34:00 +0800 Subject: [PATCH 23/61] wip: remove DateTime initialization --- lib/src/sdk/logs/log_record.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/sdk/logs/log_record.dart b/lib/src/sdk/logs/log_record.dart index d635f04a..1b6594fb 100644 --- a/lib/src/sdk/logs/log_record.dart +++ b/lib/src/sdk/logs/log_record.dart @@ -77,7 +77,7 @@ class LogRecord implements ReadWriteLogRecord { _body = body, _attributes = sdk.Attributes.empty(), _severityNumber = severityNumber, - _timeStamp = timeStamp ?? DateTime.now(), + _timeStamp = timeStamp, _observedTimestamp = observedTimestamp, _timeProvider = timeProvider ?? sdk.DateTimeTimeProvider() { if (attributes.isNotEmpty) setAttributes(attributes); From 078bd4adb066eac3d8227dfe9ba3e641e344841b Mon Sep 17 00:00:00 2001 From: yusuf Date: Mon, 10 Feb 2025 13:34:05 +0800 Subject: [PATCH 24/61] wip: fix test --- test/unit/sdk/logs/log_record_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/sdk/logs/log_record_test.dart b/test/unit/sdk/logs/log_record_test.dart index c8005640..7889da88 100644 --- a/test/unit/sdk/logs/log_record_test.dart +++ b/test/unit/sdk/logs/log_record_test.dart @@ -32,9 +32,9 @@ void main() { expect(logRecord.attributes?.keys, const []); expect(logRecord.droppedAttributesCount, 0); expect(logRecord.timeStamp, - DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt())); + DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt() ~/ 1000)); expect(logRecord.observedTimestamp, - DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt())); + DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt() ~/ 1000)); }); test('logRecord call setter', () { @@ -55,9 +55,9 @@ void main() { expect(logRecord.attributes?.keys, const ['key', 'key2']); expect(logRecord.droppedAttributesCount, 0); expect(logRecord.timeStamp, - DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt())); + DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt() ~/ 1000)); expect(logRecord.observedTimestamp, - DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt())); + DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt() ~/ 1000)); }); test('logRecord update same attribute will create attributesCount diff', () { From 90ecfdc6a1b3f431ea3ab53edff54ed41a09ca96 Mon Sep 17 00:00:00 2001 From: yusuf Date: Wed, 12 Feb 2025 17:21:42 +0800 Subject: [PATCH 25/61] wip: remove `processors` --- lib/src/sdk/logs/logger_provider.dart | 9 ++--- test/unit/sdk/logs/logger_provider_test.dart | 36 -------------------- 2 files changed, 3 insertions(+), 42 deletions(-) diff --git a/lib/src/sdk/logs/logger_provider.dart b/lib/src/sdk/logs/logger_provider.dart index adac7dfb..a363b8f7 100644 --- a/lib/src/sdk/logs/logger_provider.dart +++ b/lib/src/sdk/logs/logger_provider.dart @@ -38,9 +38,6 @@ class LoggerProvider implements api.LoggerProvider { _resource = resource ?? sdk.Resource([]), _timeProvider = timeProvider ?? sdk.DateTimeTimeProvider(); - UnmodifiableListView get processors => - UnmodifiableListView(_processors); - @override api.Logger get( String name, { @@ -62,7 +59,7 @@ class LoggerProvider implements api.LoggerProvider { loggerName, version, schemaUrl, attributes), timeProvider: _timeProvider, onLogEmit: (log) { - for (final processor in processors) { + for (final processor in _processors) { processor.onEmit(log); } }, @@ -71,10 +68,10 @@ class LoggerProvider implements api.LoggerProvider { } Future forceFlush() async { - await Future.forEach(processors, (e) => e.forceFlush()); + await Future.forEach(_processors, (e) => e.forceFlush()); } Future shutdown() async { - await Future.forEach(processors, (e) => e.shutdown()); + await Future.forEach(_processors, (e) => e.shutdown()); } } diff --git a/test/unit/sdk/logs/logger_provider_test.dart b/test/unit/sdk/logs/logger_provider_test.dart index cdd20b07..7b7819e6 100644 --- a/test/unit/sdk/logs/logger_provider_test.dart +++ b/test/unit/sdk/logs/logger_provider_test.dart @@ -20,32 +20,6 @@ void main() { )); }); - test('getLogger stores tracers by name', () { - final provider = sdk.LoggerProvider(); - final fooTracer = provider.get('foo'); - final barTracer = provider.get('bar'); - final fooWithVersionTracer = provider.get('foo', version: '1.0'); - - expect( - fooTracer, - allOf([ - isNot(barTracer), - isNot(fooWithVersionTracer), - same(provider.get('foo')) - ])); - - expect(provider.processors, isA>()); - }); - - test('tracerProvider custom span processors', () { - final mockProcessor1 = MockLogRecordProcessor(); - final mockProcessor2 = MockLogRecordProcessor(); - final provider = - sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]); - - expect(provider.processors, [mockProcessor1, mockProcessor2]); - }); - test('traceProvider custom timeProvider', () { final mockTimeProvider = FakeTimeProvider(now: Int64(123)); final mockProcessor1 = MockLogRecordProcessor(); @@ -83,14 +57,4 @@ void main() { verify(mockProcessor1.shutdown).called(1); verify(mockProcessor2.shutdown).called(1); }); - - test('loggerProvider processors is immutable', () async { - final mockProcessor1 = MockLogRecordProcessor(); - final mockProcessor2 = MockLogRecordProcessor(); - when(mockProcessor1.shutdown).thenAnswer((_) async => Future.value()); - when(mockProcessor2.shutdown).thenAnswer((_) async => Future.value()); - final provider = sdk.LoggerProvider(processors: [mockProcessor1]); - - expect(() => provider.processors.add(mockProcessor2), throwsUnsupportedError); - }); } From 4ab30a2752c81cf3571645c5865f1bd5cde5c477 Mon Sep 17 00:00:00 2001 From: yusuf Date: Wed, 12 Feb 2025 17:22:53 +0800 Subject: [PATCH 26/61] wip: remove `async` `await` --- lib/src/sdk/logs/logger_provider.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/sdk/logs/logger_provider.dart b/lib/src/sdk/logs/logger_provider.dart index a363b8f7..a846f29e 100644 --- a/lib/src/sdk/logs/logger_provider.dart +++ b/lib/src/sdk/logs/logger_provider.dart @@ -67,11 +67,11 @@ class LoggerProvider implements api.LoggerProvider { ); } - Future forceFlush() async { - await Future.forEach(_processors, (e) => e.forceFlush()); + Future forceFlush() { + return Future.forEach(_processors, (e) => e.forceFlush()); } - Future shutdown() async { - await Future.forEach(_processors, (e) => e.shutdown()); + Future shutdown() { + return Future.forEach(_processors, (e) => e.shutdown()); } } From b3f744e5f7b23fd79689f627a1908ea83ae6ab68 Mon Sep 17 00:00:00 2001 From: yusuf Date: Wed, 12 Feb 2025 17:26:47 +0800 Subject: [PATCH 27/61] wip: changed to `FutureOr` --- lib/src/sdk/logs/logger_provider.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/sdk/logs/logger_provider.dart b/lib/src/sdk/logs/logger_provider.dart index a846f29e..ad563d03 100644 --- a/lib/src/sdk/logs/logger_provider.dart +++ b/lib/src/sdk/logs/logger_provider.dart @@ -1,7 +1,8 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information -import 'package:collection/collection.dart'; +import 'dart:async'; + import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/sdk.dart' as sdk; import 'package:opentelemetry/src/experimental_api.dart' as api; @@ -67,11 +68,11 @@ class LoggerProvider implements api.LoggerProvider { ); } - Future forceFlush() { + FutureOr forceFlush() { return Future.forEach(_processors, (e) => e.forceFlush()); } - Future shutdown() { + FutureOr shutdown() { return Future.forEach(_processors, (e) => e.shutdown()); } } From 9978329d71126f697b4c188b6dbf8b5df414bad9 Mon Sep 17 00:00:00 2001 From: yusuf Date: Thu, 13 Feb 2025 08:17:36 +0800 Subject: [PATCH 28/61] wip: change forceFlush and shutdown to `void` --- lib/src/sdk/logs/logger_provider.dart | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/src/sdk/logs/logger_provider.dart b/lib/src/sdk/logs/logger_provider.dart index ad563d03..6ab5b14c 100644 --- a/lib/src/sdk/logs/logger_provider.dart +++ b/lib/src/sdk/logs/logger_provider.dart @@ -1,8 +1,6 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information -import 'dart:async'; - import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/sdk.dart' as sdk; import 'package:opentelemetry/src/experimental_api.dart' as api; @@ -56,8 +54,7 @@ class LoggerProvider implements api.LoggerProvider { () => sdk.Logger( logRecordLimits: _logRecordLimits, resource: _resource, - instrumentationScope: sdk.InstrumentationScope( - loggerName, version, schemaUrl, attributes), + instrumentationScope: sdk.InstrumentationScope(loggerName, version, schemaUrl, attributes), timeProvider: _timeProvider, onLogEmit: (log) { for (final processor in _processors) { @@ -68,11 +65,11 @@ class LoggerProvider implements api.LoggerProvider { ); } - FutureOr forceFlush() { - return Future.forEach(_processors, (e) => e.forceFlush()); + void forceFlush() { + return _processors.forEach((e) => e.forceFlush()); } - FutureOr shutdown() { - return Future.forEach(_processors, (e) => e.shutdown()); + void shutdown() { + return _processors.forEach((e) => e.shutdown()); } } From 849e0b0d615a6555425252540027c9d0a8bd4002 Mon Sep 17 00:00:00 2001 From: yusuf Date: Thu, 13 Feb 2025 10:48:55 +0800 Subject: [PATCH 29/61] wip: fix test --- test/unit/sdk/logs/logger_provider_test.dart | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/test/unit/sdk/logs/logger_provider_test.dart b/test/unit/sdk/logs/logger_provider_test.dart index 7b7819e6..f2a4c305 100644 --- a/test/unit/sdk/logs/logger_provider_test.dart +++ b/test/unit/sdk/logs/logger_provider_test.dart @@ -14,8 +14,7 @@ import '../../mocks.dart'; void main() { setUpAll(() { registerFallbackValue(sdk.LogRecord( - instrumentationScope: sdk.InstrumentationScope( - 'library_name', 'library_version', 'url://schema', []), + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), logRecordLimits: LogRecordLimitsImpl(), )); }); @@ -23,13 +22,13 @@ void main() { test('traceProvider custom timeProvider', () { final mockTimeProvider = FakeTimeProvider(now: Int64(123)); final mockProcessor1 = MockLogRecordProcessor(); - final provider = sdk.LoggerProvider( - timeProvider: mockTimeProvider, processors: [mockProcessor1]); + final provider = sdk.LoggerProvider(timeProvider: mockTimeProvider, processors: [mockProcessor1]); provider.get('foo').emit(); verify(() => mockProcessor1.onEmit(any( that: predicate((a) { if (a is! sdk.ReadWriteLogRecord) return false; - return a.timeStamp == DateTime.fromMicrosecondsSinceEpoch(123 ~/ 1000) && a.observedTimestamp == DateTime.fromMicrosecondsSinceEpoch(123 ~/ 1000); + return a.timeStamp == DateTime.fromMicrosecondsSinceEpoch(123 ~/ 1000) && + a.observedTimestamp == DateTime.fromMicrosecondsSinceEpoch(123 ~/ 1000); }), ))).called(1); }); @@ -39,8 +38,7 @@ void main() { final mockProcessor2 = MockLogRecordProcessor(); when(mockProcessor1.forceFlush).thenAnswer((_) async => Future.value()); when(mockProcessor2.forceFlush).thenAnswer((_) async => Future.value()); - await sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]) - .forceFlush(); + sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]).forceFlush(); verify(mockProcessor1.forceFlush).called(1); verify(mockProcessor2.forceFlush).called(1); @@ -51,8 +49,7 @@ void main() { final mockProcessor2 = MockLogRecordProcessor(); when(mockProcessor1.shutdown).thenAnswer((_) async => Future.value()); when(mockProcessor2.shutdown).thenAnswer((_) async => Future.value()); - await sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]) - .shutdown(); + sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]).shutdown(); verify(mockProcessor1.shutdown).called(1); verify(mockProcessor2.shutdown).called(1); From bebc35ecb1df7753be9c61bfd1f2312c8a3fe443 Mon Sep 17 00:00:00 2001 From: yusuf Date: Mon, 3 Mar 2025 09:52:23 +0800 Subject: [PATCH 30/61] wip: change LogRecordLimits from abstract class to class --- lib/src/sdk/logs/log_record_limit.dart | 15 ++------------- lib/src/sdk/logs/logger_provider.dart | 2 +- test/unit/sdk/common/limits_test.dart | 4 ++-- test/unit/sdk/logs/log_record_test.dart | 12 ++++++------ test/unit/sdk/logs/logger_provider_test.dart | 2 +- test/unit/sdk/logs/logger_test.dart | 4 ++-- 6 files changed, 14 insertions(+), 25 deletions(-) diff --git a/lib/src/sdk/logs/log_record_limit.dart b/lib/src/sdk/logs/log_record_limit.dart index 7e3516a1..2fb07d73 100644 --- a/lib/src/sdk/logs/log_record_limit.dart +++ b/lib/src/sdk/logs/log_record_limit.dart @@ -1,28 +1,17 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information -// https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecord-limits -abstract class LogRecordLimits { - // https://opentelemetry.io/docs/specs/otel/common/#configurable-parameters - - int get attributeCountLimit; - - int get attributeValueLengthLimit; -} - -class LogRecordLimitsImpl implements LogRecordLimits { +class LogRecordLimits { final int _attributeCountLimit; final int _attributeValueLengthLimit; - const LogRecordLimitsImpl({ + const LogRecordLimits({ int attributeCountLimit = 128, int attributeValueLengthLimit = -1, }) : _attributeCountLimit = attributeCountLimit, _attributeValueLengthLimit = attributeValueLengthLimit; - @override int get attributeCountLimit => _attributeCountLimit; - @override int get attributeValueLengthLimit => _attributeValueLengthLimit; } diff --git a/lib/src/sdk/logs/logger_provider.dart b/lib/src/sdk/logs/logger_provider.dart index 6ab5b14c..8fb9a5fa 100644 --- a/lib/src/sdk/logs/logger_provider.dart +++ b/lib/src/sdk/logs/logger_provider.dart @@ -27,7 +27,7 @@ class LoggerProvider implements api.LoggerProvider { LoggerProvider({ LoggerConfig config = const LoggerConfig(), - sdk.LogRecordLimits logRecordLimits = const LogRecordLimitsImpl(), + sdk.LogRecordLimits logRecordLimits = const LogRecordLimits(), sdk.Resource? resource, List? processors, sdk.TimeProvider? timeProvider, diff --git a/test/unit/sdk/common/limits_test.dart b/test/unit/sdk/common/limits_test.dart index a61f7b8d..6365522b 100644 --- a/test/unit/sdk/common/limits_test.dart +++ b/test/unit/sdk/common/limits_test.dart @@ -10,7 +10,7 @@ void main() { test('test log record limit', () { final logLimit = applyAttributeLimitsForLog( api.Attribute.fromString('key', 'value'), - LogRecordLimitsImpl(attributeValueLengthLimit: 2), + LogRecordLimits(attributeValueLengthLimit: 2), ); expect(logLimit.value, 'va'); @@ -19,7 +19,7 @@ void main() { test('test log record limit list', () { final logLimit = applyAttributeLimitsForLog( api.Attribute.fromStringList('key', ['value1', 'value2']), - LogRecordLimitsImpl(attributeValueLengthLimit: 2), + LogRecordLimits(attributeValueLengthLimit: 2), ); expect(logLimit.value, ['va', 'va']); diff --git a/test/unit/sdk/logs/log_record_test.dart b/test/unit/sdk/logs/log_record_test.dart index 7889da88..d5233ab4 100644 --- a/test/unit/sdk/logs/log_record_test.dart +++ b/test/unit/sdk/logs/log_record_test.dart @@ -17,7 +17,7 @@ void main() { final logRecord = sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope( 'library_name', 'library_version', 'url://schema', []), - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), timeProvider: FakeTimeProvider(now: Int64(123))) ..makeReadonly() ..body = 'Log Message' @@ -41,7 +41,7 @@ void main() { final logRecord = sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope( 'library_name', 'library_version', 'url://schema', []), - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), timeProvider: FakeTimeProvider(now: Int64(123))) ..body = 'Log Message' ..severityNumber = api.Severity.debug @@ -64,7 +64,7 @@ void main() { final logRecord = sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope( 'library_name', 'library_version', 'url://schema', []), - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), ) ..setAttributes([api.Attribute.fromString('key2', 'value')]) ..setAttribute(api.Attribute.fromString('key2', 'value2')); @@ -79,7 +79,7 @@ void main() { 'library_name', 'library_version', 'url://schema', []), timeStamp: now, observedTimestamp: now, - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), ) ..setAttributes([api.Attribute.fromString('key2', 'value')]) ..setAttribute(api.Attribute.fromString('key2', 'value2')); @@ -95,7 +95,7 @@ void main() { 'library_name', 'library_version', 'url://schema', []), timeStamp: now, observedTimestamp: now, - logRecordLimits: LogRecordLimitsImpl(attributeValueLengthLimit: 2), + logRecordLimits: LogRecordLimits(attributeValueLengthLimit: 2), ) ..setAttribute(api.Attribute.fromString('key', 'value')) ..setAttribute(api.Attribute.fromBoolean('key2', true)) @@ -121,7 +121,7 @@ void main() { 'library_name', 'library_version', 'url://schema', []), timeStamp: now, observedTimestamp: now, - logRecordLimits: LogRecordLimitsImpl(attributeValueLengthLimit: 2), + logRecordLimits: LogRecordLimits(attributeValueLengthLimit: 2), ) ..setAttribute(api.Attribute.fromString('key', 'value')) ..setAttribute(api.Attribute.fromStringList('key2', ['value2'])); diff --git a/test/unit/sdk/logs/logger_provider_test.dart b/test/unit/sdk/logs/logger_provider_test.dart index f2a4c305..a6f1f90d 100644 --- a/test/unit/sdk/logs/logger_provider_test.dart +++ b/test/unit/sdk/logs/logger_provider_test.dart @@ -15,7 +15,7 @@ void main() { setUpAll(() { registerFallbackValue(sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), )); }); diff --git a/test/unit/sdk/logs/logger_test.dart b/test/unit/sdk/logs/logger_test.dart index 12f9a5e4..a18e5516 100644 --- a/test/unit/sdk/logs/logger_test.dart +++ b/test/unit/sdk/logs/logger_test.dart @@ -20,14 +20,14 @@ void main() { setUpAll(() { registerFallbackValue(sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), )); }); test('emit new log', () { final callBack = CallbackMock(); sdk.Logger( - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), instrumentationScope: sdk.InstrumentationScope( 'library_name', 'library_version', From 032fd6518924e9883dcecbcc93a01a6913130300 Mon Sep 17 00:00:00 2001 From: yusuf Date: Mon, 3 Mar 2025 09:59:20 +0800 Subject: [PATCH 31/61] wip: avoid nullable values --- lib/src/sdk/logs/log_record.dart | 58 +++++++++++++------------ test/unit/sdk/logs/log_record_test.dart | 16 +++---- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/lib/src/sdk/logs/log_record.dart b/lib/src/sdk/logs/log_record.dart index 1b6594fb..09925d9a 100644 --- a/lib/src/sdk/logs/log_record.dart +++ b/lib/src/sdk/logs/log_record.dart @@ -10,23 +10,23 @@ import 'package:opentelemetry/src/sdk/common/limits.dart'; /// https://opentelemetry.io/docs/specs/otel/logs/sdk/#readwritelogrecord abstract class ReadableLogRecord { - DateTime? get timeStamp; + DateTime get timeStamp; - DateTime? get observedTimestamp; + DateTime get observedTimestamp; - String? get severityText; + String get severityText; - api.Severity? get severityNumber; + api.Severity get severityNumber; dynamic get body; - sdk.Attributes? get attributes; + sdk.Attributes get attributes; - api.SpanContext? get spanContext; + api.SpanContext get spanContext; - sdk.Resource? get resource; + sdk.Resource get resource; - sdk.InstrumentationScope? get instrumentationScope; + sdk.InstrumentationScope get instrumentationScope; int get droppedAttributesCount; } @@ -34,17 +34,16 @@ abstract class ReadableLogRecord { abstract class ReadWriteLogRecord extends ReadableLogRecord { set body(dynamic severity); - set severityText(String? severity); + set severityText(String severity); - set severityNumber(api.Severity? severity); + set severityNumber(api.Severity severity); } class LogRecord implements ReadWriteLogRecord { @override final sdk.InstrumentationScope instrumentationScope; - @override - final sdk.Resource? resource; + final sdk.Resource _resource; final sdk.TimeProvider _timeProvider; final api.Context _context; @@ -53,8 +52,8 @@ class LogRecord implements ReadWriteLogRecord { final DateTime? _observedTimestamp; bool _isReadonly = false; - String? _severityText; - api.Severity? _severityNumber; + String _severityText; + api.Severity _severityNumber; dynamic _body; int _totalAttributesCount = 0; @@ -70,21 +69,26 @@ class LogRecord implements ReadWriteLogRecord { DateTime? observedTimestamp, api.Context? context, dynamic body, - this.resource, + sdk.Resource? resource, sdk.TimeProvider? timeProvider, - }) : _severityText = severityText, + }) : _severityText = severityText ?? api.Severity.unspecified.name, + _resource = resource ?? sdk.Resource([]), _context = context ?? api.Context.current, _body = body, _attributes = sdk.Attributes.empty(), - _severityNumber = severityNumber, + _severityNumber = severityNumber ?? api.Severity.unspecified, _timeStamp = timeStamp, _observedTimestamp = observedTimestamp, _timeProvider = timeProvider ?? sdk.DateTimeTimeProvider() { if (attributes.isNotEmpty) setAttributes(attributes); } + + @override + sdk.Resource get resource => _resource; + @override - sdk.Attributes? get attributes => _attributes; + sdk.Attributes get attributes => _attributes; @override dynamic get body => _body; @@ -96,32 +100,32 @@ class LogRecord implements ReadWriteLogRecord { } @override - api.SpanContext? get spanContext => api.spanContextFromContext(_context); + api.SpanContext get spanContext => api.spanContextFromContext(_context); @override - int get droppedAttributesCount => - _totalAttributesCount - (attributes?.length ?? 0); + int get droppedAttributesCount => _totalAttributesCount - attributes.length; @override - DateTime? get timeStamp => _timeStamp ?? DateTime.fromMicrosecondsSinceEpoch((_timeProvider.now ~/ 1000).toInt()); + DateTime get timeStamp => _timeStamp ?? DateTime.fromMicrosecondsSinceEpoch((_timeProvider.now ~/ 1000).toInt()); @override - DateTime? get observedTimestamp => _observedTimestamp ?? DateTime.fromMicrosecondsSinceEpoch((_timeProvider.now ~/ 1000).toInt()); + DateTime get observedTimestamp => + _observedTimestamp ?? DateTime.fromMicrosecondsSinceEpoch((_timeProvider.now ~/ 1000).toInt()); @override - api.Severity? get severityNumber => _severityNumber; + api.Severity get severityNumber => _severityNumber; @override - set severityNumber(api.Severity? severity) { + set severityNumber(api.Severity severity) { if (_isReadonly) return; _severityNumber = severity; } @override - String? get severityText => _severityText; + String get severityText => _severityText; @override - set severityText(String? severity) { + set severityText(String severity) { if (_isReadonly) return; _severityText = severity; } diff --git a/test/unit/sdk/logs/log_record_test.dart b/test/unit/sdk/logs/log_record_test.dart index d5233ab4..3f274755 100644 --- a/test/unit/sdk/logs/log_record_test.dart +++ b/test/unit/sdk/logs/log_record_test.dart @@ -27,9 +27,9 @@ void main() { ..setAttribute(api.Attribute.fromString('key2', 'value2')); expect(logRecord.body, null); - expect(logRecord.severityNumber, null); - expect(logRecord.severityText, null); - expect(logRecord.attributes?.keys, const []); + expect(logRecord.severityNumber, api.Severity.unspecified); + expect(logRecord.severityText, api.Severity.unspecified.name); + expect(logRecord.attributes.keys, const []); expect(logRecord.droppedAttributesCount, 0); expect(logRecord.timeStamp, DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt() ~/ 1000)); @@ -52,7 +52,7 @@ void main() { expect(logRecord.body, 'Log Message'); expect(logRecord.severityNumber, api.Severity.debug); expect(logRecord.severityText, 'DEBUG'); - expect(logRecord.attributes?.keys, const ['key', 'key2']); + expect(logRecord.attributes.keys, const ['key', 'key2']); expect(logRecord.droppedAttributesCount, 0); expect(logRecord.timeStamp, DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt() ~/ 1000)); @@ -108,10 +108,10 @@ void main() { expect(logRecord.droppedAttributesCount, 0); expect( - logRecord.attributes?.keys, + logRecord.attributes.keys, const ['key', 'key2', 'key3', 'key4', 'key5', 'key6', 'key7', 'key8'], ); - expect(logRecord.attributes?.get('key'), 'va'); + expect(logRecord.attributes.get('key'), 'va'); }); test('logRecord set attribute with limit', () { @@ -126,7 +126,7 @@ void main() { ..setAttribute(api.Attribute.fromString('key', 'value')) ..setAttribute(api.Attribute.fromStringList('key2', ['value2'])); - expect(logRecord.attributes?.get('key'), 'va'); - expect(logRecord.attributes?.get('key2'), const ['va']); + expect(logRecord.attributes.get('key'), 'va'); + expect(logRecord.attributes.get('key2'), const ['va']); }); } From 442b466a3b327827162ee214f987e2228d164b87 Mon Sep 17 00:00:00 2001 From: yusuf Date: Mon, 3 Mar 2025 10:09:16 +0800 Subject: [PATCH 32/61] wip: mark protected --- lib/src/sdk/logs/log_record.dart | 1 + lib/src/sdk/logs/logger.dart | 1 + lib/src/sdk/logs/logger_provider.dart | 6 +----- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/src/sdk/logs/log_record.dart b/lib/src/sdk/logs/log_record.dart index 09925d9a..8aca2f7f 100644 --- a/lib/src/sdk/logs/log_record.dart +++ b/lib/src/sdk/logs/log_record.dart @@ -59,6 +59,7 @@ class LogRecord implements ReadWriteLogRecord { final sdk.Attributes _attributes; + @protected LogRecord({ required this.instrumentationScope, required this.logRecordLimits, diff --git a/lib/src/sdk/logs/logger.dart b/lib/src/sdk/logs/logger.dart index 4efe6b5b..6d6f60eb 100644 --- a/lib/src/sdk/logs/logger.dart +++ b/lib/src/sdk/logs/logger.dart @@ -14,6 +14,7 @@ class Logger extends api.Logger { final sdk.LogRecordLimits logRecordLimits; final sdk.TimeProvider? timeProvider; + @protected Logger({ required this.instrumentationScope, required this.logRecordLimits, diff --git a/lib/src/sdk/logs/logger_provider.dart b/lib/src/sdk/logs/logger_provider.dart index 8fb9a5fa..c2fb0267 100644 --- a/lib/src/sdk/logs/logger_provider.dart +++ b/lib/src/sdk/logs/logger_provider.dart @@ -56,11 +56,7 @@ class LoggerProvider implements api.LoggerProvider { resource: _resource, instrumentationScope: sdk.InstrumentationScope(loggerName, version, schemaUrl, attributes), timeProvider: _timeProvider, - onLogEmit: (log) { - for (final processor in _processors) { - processor.onEmit(log); - } - }, + processors: _processors ), ); } From 3fc382e6ee62ad0924e72275ce8f3a0198fbbf64 Mon Sep 17 00:00:00 2001 From: yusuf Date: Mon, 3 Mar 2025 10:10:07 +0800 Subject: [PATCH 33/61] wip: remove nullable and pass processors instead of callback. --- lib/src/sdk/logs/logger.dart | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/src/sdk/logs/logger.dart b/lib/src/sdk/logs/logger.dart index 6d6f60eb..8276f5fe 100644 --- a/lib/src/sdk/logs/logger.dart +++ b/lib/src/sdk/logs/logger.dart @@ -1,27 +1,28 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information -import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:meta/meta.dart'; import 'package:opentelemetry/api.dart' as api; +import 'package:opentelemetry/sdk.dart' as sdk; import 'package:opentelemetry/src/api/context/context.dart'; import 'package:opentelemetry/src/experimental_api.dart' as api; import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; class Logger extends api.Logger { final sdk.InstrumentationScope instrumentationScope; - final sdk.Resource? resource; - final Function(sdk.ReadWriteLogRecord)? onLogEmit; + final sdk.Resource _resource; final sdk.LogRecordLimits logRecordLimits; - final sdk.TimeProvider? timeProvider; + final sdk.TimeProvider timeProvider; + final List processors; @protected Logger({ required this.instrumentationScope, required this.logRecordLimits, - this.onLogEmit, - this.resource, - this.timeProvider, - }); + required this.timeProvider, + this.processors = const [], + sdk.Resource? resource, + }) : _resource = resource ?? sdk.Resource([]); @override void emit({ @@ -35,7 +36,7 @@ class Logger extends api.Logger { }) { final log = sdk.LogRecord( logRecordLimits: logRecordLimits, - resource: resource, + resource: _resource, instrumentationScope: instrumentationScope, context: context, severityText: severityText, @@ -44,7 +45,9 @@ class Logger extends api.Logger { body: body, timeProvider: timeProvider, ); - onLogEmit?.call(log); + for (final processor in processors) { + processor.onEmit(log); + } log.makeReadonly(); } } From f0e65ab78cf7d17687993ff28d9a6c626b5aeac4 Mon Sep 17 00:00:00 2001 From: yusuf Date: Mon, 3 Mar 2025 10:12:38 +0800 Subject: [PATCH 34/61] fix: failing unit test --- test/unit/sdk/logs/logger_test.dart | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/test/unit/sdk/logs/logger_test.dart b/test/unit/sdk/logs/logger_test.dart index a18e5516..250ff1b8 100644 --- a/test/unit/sdk/logs/logger_test.dart +++ b/test/unit/sdk/logs/logger_test.dart @@ -2,19 +2,16 @@ // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information @TestOn('vm') +import 'package:fixnum/fixnum.dart'; import 'package:mocktail/mocktail.dart'; import 'package:opentelemetry/sdk.dart' as sdk; import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart'; import 'package:test/test.dart'; -class Callback { - const Callback(); +import '../../mocks.dart'; - void call(sdk.ReadWriteLogRecord logRecord) {} -} - -class CallbackMock extends Mock implements Callback {} +class MockLockRecordProcessor extends Mock implements sdk.LogRecordProcessor {} void main() { setUpAll(() { @@ -25,7 +22,7 @@ void main() { }); test('emit new log', () { - final callBack = CallbackMock(); + final processor = MockLockRecordProcessor(); sdk.Logger( logRecordLimits: LogRecordLimits(), instrumentationScope: sdk.InstrumentationScope( @@ -35,15 +32,16 @@ void main() { [], ), resource: sdk.Resource([]), - onLogEmit: callBack, + processors: [processor], + timeProvider: FakeTimeProvider(now: Int64(60)), ).emit(body: 'TEST!'); - verify(() => callBack.call(any(that: predicate((it) { - return it.attributes?.keys.isEmpty == true && + verify(() => processor.onEmit(any(that: predicate((it) { + return it.attributes.keys.isEmpty == true && it.instrumentationScope.name == 'library_name' && it.instrumentationScope.version == 'library_version' && it.instrumentationScope.schemaUrl == 'url://schema' && - it.resource?.attributes.keys.isEmpty == true; + it.resource.attributes.keys.isEmpty == true; })))).called(1); }); } From b97de950576ab779cd37c5c75e4a5831d871ad7e Mon Sep 17 00:00:00 2001 From: yusuf Date: Wed, 5 Mar 2025 00:35:01 +0800 Subject: [PATCH 35/61] wip: remove NoopLogRecordProcessor --- lib/src/experimental_sdk.dart | 1 - .../sdk/logs/processors/noop_log_processor.dart | 17 ----------------- 2 files changed, 18 deletions(-) delete mode 100644 lib/src/sdk/logs/processors/noop_log_processor.dart diff --git a/lib/src/experimental_sdk.dart b/lib/src/experimental_sdk.dart index 1dc5c1b1..846eef85 100644 --- a/lib/src/experimental_sdk.dart +++ b/lib/src/experimental_sdk.dart @@ -11,7 +11,6 @@ export 'sdk/logs/log_record_limit.dart' show LogRecordLimits; export 'sdk/logs/logger.dart' show Logger; export 'sdk/logs/logger_provider.dart' show LoggerProvider; export 'sdk/logs/processors/log_record_processor.dart' show LogRecordProcessor; -export 'sdk/logs/processors/noop_log_processor.dart' show NoopLogRecordProcessor; export 'sdk/metrics/counter.dart' show Counter; export 'sdk/metrics/meter.dart' show Meter; export 'sdk/metrics/meter_provider.dart' show MeterProvider; diff --git a/lib/src/sdk/logs/processors/noop_log_processor.dart b/lib/src/sdk/logs/processors/noop_log_processor.dart deleted file mode 100644 index 22893c74..00000000 --- a/lib/src/sdk/logs/processors/noop_log_processor.dart +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2021-2022 Workiva. -// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information - -import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; - -class NoopLogRecordProcessor implements sdk.LogRecordProcessor { - const NoopLogRecordProcessor(); - - @override - Future forceFlush() async {} - - @override - void onEmit(sdk.ReadableLogRecord logRecord) {} - - @override - Future shutdown() async {} -} From d9c04299964d1ffb2ed23bf0b46fa48e70de3102 Mon Sep 17 00:00:00 2001 From: yusuf Date: Wed, 5 Mar 2025 00:37:28 +0800 Subject: [PATCH 36/61] wip: inline --- lib/src/sdk/common/limits.dart | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/lib/src/sdk/common/limits.dart b/lib/src/sdk/common/limits.dart index 52381ac4..d6e79128 100644 --- a/lib/src/sdk/common/limits.dart +++ b/lib/src/sdk/common/limits.dart @@ -9,8 +9,7 @@ import '../../experimental_sdk.dart' as sdk; /// Applies given [sdk.SpanLimits] to a list of [api.SpanLink]s. @protected -List applyLinkLimits( - List links, sdk.SpanLimits limits) { +List applyLinkLimits(List links, sdk.SpanLimits limits) { final spanLink = []; for (final link in links) { @@ -29,8 +28,7 @@ List applyLinkLimits( for (final attr in link.attributes) { // if attributes num is already greater than maxNumAttributesPerLink // and this key doesn't exist in the list, drop it. - if (attributeMap.length >= limits.maxNumAttributesPerLink && - !attributeMap.containsKey(attr.key)) { + if (attributeMap.length >= limits.maxNumAttributesPerLink && !attributeMap.containsKey(attr.key)) { droppedAttributes++; continue; } @@ -51,8 +49,7 @@ List applyLinkLimits( } } - spanLink.add(api.SpanLink(link.context, - attributes: linkAttributes, droppedAttributes: droppedAttributes)); + spanLink.add(api.SpanLink(link.context, attributes: linkAttributes, droppedAttributes: droppedAttributes)); } return spanLink; } @@ -65,14 +62,11 @@ api.Attribute applyAttributeLimits(api.Attribute attr, sdk.SpanLimits limits) { if (attr.value is String) { attr = api.Attribute.fromString( - attr.key, - applyAttributeLengthLimit( - attr.value as String, limits.maxNumAttributeLength)); + attr.key, applyAttributeLengthLimit(attr.value as String, limits.maxNumAttributeLength)); } else if (attr.value is List) { final listString = attr.value as List; for (var j = 0; j < listString.length; j++) { - listString[j] = applyAttributeLengthLimit( - listString[j], limits.maxNumAttributeLength); + listString[j] = applyAttributeLengthLimit(listString[j], limits.maxNumAttributeLength); } attr = api.Attribute.fromStringList(attr.key, listString); } @@ -88,18 +82,13 @@ api.Attribute applyAttributeLimitsForLog( if (limits.attributeValueLengthLimit < 0) return attr; if (attr.value is String) { - final truncatedValue = applyAttributeLengthLimit( - attr.value as String, limits.attributeValueLengthLimit); - - if (truncatedValue == attr.value) return attr; - - return api.Attribute.fromString(attr.key, truncatedValue); + return (attr.value as String).length > limits.attributeValueLengthLimit + ? api.Attribute.fromString(attr.key, (attr.value as String).substring(0, limits.attributeValueLengthLimit)) + : attr; } else if (attr.value is List) { final listString = attr.value as List; - final truncatedValues = listString - .map((e) => - applyAttributeLengthLimit(e, limits.attributeValueLengthLimit)) - .toList(); + final truncatedValues = + listString.map((e) => applyAttributeLengthLimit(e, limits.attributeValueLengthLimit)).toList(); final equal = const ListEquality().equals(listString, truncatedValues); if (equal) return attr; From 37fae2674a03353163c4ceeeffd6c529c6460e06 Mon Sep 17 00:00:00 2001 From: yusuf Date: Wed, 5 Mar 2025 00:57:06 +0800 Subject: [PATCH 37/61] wip: limit list --- lib/src/sdk/common/limits.dart | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/src/sdk/common/limits.dart b/lib/src/sdk/common/limits.dart index d6e79128..4923b88b 100644 --- a/lib/src/sdk/common/limits.dart +++ b/lib/src/sdk/common/limits.dart @@ -1,6 +1,5 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information -import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import '../../../api.dart' as api; @@ -86,14 +85,18 @@ api.Attribute applyAttributeLimitsForLog( ? api.Attribute.fromString(attr.key, (attr.value as String).substring(0, limits.attributeValueLengthLimit)) : attr; } else if (attr.value is List) { - final listString = attr.value as List; - final truncatedValues = - listString.map((e) => applyAttributeLengthLimit(e, limits.attributeValueLengthLimit)).toList(); - - final equal = const ListEquality().equals(listString, truncatedValues); - if (equal) return attr; - - return api.Attribute.fromStringList(attr.key, truncatedValues); + final list = (attr.value as List); + List? truncated; + for (int i = 0; i < list.length; i++) { + final s = list[i]; + if (s.length > limits.attributeValueLengthLimit) { + truncated ??= List.from(list, growable: false); + truncated[i] = s.substring(0, limits.attributeValueLengthLimit); + } + } + if (truncated != null) { + return api.Attribute.fromStringList(attr.key, truncated); + } } return attr; } From b9598de637fe89d4018f63fd7850df0785eafa1c Mon Sep 17 00:00:00 2001 From: yusuf Date: Wed, 5 Mar 2025 22:52:36 +0800 Subject: [PATCH 38/61] wip: onEmit ReadableLogRecord -> ReadWriteLogRecord --- lib/src/sdk/logs/processors/log_record_processor.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/sdk/logs/processors/log_record_processor.dart b/lib/src/sdk/logs/processors/log_record_processor.dart index cde26d50..dfe2b714 100644 --- a/lib/src/sdk/logs/processors/log_record_processor.dart +++ b/lib/src/sdk/logs/processors/log_record_processor.dart @@ -5,7 +5,7 @@ import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; /// https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecordprocessor abstract class LogRecordProcessor { - void onEmit(sdk.ReadableLogRecord logRecord); + void onEmit(sdk.ReadWriteLogRecord logRecord); Future forceFlush(); From 741ed89e77f37b7edd7fd6f5d65c4b01f1fa2563 Mon Sep 17 00:00:00 2001 From: yusuf Date: Tue, 18 Mar 2025 12:39:15 +0800 Subject: [PATCH 39/61] wip: update DateTimeProvider --- .../sdk/platforms/web/time_providers/web_time_provider.dart | 3 +++ lib/src/sdk/time_providers/datetime_time_provider.dart | 3 +++ lib/src/sdk/time_providers/time_provider.dart | 3 +++ 3 files changed, 9 insertions(+) diff --git a/lib/src/sdk/platforms/web/time_providers/web_time_provider.dart b/lib/src/sdk/platforms/web/time_providers/web_time_provider.dart index ff4a0e32..4bb5375c 100644 --- a/lib/src/sdk/platforms/web/time_providers/web_time_provider.dart +++ b/lib/src/sdk/platforms/web/time_providers/web_time_provider.dart @@ -51,4 +51,7 @@ class WebTimeProvider implements TimeProvider { /// for more information. @override Int64 get now => fromDOMHighResTimeStamp(window.performance.now()); + + @override + double get nowNanoseconds => window.performance.now(); } diff --git a/lib/src/sdk/time_providers/datetime_time_provider.dart b/lib/src/sdk/time_providers/datetime_time_provider.dart index e543890a..8a79fc08 100644 --- a/lib/src/sdk/time_providers/datetime_time_provider.dart +++ b/lib/src/sdk/time_providers/datetime_time_provider.dart @@ -8,4 +8,7 @@ import 'time_provider.dart'; class DateTimeTimeProvider implements TimeProvider { @override Int64 get now => Int64(DateTime.now().microsecondsSinceEpoch) * 1000; + + @override + double get nowNanoseconds => DateTime.now().microsecondsSinceEpoch * 1000.0; } diff --git a/lib/src/sdk/time_providers/time_provider.dart b/lib/src/sdk/time_providers/time_provider.dart index 360a8248..10096b82 100644 --- a/lib/src/sdk/time_providers/time_provider.dart +++ b/lib/src/sdk/time_providers/time_provider.dart @@ -15,5 +15,8 @@ abstract class TimeProvider { static const int nanosecondsPerMillisecond = 1000000; /// The current time, in nanoseconds since Unix Epoch. + @Deprecated('This getter will be removed in future without replacement.') Int64 get now; + + double get nowNanoseconds; } From 4cb0cbbff2ecd3c281699a22ad68ae64462e1f4b Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 18 Jan 2025 09:35:50 +0800 Subject: [PATCH 40/61] wip: add ExportResult --- lib/src/experimental_sdk.dart | 1 + lib/src/sdk/logs/export_result.dart | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 lib/src/sdk/logs/export_result.dart diff --git a/lib/src/experimental_sdk.dart b/lib/src/experimental_sdk.dart index 846eef85..fd4305d7 100644 --- a/lib/src/experimental_sdk.dart +++ b/lib/src/experimental_sdk.dart @@ -10,6 +10,7 @@ export 'sdk/logs/log_record.dart' show ReadableLogRecord, ReadWriteLogRecord, Lo export 'sdk/logs/log_record_limit.dart' show LogRecordLimits; export 'sdk/logs/logger.dart' show Logger; export 'sdk/logs/logger_provider.dart' show LoggerProvider; +export 'sdk/logs/export_result.dart' show ExportResult, ExportResultCode; export 'sdk/logs/processors/log_record_processor.dart' show LogRecordProcessor; export 'sdk/metrics/counter.dart' show Counter; export 'sdk/metrics/meter.dart' show Meter; diff --git a/lib/src/sdk/logs/export_result.dart b/lib/src/sdk/logs/export_result.dart new file mode 100644 index 00000000..3807b095 --- /dev/null +++ b/lib/src/sdk/logs/export_result.dart @@ -0,0 +1,15 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +class ExportResult { + final ExportResultCode code; + final Exception? error; + final StackTrace? stackTrace; + + ExportResult({required this.code, this.error, this.stackTrace}); +} + +enum ExportResultCode { + success, + failed, +} From 6912ab9d6c4634ba264f7138b7b809205ddcdc9b Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 18 Jan 2025 09:36:42 +0800 Subject: [PATCH 41/61] wip: add LogRecordExporter --- lib/src/sdk/logs/exporters/log_record_exporter.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 lib/src/sdk/logs/exporters/log_record_exporter.dart diff --git a/lib/src/sdk/logs/exporters/log_record_exporter.dart b/lib/src/sdk/logs/exporters/log_record_exporter.dart new file mode 100644 index 00000000..aebafd34 --- /dev/null +++ b/lib/src/sdk/logs/exporters/log_record_exporter.dart @@ -0,0 +1,10 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; + +abstract class LogRecordExporter { + Future export(List logs); + + Future shutdown(); +} From 20c3edded0b78abdb93d45543c755db5d580059d Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 18 Jan 2025 09:38:36 +0800 Subject: [PATCH 42/61] wip: add BatchLogRecordProcessor --- lib/src/experimental_sdk.dart | 4 +- .../batch_log_record_processor.dart | 101 ++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 lib/src/sdk/logs/processors/batch_log_record_processor.dart diff --git a/lib/src/experimental_sdk.dart b/lib/src/experimental_sdk.dart index fd4305d7..1013c2e9 100644 --- a/lib/src/experimental_sdk.dart +++ b/lib/src/experimental_sdk.dart @@ -6,11 +6,13 @@ library experimental_sdk; import 'package:meta/meta.dart'; +export 'sdk/logs/export_result.dart' show ExportResult, ExportResultCode; +export 'sdk/logs/exporters/log_record_exporter.dart' show LogRecordExporter; export 'sdk/logs/log_record.dart' show ReadableLogRecord, ReadWriteLogRecord, LogRecord; export 'sdk/logs/log_record_limit.dart' show LogRecordLimits; export 'sdk/logs/logger.dart' show Logger; export 'sdk/logs/logger_provider.dart' show LoggerProvider; -export 'sdk/logs/export_result.dart' show ExportResult, ExportResultCode; +export 'sdk/logs/processors/batch_log_record_processor.dart' show BatchLogRecordProcessor; export 'sdk/logs/processors/log_record_processor.dart' show LogRecordProcessor; export 'sdk/metrics/counter.dart' show Counter; export 'sdk/metrics/meter.dart' show Meter; diff --git a/lib/src/sdk/logs/processors/batch_log_record_processor.dart b/lib/src/sdk/logs/processors/batch_log_record_processor.dart new file mode 100644 index 00000000..3a7c76dd --- /dev/null +++ b/lib/src/sdk/logs/processors/batch_log_record_processor.dart @@ -0,0 +1,101 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +import 'dart:async'; +import 'dart:math'; + +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; + +class BatchLogRecordProcessor extends sdk.LogRecordProcessor { + static const OTEL_BLRP_MAX_EXPORT_BATCH_SIZE = 512; + static const OTEL_BLRP_MAX_QUEUE_SIZE = 2048; + static const OTEL_BLRP_SCHEDULE_DELAY = 5000; + static const OTEL_BLRP_EXPORT_TIMEOUT = 30000; + + final int maxExportBatchSize; + final int scheduledDelayMillis; + final int exportTimeoutMillis; + final int maxQueueSize; + final sdk.LogRecordExporter exporter; + + final _finishedLogRecords = []; + + bool _isShutDown = false; + Timer? _timer; + + BatchLogRecordProcessor({ + required this.exporter, + this.maxExportBatchSize = OTEL_BLRP_MAX_EXPORT_BATCH_SIZE, + this.scheduledDelayMillis = OTEL_BLRP_SCHEDULE_DELAY, + this.exportTimeoutMillis = OTEL_BLRP_EXPORT_TIMEOUT, + this.maxQueueSize = OTEL_BLRP_MAX_QUEUE_SIZE, + }) : assert( + maxQueueSize >= maxExportBatchSize, + 'BatchLogRecordProcessor: maxExportBatchSize must be smaller or equal to maxQueueSize', + ); + + @override + void onEmit(sdk.ReadableLogRecord logRecord) { + if (_isShutDown) return; + _addToBuffer(logRecord); + } + + @override + Future forceFlush() async { + if (_isShutDown) return; + await _flushAll(); + } + + @override + Future shutdown() async { + _isShutDown = true; + await _flushAll(); + await exporter.shutdown(); + } + + void _addToBuffer(sdk.ReadableLogRecord logRecord) { + if (_finishedLogRecords.length >= maxQueueSize) { + return; + } + _finishedLogRecords.add(logRecord); + _maybeStartTimer(); + } + + Future _flushAll() { + return Future(() async { + final promises = >[]; + final batchCount = (_finishedLogRecords.length / maxExportBatchSize).ceil(); + + for (var i = 0; i < batchCount; i++) { + promises.add(_flushOneBatch()); + } + + try { + await Future.wait(promises); + } catch (e) { + rethrow; + } + }); + } + + Future _flushOneBatch() async { + _clearTimer(); + if (_finishedLogRecords.isEmpty) return; + final result = _finishedLogRecords.sublist(0, min(maxExportBatchSize, _finishedLogRecords.length)); + _finishedLogRecords.removeRange(0, result.length); + await exporter.export(result); + } + + void _maybeStartTimer() { + if (_timer != null) return; + _timer = Timer(Duration(milliseconds: scheduledDelayMillis), () async { + await _flushOneBatch(); + _clearTimer(); + _maybeStartTimer(); + }); + } + + void _clearTimer() { + _timer = null; + } +} From 813f433c5c7b7a5e145859f4fda90ac483a17cb0 Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 18 Jan 2025 09:38:47 +0800 Subject: [PATCH 43/61] wip: unit test BatchLogRecordProcessor --- test/unit/mocks.dart | 2 + .../batch_log_record_processor_test.dart | 114 ++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 test/unit/sdk/logs/processors/batch_log_record_processor_test.dart diff --git a/test/unit/mocks.dart b/test/unit/mocks.dart index ce6eb842..aca47917 100644 --- a/test/unit/mocks.dart +++ b/test/unit/mocks.dart @@ -21,6 +21,8 @@ class MockSpanProcessor extends Mock implements SpanProcessor {} class MockLogRecordProcessor extends Mock implements LogRecordProcessor {} +class MockLogRecordExporter extends Mock implements LogRecordExporter {} + class FakeTimeProvider extends Mock implements TimeProvider { FakeTimeProvider({required Int64 now}) : _now = now; final Int64 _now; diff --git a/test/unit/sdk/logs/processors/batch_log_record_processor_test.dart b/test/unit/sdk/logs/processors/batch_log_record_processor_test.dart new file mode 100644 index 00000000..62bb6b8e --- /dev/null +++ b/test/unit/sdk/logs/processors/batch_log_record_processor_test.dart @@ -0,0 +1,114 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +@TestOn('vm') +import 'package:fixnum/fixnum.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:opentelemetry/api.dart' as api; +import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/experimental_api.dart' as api; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; +import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart'; +import 'package:test/test.dart'; + +import '../../../mocks.dart'; + +void main() { + test('test processor', () async { + final exporter = MockLogRecordExporter(); + when(() => exporter.export(any())).thenAnswer((_) async => sdk.ExportResult(code: sdk.ExportResultCode.success)); + final processor = sdk.BatchLogRecordProcessor( + exporter: exporter, + scheduledDelayMillis: Duration.zero.inMilliseconds, + ); + + final tracer = sdk.TracerProviderBase().getTracer('test'); + final parent = tracer.startSpan('parent'); + final context = api.contextWithSpan(api.Context.current, parent); + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + logRecordLimits: LogRecordLimitsImpl(), + context: context, + timeProvider: FakeTimeProvider(now: Int64(123))); + final logRecordA = logRecord + ..body = 'test log' + ..severityNumber = api.Severity.fatal3; + processor.onEmit(logRecordA); + + await Future.delayed(const Duration(milliseconds: 100)); + verify(() => exporter.export(any>( + that: predicate>((a) { + final first = a.first; + return first.body == 'test log' && + first.spanContext?.spanId == parent.spanContext.spanId && + first.severityNumber == api.Severity.fatal3; + }), + ))).called(1); + }); + + test('processor shut down', () async { + final exporter = MockLogRecordExporter(); + when(exporter.shutdown).thenAnswer((_) async => sdk.ExportResult(code: sdk.ExportResultCode.success)); + + final processor = sdk.BatchLogRecordProcessor( + exporter: exporter, + scheduledDelayMillis: Duration.zero.inMilliseconds, + ); + + await processor.shutdown(); + + verify(exporter.shutdown).called(1); + }); + + test('processor shut down will not emit log', () async { + final exporter = MockLogRecordExporter(); + when(exporter.shutdown).thenAnswer((_) async => sdk.ExportResult(code: sdk.ExportResultCode.success)); + + final processor = sdk.BatchLogRecordProcessor( + exporter: exporter, + scheduledDelayMillis: Duration.zero.inMilliseconds, + ); + + await processor.shutdown(); + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + logRecordLimits: LogRecordLimitsImpl(), + ); + final logRecordA = logRecord + ..body = 'test log' + ..severityNumber = api.Severity.fatal3; + processor.onEmit(logRecordA); + + await Future.delayed(const Duration(milliseconds: 100)); + + verifyNever(() => exporter.export(any())); + }); + + test('processor force flush', () async { + final exporter = MockLogRecordExporter(); + when(() => exporter.export(any())).thenAnswer((_) async => sdk.ExportResult(code: sdk.ExportResultCode.success)); + + final processor = sdk.BatchLogRecordProcessor( + exporter: exporter, + scheduledDelayMillis: Duration(seconds: 5).inMilliseconds, + ); + + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + logRecordLimits: LogRecordLimitsImpl(), + ); + final logRecordA = logRecord + ..body = 'test log' + ..severityNumber = api.Severity.fatal3; + processor.onEmit(logRecordA); + + await processor.forceFlush(); + + verify(() => exporter.export(any>( + that: predicate>((a) { + final first = a.first; + return first.body == 'test log' && first.severityNumber == api.Severity.fatal3; + }), + ))).called(1); + }); +} From 4ac0c9a3d9f4db5006e9948e8c0672ce47b44fc5 Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 18 Jan 2025 09:39:40 +0800 Subject: [PATCH 44/61] wip: add SimpleLogRecordProcessor --- .../simple_log_record_processor.dart | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 lib/src/sdk/logs/processors/simple_log_record_processor.dart diff --git a/lib/src/sdk/logs/processors/simple_log_record_processor.dart b/lib/src/sdk/logs/processors/simple_log_record_processor.dart new file mode 100644 index 00000000..63fa7914 --- /dev/null +++ b/lib/src/sdk/logs/processors/simple_log_record_processor.dart @@ -0,0 +1,49 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +import 'dart:async'; + +import 'package:logging/logging.dart' as logging; +import 'package:meta/meta.dart'; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; + +class SimpleLogRecordProcessor implements sdk.LogRecordProcessor { + final logger = logging.Logger('opentelemetry.sdk.logs.simplelogrecordprocessor'); + final sdk.LogRecordExporter exporter; + bool _shutDownOnce = false; + + @visibleForTesting + final exportsCompletion = []; + + SimpleLogRecordProcessor({required this.exporter}); + + bool _isForcedFlushed = false; + + @override + void onEmit(sdk.ReadableLogRecord logRecord) { + if (_shutDownOnce) return; + final completer = Completer(); + exportsCompletion.add(completer); + exporter.export([logRecord]).then((result) { + if (result.code != sdk.ExportResultCode.success) { + logger.shout('SimpleLogRecordProcessor: log record export failed', result.error, result.stackTrace); + } + }).whenComplete(() { + completer.complete(); + if (_isForcedFlushed) return; + exportsCompletion.remove(completer); + }); + } + + @override + Future forceFlush() async { + _isForcedFlushed = true; + await Future.forEach(exportsCompletion, (completer) => completer.future); + } + + @override + Future shutdown() async { + _shutDownOnce = true; + await exporter.shutdown(); + } +} From d3de3f257e4a1bb86c402e1f107f37ccbd2515e5 Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 18 Jan 2025 09:39:48 +0800 Subject: [PATCH 45/61] wip: unit test SimpleLogRecordProcessor --- .../simple_log_record_processor_test.dart | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 test/unit/sdk/logs/processors/simple_log_record_processor_test.dart diff --git a/test/unit/sdk/logs/processors/simple_log_record_processor_test.dart b/test/unit/sdk/logs/processors/simple_log_record_processor_test.dart new file mode 100644 index 00000000..57ed5d4c --- /dev/null +++ b/test/unit/sdk/logs/processors/simple_log_record_processor_test.dart @@ -0,0 +1,90 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +@TestOn('vm') +import 'dart:async'; + +import 'package:fixnum/fixnum.dart'; +import 'package:logging/logging.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; +import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart'; +import 'package:opentelemetry/src/sdk/logs/processors/simple_log_record_processor.dart'; +import 'package:test/test.dart'; + +import '../../../mocks.dart'; + +void main() { + late sdk.LogRecordExporter exporter; + late sdk.LogRecordProcessor processor; + + setUp(() { + exporter = MockLogRecordExporter(); + processor = SimpleLogRecordProcessor(exporter: exporter); + when(() => exporter.export(any())).thenAnswer((_) async => sdk.ExportResult(code: sdk.ExportResultCode.success)); + when(() => exporter.shutdown()).thenAnswer((_) => Future.value()); + }); + + test('executes export', () { + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + logRecordLimits: LogRecordLimitsImpl(), + ); + + processor.onEmit(logRecord); + + verify(() => exporter.export([logRecord])).called(1); + }); + + test('executes export and fail', () async { + var errorMessage = ''; + Logger.root.onRecord.listen((data) { + errorMessage = data.message; + }); + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + logRecordLimits: LogRecordLimitsImpl(), + ); + + when(() => exporter.export(any())).thenAnswer((_) async => sdk.ExportResult(code: sdk.ExportResultCode.failed)); + + processor.onEmit(logRecord); + + await Future.delayed(const Duration(milliseconds: 50)); + + expect(errorMessage, 'SimpleLogRecordProcessor: log record export failed'); + }); + + test('shutdown exporters on forced flush', () async { + await processor.shutdown(); + + verify(exporter.shutdown).called(1); + }); + + test('forceFlush waits for all pending exports to complete', () async { + when(() => exporter.export(any())).thenAnswer((_) async { + await Future.delayed(const Duration(seconds: 1)); + return sdk.ExportResult(code: sdk.ExportResultCode.success); + }); + + // Emit two log records, creating two pending exports. + processor.onEmit( + sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + logRecordLimits: LogRecordLimitsImpl(), + timeProvider: FakeTimeProvider(now: Int64(123))), + ); + await Future.delayed(Duration(milliseconds: 50)); + processor.onEmit( + sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + logRecordLimits: LogRecordLimitsImpl(), + timeProvider: FakeTimeProvider(now: Int64(123))), + ); + expect((processor as SimpleLogRecordProcessor).exportsCompletion.length, 2); + // Ensure the exports are pending. + final flushFuture = processor.forceFlush(); + expect(flushFuture, completes); + }); +} From 4b3257906e54988370486f7eb3080dd750e1ebb8 Mon Sep 17 00:00:00 2001 From: yusuf Date: Mon, 3 Mar 2025 10:24:05 +0800 Subject: [PATCH 46/61] feat: rebase from logger-provider and fix error --- .../logs/processors/batch_log_record_processor_test.dart | 8 ++++---- .../logs/processors/simple_log_record_processor_test.dart | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/unit/sdk/logs/processors/batch_log_record_processor_test.dart b/test/unit/sdk/logs/processors/batch_log_record_processor_test.dart index 62bb6b8e..6d71ec93 100644 --- a/test/unit/sdk/logs/processors/batch_log_record_processor_test.dart +++ b/test/unit/sdk/logs/processors/batch_log_record_processor_test.dart @@ -27,7 +27,7 @@ void main() { final context = api.contextWithSpan(api.Context.current, parent); final logRecord = sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), context: context, timeProvider: FakeTimeProvider(now: Int64(123))); final logRecordA = logRecord @@ -40,7 +40,7 @@ void main() { that: predicate>((a) { final first = a.first; return first.body == 'test log' && - first.spanContext?.spanId == parent.spanContext.spanId && + first.spanContext.spanId == parent.spanContext.spanId && first.severityNumber == api.Severity.fatal3; }), ))).called(1); @@ -72,7 +72,7 @@ void main() { await processor.shutdown(); final logRecord = sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), ); final logRecordA = logRecord ..body = 'test log' @@ -95,7 +95,7 @@ void main() { final logRecord = sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), ); final logRecordA = logRecord ..body = 'test log' diff --git a/test/unit/sdk/logs/processors/simple_log_record_processor_test.dart b/test/unit/sdk/logs/processors/simple_log_record_processor_test.dart index 57ed5d4c..1a6ba544 100644 --- a/test/unit/sdk/logs/processors/simple_log_record_processor_test.dart +++ b/test/unit/sdk/logs/processors/simple_log_record_processor_test.dart @@ -29,7 +29,7 @@ void main() { test('executes export', () { final logRecord = sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), ); processor.onEmit(logRecord); @@ -44,7 +44,7 @@ void main() { }); final logRecord = sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), ); when(() => exporter.export(any())).thenAnswer((_) async => sdk.ExportResult(code: sdk.ExportResultCode.failed)); @@ -72,14 +72,14 @@ void main() { processor.onEmit( sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), timeProvider: FakeTimeProvider(now: Int64(123))), ); await Future.delayed(Duration(milliseconds: 50)); processor.onEmit( sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), timeProvider: FakeTimeProvider(now: Int64(123))), ); expect((processor as SimpleLogRecordProcessor).exportsCompletion.length, 2); From 9eed89d57e274caf80899117b68a5bcb0f52fbc3 Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 18 Jan 2025 10:07:55 +0800 Subject: [PATCH 47/61] wip: add ConsoleLogRecordExporter --- lib/src/experimental_sdk.dart | 1 + .../console_log_record_exporter.dart | 64 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 lib/src/sdk/logs/exporters/console_log_record_exporter.dart diff --git a/lib/src/experimental_sdk.dart b/lib/src/experimental_sdk.dart index 1013c2e9..7fb78bc1 100644 --- a/lib/src/experimental_sdk.dart +++ b/lib/src/experimental_sdk.dart @@ -7,6 +7,7 @@ library experimental_sdk; import 'package:meta/meta.dart'; export 'sdk/logs/export_result.dart' show ExportResult, ExportResultCode; +export 'sdk/logs/exporters/console_log_record_exporter.dart' show ConsoleLogRecordExporter; export 'sdk/logs/exporters/log_record_exporter.dart' show LogRecordExporter; export 'sdk/logs/log_record.dart' show ReadableLogRecord, ReadWriteLogRecord, LogRecord; export 'sdk/logs/log_record_limit.dart' show LogRecordLimits; diff --git a/lib/src/sdk/logs/exporters/console_log_record_exporter.dart b/lib/src/sdk/logs/exporters/console_log_record_exporter.dart new file mode 100644 index 00000000..45b9f677 --- /dev/null +++ b/lib/src/sdk/logs/exporters/console_log_record_exporter.dart @@ -0,0 +1,64 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +import 'package:opentelemetry/api.dart' as api; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; + +/// This is implementation of [sdk.ReadWriteLogRecordExporter] that prints LogRecords to the +/// console. This class can be used for diagnostic purposes. +/// +/// NOTE: This [sdk.LogRecordExporter] is intended for diagnostics use only, output rendered to the console may change at any time. +class ConsoleLogRecordExporter implements sdk.LogRecordExporter { + @override + Future export(List logs) async { + return _sendLogRecords(logs); + } + + /// Shutdown the exporter. + @override + Future shutdown() async {} + + /// Showing logs in console + sdk.ExportResult _sendLogRecords(List logs) { + for (final log in logs) { + print(_makeObject(log)); + } + return sdk.ExportResult(code: sdk.ExportResultCode.success); + } + + /// converts logRecord info into more readable format + Map _makeObject(sdk.ReadableLogRecord log) { + final contextInfo = {}; + if (log.spanContext != null) { + contextInfo.addAll({ + 'traceId': log.spanContext!.traceId, + 'spanId': log.spanContext!.spanId, + 'traceFlags': log.spanContext!.traceFlags, + }); + } + return { + 'resource': { + 'attributes': { + for (final attribute in log.resource?.attributes.keys ?? []) + attribute: log.resource!.attributes.get(attribute), + }, + }, + 'instrumentationScope': { + 'name': log.instrumentationScope?.name, + 'version': log.instrumentationScope?.version, + 'schemaUrl': log.instrumentationScope?.schemaUrl, + 'attributes': { + for (final attribute in log.instrumentationScope?.attributes ?? []) attribute.key: attribute.value, + } + }, + 'timestamp': log.timeStamp, + 'severityText': log.severityText, + 'severityNumber': log.severityNumber, + 'body': log.body, + 'attributes': { + for (final attribute in log.attributes?.keys ?? []) attribute: log.resource!.attributes.get(attribute), + }, + ...contextInfo, + }; + } +} From 2b7a52abe0b7cca77f1ce9932227e6d57482383d Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 18 Jan 2025 10:08:04 +0800 Subject: [PATCH 48/61] wip: unit test ConsoleLogRecordExporter --- test/unit/mocks.dart | 13 ++++- .../console_log_record_exporter_test.dart | 50 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 test/unit/sdk/logs/exporters/console_log_record_exporter_test.dart diff --git a/test/unit/mocks.dart b/test/unit/mocks.dart index aca47917..8b8ab42a 100644 --- a/test/unit/mocks.dart +++ b/test/unit/mocks.dart @@ -1,6 +1,8 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information +import 'dart:async'; + import 'package:fixnum/fixnum.dart'; import 'package:http/http.dart' as http; import 'package:mocktail/mocktail.dart'; @@ -29,4 +31,13 @@ class FakeTimeProvider extends Mock implements TimeProvider { @override Int64 get now => _now; -} \ No newline at end of file +} + +// reference: https://stackoverflow.com/a/38709440/7676003 +void Function() overridePrint(void Function() testFn, Function(String msg) onPrint) => () { + final spec = ZoneSpecification(print: (_, __, ___, msg) { + // Add to log instead of printing to stdout + onPrint(msg); + }); + return Zone.current.fork(specification: spec).run(testFn); + }; diff --git a/test/unit/sdk/logs/exporters/console_log_record_exporter_test.dart b/test/unit/sdk/logs/exporters/console_log_record_exporter_test.dart new file mode 100644 index 00000000..8a95c2da --- /dev/null +++ b/test/unit/sdk/logs/exporters/console_log_record_exporter_test.dart @@ -0,0 +1,50 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +@TestOn('vm') +import 'package:fixnum/fixnum.dart'; +import 'package:opentelemetry/api.dart' as api; +import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; +import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart'; +import 'package:test/test.dart'; + +import '../../../mocks.dart'; + +void main() { + List log = []; + + tearDown(() { + log = []; + }); + + test( + 'Test exporter', + overridePrint( + () async { + final exporter = sdk.ConsoleLogRecordExporter(); + final tracer = sdk.TracerProviderBase().getTracer('test'); + final parent = tracer.startSpan('parent'); + final context = api.contextWithSpan(api.Context.current, parent); + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + context: context, + logRecordLimits: LogRecordLimitsImpl(), + timeProvider: FakeTimeProvider(now: Int64(123)), + resource: sdk.Resource([api.Attribute.fromString('resource.name', 'test')]), + ) + ..makeReadonly() + ..body = 'Log Message'; + final spanContext = parent.spanContext; + + await exporter.export([logRecord]); + + expect(log, [ + '{resource: {attributes: {resource.name: test}}, instrumentationScope: {name: library_name, version: library_version, schemaUrl: url://schema, attributes: {}}, timestamp: 123, severityText: null, severityNumber: null, body: null, attributes: {}, traceId: ${spanContext.traceId}, spanId: ${spanContext.spanId}, traceFlags: ${spanContext.traceFlags}}' + ]); + + await exporter.shutdown(); + }, + log.add, + )); +} From 3f8ed8f6aebfa24f8ac9793958fc93bd6c8fe682 Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 18 Jan 2025 10:52:10 +0800 Subject: [PATCH 49/61] wip: add InMemoryLogRecordExporter --- lib/src/experimental_sdk.dart | 1 + .../inmemory_log_record_exporter.dart | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 lib/src/sdk/logs/exporters/inmemory_log_record_exporter.dart diff --git a/lib/src/experimental_sdk.dart b/lib/src/experimental_sdk.dart index 7fb78bc1..654c5317 100644 --- a/lib/src/experimental_sdk.dart +++ b/lib/src/experimental_sdk.dart @@ -8,6 +8,7 @@ import 'package:meta/meta.dart'; export 'sdk/logs/export_result.dart' show ExportResult, ExportResultCode; export 'sdk/logs/exporters/console_log_record_exporter.dart' show ConsoleLogRecordExporter; +export 'sdk/logs/exporters/inmemory_log_record_exporter.dart' show InMemoryLogRecordExporter; export 'sdk/logs/exporters/log_record_exporter.dart' show LogRecordExporter; export 'sdk/logs/log_record.dart' show ReadableLogRecord, ReadWriteLogRecord, LogRecord; export 'sdk/logs/log_record_limit.dart' show LogRecordLimits; diff --git a/lib/src/sdk/logs/exporters/inmemory_log_record_exporter.dart b/lib/src/sdk/logs/exporters/inmemory_log_record_exporter.dart new file mode 100644 index 00000000..f2da176c --- /dev/null +++ b/lib/src/sdk/logs/exporters/inmemory_log_record_exporter.dart @@ -0,0 +1,43 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +import 'package:meta/meta.dart'; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; + +/// This class can be used for testing purposes. It stores the exported LogRecords +/// in a list in memory that can be retrieved using the `getFinishedLogRecords()` +/// method. +class InMemoryLogRecordExporter implements sdk.LogRecordExporter { + var _finishedLogRecords = []; + + /// Indicates if the exporter has been "shutdown." + /// When false, exported log records will not be stored in-memory. + @protected + bool _stopped = false; + + @override + Future export(List logs) async { + if (_stopped) { + return sdk.ExportResult( + code: sdk.ExportResultCode.failed, + error: Exception('Exporter has been stopped'), + stackTrace: StackTrace.current, + ); + } + _finishedLogRecords.addAll(logs); + + return sdk.ExportResult(code: sdk.ExportResultCode.success); + } + + @override + Future shutdown() async { + _stopped = true; + reset(); + } + + List get finishedLogRecords => List.unmodifiable(_finishedLogRecords); + + void reset() { + _finishedLogRecords = []; + } +} From 1f8ee91d42e62ef24cfdb8d92bf2177c55b29549 Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 18 Jan 2025 10:52:36 +0800 Subject: [PATCH 50/61] wip: unit test InMemoryLogRecordExporter --- .../inmemory_log_record_exporter_test.dart | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/unit/sdk/logs/exporters/inmemory_log_record_exporter_test.dart diff --git a/test/unit/sdk/logs/exporters/inmemory_log_record_exporter_test.dart b/test/unit/sdk/logs/exporters/inmemory_log_record_exporter_test.dart new file mode 100644 index 00000000..0285f00b --- /dev/null +++ b/test/unit/sdk/logs/exporters/inmemory_log_record_exporter_test.dart @@ -0,0 +1,35 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +@TestOn('vm') +import 'package:fixnum/fixnum.dart'; +import 'package:opentelemetry/api.dart' as api; +import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; +import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart'; +import 'package:test/test.dart'; + +import '../../../mocks.dart'; + +void main() { + test('Test exporter', () async { + final exporter = sdk.InMemoryLogRecordExporter(); + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + logRecordLimits: LogRecordLimitsImpl(), + timeProvider: FakeTimeProvider(now: Int64(123)), + resource: sdk.Resource([api.Attribute.fromString('resource.name', 'test')]), + ) + ..makeReadonly() + ..body = 'Log Message'; + + await exporter.export([logRecord]); + + expect(exporter.finishedLogRecords.length, 1); + expect(exporter.finishedLogRecords.first.instrumentationScope?.name, 'library_name'); + + await exporter.shutdown(); + + expect(exporter.finishedLogRecords.length, 0); + }); +} From 1f6b041e8de05f5c3838c0f33b322f2ac6a021cc Mon Sep 17 00:00:00 2001 From: yusuf Date: Mon, 3 Mar 2025 10:26:03 +0800 Subject: [PATCH 51/61] wip: fix test error --- .../sdk/logs/exporters/console_log_record_exporter_test.dart | 2 +- .../sdk/logs/exporters/inmemory_log_record_exporter_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/sdk/logs/exporters/console_log_record_exporter_test.dart b/test/unit/sdk/logs/exporters/console_log_record_exporter_test.dart index 8a95c2da..ac35f0f7 100644 --- a/test/unit/sdk/logs/exporters/console_log_record_exporter_test.dart +++ b/test/unit/sdk/logs/exporters/console_log_record_exporter_test.dart @@ -29,7 +29,7 @@ void main() { final logRecord = sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), context: context, - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), timeProvider: FakeTimeProvider(now: Int64(123)), resource: sdk.Resource([api.Attribute.fromString('resource.name', 'test')]), ) diff --git a/test/unit/sdk/logs/exporters/inmemory_log_record_exporter_test.dart b/test/unit/sdk/logs/exporters/inmemory_log_record_exporter_test.dart index 0285f00b..fbc5d62b 100644 --- a/test/unit/sdk/logs/exporters/inmemory_log_record_exporter_test.dart +++ b/test/unit/sdk/logs/exporters/inmemory_log_record_exporter_test.dart @@ -16,7 +16,7 @@ void main() { final exporter = sdk.InMemoryLogRecordExporter(); final logRecord = sdk.LogRecord( instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), - logRecordLimits: LogRecordLimitsImpl(), + logRecordLimits: LogRecordLimits(), timeProvider: FakeTimeProvider(now: Int64(123)), resource: sdk.Resource([api.Attribute.fromString('resource.name', 'test')]), ) From f7d9433785a0387eda3a9c0b7ed517410f0b034b Mon Sep 17 00:00:00 2001 From: yusuf Date: Mon, 3 Mar 2025 10:27:09 +0800 Subject: [PATCH 52/61] wip: fix lint --- .../console_log_record_exporter.dart | 27 +++++++++---------- .../inmemory_log_record_exporter_test.dart | 2 +- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/lib/src/sdk/logs/exporters/console_log_record_exporter.dart b/lib/src/sdk/logs/exporters/console_log_record_exporter.dart index 45b9f677..b1650aa9 100644 --- a/lib/src/sdk/logs/exporters/console_log_record_exporter.dart +++ b/lib/src/sdk/logs/exporters/console_log_record_exporter.dart @@ -1,7 +1,6 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information -import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; /// This is implementation of [sdk.ReadWriteLogRecordExporter] that prints LogRecords to the @@ -29,26 +28,24 @@ class ConsoleLogRecordExporter implements sdk.LogRecordExporter { /// converts logRecord info into more readable format Map _makeObject(sdk.ReadableLogRecord log) { final contextInfo = {}; - if (log.spanContext != null) { - contextInfo.addAll({ - 'traceId': log.spanContext!.traceId, - 'spanId': log.spanContext!.spanId, - 'traceFlags': log.spanContext!.traceFlags, - }); - } + contextInfo.addAll({ + 'traceId': log.spanContext.traceId, + 'spanId': log.spanContext.spanId, + 'traceFlags': log.spanContext.traceFlags, + }); return { 'resource': { 'attributes': { - for (final attribute in log.resource?.attributes.keys ?? []) - attribute: log.resource!.attributes.get(attribute), + for (final attribute in log.resource.attributes.keys) + attribute: log.resource.attributes.get(attribute), }, }, 'instrumentationScope': { - 'name': log.instrumentationScope?.name, - 'version': log.instrumentationScope?.version, - 'schemaUrl': log.instrumentationScope?.schemaUrl, + 'name': log.instrumentationScope.name, + 'version': log.instrumentationScope.version, + 'schemaUrl': log.instrumentationScope.schemaUrl, 'attributes': { - for (final attribute in log.instrumentationScope?.attributes ?? []) attribute.key: attribute.value, + for (final attribute in log.instrumentationScope.attributes) attribute.key: attribute.value, } }, 'timestamp': log.timeStamp, @@ -56,7 +53,7 @@ class ConsoleLogRecordExporter implements sdk.LogRecordExporter { 'severityNumber': log.severityNumber, 'body': log.body, 'attributes': { - for (final attribute in log.attributes?.keys ?? []) attribute: log.resource!.attributes.get(attribute), + for (final attribute in log.attributes.keys) attribute: log.resource.attributes.get(attribute), }, ...contextInfo, }; diff --git a/test/unit/sdk/logs/exporters/inmemory_log_record_exporter_test.dart b/test/unit/sdk/logs/exporters/inmemory_log_record_exporter_test.dart index fbc5d62b..87ad915a 100644 --- a/test/unit/sdk/logs/exporters/inmemory_log_record_exporter_test.dart +++ b/test/unit/sdk/logs/exporters/inmemory_log_record_exporter_test.dart @@ -26,7 +26,7 @@ void main() { await exporter.export([logRecord]); expect(exporter.finishedLogRecords.length, 1); - expect(exporter.finishedLogRecords.first.instrumentationScope?.name, 'library_name'); + expect(exporter.finishedLogRecords.first.instrumentationScope.name, 'library_name'); await exporter.shutdown(); From efb46dc2743ff43ee39465fb0a2aa47ecfd24c2c Mon Sep 17 00:00:00 2001 From: yusuf Date: Mon, 3 Mar 2025 10:40:49 +0800 Subject: [PATCH 53/61] fix: test --- .../logs/exporters/console_log_record_exporter_test.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/unit/sdk/logs/exporters/console_log_record_exporter_test.dart b/test/unit/sdk/logs/exporters/console_log_record_exporter_test.dart index ac35f0f7..a0bdf8fb 100644 --- a/test/unit/sdk/logs/exporters/console_log_record_exporter_test.dart +++ b/test/unit/sdk/logs/exporters/console_log_record_exporter_test.dart @@ -5,6 +5,7 @@ import 'package:fixnum/fixnum.dart'; import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/experimental_api.dart' as api; import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart'; import 'package:test/test.dart'; @@ -22,6 +23,8 @@ void main() { 'Test exporter', overridePrint( () async { + final timeProvider = FakeTimeProvider(now: Int64(123)); + final severityDefault = api.Severity.unspecified; final exporter = sdk.ConsoleLogRecordExporter(); final tracer = sdk.TracerProviderBase().getTracer('test'); final parent = tracer.startSpan('parent'); @@ -30,7 +33,7 @@ void main() { instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), context: context, logRecordLimits: LogRecordLimits(), - timeProvider: FakeTimeProvider(now: Int64(123)), + timeProvider: timeProvider, resource: sdk.Resource([api.Attribute.fromString('resource.name', 'test')]), ) ..makeReadonly() @@ -40,7 +43,7 @@ void main() { await exporter.export([logRecord]); expect(log, [ - '{resource: {attributes: {resource.name: test}}, instrumentationScope: {name: library_name, version: library_version, schemaUrl: url://schema, attributes: {}}, timestamp: 123, severityText: null, severityNumber: null, body: null, attributes: {}, traceId: ${spanContext.traceId}, spanId: ${spanContext.spanId}, traceFlags: ${spanContext.traceFlags}}' + '{resource: {attributes: {resource.name: test}}, instrumentationScope: {name: library_name, version: library_version, schemaUrl: url://schema, attributes: {}}, timestamp: ${DateTime.fromMicrosecondsSinceEpoch(timeProvider.now.toInt() ~/ 1000)}, severityText: ${severityDefault.name}, severityNumber: $severityDefault, body: null, attributes: {}, traceId: ${spanContext.traceId}, spanId: ${spanContext.spanId}, traceFlags: ${spanContext.traceFlags}}' ]); await exporter.shutdown(); From 67ad6c4765aecbe3eff2830ad0cb2a7cc3b9ee2c Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 18 Jan 2025 09:35:50 +0800 Subject: [PATCH 54/61] wip: add ExportResult --- lib/src/experimental_sdk.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/experimental_sdk.dart b/lib/src/experimental_sdk.dart index 654c5317..036c3c99 100644 --- a/lib/src/experimental_sdk.dart +++ b/lib/src/experimental_sdk.dart @@ -15,6 +15,7 @@ export 'sdk/logs/log_record_limit.dart' show LogRecordLimits; export 'sdk/logs/logger.dart' show Logger; export 'sdk/logs/logger_provider.dart' show LoggerProvider; export 'sdk/logs/processors/batch_log_record_processor.dart' show BatchLogRecordProcessor; +export 'sdk/logs/export_result.dart' show ExportResult, ExportResultCode; export 'sdk/logs/processors/log_record_processor.dart' show LogRecordProcessor; export 'sdk/metrics/counter.dart' show Counter; export 'sdk/metrics/meter.dart' show Meter; From 430bd73f1ed37c11e5afe0fd52327859a01c65fd Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 18 Jan 2025 09:36:42 +0800 Subject: [PATCH 55/61] wip: add LogRecordExporter --- lib/src/experimental_sdk.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/experimental_sdk.dart b/lib/src/experimental_sdk.dart index 036c3c99..654c5317 100644 --- a/lib/src/experimental_sdk.dart +++ b/lib/src/experimental_sdk.dart @@ -15,7 +15,6 @@ export 'sdk/logs/log_record_limit.dart' show LogRecordLimits; export 'sdk/logs/logger.dart' show Logger; export 'sdk/logs/logger_provider.dart' show LoggerProvider; export 'sdk/logs/processors/batch_log_record_processor.dart' show BatchLogRecordProcessor; -export 'sdk/logs/export_result.dart' show ExportResult, ExportResultCode; export 'sdk/logs/processors/log_record_processor.dart' show LogRecordProcessor; export 'sdk/metrics/counter.dart' show Counter; export 'sdk/metrics/meter.dart' show Meter; From 80cbb6924120df0b896e8a2fc73812acc36009be Mon Sep 17 00:00:00 2001 From: yusuf Date: Sat, 18 Jan 2025 10:07:55 +0800 Subject: [PATCH 56/61] wip: add ConsoleLogRecordExporter --- lib/src/sdk/logs/exporters/console_log_record_exporter.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/sdk/logs/exporters/console_log_record_exporter.dart b/lib/src/sdk/logs/exporters/console_log_record_exporter.dart index b1650aa9..4e32b083 100644 --- a/lib/src/sdk/logs/exporters/console_log_record_exporter.dart +++ b/lib/src/sdk/logs/exporters/console_log_record_exporter.dart @@ -1,6 +1,7 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information +import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; /// This is implementation of [sdk.ReadWriteLogRecordExporter] that prints LogRecords to the From f499433a20eb6c58fe886235818545ea9dba0a31 Mon Sep 17 00:00:00 2001 From: yusuf Date: Tue, 21 Jan 2025 00:43:45 +0800 Subject: [PATCH 57/61] feat: OTLPLogExporter --- lib/src/experimental_sdk.dart | 2 + .../sdk/logs/exporters/otlp_log_exporter.dart | 178 +++++++++++++ .../exporters/otlp_log_exporter_test.dart | 235 ++++++++++++++++++ 3 files changed, 415 insertions(+) create mode 100644 lib/src/sdk/logs/exporters/otlp_log_exporter.dart create mode 100644 test/unit/sdk/logs/exporters/otlp_log_exporter_test.dart diff --git a/lib/src/experimental_sdk.dart b/lib/src/experimental_sdk.dart index 654c5317..f7b33375 100644 --- a/lib/src/experimental_sdk.dart +++ b/lib/src/experimental_sdk.dart @@ -11,6 +11,8 @@ export 'sdk/logs/exporters/console_log_record_exporter.dart' show ConsoleLogReco export 'sdk/logs/exporters/inmemory_log_record_exporter.dart' show InMemoryLogRecordExporter; export 'sdk/logs/exporters/log_record_exporter.dart' show LogRecordExporter; export 'sdk/logs/log_record.dart' show ReadableLogRecord, ReadWriteLogRecord, LogRecord; +export 'sdk/logs/exporters/otlp_log_exporter.dart' show OTLPLogExporter; +export 'sdk/logs/log_record.dart' show ReadableLogRecord, ReadWriteLogRecord, LogRecord; export 'sdk/logs/log_record_limit.dart' show LogRecordLimits; export 'sdk/logs/logger.dart' show Logger; export 'sdk/logs/logger_provider.dart' show LoggerProvider; diff --git a/lib/src/sdk/logs/exporters/otlp_log_exporter.dart b/lib/src/sdk/logs/exporters/otlp_log_exporter.dart new file mode 100644 index 00000000..0d04caf6 --- /dev/null +++ b/lib/src/sdk/logs/exporters/otlp_log_exporter.dart @@ -0,0 +1,178 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +import 'dart:async'; + +import 'package:fixnum/fixnum.dart'; +import 'package:http/http.dart' as http; +import 'package:logging/logging.dart'; +import 'package:opentelemetry/api.dart' as api; +import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/experimental_api.dart' as api; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; + +import '../../proto/opentelemetry/proto/collector/logs/v1/logs_service.pb.dart' as pb_logs_service; +import '../../proto/opentelemetry/proto/common/v1/common.pb.dart' as pb_common; +import '../../proto/opentelemetry/proto/logs/v1/logs.pb.dart' as pb_logs; +import '../../proto/opentelemetry/proto/logs/v1/logs.pbenum.dart' as pg_logs_enum; +import '../../proto/opentelemetry/proto/resource/v1/resource.pb.dart' as pb_resource; + +class OTLPLogExporter implements sdk.LogRecordExporter { + final Logger _log = Logger('opentelemetry.LogCollectorExporter'); + + final Uri uri; + final http.Client client; + final Map headers; + var _isShutdown = false; + + OTLPLogExporter( + this.uri, { + http.Client? httpClient, + this.headers = const {}, + }) : client = httpClient ?? http.Client(); + + @override + Future export(List logRecords) async { + if (_isShutdown) { + return sdk.ExportResult( + code: sdk.ExportResultCode.failed, + ); + } + + if (logRecords.isEmpty) { + return sdk.ExportResult( + code: sdk.ExportResultCode.success, + ); + } + + return _send(uri, logRecords).then((_) { + return sdk.ExportResult( + code: sdk.ExportResultCode.success, + ); + }).catchError((e, st) { + return sdk.ExportResult( + code: sdk.ExportResultCode.failed, + error: e, + stackTrace: st, + ); + }); + } + + Future _send( + Uri uri, + List logRecords, + ) async { + try { + final body = pb_logs_service.ExportLogsServiceRequest(resourceLogs: _logsToProtobuf(logRecords)); + + final headers = {'Content-Type': 'application/x-protobuf'}..addAll(this.headers); + + await client.post(uri, body: body.writeToBuffer(), headers: headers); + } catch (e) { + _log.warning('Failed to export ${logRecords.length} logs.', e); + } + } + + @override + Future shutdown() async { + _isShutdown = true; + client.close(); + } + + /// Group and construct the protobuf equivalent of the given list of [api.LogRecord]s. + /// Logs are grouped by a trace provider's [sdk.Resource] and a tracer's + /// [sdk.InstrumentationScope]. + Iterable _logsToProtobuf(List logRecords) { + // use a map of maps to group spans by resource and instrumentation library + final rsm = >>{}; + for (final logRecord in logRecords) { + final il = rsm[logRecord.resource] ?? >{}; + + if (logRecord.instrumentationScope != null) { + il[logRecord.instrumentationScope!] = il[logRecord.instrumentationScope] ?? [] + ..add(_logToProtobuf(logRecord)); + } + if (logRecord.resource != null) { + rsm[logRecord.resource!] = il; + } + } + + final rss = []; + for (final il in rsm.entries) { + // for each distinct resource, construct the protobuf equivalent + final attrs = []; + for (final attr in il.key.attributes.keys) { + attrs.add(pb_common.KeyValue(key: attr, value: _attributeValueToProtobuf(il.key.attributes.get(attr)!))); + } + + final rs = pb_logs.ResourceLogs(resource: pb_resource.Resource(attributes: attrs)); + // for each distinct instrumentation library, construct the protobuf equivalent + for (final ils in il.value.entries) { + rs.scopeLogs.add(pb_logs.ScopeLogs( + logRecords: ils.value, + scope: pb_common.InstrumentationScope(name: ils.key.name, version: ils.key.version))); + } + rss.add(rs); + } + return rss; + } + + pb_logs.LogRecord _logToProtobuf(sdk.ReadableLogRecord log) { + var spanId = []; + var traceId = []; + if (log.spanContext != null) { + spanId = log.spanContext!.spanId.get(); + traceId = log.spanContext!.traceId.get(); + } + return pb_logs.LogRecord( + timeUnixNano: log.timeStamp, + severityNumber: + log.severityNumber != null ? pg_logs_enum.SeverityNumber.valueOf(log.severityNumber!.index) : null, + severityText: log.severityText, + droppedAttributesCount: log.droppedAttributesCount, + body: _attributeONEValueToProtobuf(log.body), + attributes: (log.attributes?.keys ?? []) + .map((key) => pb_common.KeyValue(key: key, value: _attributeValueToProtobuf(log.attributes!.get(key)!))), + spanId: spanId, + traceId: traceId, + observedTimeUnixNano: log.observedTimestamp); + } + + pb_common.AnyValue _attributeONEValueToProtobuf(Object value) { + switch (value.runtimeType) { + case String: + return pb_common.AnyValue(stringValue: value as String); + case bool: + return pb_common.AnyValue(boolValue: value as bool); + case double: + return pb_common.AnyValue(doubleValue: value as double); + case int: + return pb_common.AnyValue(intValue: Int64(value as int)); + } + return pb_common.AnyValue(); + } + + pb_common.AnyValue _attributeValueToProtobuf(Object value) { + if (value is String) { + return pb_common.AnyValue(stringValue: value); + } + if (value is bool) { + return pb_common.AnyValue(boolValue: value); + } + if (value is double) { + return pb_common.AnyValue(doubleValue: value); + } + if (value is int) { + return pb_common.AnyValue(intValue: Int64(value)); + } + if (value is List || value is List || value is List || value is List) { + final output = []; + final values = value as List; + for (final i in values) { + output.add(_attributeValueToProtobuf(i)); + } + return pb_common.AnyValue(arrayValue: pb_common.ArrayValue(values: output)); + } + return pb_common.AnyValue(); + } +} diff --git a/test/unit/sdk/logs/exporters/otlp_log_exporter_test.dart b/test/unit/sdk/logs/exporters/otlp_log_exporter_test.dart new file mode 100644 index 00000000..c1391ecd --- /dev/null +++ b/test/unit/sdk/logs/exporters/otlp_log_exporter_test.dart @@ -0,0 +1,235 @@ +// Copyright 2021-2022 Workiva. +// Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information + +@TestOn('vm') +import 'dart:typed_data'; + +import 'package:fixnum/fixnum.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:opentelemetry/api.dart' as api; +import 'package:opentelemetry/sdk.dart' as sdk; +import 'package:opentelemetry/src/experimental_api.dart' as api; +import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; +import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart'; +import 'package:opentelemetry/src/sdk/proto/opentelemetry/proto/collector/logs/v1/logs_service.pb.dart' +as pb_log_service; +import 'package:opentelemetry/src/sdk/proto/opentelemetry/proto/common/v1/common.pb.dart' as pb_common; +import 'package:opentelemetry/src/sdk/proto/opentelemetry/proto/logs/v1/logs.pb.dart' as pb_logs; +import 'package:opentelemetry/src/sdk/proto/opentelemetry/proto/logs/v1/logs.pbenum.dart' as pg_logs_enum; +import 'package:opentelemetry/src/sdk/proto/opentelemetry/proto/resource/v1/resource.pb.dart' as pb_resource; +import 'package:test/test.dart'; + +import '../../../mocks.dart'; + +void main() { + late MockHttpClient mockClient; + final uri = Uri.parse('https://h.wdesk.org/s/opentelemetry-collector/v1/traces'); + + setUp(() { + mockClient = MockHttpClient(); + }); + + tearDown(() { + reset(mockClient); + }); + + test('sends logs', () { + final resource = sdk.Resource([api.Attribute.fromString('service.name', 'bar')]); + final instrumentationLibrary = sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []); + final logLimit = LogRecordLimitsImpl(attributeCountLimit: 10, attributeValueLengthLimit: 5); + + final tracer = sdk.TracerProviderBase().getTracer('test'); + final parent = tracer.startSpan('parent'); + final context = api.contextWithSpan(api.Context.current, parent); + + final log1 = sdk.LogRecord( + resource: resource, + instrumentationScope: instrumentationLibrary, + body: 'test log', + severityNumber: api.Severity.fatal3, + logRecordLimits: logLimit, + context: context, + timeProvider: FakeTimeProvider(now: Int64(123))) + ..setAttribute('key', 'value'); + + final log2 = sdk.LogRecord( + resource: resource, + instrumentationScope: instrumentationLibrary, + context: context, + body: 2, + severityNumber: api.Severity.fatal3, + attributes: sdk.Attributes.empty() + ..addAll([ + api.Attribute.fromBoolean('fromBoolean', false), + api.Attribute.fromDouble('fromDouble', 1.1), + api.Attribute.fromInt('fromInt', 1), + api.Attribute.fromBooleanList('fromBooleanList', [false]), + api.Attribute.fromDoubleList('fromDoubleList', [1.1]), + api.Attribute.fromIntList('fromIntList', [1]), + ]), + logRecordLimits: logLimit, + timeProvider: FakeTimeProvider(now: Int64(123)), + )..setAttribute('key', 'value'); + + final log3 = sdk.LogRecord( + resource: resource, + instrumentationScope: instrumentationLibrary, + context: context, + body: 2.2, + severityNumber: api.Severity.fatal3, + logRecordLimits: logLimit, + timeProvider: FakeTimeProvider(now: Int64(123))) + ..setAttribute('key', 'value'); + + final log4 = sdk.LogRecord( + resource: resource, + instrumentationScope: instrumentationLibrary, + context: context, + body: true, + severityNumber: api.Severity.fatal3, + logRecordLimits: logLimit, + timeProvider: FakeTimeProvider(now: Int64(123))) + ..setAttribute('key', 'value'); + + sdk.OTLPLogExporter(uri, httpClient: mockClient).export([log1, log2, log3, log4]); + + final expected = pb_log_service.ExportLogsServiceRequest(resourceLogs: [ + pb_logs.ResourceLogs( + resource: pb_resource.Resource( + attributes: [pb_common.KeyValue(key: 'service.name', value: pb_common.AnyValue(stringValue: 'bar'))]), + scopeLogs: [ + pb_logs.ScopeLogs( + logRecords: [ + pb_logs.LogRecord( + timeUnixNano: Int64(123), + severityNumber: pg_logs_enum.SeverityNumber.valueOf(log1.severityNumber!.index), + attributes: [pb_common.KeyValue(key: 'key', value: pb_common.AnyValue(stringValue: 'value'))], + traceId: parent.spanContext.traceId.get(), + spanId: parent.spanContext.spanId.get(), + body: pb_common.AnyValue(stringValue: 'test log'), + observedTimeUnixNano: Int64(123), + droppedAttributesCount: 0, + ), + pb_logs.LogRecord( + timeUnixNano: Int64(123), + severityNumber: pg_logs_enum.SeverityNumber.valueOf(log2.severityNumber!.index), + attributes: [ + pb_common.KeyValue(key: 'fromBoolean', value: pb_common.AnyValue(boolValue: false)), + pb_common.KeyValue(key: 'fromDouble', value: pb_common.AnyValue(doubleValue: 1.1)), + pb_common.KeyValue(key: 'fromInt', value: pb_common.AnyValue(intValue: Int64(1))), + pb_common.KeyValue( + key: 'fromBooleanList', + value: pb_common.AnyValue( + arrayValue: pb_common.ArrayValue(values: [pb_common.AnyValue(boolValue: false)]), + ), + ), + pb_common.KeyValue( + key: 'fromDoubleList', + value: pb_common.AnyValue( + arrayValue: pb_common.ArrayValue(values: [pb_common.AnyValue(doubleValue: 1.1)]), + ), + ), + pb_common.KeyValue( + key: 'fromIntList', + value: pb_common.AnyValue( + arrayValue: pb_common.ArrayValue(values: [pb_common.AnyValue(intValue: Int64(1))]), + ), + ), + pb_common.KeyValue(key: 'key', value: pb_common.AnyValue(stringValue: 'value')), + ], + traceId: parent.spanContext.traceId.get(), + spanId: parent.spanContext.spanId.get(), + body: pb_common.AnyValue(intValue: Int64(2)), + observedTimeUnixNano: Int64(123), + droppedAttributesCount: 0, + ), + pb_logs.LogRecord( + timeUnixNano: Int64(123), + severityNumber: pg_logs_enum.SeverityNumber.valueOf(log1.severityNumber!.index), + attributes: [pb_common.KeyValue(key: 'key', value: pb_common.AnyValue(stringValue: 'value'))], + traceId: parent.spanContext.traceId.get(), + spanId: parent.spanContext.spanId.get(), + body: pb_common.AnyValue(doubleValue: 2.2), + observedTimeUnixNano: Int64(123), + droppedAttributesCount: 0, + ), + pb_logs.LogRecord( + timeUnixNano: Int64(123), + severityNumber: pg_logs_enum.SeverityNumber.valueOf(log1.severityNumber!.index), + attributes: [pb_common.KeyValue(key: 'key', value: pb_common.AnyValue(stringValue: 'value'))], + traceId: parent.spanContext.traceId.get(), + spanId: parent.spanContext.spanId.get(), + body: pb_common.AnyValue(boolValue: true), + observedTimeUnixNano: Int64(123), + droppedAttributesCount: 0, + ), + ], + scope: pb_common.InstrumentationScope(name: 'library_name', version: 'library_version'), + ) + ]) + ]); + final verifyResult = verify(() => + mockClient.post(uri, body: captureAny(named: 'body'), headers: {'Content-Type': 'application/x-protobuf'})) + ..called(1); + final captured = verifyResult.captured; + + final traceRequest = pb_log_service.ExportLogsServiceRequest.fromBuffer(captured[0] as Uint8List); + expect(traceRequest, equals(expected)); + }); + + test('does not send log when shutdown', () { + final instrumentationLibrary = sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []); + final logLimit = LogRecordLimitsImpl(attributeCountLimit: 10, attributeValueLengthLimit: 5); + + final tracer = sdk.TracerProviderBase().getTracer('test'); + final parent = tracer.startSpan('parent'); + final context = api.contextWithSpan(api.Context.current, parent); + + final log1 = sdk.LogRecord( + instrumentationScope: instrumentationLibrary, + context: context, + body: 'test log', + severityNumber: api.Severity.fatal3, + logRecordLimits: logLimit, + timeProvider: FakeTimeProvider(now: Int64(123))) + ..setAttribute('key', 'value'); + + sdk.OTLPLogExporter(uri, httpClient: mockClient) + ..shutdown() + ..export([log1]); + + verify(() => mockClient.close()).called(1); + verifyNever(() => mockClient.post(uri, body: anything, headers: {'Content-Type': 'application/x-protobuf'})); + }); + + test('supplies HTTP headers', () { + final instrumentationLibrary = sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []); + final logLimit = LogRecordLimitsImpl(attributeCountLimit: 10, attributeValueLengthLimit: 5); + + final tracer = sdk.TracerProviderBase().getTracer('test'); + final parent = tracer.startSpan('parent'); + final context = api.contextWithSpan(api.Context.current, parent); + + final log1 = sdk.LogRecord( + instrumentationScope: instrumentationLibrary, + body: 'test log', + severityNumber: api.Severity.fatal3, + logRecordLimits: logLimit, + context: context, + timeProvider: FakeTimeProvider(now: Int64(123))) + ..setAttribute('key', 'value'); + + final suppliedHeaders = { + 'header-param-key-1': 'header-param-value-1', + 'header-param-key-2': 'header-param-value-2', + }; + final expectedHeaders = { + 'Content-Type': 'application/x-protobuf', + ...suppliedHeaders, + }; + + sdk.OTLPLogExporter(uri, httpClient: mockClient, headers: suppliedHeaders).export([log1]); + + verify(() => mockClient.post(uri, body: anything, headers: expectedHeaders)).called(1); + }); +} From 5c7ec7b656d236aecdae5e31cf2e1ccbbc7adad4 Mon Sep 17 00:00:00 2001 From: yusuf Date: Tue, 21 Jan 2025 00:56:17 +0800 Subject: [PATCH 58/61] wip: fix import arrange --- lib/src/experimental_sdk.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/experimental_sdk.dart b/lib/src/experimental_sdk.dart index f7b33375..f68f8ea4 100644 --- a/lib/src/experimental_sdk.dart +++ b/lib/src/experimental_sdk.dart @@ -10,7 +10,6 @@ export 'sdk/logs/export_result.dart' show ExportResult, ExportResultCode; export 'sdk/logs/exporters/console_log_record_exporter.dart' show ConsoleLogRecordExporter; export 'sdk/logs/exporters/inmemory_log_record_exporter.dart' show InMemoryLogRecordExporter; export 'sdk/logs/exporters/log_record_exporter.dart' show LogRecordExporter; -export 'sdk/logs/log_record.dart' show ReadableLogRecord, ReadWriteLogRecord, LogRecord; export 'sdk/logs/exporters/otlp_log_exporter.dart' show OTLPLogExporter; export 'sdk/logs/log_record.dart' show ReadableLogRecord, ReadWriteLogRecord, LogRecord; export 'sdk/logs/log_record_limit.dart' show LogRecordLimits; From 3f2eeec23a4234d5cd01436e14931e917f89bfbc Mon Sep 17 00:00:00 2001 From: yusuf Date: Mon, 3 Mar 2025 10:34:57 +0800 Subject: [PATCH 59/61] wip: fix lint --- .../sdk/logs/exporters/otlp_log_exporter.dart | 39 ++++++---------- .../exporters/otlp_log_exporter_test.dart | 45 +++++++++---------- 2 files changed, 36 insertions(+), 48 deletions(-) diff --git a/lib/src/sdk/logs/exporters/otlp_log_exporter.dart b/lib/src/sdk/logs/exporters/otlp_log_exporter.dart index 0d04caf6..cc6b2caa 100644 --- a/lib/src/sdk/logs/exporters/otlp_log_exporter.dart +++ b/lib/src/sdk/logs/exporters/otlp_log_exporter.dart @@ -87,14 +87,9 @@ class OTLPLogExporter implements sdk.LogRecordExporter { final rsm = >>{}; for (final logRecord in logRecords) { final il = rsm[logRecord.resource] ?? >{}; - - if (logRecord.instrumentationScope != null) { - il[logRecord.instrumentationScope!] = il[logRecord.instrumentationScope] ?? [] - ..add(_logToProtobuf(logRecord)); - } - if (logRecord.resource != null) { - rsm[logRecord.resource!] = il; - } + il[logRecord.instrumentationScope] = il[logRecord.instrumentationScope] ?? [] + ..add(_logToProtobuf(logRecord)); + rsm[logRecord.resource] = il; } final rss = []; @@ -118,24 +113,18 @@ class OTLPLogExporter implements sdk.LogRecordExporter { } pb_logs.LogRecord _logToProtobuf(sdk.ReadableLogRecord log) { - var spanId = []; - var traceId = []; - if (log.spanContext != null) { - spanId = log.spanContext!.spanId.get(); - traceId = log.spanContext!.traceId.get(); - } return pb_logs.LogRecord( - timeUnixNano: log.timeStamp, - severityNumber: - log.severityNumber != null ? pg_logs_enum.SeverityNumber.valueOf(log.severityNumber!.index) : null, - severityText: log.severityText, - droppedAttributesCount: log.droppedAttributesCount, - body: _attributeONEValueToProtobuf(log.body), - attributes: (log.attributes?.keys ?? []) - .map((key) => pb_common.KeyValue(key: key, value: _attributeValueToProtobuf(log.attributes!.get(key)!))), - spanId: spanId, - traceId: traceId, - observedTimeUnixNano: log.observedTimestamp); + timeUnixNano: Int64(log.timeStamp.microsecondsSinceEpoch), + severityNumber: pg_logs_enum.SeverityNumber.valueOf(log.severityNumber.index), + severityText: log.severityText, + droppedAttributesCount: log.droppedAttributesCount, + body: _attributeONEValueToProtobuf(log.body), + attributes: log.attributes.keys + .map((key) => pb_common.KeyValue(key: key, value: _attributeValueToProtobuf(log.attributes.get(key)!))), + spanId: log.spanContext.spanId.get(), + traceId: log.spanContext.traceId.get(), + observedTimeUnixNano: Int64(log.observedTimestamp.microsecondsSinceEpoch), + ); } pb_common.AnyValue _attributeONEValueToProtobuf(Object value) { diff --git a/test/unit/sdk/logs/exporters/otlp_log_exporter_test.dart b/test/unit/sdk/logs/exporters/otlp_log_exporter_test.dart index c1391ecd..902b228b 100644 --- a/test/unit/sdk/logs/exporters/otlp_log_exporter_test.dart +++ b/test/unit/sdk/logs/exporters/otlp_log_exporter_test.dart @@ -12,7 +12,7 @@ import 'package:opentelemetry/src/experimental_api.dart' as api; import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; import 'package:opentelemetry/src/sdk/logs/log_record_limit.dart'; import 'package:opentelemetry/src/sdk/proto/opentelemetry/proto/collector/logs/v1/logs_service.pb.dart' -as pb_log_service; + as pb_log_service; import 'package:opentelemetry/src/sdk/proto/opentelemetry/proto/common/v1/common.pb.dart' as pb_common; import 'package:opentelemetry/src/sdk/proto/opentelemetry/proto/logs/v1/logs.pb.dart' as pb_logs; import 'package:opentelemetry/src/sdk/proto/opentelemetry/proto/logs/v1/logs.pbenum.dart' as pg_logs_enum; @@ -36,7 +36,7 @@ void main() { test('sends logs', () { final resource = sdk.Resource([api.Attribute.fromString('service.name', 'bar')]); final instrumentationLibrary = sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []); - final logLimit = LogRecordLimitsImpl(attributeCountLimit: 10, attributeValueLengthLimit: 5); + final logLimit = LogRecordLimits(attributeCountLimit: 10, attributeValueLengthLimit: 5); final tracer = sdk.TracerProviderBase().getTracer('test'); final parent = tracer.startSpan('parent'); @@ -50,7 +50,7 @@ void main() { logRecordLimits: logLimit, context: context, timeProvider: FakeTimeProvider(now: Int64(123))) - ..setAttribute('key', 'value'); + ..setAttribute(api.Attribute.fromString('key', 'value')); final log2 = sdk.LogRecord( resource: resource, @@ -58,18 +58,17 @@ void main() { context: context, body: 2, severityNumber: api.Severity.fatal3, - attributes: sdk.Attributes.empty() - ..addAll([ - api.Attribute.fromBoolean('fromBoolean', false), - api.Attribute.fromDouble('fromDouble', 1.1), - api.Attribute.fromInt('fromInt', 1), - api.Attribute.fromBooleanList('fromBooleanList', [false]), - api.Attribute.fromDoubleList('fromDoubleList', [1.1]), - api.Attribute.fromIntList('fromIntList', [1]), - ]), + attributes: [ + api.Attribute.fromBoolean('fromBoolean', false), + api.Attribute.fromDouble('fromDouble', 1.1), + api.Attribute.fromInt('fromInt', 1), + api.Attribute.fromBooleanList('fromBooleanList', [false]), + api.Attribute.fromDoubleList('fromDoubleList', [1.1]), + api.Attribute.fromIntList('fromIntList', [1]), + ], logRecordLimits: logLimit, timeProvider: FakeTimeProvider(now: Int64(123)), - )..setAttribute('key', 'value'); + )..setAttribute(api.Attribute.fromString('key', 'value')); final log3 = sdk.LogRecord( resource: resource, @@ -79,7 +78,7 @@ void main() { severityNumber: api.Severity.fatal3, logRecordLimits: logLimit, timeProvider: FakeTimeProvider(now: Int64(123))) - ..setAttribute('key', 'value'); + ..setAttribute(api.Attribute.fromString('key', 'value')); final log4 = sdk.LogRecord( resource: resource, @@ -89,7 +88,7 @@ void main() { severityNumber: api.Severity.fatal3, logRecordLimits: logLimit, timeProvider: FakeTimeProvider(now: Int64(123))) - ..setAttribute('key', 'value'); + ..setAttribute(api.Attribute.fromString('key', 'value')); sdk.OTLPLogExporter(uri, httpClient: mockClient).export([log1, log2, log3, log4]); @@ -102,7 +101,7 @@ void main() { logRecords: [ pb_logs.LogRecord( timeUnixNano: Int64(123), - severityNumber: pg_logs_enum.SeverityNumber.valueOf(log1.severityNumber!.index), + severityNumber: pg_logs_enum.SeverityNumber.valueOf(log1.severityNumber.index), attributes: [pb_common.KeyValue(key: 'key', value: pb_common.AnyValue(stringValue: 'value'))], traceId: parent.spanContext.traceId.get(), spanId: parent.spanContext.spanId.get(), @@ -112,7 +111,7 @@ void main() { ), pb_logs.LogRecord( timeUnixNano: Int64(123), - severityNumber: pg_logs_enum.SeverityNumber.valueOf(log2.severityNumber!.index), + severityNumber: pg_logs_enum.SeverityNumber.valueOf(log2.severityNumber.index), attributes: [ pb_common.KeyValue(key: 'fromBoolean', value: pb_common.AnyValue(boolValue: false)), pb_common.KeyValue(key: 'fromDouble', value: pb_common.AnyValue(doubleValue: 1.1)), @@ -145,7 +144,7 @@ void main() { ), pb_logs.LogRecord( timeUnixNano: Int64(123), - severityNumber: pg_logs_enum.SeverityNumber.valueOf(log1.severityNumber!.index), + severityNumber: pg_logs_enum.SeverityNumber.valueOf(log1.severityNumber.index), attributes: [pb_common.KeyValue(key: 'key', value: pb_common.AnyValue(stringValue: 'value'))], traceId: parent.spanContext.traceId.get(), spanId: parent.spanContext.spanId.get(), @@ -155,7 +154,7 @@ void main() { ), pb_logs.LogRecord( timeUnixNano: Int64(123), - severityNumber: pg_logs_enum.SeverityNumber.valueOf(log1.severityNumber!.index), + severityNumber: pg_logs_enum.SeverityNumber.valueOf(log1.severityNumber.index), attributes: [pb_common.KeyValue(key: 'key', value: pb_common.AnyValue(stringValue: 'value'))], traceId: parent.spanContext.traceId.get(), spanId: parent.spanContext.spanId.get(), @@ -179,7 +178,7 @@ void main() { test('does not send log when shutdown', () { final instrumentationLibrary = sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []); - final logLimit = LogRecordLimitsImpl(attributeCountLimit: 10, attributeValueLengthLimit: 5); + final logLimit = LogRecordLimits(attributeCountLimit: 10, attributeValueLengthLimit: 5); final tracer = sdk.TracerProviderBase().getTracer('test'); final parent = tracer.startSpan('parent'); @@ -192,7 +191,7 @@ void main() { severityNumber: api.Severity.fatal3, logRecordLimits: logLimit, timeProvider: FakeTimeProvider(now: Int64(123))) - ..setAttribute('key', 'value'); + ..setAttribute(api.Attribute.fromString('key', 'value')); sdk.OTLPLogExporter(uri, httpClient: mockClient) ..shutdown() @@ -204,7 +203,7 @@ void main() { test('supplies HTTP headers', () { final instrumentationLibrary = sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []); - final logLimit = LogRecordLimitsImpl(attributeCountLimit: 10, attributeValueLengthLimit: 5); + final logLimit = LogRecordLimits(attributeCountLimit: 10, attributeValueLengthLimit: 5); final tracer = sdk.TracerProviderBase().getTracer('test'); final parent = tracer.startSpan('parent'); @@ -217,7 +216,7 @@ void main() { logRecordLimits: logLimit, context: context, timeProvider: FakeTimeProvider(now: Int64(123))) - ..setAttribute('key', 'value'); + ..setAttribute(api.Attribute.fromString('key', 'value')); final suppliedHeaders = { 'header-param-key-1': 'header-param-value-1', From 539b77b639daed445ae125cbbf20be0155bcfbdb Mon Sep 17 00:00:00 2001 From: yusuf Date: Mon, 3 Mar 2025 10:54:45 +0800 Subject: [PATCH 60/61] fix: test --- .../exporters/otlp_log_exporter_test.dart | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/test/unit/sdk/logs/exporters/otlp_log_exporter_test.dart b/test/unit/sdk/logs/exporters/otlp_log_exporter_test.dart index 902b228b..bbddce33 100644 --- a/test/unit/sdk/logs/exporters/otlp_log_exporter_test.dart +++ b/test/unit/sdk/logs/exporters/otlp_log_exporter_test.dart @@ -41,16 +41,17 @@ void main() { final tracer = sdk.TracerProviderBase().getTracer('test'); final parent = tracer.startSpan('parent'); final context = api.contextWithSpan(api.Context.current, parent); + final timeProvider = FakeTimeProvider(now: Int64(123000)); final log1 = sdk.LogRecord( - resource: resource, - instrumentationScope: instrumentationLibrary, - body: 'test log', - severityNumber: api.Severity.fatal3, - logRecordLimits: logLimit, - context: context, - timeProvider: FakeTimeProvider(now: Int64(123))) - ..setAttribute(api.Attribute.fromString('key', 'value')); + resource: resource, + instrumentationScope: instrumentationLibrary, + body: 'test log', + severityNumber: api.Severity.fatal3, + logRecordLimits: logLimit, + context: context, + timeProvider: timeProvider, + )..setAttribute(api.Attribute.fromString('key', 'value')); final log2 = sdk.LogRecord( resource: resource, @@ -67,7 +68,7 @@ void main() { api.Attribute.fromIntList('fromIntList', [1]), ], logRecordLimits: logLimit, - timeProvider: FakeTimeProvider(now: Int64(123)), + timeProvider: timeProvider, )..setAttribute(api.Attribute.fromString('key', 'value')); final log3 = sdk.LogRecord( @@ -77,7 +78,7 @@ void main() { body: 2.2, severityNumber: api.Severity.fatal3, logRecordLimits: logLimit, - timeProvider: FakeTimeProvider(now: Int64(123))) + timeProvider: timeProvider) ..setAttribute(api.Attribute.fromString('key', 'value')); final log4 = sdk.LogRecord( @@ -87,7 +88,7 @@ void main() { body: true, severityNumber: api.Severity.fatal3, logRecordLimits: logLimit, - timeProvider: FakeTimeProvider(now: Int64(123))) + timeProvider: timeProvider) ..setAttribute(api.Attribute.fromString('key', 'value')); sdk.OTLPLogExporter(uri, httpClient: mockClient).export([log1, log2, log3, log4]); @@ -102,6 +103,7 @@ void main() { pb_logs.LogRecord( timeUnixNano: Int64(123), severityNumber: pg_logs_enum.SeverityNumber.valueOf(log1.severityNumber.index), + severityText: log1.severityText, attributes: [pb_common.KeyValue(key: 'key', value: pb_common.AnyValue(stringValue: 'value'))], traceId: parent.spanContext.traceId.get(), spanId: parent.spanContext.spanId.get(), @@ -112,6 +114,7 @@ void main() { pb_logs.LogRecord( timeUnixNano: Int64(123), severityNumber: pg_logs_enum.SeverityNumber.valueOf(log2.severityNumber.index), + severityText: log2.severityText, attributes: [ pb_common.KeyValue(key: 'fromBoolean', value: pb_common.AnyValue(boolValue: false)), pb_common.KeyValue(key: 'fromDouble', value: pb_common.AnyValue(doubleValue: 1.1)), @@ -144,7 +147,8 @@ void main() { ), pb_logs.LogRecord( timeUnixNano: Int64(123), - severityNumber: pg_logs_enum.SeverityNumber.valueOf(log1.severityNumber.index), + severityNumber: pg_logs_enum.SeverityNumber.valueOf(log3.severityNumber.index), + severityText: log3.severityText, attributes: [pb_common.KeyValue(key: 'key', value: pb_common.AnyValue(stringValue: 'value'))], traceId: parent.spanContext.traceId.get(), spanId: parent.spanContext.spanId.get(), @@ -154,7 +158,8 @@ void main() { ), pb_logs.LogRecord( timeUnixNano: Int64(123), - severityNumber: pg_logs_enum.SeverityNumber.valueOf(log1.severityNumber.index), + severityNumber: pg_logs_enum.SeverityNumber.valueOf(log4.severityNumber.index), + severityText: log4.severityText, attributes: [pb_common.KeyValue(key: 'key', value: pb_common.AnyValue(stringValue: 'value'))], traceId: parent.spanContext.traceId.get(), spanId: parent.spanContext.spanId.get(), From 83caa79b4df0cecece999acd9a31ab9f734d52f1 Mon Sep 17 00:00:00 2001 From: yusuf Date: Tue, 18 Mar 2025 12:52:19 +0800 Subject: [PATCH 61/61] wip: fix lint --- lib/src/sdk/logs/exporters/console_log_record_exporter.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/sdk/logs/exporters/console_log_record_exporter.dart b/lib/src/sdk/logs/exporters/console_log_record_exporter.dart index 4e32b083..b1650aa9 100644 --- a/lib/src/sdk/logs/exporters/console_log_record_exporter.dart +++ b/lib/src/sdk/logs/exporters/console_log_record_exporter.dart @@ -1,7 +1,6 @@ // Copyright 2021-2022 Workiva. // Licensed under the Apache License, Version 2.0. Please see https://github.com/Workiva/opentelemetry-dart/blob/master/LICENSE for more information -import 'package:opentelemetry/api.dart' as api; import 'package:opentelemetry/src/experimental_sdk.dart' as sdk; /// This is implementation of [sdk.ReadWriteLogRecordExporter] that prints LogRecords to the