---
title: "Tune the rules"
description: "Every lint rule has a row on the Rules tab of the Linting page: a toggle, its thresholds, its exceptions, and a reset. The settings belong to the workspace."
source: "https://getloggerhead.com/docs/telemetry-linting/tune-the-rules/"
---

# Tune the rules

> Every lint rule has a row on the Rules tab of the Linting page: a toggle, its thresholds, its exceptions, and a reset. The settings belong to the workspace.

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

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

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

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

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

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

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

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

Loggerhead scans the last hour again every 30 seconds. The shares below are of that window.

### Logs

| 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

| 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

| 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

| 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

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](/docs/reference/settings/) 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](/docs/mcp-server/tools-reference/).
