Rulegeneral
Telemetry Events Rule
Rules for telemetry events including important rules of where to create events, naming, and what properties to track
Telemetry Events
Guidelines for implementing telemetry events in the backend, including event structure, naming, and publishing practices.
Implementation
- Create telemetry events to collect information about application behavior and usage, helping stakeholders understand how the application is used
- Always create telemetry events in
/[scs-name]/Core/TelemetryEvents.csto maintain consistent naming - Name telemetry events in past tense (e.g.,
LoginCompleted,UserCreated) and inherit fromTelemetryEvent - Ensure events are sorted alphabetically in the
TelemetryEvents.csfile - Use primary constructors to define event properties
- Include relevant contextual information for better insights:
- Include the ID of aggregates and entities when performing mutations (UserId and TenantId are tracked from execution context)
- Example: in
LoginCompleted, includeUserIdandLoginTimeInSecondsto measure login duration
- Use snake_case for property names in event data to align with OpenTelemetry conventions
- Collect events using
events.CollectEvent()in command handlers just before returning - Events are only collected for successful commands by default—set
commitChanges: truefor failed commands - Don't track IDs of many-to-many aggregates—track the IDs of the two main aggregates instead
- Don't track IDs of single-use operations that are merely unique identifiers, as they provide no analytical value and may contain PII
Note: Telemetry events are automatically annotated with extra data from the request, including current tenant ID, authenticated user ID, user role, application version, user's location, device type, browser, etc.
Examples
Example 1 - Telemetry Event Definitions
// ✅ DO: Use past tense naming and snake_case for property names
public sealed class EmailConfirmationFailed(EmailConfirmationId emailConfirmationId, EmailConfirmationType emailConfirmationType, int retryCount)
: TelemetryEvent(("email_confirmation_id", emailConfirmationId), ("email_confirmation_type", emailConfirmationType), ("retry_count", retryCount));
public sealed class LoginCompleted(UserId userId, int loginTimeInSeconds)
: TelemetryEvent(("user_id", userId), ("login_time_in_seconds", loginTimeInSeconds));
public sealed class UserRoleChanged(UserId userId, UserRole fromRole, UserRole toRole)
: TelemetryEvent(("user_id", userId), ("from_role", fromRole), ("to_role", toRole));
// ❌ DON'T: Use present tense or collect personal information
public sealed class CompleteLogin(LoginID loginId, UserId userId, string email, string ipAddress) // ❌ LoginId is not meaningful in events, Email and IP are PII data
: TelemetryEvent(("user_id", userId), ("email", email), ("ip_address", ipAddress));
Example 2 - Using Telemetry Events in Command Handlers
// ✅ DO: Collect events just before returning and use commitChanges for failed commands
public async Task<Result> Handle(CompleteLoginCommand command, CancellationToken cancellationToken)
{
// Business logic...
if (login.HasExpired())
{
events.CollectEvent(new LoginExpired(login.UserId, login.SecondsSinceStarted));
return Result.BadRequest("The code is no longer valid.", commitChanges: true);
}
// More business logic...
events.CollectEvent(new LoginCompleted(user.Id, login.SecondsSinceStarted));
return Result.Success();
}
// ❌ DON'T: Collect events throughout the method or forget to use commitChanges for failed commands
public async Task<Result> Handle(BadCompleteLoginCommand command, CancellationToken cancellationToken)
{
// Wrong: collecting events too early
events.CollectEvent(new LoginStarted(command.Id));
// Business logic...
if (login.HasExpired())
{
// Wrong: missing commitChanges: true, so event won't be published
return Result.BadRequest("The code is no longer valid.");
}
return Result.Success();
}