Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
767153c
Add setAttributeS
buenaflor Nov 18, 2025
578f81d
Update
buenaflor Nov 18, 2025
803f85b
Update
buenaflor Nov 18, 2025
58789a8
Add removeAttribute
buenaflor Nov 18, 2025
ad4bef1
Add scope test
buenaflor Nov 18, 2025
c773c5f
Update
buenaflor Nov 18, 2025
58d3d29
Update
buenaflor Nov 18, 2025
e26d83a
Update
buenaflor Nov 18, 2025
2cf1cdb
Update
buenaflor Nov 18, 2025
1ad1c46
Update
buenaflor Nov 18, 2025
6a6fab2
Update
buenaflor Nov 18, 2025
58126a6
Update
buenaflor Nov 18, 2025
cbd6718
Update
buenaflor Nov 18, 2025
ba81f2f
Update
buenaflor Nov 18, 2025
3b83bd1
Update
buenaflor Nov 18, 2025
968e8d1
Update
buenaflor Nov 19, 2025
1e8dbbc
Update
buenaflor Nov 19, 2025
99a1dc1
Update
buenaflor Nov 19, 2025
9a99d54
Update
buenaflor Nov 19, 2025
f33d4f9
Update
buenaflor Nov 19, 2025
224eab1
Update
buenaflor Nov 19, 2025
ad95fde
Merge scope
buenaflor Nov 20, 2025
2e39532
Update
buenaflor Nov 20, 2025
625bf76
Fix analyzer
buenaflor Nov 20, 2025
796bba9
Fix analyzer
buenaflor Nov 20, 2025
03bbc0e
Merge branch 'main' into feat/set-attributes
buenaflor Nov 20, 2025
ceddd30
Merge branch 'main' into feat/set-attributes
buenaflor Nov 20, 2025
4d96d5c
Add Spanv2 protocol
buenaflor Nov 20, 2025
b233b32
Add Spanv2 protocol
buenaflor Nov 20, 2025
b4c7ce1
Update
buenaflor Nov 24, 2025
0be13b0
Merge branch 'main' into feat/span-protocol
buenaflor Nov 24, 2025
2a1efd5
Rename
buenaflor Nov 24, 2025
fb13e85
Update
buenaflor Nov 24, 2025
6d6e8da
Update
buenaflor Nov 24, 2025
0a60229
Update
buenaflor Nov 24, 2025
bded9d6
Update
buenaflor Nov 25, 2025
3707531
Update
buenaflor Nov 25, 2025
acb0a1a
Analyze
buenaflor Nov 25, 2025
f4797ee
Consistency
buenaflor Nov 26, 2025
a869de4
Update API
buenaflor Nov 26, 2025
588f54b
Update API
buenaflor Nov 26, 2025
9996397
Remove debug print
buenaflor Nov 26, 2025
9219c7b
Fix analyze
buenaflor Nov 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Update
  • Loading branch information
buenaflor committed Nov 18, 2025
commit 803f85bf45a9e62f084f1a84ed435603d18df1a1
12 changes: 11 additions & 1 deletion packages/dart/lib/src/hub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,17 @@ class Hub {
return scope;
}

void setAttributes(Map<String, SentryAttribute> attributes) {}
void setAttributes(Map<String, SentryAttribute> attributes) {
if (!_isEnabled) {
_options.log(
SentryLevel.warning,
"Instance is disabled and this 'setAttributes' call is a no-op.",
);
} else {
final item = _peek();
item.scope.setAttributes(attributes);
}
}

