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/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. 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/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 5536a699..846eef85 100644 --- a/lib/src/experimental_sdk.dart +++ b/lib/src/experimental_sdk.dart @@ -6,7 +6,12 @@ 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/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/common/limits.dart b/lib/src/sdk/common/limits.dart index 506c50ff..4923b88b 100644 --- a/lib/src/sdk/common/limits.dart +++ b/lib/src/sdk/common/limits.dart @@ -4,11 +4,11 @@ import 'package:meta/meta.dart'; import '../../../api.dart' as api; import '../../../sdk.dart' as sdk; +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) { @@ -27,8 +27,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; } @@ -49,8 +48,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; } @@ -63,20 +61,46 @@ 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); } return attr; } +@protected +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) { + 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 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; +} + /// 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.dart b/lib/src/sdk/logs/log_record.dart new file mode 100644 index 00000000..dcc546f5 --- /dev/null +++ b/lib/src/sdk/logs/log_record.dart @@ -0,0 +1,140 @@ +// 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/common/limits.dart'; + +/// https://opentelemetry.io/docs/specs/otel/logs/sdk/#readwritelogrecord +abstract class ReadableLogRecord { + DateTime get timeStamp; + + DateTime 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; + + final sdk.Resource _resource; + + final sdk.TimeProvider _timeProvider; + final api.Context _context; + final sdk.LogRecordLimits logRecordLimits; + final DateTime? _timeStamp; + final DateTime? _observedTimestamp; + + String _severityText; + api.Severity _severityNumber; + dynamic _body; + int _totalAttributesCount = 0; + + final sdk.Attributes _attributes; + + @protected + LogRecord({ + required this.instrumentationScope, + required this.logRecordLimits, + api.Severity? severityNumber, + String? severityText, + List attributes = const [], + DateTime? timeStamp, + DateTime? observedTimestamp, + api.Context? context, + dynamic body, + sdk.Resource? resource, + sdk.TimeProvider? timeProvider, + }) : _severityText = severityText ?? api.Severity.unspecified.name, + _resource = resource ?? sdk.Resource([]), + _context = context ?? api.Context.current, + _body = body, + _attributes = sdk.Attributes.empty(), + _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; + + @override + dynamic get body => _body; + + @override + set body(dynamic body) { + _body = body; + } + + @override + api.SpanContext get spanContext => api.spanContextFromContext(_context); + + @override + int get droppedAttributesCount => _totalAttributesCount - attributes.length; + + @override + DateTime get timeStamp => _timeStamp ?? DateTime.fromMicrosecondsSinceEpoch((_timeProvider.now ~/ 1000).toInt()); + + @override + DateTime get observedTimestamp => + _observedTimestamp ?? DateTime.fromMicrosecondsSinceEpoch((_timeProvider.now ~/ 1000).toInt()); + + @override + api.Severity get severityNumber => _severityNumber; + + @override + set severityNumber(api.Severity severity) { + _severityNumber = severity; + } + + @override + String get severityText => _severityText; + + @override + set severityText(String severity) { + _severityText = severity; + } + + void setAttributes(List attributes) { + attributes.forEach(setAttribute); + } + + void setAttribute(api.Attribute attribute) { + if (attribute.key.isEmpty) return; + if (logRecordLimits.attributeCountLimit == 0) return; + _totalAttributesCount += 1; + _attributes.add(applyAttributeLimitsForLog(attribute, logRecordLimits)); + } +} 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..4da0acf4 --- /dev/null +++ b/lib/src/sdk/logs/log_record_limit.dart @@ -0,0 +1,12 @@ +// 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 LogRecordLimits { + final int attributeCountLimit; + final int attributeValueLengthLimit; + + const LogRecordLimits({ + this.attributeCountLimit = 128, + this.attributeValueLengthLimit = -1, + }); +} diff --git a/lib/src/sdk/logs/logger.dart b/lib/src/sdk/logs/logger.dart new file mode 100644 index 00000000..046c256c --- /dev/null +++ b/lib/src/sdk/logs/logger.dart @@ -0,0 +1,52 @@ +// 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/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 sdk.LogRecordLimits _logRecordLimits; + final sdk.TimeProvider _timeProvider; + final List _processors; + + @protected + Logger( + this._instrumentationScope, + this._logRecordLimits, + this._timeProvider, + this._processors, + this._resource, + ); + + @override + void emit({ + List attributes = const [], + 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, + ); + for (final processor in _processors) { + processor.onEmit(log); + } + } +} diff --git a/lib/src/sdk/logs/logger_config.dart b/lib/src/sdk/logs/logger_config.dart new file mode 100644 index 00000000..e54d4f47 --- /dev/null +++ b/lib/src/sdk/logs/logger_config.dart @@ -0,0 +1,14 @@ +// 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, + /// 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, + }); +} diff --git a/lib/src/sdk/logs/logger_provider.dart b/lib/src/sdk/logs/logger_provider.dart new file mode 100644 index 00000000..8647eae3 --- /dev/null +++ b/lib/src/sdk/logs/logger_provider.dart @@ -0,0 +1,71 @@ +// 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/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 = 'opentelemetry'; + +// https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerprovider +class LoggerProvider implements api.LoggerProvider { + final Map _loggers = {}; + + final LoggerConfig _config; + + final List _processors; + + final sdk.Resource _resource; + + final sdk.LogRecordLimits _logRecordLimits; + + final sdk.TimeProvider _timeProvider; + + LoggerProvider({ + LoggerConfig config = const LoggerConfig(), + sdk.LogRecordLimits logRecordLimits = const LogRecordLimits(), + sdk.Resource? resource, + List? processors, + sdk.TimeProvider? timeProvider, + }) : _processors = processors ?? const [], + _config = config, + _logRecordLimits = logRecordLimits, + _resource = resource ?? sdk.Resource([]), + _timeProvider = timeProvider ?? sdk.DateTimeTimeProvider(); + + @override + api.Logger get( + String name, { + String version = '', + String schemaUrl = '', + List attributes = const [], + }) { + final loggerName = name.isNotEmpty ? name : defaultLoggerName; + final key = hash3(loggerName, version, schemaUrl); + if (_config.disabled) { + return api.NoopLogger(); + } + return _loggers.putIfAbsent( + key, + () => sdk.Logger( + sdk.InstrumentationScope(loggerName, version, schemaUrl, attributes), + _logRecordLimits, + _timeProvider, + _processors, + _resource, + ), + ); + } + + void forceFlush() { + return _processors.forEach((e) => e.forceFlush()); + } + + void shutdown() { + return _processors.forEach((e) => e.shutdown()); + } +} 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..dfe2b714 --- /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.ReadWriteLogRecord logRecord); + + Future forceFlush(); + + Future shutdown(); +} 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..e1ddaf46 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 @@ -15,17 +15,15 @@ Int64 msToNs(num n, {int? fractionDigits}) { final frac = ((n - whole) * nsPerMs).round(); return Int64(whole * nsPerMs + frac); } - final frac = - double.parse((n - whole).toStringAsFixed(fractionDigits)) * nsPerMs; + final frac = double.parse((n - whole).toStringAsFixed(fractionDigits)) * nsPerMs; return Int64(whole) * nsPerMs + Int64(frac.round()); } /// Time when navigation started or the service worker was started in /// nanoseconds. @experimental -final Int64 timeOrigin = msToNs( - window.performance.timeOrigin ?? window.performance.timing.navigationStart, - fractionDigits: 1); +final Int64 timeOrigin = + msToNs(window.performance.timeOrigin ?? window.performance.timing.navigationStart, fractionDigits: 1); /// Converts a high-resolution timestamp from the browser performance API to an /// Int64 representing nanoseconds since Unix Epoch. @@ -44,6 +42,10 @@ Int64 fromDOMHighResTimeStamp(num ts) { /// for sleep. See https://github.com/open-telemetry/opentelemetry-js/issues/852 /// for more information. class WebTimeProvider implements TimeProvider { + static final Duration timeOrigin = Duration( + milliseconds: (window.performance.timeOrigin ?? window.performance.timing.navigationStart).round(), + ); + /// The current time, in nanoseconds since Unix Epoch. /// /// Note that this time may be inaccurate if the executing system is suspended @@ -51,4 +53,8 @@ class WebTimeProvider implements TimeProvider { /// for more information. @override Int64 get now => fromDOMHighResTimeStamp(window.performance.now()); + + @override + Duration get nowDuration => + WebTimeProvider.timeOrigin + Duration(microseconds: (window.performance.now() * 1000).round()); } diff --git a/lib/src/sdk/time_providers/datetime_time_provider.dart b/lib/src/sdk/time_providers/datetime_time_provider.dart index e543890a..b9b9cf01 100644 --- a/lib/src/sdk/time_providers/datetime_time_provider.dart +++ b/lib/src/sdk/time_providers/datetime_time_provider.dart @@ -2,10 +2,14 @@ // 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 'time_provider.dart'; /// DateTimeTimeProvider retrieves timestamps using DateTime. class DateTimeTimeProvider implements TimeProvider { @override Int64 get now => Int64(DateTime.now().microsecondsSinceEpoch) * 1000; + + @override + Duration get nowDuration => Duration(microseconds: DateTime.now().microsecondsSinceEpoch); } diff --git a/lib/src/sdk/time_providers/time_provider.dart b/lib/src/sdk/time_providers/time_provider.dart index 360a8248..a13581ae 100644 --- a/lib/src/sdk/time_providers/time_provider.dart +++ b/lib/src/sdk/time_providers/time_provider.dart @@ -14,6 +14,19 @@ abstract class TimeProvider { @Deprecated('This constant will be removed in 0.19.0 without replacement.') static const int nanosecondsPerMillisecond = 1000000; - /// The current time, in nanoseconds since Unix Epoch. + /// The current time in nanoseconds since Unix Epoch. + /// + /// **Warning:** Return type will change to `Duration` in 0.19.0. + /// Use [nowDuration] instead. + @Deprecated( + 'Return type will change to `Duration` in 0.19.0. ' + 'Use `nowDuration` for now, and migrate back to `now` after 0.19.0.' + ) Int64 get now; + + /// The current time as a [Duration] since Unix Epoch. + /// + /// **Warning:** Temporary API, will be removed in 0.20.0. + /// Use this for intermediate migration of `now` prior to 0.19.0. + Duration get nowDuration; } 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/common/limits_test.dart b/test/unit/sdk/common/limits_test.dart new file mode 100644 index 00000000..6365522b --- /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'), + LogRecordLimits(attributeValueLengthLimit: 2), + ); + + expect(logLimit.value, 'va'); + }); + + test('test log record limit list', () { + final logLimit = applyAttributeLimitsForLog( + api.Attribute.fromStringList('key', ['value1', 'value2']), + 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 new file mode 100644 index 00000000..2d44f57e --- /dev/null +++ b/test/unit/sdk/logs/log_record_test.dart @@ -0,0 +1,108 @@ +// 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:opentelemetry/src/sdk/logs/log_record_limit.dart'; +import 'package:test/test.dart'; + +import '../trace_provider_test.dart'; + +void main() { + test('logRecord call setter', () { + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope( + 'library_name', 'library_version', 'url://schema', []), + logRecordLimits: LogRecordLimits(), + timeProvider: FakeTimeProvider(now: Int64(123))) + ..body = 'Log Message' + ..severityNumber = api.Severity.debug + ..severityText = 'DEBUG' + ..setAttributes([api.Attribute.fromString('key', 'value')]) + ..setAttribute(api.Attribute.fromString('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, + DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt() ~/ 1000)); + expect(logRecord.observedTimestamp, + DateTime.fromMicrosecondsSinceEpoch(Int64(123).toInt() ~/ 1000)); + }); + + test('logRecord update same attribute will create attributesCount diff', () { + final logRecord = sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope( + 'library_name', 'library_version', 'url://schema', []), + logRecordLimits: LogRecordLimits(), + ) + ..setAttributes([api.Attribute.fromString('key2', 'value')]) + ..setAttribute(api.Attribute.fromString('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: LogRecordLimits(), + ) + ..setAttributes([api.Attribute.fromString('key2', 'value')]) + ..setAttribute(api.Attribute.fromString('key2', 'value2')); + + expect(logRecord.timeStamp, now); + expect(logRecord.observedTimestamp, now); + }); + + 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: LogRecordLimits(attributeValueLengthLimit: 2), + ) + ..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.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: LogRecordLimits(attributeValueLengthLimit: 2), + ) + ..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']); + }); +} 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..a6f1f90d --- /dev/null +++ b/test/unit/sdk/logs/logger_provider_test.dart @@ -0,0 +1,57 @@ +// 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: LogRecordLimits(), + )); + }); + + 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 == DateTime.fromMicrosecondsSinceEpoch(123 ~/ 1000) && + a.observedTimestamp == DateTime.fromMicrosecondsSinceEpoch(123 ~/ 1000); + }), + ))).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()); + 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()); + sdk.LoggerProvider(processors: [mockProcessor1, mockProcessor2]).shutdown(); + + verify(mockProcessor1.shutdown).called(1); + verify(mockProcessor2.shutdown).called(1); + }); +} diff --git a/test/unit/sdk/logs/logger_test.dart b/test/unit/sdk/logs/logger_test.dart new file mode 100644 index 00000000..28873851 --- /dev/null +++ b/test/unit/sdk/logs/logger_test.dart @@ -0,0 +1,47 @@ +// 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/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'; + +class MockLockRecordProcessor extends Mock implements sdk.LogRecordProcessor {} + +void main() { + setUpAll(() { + registerFallbackValue(sdk.LogRecord( + instrumentationScope: sdk.InstrumentationScope('library_name', 'library_version', 'url://schema', []), + logRecordLimits: LogRecordLimits(), + )); + }); + + test('emit new log', () { + final processor = MockLockRecordProcessor(); + sdk.Logger( + sdk.InstrumentationScope( + 'library_name', + 'library_version', + 'url://schema', + [], + ), + LogRecordLimits(), + FakeTimeProvider(now: Int64(60)), + [processor], + sdk.Resource([]), + ).emit(body: 'TEST!'); + + 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; + })))).called(1); + }); +}