| name | log-events |
| description | Workflow for adding, modifying, or removing log events, domain errors, and log messages in the Warp project. Use when: adding new log events to log-events.json; creating or updating domain errors; adding structured logging to a service; the user mentions 'log event', 'log message', 'domain error', 'LogEvents', 'DomainErrors', 'LogMessages', 'code generator', 'CodeGen', or 'log-events.json'. Also use when diagnosing FormatException errors in logging or build failures related to generated files. |
Log Events Workflow
This skill covers the full lifecycle of adding, modifying, or removing structured log events in the Warp project. The project uses a code generator (Warp.CodeGen) that reads a single JSON source of truth and produces three auto-generated C# files and one TypeScript file per locale. Never edit the generated files directly.
Architecture
Source of Truth
Warp.WebApp/CodeGeneration/log-events.json — single JSON file defining all log events, grouped by category.
Generated Files (never edit these)
| File | What it contains |
|---|
Warp.WebApp/Constants/Logging/LogEvents.cs | EventId constants |
Warp.WebApp/Telemetry/Logging/LogMessages.cs | [LoggerMessage] partial method declarations |
Warp.WebApp/Extensions/DomainErrors.cs | Static factory methods for DomainError instances |
Warp.ClientApp/src/i18n/generated/domain-errors.{locale}.ts | TypeScript record mapping event IDs to localized message templates (one file per locale) |
Generator
Run from the workspace root:
dotnet run --project Warp.CodeGen
Launch settings in Warp.CodeGen/Properties/launchSettings.json supply the file paths.
Step-by-Step: Adding a New Log Event
Step 1 — Choose the category and ID
Open log-events.json. Events are grouped under loggingCategories[].events[]. Each category has a name prefix that determines the ID range. Pick the next available ID within the correct category.
Existing categories and their ID ranges (check the file for current values):
Generic — 10xxx
Startup — 11xxx
Infrastructure — 12xxx
Domain.Entry — 201xx
Domain.Creator — 202xx
Domain.OpenGraph — 203xx
Domain.Image — 204xx
Domain.File — 205xx
Step 2 — Write the event JSON
Add an entry to the correct category's events array:
{
"id": 20407,
"name": "MyNewEvent",
"description": "Something happened to image '{ImageId:Guid}' in entry '{EntryId:Guid}'.",
"domainErrorDescription": {
"en": "Something went wrong with the image.",
"es": "Algo salió mal con la imagen."
},
"logLevel": "Warning",
"generateLogMessage": true,
"obsolete": false,
"httpCode": 400
}
Field reference:
| Field | Required | Notes |
|---|
id | Yes | Unique integer. Must be sequential within its category. |
name | Yes | PascalCase. Becomes the LogEvents.MyNewEvent constant and LogMessages.LogMyNewEvent() method. |
description | Yes | Message template. Parameters use {ParamName:Type} syntax (see below). |
domainErrorDescription | No | Locale object { "en": "...", "es": "..." }. If present, generates a DomainErrors.MyNewEvent() factory method and a corresponding entry in every domain-errors.{locale}.ts file. May use {ParamName:Type} for C# method parameters; the frontend template uses positional {0}, {1} placeholders. This text is returned to the client — never include sensitive details such as internal IDs, stack traces, or infrastructure names. Always include at minimum the "en" key. Add "es" (and any other supported locales) at the same time. |
logLevel | Yes | One of: Trace, Debug, Information, Warning, Error, Critical. |
generateLogMessage | Yes | true to emit a LogMessages.LogMyNewEvent() method; false for events that only need a constant. |
includeException | No | true to add an Exception exception parameter to the log method signature. |
obsolete | Yes | true marks the event with [Obsolete]. |
httpCode | Yes | HTTP status code associated with the event (used in domain error responses). |
Step 3 — Parameter syntax in description
Use {ParamName:Type} where Type is a C# type name:
| Annotation | Generated parameter | Example |
|---|
{ImageId:Guid} | Guid imageId | IDs |
{ErrorMessage:string} | string errorMessage | Freeform text |
{Count:int} | int count | Numeric values |
{Name:string?} | string? name | Nullable strings |
{Value} (no type) | string? value | Default fallback |
Important: The :Type annotation is stripped from the [LoggerMessage] template at generation time. It is only used to determine the C# parameter type. The generated template will contain {ImageId}, not {ImageId:Guid}. This prevents FormatException errors at runtime (e.g., Guid.ToString() does not accept Guid as a format string).
Step 4 — Run the code generator
dotnet run --project Warp.CodeGen
This regenerates all four output files (three C# files and one TypeScript file per locale). Verify the console output shows all files generated successfully.
Step 5 — Build
dotnet build Warp.sln
The build must succeed. Common failure causes:
- Missing parameter in test constructors — if you added a new service dependency to support the log event, update all test files that construct that service.
- JSON syntax error — the generator will print
Error: JSON file not found or invalid.
Step 6 — Use the generated methods
In your service code:
_logger.LogMyNewEvent(imageId, entryId);
return DomainErrors.MyNewEvent(imageId, entryId);
var eventId = LogEvents.MyNewEvent;
Step-by-Step: Adding a Domain Error (without log message)
If you only need a DomainErrors.X() factory method (no runtime logging):
- Add the event with
"generateLogMessage": false
- Include
"domainErrorDescription" with the user-facing message
- Run the code generator
- Build
Common Mistakes
| Mistake | Symptom | Fix |
|---|
Editing LogMessages.cs, LogEvents.cs, or DomainErrors.cs directly | Changes are overwritten on next generator run | Edit log-events.json instead, then run the generator |
Forgetting to run the generator after editing log-events.json | Build errors: method not found | Run dotnet run --project Warp.CodeGen |
| Forgetting to build after running the generator | Stale binaries, runtime errors | Run dotnet build Warp.sln |
| Duplicate event ID | Generator may succeed but runtime EventId collision | Check IDs are unique and sequential |
Using a type annotation as format specifier (e.g. {Id:Guid} in a hand-written [LoggerMessage]) | FormatException: Format string can be only "D", "d", "N", "n", "P", "p", "B", "b", "X" or "x". | Always go through the code generator; it strips type annotations from templates |
Checklist
Use this after every log event change: