Skip to content

Commit 3dc7afc

Browse files
committed
Merge branch 'main' into chore/update-gh-actions-run-vars
2 parents e6f2488 + 6ea1f55 commit 3dc7afc

File tree

6 files changed

+657
-2
lines changed

6 files changed

+657
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
## Unreleased
44

5-
### Features
5+
## Features
66

77
- Add os and device attributes to Flutter logs ([#2978](https://github.com/getsentry/sentry-dart/pull/2978))
8+
- String templating for structured logs #3002 ([#3002](https://github.com/getsentry/sentry-dart/pull/3002))
9+
### Features
810

911
## 9.1.0
1012

@@ -48,7 +50,7 @@ void initState() {
4850
```
4951
- Add `message` parameter to `captureException()` ([#2882](https://github.com/getsentry/sentry-dart/pull/2882))
5052
- Add module in SentryStackFrame ([#2931](https://github.com/getsentry/sentry-dart/pull/2931))
51-
- Set `SentryOptions.includeModuleInStackTrace = true` to enable this. This may change grouping of exceptions.
53+
- Set `SentryOptions.includeModuleInStackTrace = true` to enable this. This may change grouping of exceptions.
5254

5355
### Dependencies
5456

dart/lib/src/sentry_logger.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import 'protocol/sentry_log.dart';
55
import 'protocol/sentry_log_level.dart';
66
import 'protocol/sentry_log_attribute.dart';
77
import 'sentry_options.dart';
8+
import 'sentry_logger_formatter.dart';
89

910
class SentryLogger {
1011
SentryLogger(this._clock, {Hub? hub}) : _hub = hub ?? HubAdapter();
1112

1213
final ClockProvider _clock;
1314
final Hub _hub;
1415

16+
late final fmt = SentryLoggerFormatter(this);
17+
1518
FutureOr<void> trace(
1619
String body, {
1720
Map<String, SentryLogAttribute>? attributes,
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import 'dart:async';
2+
import 'protocol/sentry_log_attribute.dart';
3+
import 'sentry_template_string.dart';
4+
import 'sentry_logger.dart';
5+
6+
class SentryLoggerFormatter {
7+
SentryLoggerFormatter(this._logger);
8+
9+
final SentryLogger _logger;
10+
11+
FutureOr<void> trace(
12+
String templateBody,
13+
List<dynamic> arguments, {
14+
Map<String, SentryLogAttribute>? attributes,
15+
}) {
16+
return _format(
17+
templateBody,
18+
arguments,
19+
attributes,
20+
(formattedBody, allAttributes) {
21+
return _logger.trace(formattedBody, attributes: allAttributes);
22+
},
23+
);
24+
}
25+
26+
FutureOr<void> debug(
27+
String templateBody,
28+
List<dynamic> arguments, {
29+
Map<String, SentryLogAttribute>? attributes,
30+
}) {
31+
return _format(
32+
templateBody,
33+
arguments,
34+
attributes,
35+
(formattedBody, allAttributes) {
36+
return _logger.debug(formattedBody, attributes: allAttributes);
37+
},
38+
);
39+
}
40+
41+
FutureOr<void> info(
42+
String templateBody,
43+
List<dynamic> arguments, {
44+
Map<String, SentryLogAttribute>? attributes,
45+
}) {
46+
return _format(
47+
templateBody,
48+
arguments,
49+
attributes,
50+
(formattedBody, allAttributes) {
51+
return _logger.info(formattedBody, attributes: allAttributes);
52+
},
53+
);
54+
}
55+
56+
FutureOr<void> warn(
57+
String templateBody,
58+
List<dynamic> arguments, {
59+
Map<String, SentryLogAttribute>? attributes,
60+
}) {
61+
return _format(
62+
templateBody,
63+
arguments,
64+
attributes,
65+
(formattedBody, allAttributes) {
66+
return _logger.warn(formattedBody, attributes: allAttributes);
67+
},
68+
);
69+
}
70+
71+
FutureOr<void> error(
72+
String templateBody,
73+
List<dynamic> arguments, {
74+
Map<String, SentryLogAttribute>? attributes,
75+
}) {
76+
return _format(
77+
templateBody,
78+
arguments,
79+
attributes,
80+
(formattedBody, allAttributes) {
81+
return _logger.error(formattedBody, attributes: allAttributes);
82+
},
83+
);
84+
}
85+
86+
FutureOr<void> fatal(
87+
String templateBody,
88+
List<dynamic> arguments, {
89+
Map<String, SentryLogAttribute>? attributes,
90+
}) {
91+
return _format(
92+
templateBody,
93+
arguments,
94+
attributes,
95+
(formattedBody, allAttributes) {
96+
return _logger.fatal(formattedBody, attributes: allAttributes);
97+
},
98+
);
99+
}
100+
101+
// Helper
102+
103+
FutureOr<void> _format(
104+
String templateBody,
105+
List<dynamic> arguments,
106+
Map<String, SentryLogAttribute>? attributes,
107+
FutureOr<void> Function(String, Map<String, SentryLogAttribute>) callback,
108+
) {
109+
final templateString = SentryTemplateString(templateBody, arguments);
110+
final formattedBody = templateString.format();
111+
final templateAttributes = _getAllAttributes(templateBody, arguments);
112+
if (attributes != null) {
113+
templateAttributes.addAll(attributes);
114+
}
115+
return callback(formattedBody, templateAttributes);
116+
}
117+
118+
Map<String, SentryLogAttribute> _getAllAttributes(
119+
String templateBody,
120+
List<dynamic> args,
121+
) {
122+
final templateAttributes = {
123+
'sentry.message.template': SentryLogAttribute.string(templateBody),
124+
};
125+
for (var i = 0; i < args.length; i++) {
126+
final argument = args[i];
127+
final key = 'sentry.message.parameter.$i';
128+
if (argument is String) {
129+
templateAttributes[key] = SentryLogAttribute.string(argument);
130+
} else if (argument is int) {
131+
templateAttributes[key] = SentryLogAttribute.int(argument);
132+
} else if (argument is bool) {
133+
templateAttributes[key] = SentryLogAttribute.bool(argument);
134+
} else if (argument is double) {
135+
templateAttributes[key] = SentryLogAttribute.double(argument);
136+
} else {
137+
try {
138+
templateAttributes[key] =
139+
SentryLogAttribute.string(argument.toString());
140+
} catch (e) {
141+
templateAttributes[key] = SentryLogAttribute.string("");
142+
}
143+
}
144+
}
145+
return templateAttributes;
146+
}
147+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
class SentryTemplateString {
2+
SentryTemplateString(this.template, this.arguments);
3+
4+
final String template;
5+
final List<dynamic> arguments;
6+
static final _regex = RegExp(r'%(?:%|s)');
7+
8+
String format() {
9+
assert(arguments.isNotEmpty, 'No arguments provided for template.');
10+
11+
int argIndex = 0;
12+
var foundPlaceholders = false;
13+
final string = template.replaceAllMapped(_regex, (Match m) {
14+
final token = m[0];
15+
if (token == '%%') {
16+
// `%%` → literal `%`
17+
return '%';
18+
}
19+
foundPlaceholders = true;
20+
21+
// `%s` → next argument or empty if none left
22+
if (argIndex < arguments.length) {
23+
final value = arguments[argIndex++];
24+
try {
25+
return value.toString();
26+
} catch (e) {
27+
// If toString() fails, return empty string
28+
return '';
29+
}
30+
}
31+
return '';
32+
});
33+
34+
assert(foundPlaceholders, 'No placeholders provided in template.');
35+
36+
return string;
37+
}
38+
39+
@override
40+
String toString() {
41+
return format();
42+
}
43+
}

0 commit comments

Comments
 (0)