Skip to content

Commit

Permalink
Add new APIs(setAttribute and setAttributes) with SpanLimit restriction
Browse files Browse the repository at this point in the history
  • Loading branch information
changliu-wk committed Apr 5, 2022
1 parent a57c636 commit 94ef370
Show file tree
Hide file tree
Showing 14 changed files with 414 additions and 17 deletions.
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;
34 changes: 26 additions & 8 deletions lib/src/api/common/attribute.dart
Original file line number Diff line number Diff line change
@@ -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<String> this.value);
Attribute.fromStringList(this.key, List<String> 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<bool> this.value);
Attribute.fromBooleanList(this.key, List<bool> 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<double> this.value);
Attribute.fromDoubleList(this.key, List<double> 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<int> this.value);
Attribute.fromIntList(this.key, List<int> this.value) {
Utils.checkArgument(key != null, "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
10 changes: 10 additions & 0 deletions lib/src/api/trace/nonrecording_span.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ class NonRecordingSpan implements api.Span {
return;
}

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

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

@override
void end() {
return;
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
7 changes: 7 additions & 0 deletions lib/src/sdk/internal/utils.dart
Original file line number Diff line number Diff line change
@@ -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);
}
}
83 changes: 81 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,83 @@ class Span implements api.Span {
@override
api.Attributes attributes;

@override
void setAttributes(List<api.Attribute> attributeList) {
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 (attr.value is String) {
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;
}
}
83 changes: 83 additions & 0 deletions lib/src/sdk/trace/span_limits.dart
Original file line number Diff line number Diff line change
@@ -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;
}
}
14 changes: 11 additions & 3 deletions lib/src/sdk/trace/tracer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ class Tracer implements api.Tracer {
final api.Sampler _sampler;
final api.IdGenerator _idGenerator;
final api.InstrumentationLibrary _instrumentationLibrary;
sdk.SpanLimits _spanLimits = sdk.SpanLimits();

Tracer(this._processors, this._resource, this._sampler, this._idGenerator,
this._instrumentationLibrary);
this._instrumentationLibrary,
[spanLimits]) {
_spanLimits = spanLimits ?? _spanLimits;
}

@override
api.Span startSpan(String name,
{api.Context context, api.Attributes attributes}) {
{api.Context context,
api.Attributes attributes,
List<api.Attribute> attribute_list}) {
context ??= api.Context.current;
attributes ??= api.Attributes.empty();

Expand Down Expand Up @@ -50,7 +56,9 @@ class Tracer implements api.Tracer {

return sdk.Span(name, spanContext, parentSpanId, _processors, _resource,
_instrumentationLibrary,
attributes: attributes);
attributes: attributes,
spanlimits: _spanLimits,
attribute_list: attribute_list);
}

/// Records a span of the given [name] for the given function
Expand Down
7 changes: 5 additions & 2 deletions lib/src/sdk/trace/tracer_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ class TracerProvider implements api.TracerProvider {
api.Resource _resource;
api.Sampler _sampler;
api.IdGenerator _idGenerator;
sdk.SpanLimits _spanLimits;

TracerProvider(
{List<api.SpanProcessor> processors,
api.Resource resource,
api.Sampler sampler,
api.IdGenerator idGenerator}) {
api.IdGenerator idGenerator,
sdk.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 ?? sdk.SpanLimits();
}

List<api.SpanProcessor> get spanProcessors => _processors;
Expand All @@ -28,7 +31,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
Expand Down
Loading

0 comments on commit 94ef370

Please sign in to comment.