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
15 changes: 4 additions & 11 deletions lib/src/api/trace/nonrecording_span.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,19 @@ import '../../../api.dart' as api;
/// This class should not be exposed to consumers and is used internally to wrap
/// [api.SpanContext] being injected or extracted for external calls.
class NonRecordingSpan implements api.Span {
final api.Attributes _attributes = api.Attributes.empty();
final api.SpanStatus _status = api.SpanStatus()..code = api.StatusCode.ok;
final api.SpanContext _spanContext;

NonRecordingSpan(this._spanContext);

@override
api.Attributes get attributes => _attributes;
void setAttribute(api.Attribute attribute) {}

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

@override
void end() {
return;
}
void end() {}

@override
Int64 get endTime => null;
Expand All @@ -42,9 +37,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
2 changes: 1 addition & 1 deletion lib/src/api/trace/noop_tracer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ 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, List<api.Attribute> attributes}) {
final parentContext = context.spanContext;

return api.NonRecordingSpan(
Expand Down
2 changes: 1 addition & 1 deletion lib/src/api/trace/sampler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ abstract class Sampler {
api.TraceId traceId,
String spanName,
bool spanIsRemote, // ignore: avoid_positional_boolean_parameters
api.Attributes spanAttributes);
List<api.Attribute> spanAttributes);

String get description;
}
2 changes: 1 addition & 1 deletion lib/src/api/trace/sampling_result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ enum Decision {
/// be processed for collection.
abstract class SamplingResult {
final api.Decision decision;
final api.Attributes spanAttributes;
final List<api.Attribute> spanAttributes;
final api.TraceState traceState;

SamplingResult(this.decision, this.spanAttributes, this.traceState);
Expand Down
8 changes: 4 additions & 4 deletions lib/src/api/trace/span.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ abstract class Span {
/// Retrieve the status of the [Span].
api.SpanStatus get status;

/// Set metadata to be included on this span.
set attributes(api.Attributes attributes);
/// set single attribute
void setAttribute(api.Attribute attribute);

/// Retrieve metadata included on this span.
api.Attributes get attributes;
/// set multiple attributes
void setAttributes(List<api.Attribute> attributes);

/// Retrieve the resource on this span.
api.Resource get resource;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/api/trace/tracer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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, List<api.Attribute> attributes});

/// Records a span of the given [name] for the given function
/// and marks the span as errored if an exception occurs.
Expand Down
5 changes: 3 additions & 2 deletions lib/src/sdk/trace/exporters/collector_exporter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 sdk;

class CollectorExporter implements api.SpanExporter {
Uri uri;
Expand Down Expand Up @@ -46,7 +47,7 @@ class CollectorExporter implements api.SpanExporter {
<api.InstrumentationLibrary, List<pb_trace.Span>>{};
il[span.instrumentationLibrary] =
il[span.instrumentationLibrary] ?? <pb_trace.Span>[]
..add(_spanToProtobuf(span));
..add(_spanToProtobuf(span as sdk.Span));
rsm[span.resource] = il;
}

Expand Down Expand Up @@ -74,7 +75,7 @@ class CollectorExporter implements api.SpanExporter {
return rss;
}

pb_trace.Span _spanToProtobuf(api.Span span) {
pb_trace.Span _spanToProtobuf(sdk.Span span) {
pb_trace.Status_StatusCode statusCode;
switch (span.status.code) {
case api.StatusCode.unset:
Expand Down
2 changes: 1 addition & 1 deletion lib/src/sdk/trace/samplers/always_off_sampler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class AlwaysOffSampler implements api.Sampler {

@override
api.SamplingResult shouldSample(api.Context context, api.TraceId traceId,
String spanName, bool spanIsRemote, api.Attributes spanAttributes) {
String spanName, bool spanIsRemote, List<api.Attribute> spanAttributes) {
return sdk.SamplingResult(api.Decision.drop, spanAttributes,
context.spanContext?.traceState ?? sdk.TraceState.empty());
}
Expand Down
17 changes: 6 additions & 11 deletions lib/src/sdk/trace/samplers/always_on_sampler.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import '../../../api/common/attributes.dart';
import '../../../api/context/context.dart';
import '../../../api/trace/sampler.dart' as api;
import '../../../api/trace/sampling_result.dart' as result_api;
import '../../../api/trace/trace_id.dart';
import '../trace_state.dart';
import 'sampling_result.dart';
import '../../../../api.dart' as api;
import '../../../../sdk.dart' as sdk;

class AlwaysOnSampler implements api.Sampler {
@override
String get description => 'AlwaysOnSampler';

@override
result_api.SamplingResult shouldSample(Context context, TraceId traceId,
String spanName, bool spanIsRemote, Attributes spanAttributes) {
return SamplingResult(result_api.Decision.recordAndSample, spanAttributes,
context.spanContext?.traceState ?? TraceState.empty());
api.SamplingResult shouldSample(api.Context context, api.TraceId traceId,
String spanName, bool spanIsRemote, List<api.Attribute> spanAttributes) {
return sdk.SamplingResult(api.Decision.recordAndSample, spanAttributes,
context.spanContext?.traceState ?? sdk.TraceState.empty());
}
}
2 changes: 1 addition & 1 deletion lib/src/sdk/trace/samplers/parent_based_sampler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ParentBasedSampler implements api.Sampler {

@override
api.SamplingResult shouldSample(api.Context context, api.TraceId traceId,
String spanName, bool spanIsRemote, api.Attributes spanAttributes) {
String spanName, bool spanIsRemote, List<api.Attribute> spanAttributes) {
final parentSpanContext = context.spanContext;

if (parentSpanContext == null || !parentSpanContext.isValid) {
Expand Down
2 changes: 1 addition & 1 deletion lib/src/sdk/trace/samplers/sampling_result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class SamplingResult implements api.SamplingResult {
@override
final api.Decision decision;
@override
final Attributes spanAttributes;
final List<Attribute> spanAttributes;
@override
final TraceState traceState;

Expand Down
75 changes: 71 additions & 4 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,9 +10,12 @@ 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;
int _droppedSpanAttributes = 0;
final api.Attributes attributes = api.Attributes.empty();

@override
String name;
Expand All @@ -22,9 +26,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}) {
{List<api.Attribute> attributes, sdk.SpanLimits spanlimits}) {
_startTime = Int64(DateTime.now().toUtc().microsecondsSinceEpoch);
this.attributes = attributes ?? api.Attributes.empty();

if (spanlimits != null) _spanLimits = spanlimits;

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

for (var i = 0; i < _processors.length; i++) {
_processors[i].onStart();
}
Expand Down Expand Up @@ -77,16 +87,73 @@ class Span implements api.Span {
_instrumentationLibrary;

@override
api.Attributes attributes;
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) {
_droppedSpanAttributes += attributeList.length;
return;
}

attributeList.forEach(setAttribute);
}

@override
void setAttribute(api.Attribute attr) {
//Don't want to have any attribute
if (_spanLimits.maxNumAttributes == 0) {
_droppedSpanAttributes++;
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) {
_droppedSpanAttributes++;
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 String _applyAttributeLengthLimit(String value, int lengthLimit) {
return value.length > lengthLimit ? value.substring(0, lengthLimit) : value;
}

int get droppedAttributes => _droppedSpanAttributes;
}
Loading