diff --git a/lib/sdk.dart b/lib/sdk.dart index b5511527..23cc5863 100644 --- a/lib/sdk.dart +++ b/lib/sdk.dart @@ -25,3 +25,4 @@ export 'src/sdk/trace/span_processors/simple_processor.dart' export 'src/sdk/trace/tracer.dart' show Tracer; export 'src/sdk/trace/tracer_provider.dart' show TracerProvider; export 'src/sdk/trace/trace_state.dart' show TraceState; +export 'src/sdk/trace/span_limits.dart' show SpanLimits; diff --git a/lib/src/api/common/attribute.dart b/lib/src/api/common/attribute.dart index aac05a70..9304180d 100644 --- a/lib/src/api/common/attribute.dart +++ b/lib/src/api/common/attribute.dart @@ -1,30 +1,48 @@ +import 'package:opentelemetry/src/sdk/internal/utils.dart'; + /// A representation of a single piece of metadata attached to trace span. class Attribute { final String key; final Object value; /// Create an Attribute from a String value. - Attribute.fromString(this.key, String this.value); + Attribute.fromString(this.key, String this.value) { + Utils.checkArgument(key != null, "key can't be null"); + } /// Create an Attribute from a boolean value. // ignore: avoid_positional_boolean_parameters - Attribute.fromBoolean(this.key, bool this.value); + Attribute.fromBoolean(this.key, bool this.value) { + Utils.checkArgument(key != null, "key can't be null"); + } /// Create an Attribute from a double-precision floating-point value. - Attribute.fromDouble(this.key, double this.value); + Attribute.fromDouble(this.key, double this.value) { + Utils.checkArgument(key != null, "key can't be null"); + } /// Create an Attribute from an integer value. - Attribute.fromInt(this.key, int this.value); + Attribute.fromInt(this.key, int this.value) { + Utils.checkArgument(key != null, "key can't be null"); + } /// Create an Attribute from a list of String values. - Attribute.fromStringList(this.key, List this.value); + Attribute.fromStringList(this.key, List this.value) { + Utils.checkArgument(key != null, "key can't be null"); + } /// Create an Attribute from a list of boolean values. - Attribute.fromBooleanList(this.key, List this.value); + Attribute.fromBooleanList(this.key, List this.value) { + Utils.checkArgument(key != null, "key can't be null"); + } /// Create an Attribute from a list of double-precision floating-point values. - Attribute.fromDoubleList(this.key, List this.value); + Attribute.fromDoubleList(this.key, List this.value) { + Utils.checkArgument(key != null, "key can't be null"); + } /// Create an Attribute from a list of integer values. - Attribute.fromIntList(this.key, List this.value); + Attribute.fromIntList(this.key, List this.value) { + Utils.checkArgument(key != null, "key can't be null"); + } } diff --git a/lib/src/api/common/attributes.dart b/lib/src/api/common/attributes.dart index ae55a183..4e5a87e3 100644 --- a/lib/src/api/common/attributes.dart +++ b/lib/src/api/common/attributes.dart @@ -12,6 +12,9 @@ class Attributes { /// Retrieve the value associated with the Attribute with key [key]. Object get(String key) => _attributes[key]; + /// + int get length => _attributes.length; + /// Retrieve the keys of all Attributes in this collection. Iterable get keys => _attributes.keys; diff --git a/lib/src/api/trace/nonrecording_span.dart b/lib/src/api/trace/nonrecording_span.dart index 50a0303f..0a4bf17b 100644 --- a/lib/src/api/trace/nonrecording_span.dart +++ b/lib/src/api/trace/nonrecording_span.dart @@ -24,6 +24,16 @@ class NonRecordingSpan implements api.Span { return; } + @override + void setAttribute(api.Attribute attribute) { + return; + } + + @override + void setAttributes(List attributes) { + return; + } + @override void end() { return; diff --git a/lib/src/api/trace/span.dart b/lib/src/api/trace/span.dart index d92ee4c7..8bbe6386 100644 --- a/lib/src/api/trace/span.dart +++ b/lib/src/api/trace/span.dart @@ -1,6 +1,7 @@ import 'package:fixnum/fixnum.dart'; import '../../../api.dart' as api; +import '../../../api.dart'; /// A representation of a single operation within a trace. /// @@ -50,6 +51,10 @@ abstract class Span { /// Retrieve metadata included on this span. api.Attributes get attributes; + void setAttribute(Attribute attribute); + + void setAttributes(List attributes); + /// Retrieve the resource on this span. api.Resource get resource; diff --git a/lib/src/sdk/internal/utils.dart b/lib/src/sdk/internal/utils.dart new file mode 100644 index 00000000..b3d77efd --- /dev/null +++ b/lib/src/sdk/internal/utils.dart @@ -0,0 +1,7 @@ +class Utils { + ///Check argument + // ignore: avoid_positional_boolean_parameters + static void checkArgument(bool isValid, String errorMessage) { + if (!isValid) throw ArgumentError(errorMessage); + } +} diff --git a/lib/src/sdk/trace/exporters/collector_exporter.dart b/lib/src/sdk/trace/exporters/collector_exporter.dart index f7338b7f..3e0fec3f 100644 --- a/lib/src/sdk/trace/exporters/collector_exporter.dart +++ b/lib/src/sdk/trace/exporters/collector_exporter.dart @@ -6,6 +6,7 @@ import 'opentelemetry/proto/collector/trace/v1/trace_service.pb.dart' import 'opentelemetry/proto/trace/v1/trace.pb.dart' as pb_trace; import 'opentelemetry/proto/resource/v1/resource.pb.dart' as pb_resource; import 'opentelemetry/proto/common/v1/common.pb.dart' as pb_common; +import '../span.dart' as spansdk; class CollectorExporter implements api.SpanExporter { Uri uri; @@ -74,7 +75,7 @@ class CollectorExporter implements api.SpanExporter { return rss; } - pb_trace.Span _spanToProtobuf(api.Span span) { + pb_trace.Span _spanToProtobuf(spansdk.Span span) { pb_trace.Status_StatusCode statusCode; switch (span.status.code) { case api.StatusCode.unset: diff --git a/lib/src/sdk/trace/span.dart b/lib/src/sdk/trace/span.dart index e2479193..ae4c9c99 100644 --- a/lib/src/sdk/trace/span.dart +++ b/lib/src/sdk/trace/span.dart @@ -1,6 +1,10 @@ +import 'dart:html'; + import 'package:fixnum/fixnum.dart'; +import 'package:opentelemetry/src/sdk/trace/span_limits.dart'; import '../../../api.dart' as api; +import '../../../api.dart'; /// A representation of a single operation within a trace. class Span implements api.Span { @@ -9,9 +13,11 @@ class Span implements api.Span { final api.SpanStatus _status = api.SpanStatus(); final List _processors; final api.Resource _resource; + SpanLimits _spanLimits = SpanLimits(); final api.InstrumentationLibrary _instrumentationLibrary; Int64 _startTime; Int64 _endTime; + api.Attributes _attributes; @override String name; @@ -22,9 +28,15 @@ class Span implements api.Span { /// Construct a [Span]. Span(this.name, this._spanContext, this._parentSpanId, this._processors, this._resource, this._instrumentationLibrary, - {api.Attributes attributes}) { + {api.Attributes attributes, SpanLimits spanlimits}) { _startTime = Int64(DateTime.now().toUtc().microsecondsSinceEpoch); - this.attributes = attributes ?? api.Attributes.empty(); + + if (spanlimits != null) _spanLimits = spanlimits; + _attributes = Attributes.empty(); + if (attributes != null) { + setAttributes(attributes); + } + for (var i = 0; i < _processors.length; i++) { _processors[i].onStart(); } @@ -79,14 +91,81 @@ class Span implements api.Span { @override api.Attributes attributes; + @override + void setAttributes(List attributeList) { + if (_spanLimits.maxNumAttributes == 0) return; + + for (var i = 0; i < attributeList.length; i++) { + final attr = attributeList[i]; + final obj = _attributes.get(attr.key); + //If current attributes.length is equal or greater than maxNumAttributes and + //key is not in current map, drop it. + if (_attributes.length >= _spanLimits.maxNumAttributes && obj == null) { + continue; + } + _attributes.add(_reBuildAttribute(attr)); + } + } + + @override + void setAttribute(Attribute attr) { + //Don't want to have any attribute + if (_spanLimits.maxNumAttributes == 0) return; + + final obj = _attributes.get(attr.key); + //If current attributes.length is equal or greater than maxNumAttributes and + //key is not in current map, drop it. + if (_attributes.length >= _spanLimits.maxNumAttributes && obj == null) { + return; + } + _attributes.add(_reBuildAttribute(attr)); + } + + /// reBuild an attribute, this way it is tightly coupled with the type we supported, + /// if later we added more types, then we need to change this method. + api.Attribute _reBuildAttribute(api.Attribute attr) { + if (attr.value is String) { + attr = api.Attribute.fromString( + attr.key, + _applyAttributeLengthLimit( + attr.value, _spanLimits.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], _spanLimits.maxNumAttributeLength); + } + attr = api.Attribute.fromStringList(attr.key, listString); + } + return attr; + } + @override void recordException(dynamic exception, {StackTrace stackTrace}) { + // ignore: todo // TODO: O11Y-1531: Consider integration of Events here. setStatus(api.StatusCode.error, description: exception.toString()); - attributes.addAll([ + setAttributes([ api.Attribute.fromBoolean('error', true), api.Attribute.fromString('exception', exception.toString()), api.Attribute.fromString('stacktrace', stackTrace.toString()), ]); } + + //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 + static Object _applyAttributeLengthLimit(Object value, int lengthLimit) { + if (value is String) { + return value.length > lengthLimit + ? value.substring(0, lengthLimit) + : value; + } else if (value is List) { + for (var i = 0; i < value.length; i++) { + value[i] = value[i].length > lengthLimit + ? value[i].substring(0, lengthLimit) + : value[i]; + } + } + return value; + } } diff --git a/lib/src/sdk/trace/span_limits.dart b/lib/src/sdk/trace/span_limits.dart new file mode 100644 index 00000000..808f099f --- /dev/null +++ b/lib/src/sdk/trace/span_limits.dart @@ -0,0 +1,83 @@ +import '../internal/utils.dart'; + +class SpanLimits { + final _DEFAULT_MAXNUM_ATTRIBUTES = 200; + final _DEFAULT_MAXNUM_EVENTS = 128; + final _DEFAULT_MAXNUM_LINKS = 128; + final _DEFAULT_MAXNUM_ATTRIBUTE_PER_EVENT = 128; + final _DEFAULT_MAXNUM_ATTRIBUTES_PER_LINK = 128; + final _DEFAULT_MAXNUM_ATTRIBUTES_LENGTH = 1000; + + int _maxNumAttributes; + int _maxNumEvents; + int _maxNumLink; + int _maxNumAttributesPerEvent; + int _maxNumAttributesPerLink; + int _maxNumAttributeLength; + + ///setters + set maxNumAttributes(int maxNumberOfAttributes) { + Utils.checkArgument(maxNumberOfAttributes >= 0, + 'maxNumberOfAttributes must be greater or equal to zero'); + _maxNumAttributes = maxNumberOfAttributes; + } + + set maxNumEvents(int maxNumEvents) { + Utils.checkArgument( + maxNumEvents >= 0, 'maxNumEvents must be greater or equal to zero'); + _maxNumEvents = maxNumEvents; + } + + set maxNumLink(int maxNumLink) { + Utils.checkArgument( + maxNumLink >= 0, 'maxNumLink must be greater or equal to zero'); + _maxNumLink = maxNumLink; + } + + set maxNumAttributesPerEvent(int maxNumAttributesPerEvent) { + Utils.checkArgument(maxNumAttributesPerEvent >= 0, + 'maxNumAttributesPerEvent must be greater or equal to zero'); + _maxNumAttributesPerEvent = maxNumAttributesPerEvent; + } + + set maxNumAttributesPerLink(int maxNumAttributesPerLink) { + Utils.checkArgument(maxNumAttributesPerLink >= 0, + 'maxNumAttributesPerLink must be greater or equal to zero'); + _maxNumAttributesPerLink = maxNumAttributesPerLink; + } + + set maxNumAttributeLength(int maxNumAttributeLength) { + Utils.checkArgument(maxNumAttributesPerLink >= 0, + 'maxNumAttributesPerLink must be greater or equal to zero'); + _maxNumAttributeLength = maxNumAttributeLength; + } + + ///getters + int get maxNumAttributes => _maxNumAttributes; + int get maxNumEvents => _maxNumEvents; + int get maxNumLink => _maxNumLink; + int get maxNumAttributesPerEvent => _maxNumAttributesPerEvent; + int get maxNumAttributesPerLink => _maxNumAttributesPerLink; + int get maxNumAttributeLength => _maxNumAttributeLength; + + ///constructor + ///https://docs.newrelic.com/docs/data-apis/manage-data/view-system-limits/ + ///https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SpanLimitsBuilder.java + SpanLimits( + {int maxNumAttributes, + int maxNumEvents, + int maxNumLink, + int maxNumAttributesPerEvent, + int maxNumAttributesPerLink, + int maxNumAttributeLength}) { + _maxNumAttributes = maxNumAttributes ?? _DEFAULT_MAXNUM_ATTRIBUTES; + _maxNumEvents = maxNumEvents ?? _DEFAULT_MAXNUM_EVENTS; + _maxNumLink = maxNumLink ?? _DEFAULT_MAXNUM_LINKS; + _maxNumAttributesPerEvent = + maxNumAttributesPerEvent ?? _DEFAULT_MAXNUM_ATTRIBUTE_PER_EVENT; + _maxNumAttributesPerLink = + maxNumAttributesPerLink ?? _DEFAULT_MAXNUM_ATTRIBUTES_PER_LINK; + _maxNumAttributeLength = + maxNumAttributeLength ?? _DEFAULT_MAXNUM_ATTRIBUTES_LENGTH; + } +} diff --git a/lib/src/sdk/trace/tracer.dart b/lib/src/sdk/trace/tracer.dart index 4bbb6542..563c4399 100644 --- a/lib/src/sdk/trace/tracer.dart +++ b/lib/src/sdk/trace/tracer.dart @@ -2,6 +2,7 @@ import 'dart:async'; import '../../../api.dart' as api; import '../../../sdk.dart' as sdk; +import 'span_limits.dart'; /// An interface for creating [api.Span]s and propagating context in-process. class Tracer implements api.Tracer { @@ -10,9 +11,13 @@ class Tracer implements api.Tracer { final api.Sampler _sampler; final api.IdGenerator _idGenerator; final api.InstrumentationLibrary _instrumentationLibrary; + SpanLimits _spanLimits = SpanLimits(); Tracer(this._processors, this._resource, this._sampler, this._idGenerator, - this._instrumentationLibrary); + this._instrumentationLibrary, + [spanLimits]) { + _spanLimits = spanLimits ?? _spanLimits; + } @override api.Span startSpan(String name, @@ -50,7 +55,7 @@ class Tracer implements api.Tracer { return sdk.Span(name, spanContext, parentSpanId, _processors, _resource, _instrumentationLibrary, - attributes: attributes); + attributes: attributes, spanlimits: _spanLimits); } /// Records a span of the given [name] for the given function diff --git a/lib/src/sdk/trace/tracer_provider.dart b/lib/src/sdk/trace/tracer_provider.dart index 8aa6b46b..40cb2f2c 100644 --- a/lib/src/sdk/trace/tracer_provider.dart +++ b/lib/src/sdk/trace/tracer_provider.dart @@ -1,5 +1,6 @@ import '../../../api.dart' as api; import '../../../sdk.dart' as sdk; +import 'span_limits.dart'; /// A registry for creating named [api.Tracer]s. class TracerProvider implements api.TracerProvider { @@ -8,16 +9,19 @@ class TracerProvider implements api.TracerProvider { api.Resource _resource; api.Sampler _sampler; api.IdGenerator _idGenerator; + SpanLimits _spanLimits; TracerProvider( {List processors, api.Resource resource, api.Sampler sampler, - api.IdGenerator idGenerator}) { + api.IdGenerator idGenerator, + SpanLimits spanLimits}) { _processors = processors ?? []; // Default to a no-op TracerProvider. _resource = resource ?? sdk.Resource(api.Attributes.empty()); _sampler = sampler ?? sdk.ParentBasedSampler(sdk.AlwaysOnSampler()); _idGenerator = idGenerator ?? sdk.IdGenerator(); + _spanLimits = spanLimits ?? SpanLimits(); } List get spanProcessors => _processors; @@ -28,7 +32,7 @@ class TracerProvider implements api.TracerProvider { return _tracers.putIfAbsent( key, () => sdk.Tracer(_processors, _resource, _sampler, _idGenerator, - sdk.InstrumentationLibrary(name, version))); + sdk.InstrumentationLibrary(name, version), _spanLimits)); } @override diff --git a/test/unit/api/attribute_test.dart b/test/unit/api/attribute_test.dart new file mode 100644 index 00000000..7e73dcbc --- /dev/null +++ b/test/unit/api/attribute_test.dart @@ -0,0 +1,58 @@ +import 'package:opentelemetry/api.dart'; +import 'package:test/test.dart'; + +void main() { + test("test bool key can't be null", () { + expect( + () => Attribute.fromBoolean(null, true), + throwsA(isA() + .having((error) => error.message, 'message', "key can't be null"))); + }); + + test("test int key can't be null", () { + expect( + () => Attribute.fromInt(null, 2), + throwsA(isA() + .having((error) => error.message, 'message', "key can't be null"))); + }); + + test("test String key can't be null", () { + expect( + () => Attribute.fromString(null, 'abc'), + throwsA(isA() + .having((error) => error.message, 'message', "key can't be null"))); + }); + + test("test double key can't be null", () { + expect( + () => Attribute.fromDouble(null, 0.1), + throwsA(isA() + .having((error) => error.message, 'message', "key can't be null"))); + }); + test("test BoolList key can't be null", () { + expect( + () => Attribute.fromBooleanList(null, [true, false]), + throwsA(isA() + .having((error) => error.message, 'message', "key can't be null"))); + }); + + test("test IntList key can't be null", () { + expect( + () => Attribute.fromIntList(null, [2, 3]), + throwsA(isA() + .having((error) => error.message, 'message', "key can't be null"))); + }); + test("test StringList key can't be null", () { + expect( + () => Attribute.fromStringList(null, ['1', '2']), + throwsA(isA() + .having((error) => error.message, 'message', "key can't be null"))); + }); + + test("test DoubleList key can't be null", () { + expect( + () => Attribute.fromDoubleList(null, [0.2, 0.1]), + throwsA(isA() + .having((error) => error.message, 'message', "key can't be null"))); + }); +} diff --git a/test/unit/sdk/span_limits_test.dart b/test/unit/sdk/span_limits_test.dart new file mode 100644 index 00000000..82ee390a --- /dev/null +++ b/test/unit/sdk/span_limits_test.dart @@ -0,0 +1,133 @@ +import 'package:opentelemetry/api.dart'; +import 'package:opentelemetry/src/api/common/attribute.dart'; +import 'package:opentelemetry/src/api/common/attributes.dart'; +import 'package:opentelemetry/src/sdk/trace/span_limits.dart'; +import 'package:test/test.dart'; +import 'package:opentelemetry/src/sdk/trace/span.dart' as sdkspan; + +void main() { + const maxAttributes = 3; + const maxAttributeLength = 5; + final attrShort = Attribute.fromString('shortkey', '55555'); + final dupShort = Attribute.fromString('shortkey', '66666'); + final attrLong = Attribute.fromString('longkey', '5555555'); + final dupLong = Attribute.fromString('longkey', '666666666'); + final attrInt = Attribute.fromInt('intKey', 12); + final attrBool = Attribute.fromBoolean('boolKey', true); + final attrDoubleArray = Attribute.fromDoubleList('doubleList', [0.1, 0.2]); + final attrStringArray = + Attribute.fromStringList('stringList', ['1111', '1111111']); + final limits = SpanLimits( + maxNumAttributes: maxAttributes, + maxNumAttributeLength: maxAttributeLength); + + setUp(() {}); + + test('test spanlimits normal', () { + final attrs = Attributes.empty() + ..addAll([attrShort, attrDoubleArray, attrStringArray]); + final span = sdkspan.Span('foo', null, SpanId([4, 5, 6]), [], null, null, + attributes: attrs, spanlimits: limits); + expect(span.attributes.length, equals(3)); + expect(span.attributes.get('shortkey'), equals('55555')); + expect(span.attributes.get('doubleList'), equals([0.1, 0.2])); + expect(span.attributes.get('stringList'), equals(['1111', '11111'])); + }); + + test('test spanlimits maxNumAttributes', () { + final attrs = Attributes.empty() + ..addAll([attrShort, attrLong, attrInt, attrBool]); + final span = sdkspan.Span('foo', null, SpanId([4, 5, 6]), [], null, null, + attributes: attrs, spanlimits: limits); + expect(span.attributes.length, equals(maxAttributes)); + expect(span.attributes.get('boolean'), equals(null)); + }); + + test('test spanlimits maxNumAttributeLength', () { + final attrs = Attributes.empty()..addAll([attrShort, attrLong]); + final span = sdkspan.Span('foo', null, SpanId([4, 5, 6]), [], null, null, + attributes: attrs, spanlimits: limits); + expect(span.attributes.get('shortkey'), equals('55555')); + expect(span.attributes.get('longkey'), equals('55555')); + }); + + test('test spanlimits normal from span', () { + final span = + sdkspan.Span('test', null, null, [], null, null, spanlimits: limits); + final attrs = Attributes.empty() + ..addAll([attrShort, attrInt, attrDoubleArray]); + span.setAttributes(attrs); + expect(span.attributes.length, equals(3)); + expect(span.attributes.get('shortkey'), equals('55555')); + expect(span.attributes.get('intKey'), equals(12)); + expect(span.attributes.get('doubleList'), equals([0.1, 0.2])); + }); + + test('test spanlimits maxNumAttributes from span', () { + final span = + sdkspan.Span('test', null, null, [], null, null, spanlimits: limits); + final attrs = Attributes.empty() + ..addAll([attrShort, attrLong, attrInt, attrBool]); + span.setAttributes(attrs); + expect(span.attributes.length, equals(maxAttributes)); + expect(span.attributes.get('boolean'), equals(null)); + }); + + test('test spanlimits maxNumAttributeLength with setAttributes', () { + final span = + sdkspan.Span('test', null, null, [], null, null, spanlimits: limits); + final attrs = Attributes.empty()..addAll([attrShort, attrLong]); + span.setAttributes(attrs); + expect(span.attributes.get('shortkey'), equals('55555')); + expect(span.attributes.get('longkey'), equals('55555')); + }); + + test('test spanlimits from span, then add more', () { + final span = + sdkspan.Span('test', null, null, [], null, null, spanlimits: limits); + final attrs = Attributes.empty()..addAll([attrShort, attrLong]); + span.setAttributes(attrs); + expect(span.attributes.get('shortkey'), equals('55555')); + expect(span.attributes.get('longkey'), equals('55555')); + span.setAttributes(Attributes.empty()..add(attrBool)); + expect(span.attributes.length, equals(maxAttributes)); + expect(span.attributes.get('boolKey'), equals(true)); + span.setAttributes(Attributes.empty()..add(attrDoubleArray)); + expect(span.attributes.length, equals(maxAttributes)); + expect(span.attributes.get('doubleList'), equals(null)); + }); + + test('test add same key twice', () { + final span = + sdkspan.Span('test', null, null, [], null, null, spanlimits: limits); + final attrs = Attributes.empty()..addAll([attrShort, dupShort]); + span.setAttributes(attrs); + expect(span.attributes.length, 1); + expect(span.attributes.get('shortkey'), equals('66666')); + span.setAttributes(Attributes.empty()..addAll([attrLong, dupLong])); + expect(span.attributes.length, 2); + expect(span.attributes.get('longkey'), equals('66666')); + }); + + test('test add same key twice', () { + final span = + sdkspan.Span('test', null, null, [], null, null, spanlimits: limits); + final attrs = Attributes.empty() + ..addAll([attrShort, dupShort, attrLong, dupLong]); + span.setAttributes(attrs); + expect(span.attributes.length, 2); + expect(span.attributes.get('shortkey'), equals('66666')); + expect(span.attributes.get('longkey'), equals('66666')); + }); + + test('test add oversized string list', () { + final span = + sdkspan.Span('test', null, null, [], null, null, spanlimits: limits); + final attrs = Attributes.empty() + ..addAll([attrShort, dupShort, attrLong, dupLong]); + span.setAttributes(attrs); + expect(span.attributes.length, 2); + expect(span.attributes.get('shortkey'), equals('66666')); + expect(span.attributes.get('longkey'), equals('66666')); + }); +}