/// Adds a breacrumb to the current Scope
Future<void> addBreadcrumb(Breadcrumb crumb, {Hint? hint}) async {
Expand Down
4 changes: 3 additions & 1 deletion packages/dart/lib/src/sentry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,9 @@ class Sentry {
/// These attributes will be applied to logs.
/// When the same attribute keys exist on the current log,
/// it takes precedence over an attribute with the same key set on any scope.
static void setAttributes(Map<String, SentryAttribute> attributes) {}
static void setAttributes(Map<String, SentryAttribute> attributes) {
_hub.setAttributes(attributes);
}

/// Configures the scope through the callback.
static FutureOr<void> configureScope(ScopeCallback callback) =>
Expand Down
138 changes: 138 additions & 0 deletions packages/dart/test/protocol/sentry_attribute_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import 'package:test/test.dart';
import 'package:sentry/sentry.dart';

void main() {
test('$SentryAttribute string to json', () {
final attribute = SentryAttribute.string('test');
final json = attribute.toJson();
expect(json, {
'value': 'test',
'type': 'string',
});
});

test('$SentryAttribute bool to json', () {
final attribute = SentryAttribute.bool(true);
final json = attribute.toJson();
expect(json, {
'value': true,
'type': 'boolean',
});
});

test('$SentryAttribute int to json', () {
final attribute = SentryAttribute.int(1);
final json = attribute.toJson();

expect(json, {
'value': 1,
'type': 'integer',
});
});

test('$SentryAttribute double to json', () {
final attribute = SentryAttribute.double(1.0);
final json = attribute.toJson();

expect(json, {
'value': 1.0,
'type': 'double',
});
});

test('$SentryAttribute units to json', () {
final cases = <SentryUnit, String>{
SentryUnit.milliseconds: 'ms',
SentryUnit.seconds: 's',
SentryUnit.bytes: 'bytes',
SentryUnit.count: 'count',
SentryUnit.percent: 'percent',
};

cases.forEach((unit, unitString) {
final attribute = SentryAttribute.int(1, unit: unit);
final json = attribute.toJson();
expect(json, {
'value': 1,
'type': 'integer',
'unit': unitString,
});
});
});

test('$SentryAttribute stringArr to json', () {
final attribute = SentryAttribute.stringArr(['a', 'b']);
final json = attribute.toJson();
expect(json, {
'value': ['a', 'b'],
'type': 'string[]',
});
});

test('$SentryAttribute stringArr with unit to json', () {
final attribute =
SentryAttribute.stringArr(['x', 'y'], unit: SentryUnit.count);
final json = attribute.toJson();
expect(json, {
'value': ['x', 'y'],
'type': 'string[]',
'unit': 'count',
});
});

test('$SentryAttribute intArr to json', () {
final attribute = SentryAttribute.intArr([1, 2, 3]);
final json = attribute.toJson();
expect(json, {
'value': [1, 2, 3],
'type': 'integer[]',
});
});

test('$SentryAttribute intArr with unit to json', () {
final attribute = SentryAttribute.intArr([4, 5], unit: SentryUnit.count);
final json = attribute.toJson();
expect(json, {
'value': [4, 5],
'type': 'integer[]',
'unit': 'count',
});
});

test('$SentryAttribute doubleArr to json', () {
final attribute = SentryAttribute.doubleArr([1.0, 2.5]);
final json = attribute.toJson();
expect(json, {
'value': [1.0, 2.5],
'type': 'double[]',
});
});

test('$SentryAttribute doubleArr with unit to json', () {
final attribute =
SentryAttribute.doubleArr([0.1, 0.2], unit: SentryUnit.seconds);
final json = attribute.toJson();
expect(json, {
'value': [0.1, 0.2],
'type': 'double[]',
'unit': 's',
});
});

test('$SentryAttribute unit set after construction to json', () {
final attribute = SentryAttribute.double(2.5);
final jsonWithoutUnit = attribute.toJson();
expect(jsonWithoutUnit, {
'value': 2.5,
'type': 'double',
});

attribute.unit = SentryUnit.bytes;
final jsonWithUnit = attribute.toJson();
expect(jsonWithUnit, {
'value': 2.5,
'type': 'double',
'unit': 'bytes',
});
});
}
42 changes: 0 additions & 42 deletions packages/dart/test/protocol/sentry_log_attribute_test.dart

This file was deleted.

26 changes: 21 additions & 5 deletions packages/dart/test/protocol/sentry_log_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ void main() {
level: SentryLogLevel.info,
body: 'fixture-body',
attributes: {
'test': SentryLogAttribute.string('fixture-test'),
'test2': SentryLogAttribute.bool(true),
'test3': SentryLogAttribute.int(9001),
'test4': SentryLogAttribute.double(9000.1),
'test': SentryAttribute.string('fixture-test'),
'test2': SentryAttribute.bool(true),
'test3': SentryAttribute.int(9001),
'test4': SentryAttribute.double(9000.1),
'test5': SentryAttribute.intArr([1, 2, 3]),
'test6': SentryAttribute.doubleArr([1.1, 2.2, 3.3]),
'test7': SentryAttribute.stringArr(['a', 'b', 'c']),
'test8': SentryAttribute.int(12, unit: SentryUnit.count),
},
severityNumber: 1,
);
Expand Down Expand Up @@ -44,6 +48,18 @@ void main() {
'value': 9000.1,
'type': 'double',
},
'test5': {
'value': [1, 2, 3],
'type': 'integer[]',
},
'test6': {
'value': [1.1, 2.2, 3.3],
'type': 'double[]',
},
'test7': {
'value': ['a', 'b', 'c'],
'type': 'string[]',
},
},
'severity_number': 1,
});
Expand All @@ -56,7 +72,7 @@ void main() {
level: SentryLogLevel.trace,
body: 'fixture-body',
attributes: {
'test': SentryLogAttribute.string('fixture-test'),
'test': SentryAttribute.string('fixture-test'),
},
);

