Tune the rules
The Rules tab of the Linting page lists the 42 checks behind the Linting page, one row per rule. Open it from the sidebar under Quality, or from a finding: the rule chip on a finding card opens the page at that rule.
The rule settings belong to the workspace. Local, Staging, and Production are deployments of the same project, so they use the same rules: a rule you switch off applies to every environment of the workspace. The caption under the toolbar names the workspace the page edits.
The page
Section titled “The page”The rows sit in four groups: Logs, Metrics, Traces, and All signals. A tab row above the groups
filters by signal, and a second tab row filters by state: All, On, Off, and Edited. The search
field reads the rule name, its id, and its description, so span finds every rule that mentions a span.
A row shows the rule name, its id, a one-line description, and a chip per threshold with the value in force. A rule with an edited threshold carries an Edited chip. A rule whose findings you ignored shows the count, with a Clear ignored action that takes them all back.
Switch a rule off
Section titled “Switch a rule off”Every row has a toggle. Switch it off and the rule skips the next scan. Its open findings leave the Improvements page at that scan, and they come back when you switch the rule on again.
The finding card has the same control. Open the card menu and choose Turn the rule off. A banner confirms it and offers the page. Loggerhead scans again at once, so the findings leave without a wait.
Edit a threshold
Section titled “Edit a threshold”Expand a row to see its thresholds. Each one has a label, a sentence that says what it means, and a field with the value in force. The field takes the unit of the threshold:
| Unit | What you type | Example |
|---|---|---|
| Share | A percentage between 0 and 100 | 30 for 30 % of the volume |
| Count | A whole number | 100 records |
| Bytes | A whole number of bytes | 4096 |
| Seconds | A whole number of seconds | 900 for 15 minutes |
| Per minute | A rate | 600 records a minute |
| List | Names separated by commas | service.name, env, request.id |
Press Save rule to apply the values. Press Reset to default to put the rule’s thresholds back. The toolbar has Reset all to defaults, which puts every rule back and switches every rule on. The reset asks first.
A saved value must fit its unit: a share stays between 0 and 100 %, a count stays at or above 0, and a list holds at most 32 names of at most 128 characters each. A value outside the range does not save, and the field says why.
Add an exception
Section titled “Add an exception”A rule can be right in general and wrong for one part of your project. For example, pool.wait_count is 0 in a
healthy system, so Broken metric flags it. An exception hides the findings of one rule that match:
| Field | Matches |
|---|---|
service.name |
The service of the finding |
| Metric name | The metric of a metrics rule |
| Attribute key | The attribute or label key of the finding |
| Operation | The operation or span name of a traces rule |
| Body pattern | Text in the log message of a logs rule, 3 characters or more |
Every field you give must match. * matches any run of characters, so db.client.connection.* matches every
connection metric, and the match ignores case. A finding that an exception matches does not show and does not
count, in every environment of the workspace.
Your AI coding tool adds an exception through the MCP server with add_lint_exception and a reason. Expand the rule
on the Rules tab of the Linting page to see its exceptions, with the reason and the source. Click Remove to take one away.
Ignore a finding
Section titled “Ignore a finding”Ignore a finding from its card on the Linting page, or let your AI coding tool call ignore_finding with a
reason. An ignore from the AI coding tool applies to every environment of the workspace, and shows under
Ignored findings on the Rules tab of the Linting page. Click Stop ignoring to show the finding again.
Changes and undo
Section titled “Changes and undo”The Changes list at the bottom of the Rules tab of the Linting page shows each change to the rules of the workspace, newest first. Each change shows its source (MCP for an AI coding tool, App for this page), its time, what changed, and its reason. Click Undo to put the setting back as it was before the change. Undo is not offered when a later change touched the same setting; undo that change first.
Shared thresholds
Section titled “Shared thresholds”Some thresholds are one setting behind several rules. Records before it fires on the log rules is one field: the seven rules that read it show the same value, and an edit on one row changes them all. The threshold sentence names the other rules that share it.
Every rule and its defaults
Section titled “Every rule and its defaults”Loggerhead scans the last hour again every 30 seconds. The shares below are of that window.
| Rule | Id | Thresholds and defaults |
|---|---|---|
| Noisy logs | noisy_logs |
One body at or above 30 % of the volume, from 100 records |
| Low-signal logs | useless_logs |
DEBUG and TRACE at or above 50 % of the volume, from 100 records |
| Errors without a trace | missing_trace_context |
50 % or more of a service’s ERROR and FATAL records with no trace id, from 20 errors of that service |
| Sensitive data in logs | sensitive_data |
1 record that looks like an email, a card number, or a token |
| Unstructured logs | unstructured_logs |
50 % or more of a service’s records with no attributes, from 50 records |
| Log shape drift | log_schema_drift |
The required fields you declare, or the keys 90 % or more of the records of one message carry; 5 or more records that miss one, from 50 records of that message |
| Oversized log bodies | oversized_log_bodies |
Bodies over 4096 bytes on 2 % or more of a service’s records, from 50 records |
| Interpolated messages | interpolated_log_messages |
80 % or more distinct bodies, from 100 records |
| Severity mismatch | severity_mismatch |
Failure text below WARN on 2 % or more of a service’s records, and on 5 records, from 50 records |
| Missing service name | missing_service_name |
10 % or more of the volume with no service.name, from 50 records |
| Chatty service | chatty_service |
More than 600 records a minute, from 50 records |
| Logs name a missing trace | orphan_log_trace |
20 % or more of the records that carry a trace id name no stored span, from 20 such records |
| The same record twice | duplicate_records |
Repeats at or above 2 % of a service’s records, from 50 records |
| Timestamps out of step | clock_skew |
20 or more records stamped more than 300 seconds ahead of the clock |
| A field named like a secret | sensitive_attribute_keys |
1 record with a key such as password, token, secret, or authorization |
| Exception without a stack trace | exception_without_stacktrace |
20 % or more of a service’s exception records with no exception.stacktrace, from 10 exceptions |
Metrics
Section titled “Metrics”| Rule | Id | Thresholds and defaults |
|---|---|---|
| Broken metric | broken_metric |
Every datapoint in the window is 0. A counter, an UpDownCounter, or a histogram at 0 is not flagged. A gauge whose name says errors, failures, pending items, or waits gets a low impact |
| Stale metric | stale_metric |
No datapoint newer than 900 seconds |
| Duplicate metrics | duplicate_metrics |
Two names that carry the same series |
| High-cardinality label | high_cardinality_label |
A label with 50 or more distinct values |
| Constant metric | constant_metric |
One value on every datapoint, from 20 points. A counter that does not change is not flagged |
| Counter goes down | counter_decrease |
A fall on 10 % or more of the steps of a monotonic sum, from 20 points. An UpDownCounter is not flagged |
| Metric without a service | unattributed_metric |
50 % or more of the points with no service.name, from 20 points |
| Metric naming | metric_naming |
Upper case, an underscore between two namespaces, or an unusual character in a name. An underscore inside one component, as in db.client.connection.pending_requests, is correct |
| Too many series on one metric | series_explosion |
1000 or more distinct label sets on one metric |
| Metric without a unit | missing_metric_unit |
1 OTLP metric whose exporter declared no unit |
| Name and type disagree | metric_type_mismatch |
1 gauge named like a running total, or 1 counter named like a level |
| Points too close together | too_frequent_datapoints |
A series that exports more often than every 5 seconds, from 100 points |
Traces
Section titled “Traces”| Rule | Id | Thresholds and defaults |
|---|---|---|
| Broken trace | broken_trace |
Spans with a missing parent at or above 5 % of a service’s child spans, and 20 or more of them |
| Invalid span duration | invalid_span_duration |
Spans over 300 seconds, or at or below zero, on 5 % or more of the spans, and 20 or more of them |
| Span names hold identifiers | span_name_cardinality |
50 or more distinct names that make up 50 % or more of the spans |
| Missing span name | missing_span_name |
1 span with a blank name or a placeholder: unknown, unknown_operation, unnamed, span, default |
| Suggested alert | suggested_alert |
An error rate at or above 5 % with no alert rule, from 50 spans |
| One-span client traces | root_client_spans |
50 % or more of a service’s traces are one client span, from 50 traces |
| The same call in a loop | repeated_sibling_spans |
One span name 20 or more times under one parent, in 10 or more traces |
| A failed span that reads as OK | error_span_without_status |
5 or more spans with a failure in the attributes and a status that is not ERROR |
| Span without a kind | missing_span_kind |
20 % or more of a service’s spans with no kind, from 20 spans |
| Oversized trace | oversized_trace |
A trace with 500 or more spans. The finding names the root service and operation, and the span names that repeat most |
| Chatty traces | chatty_traces |
More than 60 traces a minute from one root operation, when 90 % or more of them have no error and a root faster than 1 second |
All signals
Section titled “All signals”| Rule | Id | Thresholds and defaults |
|---|---|---|
| A service sends one signal only | signal_coverage_gap |
50 or more records in one signal and none in the other |
| Records with no release, deployment, or host | missing_resource_attributes |
50 % or more of a service’s records with no service.version, deployment.environment.name (or the older deployment.environment), or host.name, from 50 records |
| Renamed attribute keys | deprecated_attributes |
20 or more records with http.method, db.statement, deployment.environment, or another key OpenTelemetry has renamed |
Where the settings live
Section titled “Where the settings live”The settings of a workspace are in one file, lint-settings.json, in the workspace folder under
workspaces/<workspace>/. The file holds the rules, the exceptions, the ignored findings, and the last 200 changes.
The first time the app opens a workspace with no such file, it copies the rule settings of the open environment
into it. The Settings reference shows the shape. The Settings page keeps a summary of
how many rules run, how many are off, and how many are edited, with a button to the page.
Your AI coding tool can read the same list through the MCP server with list_lint_rules, and it can change a rule
with a reason: switch it on or off, set a threshold, add an exception, or ignore a finding. The Changes list on
the Rules tab of the Linting page shows each change with its source (MCP or App), its time, and its reason. Click Undo to take
a change back. See the Tools reference.