Do not start observability work by drawing dashboards. Start by making the logs queryable.

A dashboard is only useful if the data behind it has stable fields. If every important fact is buried inside a different message string, the dashboard becomes text search with nicer colors. You can count errors, but you cannot reliably group them by tenant, dependency, workflow step, model, queue, or failure reason.

In .NET, use message templates instead of string interpolation or concatenation. For recurring application events, prefer source-generated logging so the event becomes a typed method instead of another ad hoc string:

public static partial class PaymentLogging
{
    [LoggerMessage(
        EventId = 1201,
        EventName = "PaymentCaptureFailed",
        Level = LogLevel.Warning,
        Message = "Payment capture failed for Provider {PaymentProvider}, StatusCode {StatusCode}, FailureReason {FailureReason}, OrderId {OrderId}")]
    public static partial void PaymentCaptureFailed(
        this ILogger logger,
        Exception exception,
        string paymentProvider,
        int statusCode,
        string failureReason,
        string orderId);
}

The calling code is now explicit: logger.PaymentCaptureFailed(exception, paymentProvider, statusCode, failureReason, orderId). The message still reads well, but PaymentProvider, StatusCode, FailureReason, and OrderId are structured fields that a capable log backend can preserve, index, filter, and aggregate. The EventId and EventName give the event a stable identity even if you later improve the wording.

Source-generated logging also gives you compile-time diagnostics when the method and template drift apart. It is faster than the plain extension-method path and avoids much of the runtime allocation work, but the bigger design win is that developers have to name the event and its fields up front.

Before adding a dashboard, decide which fields you expect to filter by. For most application logs, that means a small set of boring dimensions:

  • operation or workflow step
  • dependency name
  • result class or failure reason
  • status code or exception type
  • model, tool, queue, cache, or database name when relevant
  • tenant, customer, or account id for investigation, when safe and intentionally indexed

Keep dashboard dimensions stable and low-cardinality. A field like PaymentProvider, StatusCode, or a normalized FailureReason is useful for charts. High-cardinality values such as order IDs, user IDs, or account IDs usually belong in drill-down and investigation, not as default metric-style dimensions. A field like raw request body, prompt text, access token, email address, or full exception detail as a tag is not. Sensitive data does not become safe because it is inside telemetry.

Also separate logs from metrics. If you need a chart for request rate, latency, queue depth, token usage, or cache hit ratio, emit a metric or use existing instrumentation. Logs explain what happened in a specific case. Metrics explain shape and trend.

Use structured logs before dashboards when you want production questions to be answerable later. Do not polish a dashboard on top of vague strings and missing fields. That only makes the blind spots look more official.