Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

O11Y-1631: add SpanLimits, implement maxNumAttributes and maxNumAttributeLength #42

Merged
merged 8 commits into from
Apr 13, 2022
1 change: 1 addition & 0 deletions lib/sdk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
48 changes: 40 additions & 8 deletions lib/src/api/common/attribute.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,59 @@ class Attribute {
final Object value;

/// Create an Attribute from a String value.
Attribute.fromString(this.key, String this.value);
Attribute.fromString(this.key, String this.value) {
if (key == null) {
throw ArgumentError("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) {
if (key == null) {
throw ArgumentError("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) {
if (key == null) {
throw ArgumentError("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) {
if (key == null) {
throw ArgumentError("key can't be null.");
}
}

/// Create an Attribute from a list of String values.
Attribute.fromStringList(this.key, List<String> this.value);
Attribute.fromStringList(this.key, List<String> this.value) {
if (key == null) {
throw ArgumentError("key can't be null.");
}
}

/// Create an Attribute from a list of boolean values.
Attribute.fromBooleanList(this.key, List<bool> this.value);
Attribute.fromBooleanList(this.key, List<bool> this.value) {
if (key == null) {
throw ArgumentError("key can't be null.");
}
}

/// Create an Attribute from a list of double-precision floating-point values.
Attribute.fromDoubleList(this.key, List<double> this.value);
Attribute.fromDoubleList(this.key, List<double> this.value) {
if (key == null) {
throw ArgumentError("key can't be null.");
}
}

/// Create an Attribute from a list of integer values.
Attribute.fromIntList(this.key, List<int> this.value);
Attribute.fromIntList(this.key, List<int> this.value) {
if (key == null) {
throw ArgumentError("key can't be null.");
}
}
}
3 changes: 3 additions & 0 deletions lib/src/api/common/attributes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> get keys => _attributes.keys;

Expand Down
18 changes: 9 additions & 9 deletions lib/src/api/trace/nonrecording_span.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ class NonRecordingSpan implements api.Span {
api.Attributes get attributes => _attributes;

@override
set attributes(api.Attributes attributes) {
return;
}
set attributes(api.Attributes attributes) {}

@override
void end() {
return;
}
void setAttribute(api.Attribute attribute) {}

@override
void setAttributes(List<api.Attribute> attributes) {}

@override
void end() {}

@override
Int64 get endTime => null;
Expand All @@ -42,9 +44,7 @@ class NonRecordingSpan implements api.Span {
api.SpanId get parentSpanId => api.SpanId.invalid();

@override
void setStatus(api.StatusCode status, {String description}) {
return;
}
void setStatus(api.StatusCode status, {String description}) {}

@override
api.SpanContext get spanContext => _spanContext;
Expand Down
4 changes: 3 additions & 1 deletion lib/src/api/trace/noop_tracer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import '../../../sdk.dart' as sdk;
class NoopTracer implements api.Tracer {
@override
api.Span startSpan(String name,
{api.Context context, api.Attributes attributes}) {
{api.Context context,
api.Attributes attributes,
List<api.Attribute> attribute_list}) {
final parentContext = context.spanContext;

return api.NonRecordingSpan(
Expand Down
4 changes: 4 additions & 0 deletions lib/src/api/trace/span.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ abstract class Span {
/// Retrieve metadata included on this span.
api.Attributes get attributes;

void setAttribute(api.Attribute attribute);

void setAttributes(List<api.Attribute> attributes);

/// Retrieve the resource on this span.
api.Resource get resource;

Expand Down
4 changes: 3 additions & 1 deletion lib/src/api/trace/tracer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ abstract class Tracer {
/// Starts a new [api.Span] without setting it as the current span in this
/// tracer's context.
api.Span startSpan(String name,
{api.Context context, api.Attributes attributes});
{api.Context context,
api.Attributes attributes,
List<api.Attribute> attribute_list});

/// Records a span of the given [name] for the given function
/// and marks the span as errored if an exception occurs.
Expand Down
87 changes: 85 additions & 2 deletions lib/src/sdk/trace/span.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:fixnum/fixnum.dart';

import '../../../api.dart' as api;
import '../../../sdk.dart' as sdk;

/// A representation of a single operation within a trace.
class Span implements api.Span {
Expand All @@ -9,6 +10,7 @@ class Span implements api.Span {
final api.SpanStatus _status = api.SpanStatus();
final List<api.SpanProcessor> _processors;
final api.Resource _resource;
sdk.SpanLimits _spanLimits = sdk.SpanLimits();
final api.InstrumentationLibrary _instrumentationLibrary;
Int64 _startTime;
Int64 _endTime;
Expand All @@ -22,9 +24,17 @@ 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,
sdk.SpanLimits spanlimits,
List<api.Attribute> attribute_list}) {
_startTime = Int64(DateTime.now().toUtc().microsecondsSinceEpoch);
this.attributes = attributes ?? api.Attributes.empty();
if (spanlimits != null) _spanLimits = spanlimits;

if (attribute_list != null) {
setAttributes(attribute_list);
}

for (var i = 0; i < _processors.length; i++) {
_processors[i].onStart();
}
Expand Down Expand Up @@ -79,14 +89,87 @@ class Span implements api.Span {
@override
api.Attributes attributes;

@override
void setAttributes(List<api.Attribute> attributeList) {
changliu-wk marked this conversation as resolved.
Show resolved Hide resolved
//Don't want to have any attribute
if (_spanLimits.maxNumAttributes == 0) return;

attributes ??= api.Attributes.empty();

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(api.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 maxNumAttributeLength is less than zero, then it has unlimited length.
if (_spanLimits.maxNumAttributeLength < 0) return attr;

if (attr.value is String) {
changliu-wk marked this conversation as resolved.
Show resolved Hide resolved
attr = api.Attribute.fromString(
attr.key,
_applyAttributeLengthLimit(
attr.value, _spanLimits.maxNumAttributeLength));
} else if (attr.value is List<String>) {
final listString = attr.value as List<String>;
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<String>) {
for (var i = 0; i < value.length; i++) {
value[i] = value[i].length > lengthLimit
? value[i].substring(0, lengthLimit)
: value[i];
}
}
return value;
}
}
106 changes: 106 additions & 0 deletions lib/src/sdk/trace/span_limits.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
class SpanLimits {
changliu-wk marked this conversation as resolved.
Show resolved Hide resolved
final _DEFAULT_MAXNUM_ATTRIBUTES = 128;
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 = -1;

int _maxNumAttributes;
int _maxNumEvents;
int _maxNumLink;
int _maxNumAttributesPerEvent;
int _maxNumAttributesPerLink;
int _maxNumAttributeLength;

///setters
///Set the max number of attributes per span
set maxNumAttributes(int maxNumberOfAttributes) {
if (maxNumberOfAttributes < 0) {
throw ArgumentError('maxNumEvents must be greater or equal to zero');
}
}

///set the max number of events per span
set maxNumEvents(int maxNumEvents) {
if (maxNumEvents < 0) {
throw ArgumentError('maxNumEvents must be greater or equal to zero');
}
}

///set the max number of links per span
set maxNumLink(int maxNumLink) {
if (maxNumLink < 0) {
throw ArgumentError('maxNumEvents must be greater than or equal to zero');
}
}

///set the max number of attributes per event
set maxNumAttributesPerEvent(int maxNumAttributesPerEvent) {
if (maxNumAttributesPerEvent < 0) {
throw ArgumentError('maxNumEvents must be greater than or equal to zero');
}
}

///set the max number of attributes per link
set maxNumAttributesPerLink(int maxNumAttributesPerLink) {
if (maxNumAttributesPerLink < 0) {
throw ArgumentError('maxNumEvents must be greater than or equal to zero');
}
}

///return the maximum allowed attribute value length.
///This limits only applies to string and string list attribute valuse.
///Any string longer than this value will be truncated to this length.
///
///default is unlimited.
set maxNumAttributeLength(int maxNumAttributeLength) {
if (maxNumAttributeLength < 0) {
throw ArgumentError('maxNumEvents must be greater than or equal to zero');
}
}

///getters
///return the max number of attributes per span
int get maxNumAttributes => _maxNumAttributes;

///return the max number of events per span
int get maxNumEvents => _maxNumEvents;

///return the max number of links per span
int get maxNumLink => _maxNumLink;

///return the max number of attributes per event
int get maxNumAttributesPerEvent => _maxNumAttributesPerEvent;

///return the max number of attributes per link
int get maxNumAttributesPerLink => _maxNumAttributesPerLink;

///return the maximum allowed attribute value length.
///This limits only applies to string and string list attribute valuse.
///Any string longer than this value will be truncated to this length.
///
///default is unlimited.
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;
}
}
Loading