trackEvent method
Track a Datadog event.
Implementation
Future<void> trackEvent(
String eventName,
Map<String, Object?> properties,
) async {
// Don't send for 3P providers.
if (getApiProvider() != 'firstParty') return;
if (!_initialized) {
final ok = initialize();
if (!ok) return;
}
if (!_datadogAllowedEvents.contains(eventName)) return;
try {
final metadata = await getEventMetadata(
model: properties['model'],
betas: properties['betas'],
);
final envCtx = metadata.envContext.toMap();
final restMetadata = metadata.toMap()..remove('envContext');
final allData = <String, Object?>{
...restMetadata,
...envCtx,
...properties,
'userBucket': getUserBucket(),
};
// Normalise MCP tool names for cardinality reduction.
if (allData['toolName'] is String &&
(allData['toolName'] as String).startsWith('mcp__')) {
allData['toolName'] = 'mcp';
}
// Normalise model names for cardinality reduction.
if (allData['model'] is String) {
final modelStr = (allData['model'] as String).replaceAll(
RegExp(r'\[1m]$', caseSensitive: false),
'',
);
final shortName = getCanonicalModelName(modelStr);
allData['model'] = isKnownModel(shortName) ? shortName : 'other';
}
// Truncate dev version.
if (allData['version'] is String) {
allData['version'] = (allData['version'] as String).replaceFirstMapped(
RegExp(r'^(\d+\.\d+\.\d+-dev\.\d{8})\.t\d+\.sha[a-f0-9]+$'),
(m) => m.group(1)!,
);
}
// Transform status to http_status.
if (allData['status'] != null) {
final statusCode = allData['status'].toString();
allData['http_status'] = statusCode;
final firstDigit = statusCode.isNotEmpty ? statusCode[0] : '';
if (firstDigit.compareTo('1') >= 0 && firstDigit.compareTo('5') <= 0) {
allData['http_status_range'] = '${firstDigit}xx';
}
allData.remove('status');
}
// Build ddtags.
final tags = <String>[
'event:$eventName',
..._tagFields
.where((f) => allData[f] != null)
.map((f) => '${_camelToSnakeCase(f)}:${allData[f]}'),
];
final log = DatadogLog(
ddsource: 'dart',
ddtags: tags.join(','),
message: eventName,
service: 'neomage',
hostname: 'neomage',
extra: {
for (final e in allData.entries)
if (e.value != null) _camelToSnakeCase(e.key): e.value,
},
);
_logBatch.add(log);
if (_logBatch.length >= _maxBatchSize) {
_flushTimer?.cancel();
_flushTimer = null;
unawaited(_flushLogs());
} else {
_scheduleFlush();
}
} catch (_) {
// Swallow — analytics must not crash the app.
}
}