KQL · cost
Noisiest trace messages by volume and severity
The log messages that cost the most over the last 24 hours, grouped by message prefix and severity level, with billed megabytes and item counts.
traces cost
let lookback = 24h;
traces
| where timestamp > ago(lookback)
| extend message_start = substring(message, 0, 80)
| summarize
items = sum(itemCount),
billed_mb = round(sum(_BilledSize) / 1e6, 1),
roles = dcount(cloud_RoleName),
example_role = take_any(cloud_RoleName)
by message_start, severityLevel
| top 25 by billed_mb desc Returns: Up to 25 rows, one per message prefix and severity level, with the sampling-adjusted number of trace items, their billed megabytes and the services that wrote them, most expensive first.
Pitfalls
- Grouping by the first 80 characters folds messages that differ only at the end, such as an ID, into one row. Messages that start with a timestamp or an ID won't group; shorten or move the prefix.
- severityLevel is a number. In the classic SeverityLevel enum, 0 is Verbose, 1 Information, 2 Warning, 3 Error and 4 Critical.
- Billed size counts what was stored. Trace items dropped by sampling cost nothing, so a sampled message can be noisier at the source than it looks here.
- message can contain personal data your code logged. Mind where you share the result.
How it works
Logs are usually the biggest and least-read part of an Application Insights bill. A handful of messages, logged on every request at a level nobody looks at, can outweigh everything else. This query finds them, ranked by what they cost rather than how often they occur.
Line by line
let lookback = 24h;covers a day; logging volume follows traffic, so a day is a fair sample.message_start = substring(message, 0, 80)keeps the first 80 characters. Log messages usually start with fixed text and end with the variable part (an order number, a duration), so the prefix works as a rough template and keeps the grouping from exploding into one row per message.items = sum(itemCount)counts trace items the sampling-aware way.billed_mb = round(sum(_BilledSize) / 1e6, 1)adds up billed bytes in megabytes._BilledSizeis a standard column Microsoft documents on every ingested Application Insights event; it only appears when you name it.rolesandexample_roleshow which services write the message, which tells you whose logging configuration to change.top 25 by billed_mb descranks by cost. Rank byitemsinstead to find the most frequent messages.
What to do with the result
For each expensive row, decide whether anyone reads it. Information-level messages written on every request are the usual culprits: raise the minimum level for that category, or log the event once per batch instead of once per item. The logging guide shows where the levels are set for ILogger in the classic SDK and the OpenTelemetry Distro. Messages you do need but not at full volume are a job for sampling, which the pricing guide compares with filtering.
If your logging library records the message template as a property, grouping by it beats a prefix. Look at customDimensions on a few rows (traces | take 10 | project message, customDimensions) to see what your provider records.
Variations
- By level only: group by
severityLevelto see how much each level costs in total. - Workspace tables: use
AppTraces,TimeGenerated,Message,SeverityLevel,AppRoleName,ItemCountand_BilledSize.
For the table-level picture first, see telemetry volume by table.