Custom Metrics & Events

Overview

While Application Insights auto-collects requests, dependencies, and exceptions, custom telemetry lets you track business-specific events and metrics that matter to your domain โ€” order completions, payment failures, queue depths, or feature usage.

TelemetryClient Basics

The TelemetryClient is the primary API for sending custom telemetry in .NET:

public class OrderService
{
    private readonly TelemetryClient _telemetry;

    public OrderService(TelemetryClient telemetryClient)
    {
        _telemetry = telemetryClient;
    }
}

๐Ÿ’ก In ASP.NET Core, TelemetryClient is registered automatically when you call AddApplicationInsightsTelemetry().

TrackEvent โ€” Custom Events

Use TrackEvent to record discrete business actions:

public async Task PlaceOrder(Order order)
{
    // ... business logic ...

    _telemetry.TrackEvent("OrderPlaced", new Dictionary<string, string>
    {
        ["OrderId"] = order.Id,
        ["CustomerId"] = order.CustomerId,
        ["Region"] = order.ShippingRegion
    }, new Dictionary<string, double>
    {
        ["ItemCount"] = order.Items.Count,
        ["TotalAmount"] = (double)order.Total
    });
}

Parameters:

  • Name โ€” The event name (appears in customEvents table)
  • Properties โ€” String key-value pairs for filtering/grouping
  • Metrics โ€” Numeric values associated with the event

TrackMetric โ€” Custom Metrics

Use TrackMetric for numeric measurements:

// Track queue depth every 30 seconds
_telemetry.TrackMetric("QueueDepth", currentDepth);

// Track with dimensions
var metric = _telemetry.GetMetric("ProcessingTime", "MessageType");
metric.TrackValue(elapsed.TotalMilliseconds, messageType);

GetMetric vs TrackMetric

MethodPre-aggregationRecommended
TrackMetricNo โ€” sends every valueLegacy
GetMetric().TrackValue()Yes โ€” aggregates locallyโœ… Preferred

GetMetric pre-aggregates values locally before sending, reducing network traffic and cost:

// Pre-aggregated metric with up to 2 dimensions
var metric = _telemetry.GetMetric("ApiLatency", "Endpoint", "StatusCode");
metric.TrackValue(responseTime, "/api/orders", "200");

Custom Dimensions

Custom dimensions (properties) let you slice and filter telemetry:

_telemetry.TrackEvent("PaymentProcessed", new Dictionary<string, string>
{
    ["PaymentMethod"] = "CreditCard",
    ["Gateway"] = "Stripe",
    ["Currency"] = "USD"
});

Query in KQL:

customEvents
| where name == "PaymentProcessed"
| extend gateway = tostring(customDimensions["Gateway"])
| summarize count() by gateway
| render piechart

Telemetry Initializers โ€” Global Properties

Add properties to ALL telemetry automatically:

public class EnvironmentInitializer : ITelemetryInitializer
{
    public void Initialize(ITelemetry telemetry)
    {
        telemetry.Context.GlobalProperties["Environment"] = "Production";
        telemetry.Context.GlobalProperties["ServiceVersion"] = "2.1.0";
    }
}

// Register in DI
builder.Services.AddSingleton<ITelemetryInitializer, EnvironmentInitializer>();

JavaScript / Browser SDK

Track events from the frontend:

import { ApplicationInsights } from "@microsoft/applicationinsights-web";

const appInsights = new ApplicationInsights({
  config: { connectionString: "InstrumentationKey=..." }
});
appInsights.loadAppInsights();

// Track a custom event
appInsights.trackEvent({ name: "ButtonClicked" }, { buttonId: "checkout", page: "cart" });

// Track a metric
appInsights.trackMetric({ name: "CartValue", average: 149.99 });

Node.js Custom Telemetry

const appInsights = require("applicationinsights");
const client = appInsights.defaultClient;

client.trackEvent({
  name: "OrderShipped",
  properties: { orderId: "12345", carrier: "FedEx" },
  measurements: { weight: 2.5 }
});

client.trackMetric({ name: "ActiveConnections", value: 42 });

Best Practices

  1. Use consistent naming conventions โ€” PascalCase for event names, camelCase for properties
  2. Keep cardinality low on dimensions โ€” Don't use unique IDs as dimension values (use properties instead)
  3. Prefer GetMetric over TrackMetric โ€” Pre-aggregation saves cost and bandwidth
  4. Don't track PII โ€” Never put emails, SSNs, or passwords in custom dimensions
  5. Batch and flush โ€” In short-lived processes, call TelemetryClient.Flush() before exit
_telemetry.TrackEvent("BatchComplete");
_telemetry.Flush();
// Wait for flush to complete
Task.Delay(5000).Wait();
  1. Use structured event names โ€” Feature.Action pattern (e.g., Cart.ItemAdded, Payment.Failed)
  2. Set operation context โ€” Ensures custom telemetry correlates with the parent request
using var operation = _telemetry.StartOperation<RequestTelemetry>("ProcessBatch");
// All telemetry inside this scope shares the operation_Id
_telemetry.TrackEvent("BatchItemProcessed");

Querying Custom Telemetry

// Events by name over time
customEvents
| where timestamp > ago(24h)
| summarize count() by name, bin(timestamp, 1h)
| render timechart

// Metric values
customMetrics
| where timestamp > ago(1h)
| where name == "QueueDepth"
| summarize avg(value), max(value) by bin(timestamp, 5m)
| render timechart

Key Takeaways

  • TrackEvent records business actions; GetMetric records numeric measurements
  • Custom dimensions enable powerful filtering and grouping in KQL
  • Use telemetry initializers for properties that apply to all telemetry
  • Pre-aggregate with GetMetric to reduce cost in high-throughput scenarios
  • Never include PII in telemetry properties