trackEvent method

Future<void> trackEvent(
  1. String eventName,
  2. Map<String, Object?> properties
)

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.
  }
}