Expand Down
81 changes: 81 additions & 0 deletions packages/dart/test/sentry_logger_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,87 @@ void main() {
'special values {"nan": NaN, "positive_infinity": Infinity, "negative_infinity": -Infinity}');
expect(logCall.logger, 'sentry_logger');
});

test('applies scope attributes to all log methods', () {
final logger = fixture.getSut();

final scopeAttributes = <String, SentryAttribute>{
'scopeString': SentryAttribute.string('fromScope'),
'scopeInt': SentryAttribute.int(42),
};
fixture.hub.scope.setAttributes(scopeAttributes);

logger.trace('trace message');
logger.debug('debug message');
logger.info('info message');
logger.warn('warn message');
logger.error('error message');
logger.fatal('fatal message');

expect(fixture.hub.captureLogCalls.length, 6);
for (final call in fixture.hub.captureLogCalls) {
final attrs = call.log.attributes;
expect(attrs['scopeString'], scopeAttributes['scopeString']);
expect(attrs['scopeInt'], scopeAttributes['scopeInt']);
}
});

test('per-log attributes override scope on same key', () {
final logger = fixture.getSut();

final scopeAttributes = <String, SentryAttribute>{
'overridden': SentryAttribute.string('fromScope'),
'kept': SentryAttribute.bool(true),
};
fixture.hub.scope.setAttributes(scopeAttributes);

final overrideAttr = SentryAttribute.string('fromLog');
logger.info('override test', attributes: {
'overridden': overrideAttr,
'logOnly': SentryAttribute.double(1.23),
});

expect(fixture.hub.captureLogCalls.length, 1);
final captured = fixture.hub.captureLogCalls[0].log;
// overridden key should come from per-log attributes
expect(captured.attributes['overridden'], overrideAttr);
// scope-only key should still be present
expect(captured.attributes['kept'], scopeAttributes['kept']);
// log-only key should be present
expect(captured.attributes['logOnly']?.type, 'double');
});

test('formatter path merges template, per-log and scope attributes', () {
final logger = fixture.getSut();

final scopeAttributes = <String, SentryAttribute>{
'scopeOnly': SentryAttribute.string('present'),
};
fixture.hub.scope.setAttributes(scopeAttributes);

logger.fmt.info(
'Hello, %s!',
['World'],
attributes: {'callOnly': SentryAttribute.string('present')},
);

expect(fixture.hub.captureLogCalls.length, 1);
final captured = fixture.hub.captureLogCalls[0].log;
final attrs = captured.attributes;

// template attributes
expect(attrs['sentry.message.template']?.type, 'string');
expect(attrs['sentry.message.template']?.value, 'Hello, %s!');
expect(attrs['sentry.message.parameter.0']?.type, 'string');
expect(attrs['sentry.message.parameter.0']?.value, 'World');

// per-log attribute
expect(attrs['callOnly']?.type, 'string');
expect(attrs['callOnly']?.value, 'present');

// scope attribute
expect(attrs['scopeOnly'], scopeAttributes['scopeOnly']);
});
}

class Fixture {
Expand Down
14 changes: 7 additions & 7 deletions packages/logging/lib/src/logging_integration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ class LoggingIntegration implements Integration<SentryOptions> {
Level minBreadcrumbLevel = Level.INFO,
Level minEventLevel = Level.SEVERE,
Level minSentryLogLevel = Level.INFO,
}) : _minBreadcrumbLevel = minBreadcrumbLevel,
_minEventLevel = minEventLevel,
_minSentryLogLevel = minSentryLogLevel;
}) : _minBreadcrumbLevel = minBreadcrumbLevel,
_minEventLevel = minEventLevel,
_minSentryLogLevel = minSentryLogLevel;

final Level _minBreadcrumbLevel;
final Level _minEventLevel;
Expand Down Expand Up @@ -100,10 +100,10 @@ class LoggingIntegration implements Integration<SentryOptions> {

if (_options.enableLogs && _isLoggable(record.level, _minSentryLogLevel)) {
final attributes = {
'loggerName': SentryLogAttribute.string(record.loggerName),
'sequenceNumber': SentryLogAttribute.int(record.sequenceNumber),
'time': SentryLogAttribute.int(record.time.millisecondsSinceEpoch),
'sentry.origin': SentryLogAttribute.string(origin),
'loggerName': SentryAttribute.string(record.loggerName),
'sequenceNumber': SentryAttribute.int(record.sequenceNumber),
'time': SentryAttribute.int(record.time.millisecondsSinceEpoch),
'sentry.origin': SentryAttribute.string(origin),
};

// Map log levels based on value ranges
Expand Down
Loading
Loading