# Log search

Source: https://www.pingtower.com/docs/service/search/

Search every kept line across your projects with one query language. Counts by field, a chart over time, the lines around a hit, and saved searches.

Search reads the lines your sources keep (see [retention](/docs/service/logtail/#retaining-lines)). It runs in the [web app](https://app.pingtower.com) under **Logs**, across every project you can read, or the ones you pick. The same query language filters the [live tail](/docs/service/logtail/#following-the-tail).

```text
error "db timeout" service:checkout env:prod level:>=warn user_id:123 latency_ms:>1.5 -healthcheck
```

## Query language

A query is terms separated by spaces, and every term must hold. `OR` between two terms or groups matches a line that satisfies either. Parentheses group terms, and `-` in front of a group leaves out every line the group matches. The empty query matches every line.

| Term | Matches | Example |
| --- | --- | --- |
| word | The message contains it, ignoring case. A substring, not a whole word. | `timeout` |
| `"phrase"` | The message contains the phrase, spaces included, ignoring case. `\"` and `\\` are the only escapes. | `"db timeout"` |
| `source:` or `service:` | The line came from a source with this name. A service is a source. | `service:checkout` |
| `env:` `host:` `run:` | The line's [field](#fields-on-every-line) equals this value exactly. | `env:prod` |
| `trace:` | The line's `trace_id` equals this. | `trace:4bf92f35` |
| `project:` | The line belongs to this project. | `project:payments` |
| `level:` | The level is in this band: `debug`, `info`, `warn` (or `warning`), `error`, `critical` (or `fatal`), or a number. `level:error` is 400 to 499. | `level:error` |
| `level:` with `>=` `>` `<=` `<` | The level compares to the band's floor. | `level:>=warn` |
| `name:value` | The line has a key `name` whose value is exactly `value`. | `gateway:stripe` |
| `name:` with `>=` `>` `<=` `<` | The key's value, read as a number, compares to this one. A missing or non-numeric value never matches. | `latency_ms:>500` |
| `-term` | The opposite of any term above. `-latency_ms:>500` also matches lines without the key. | `-healthcheck` |
| `a OR b` | Either side holds. `OR` is any case: `or` works too. | `timeout OR refused` |
| `( … )` | The terms inside, as one unit. | `service:checkout (timeout OR refused)` |
| `-( … )` | Neither of the terms inside holds: `-(debug OR trace)` is the same as `-debug -trace`. | `-(debug OR trace)` |

A value with a space goes in quotes: `host:"web 1"`. Field names (`env`, `level`, …) ignore case; key names and values do not.

Spaces bind tighter than `OR`: `api timeout OR refused` means `(api timeout) OR refused`. Write `api (timeout OR refused)` to keep `api` on both sides. Groups nest up to 16 deep.

`OR` is a keyword only as a bare word between two terms or groups. To search for the word itself, quote it: `"or"`. An `or` at the start or end of the query or of a group, `-or` and `or:value` are ordinary terms. A `(` opens a group only at the start of a term, so `connect()` is a plain word.

A query holds at most 1,024 bytes and 32 terms. `OR` and parentheses do not count as terms. A query that does not parse, has a `(` that is never closed, has an empty `()` or nests groups more than 16 deep is refused with `422` and the byte offset of the problem, so the web app can underline it:

```json
{ "error": "level takes debug, info, warn, error, critical or a number", "offset": 6 }
```

A query can also page you: a [log-query rule](/docs/service/rules/#log-query-rules) fires when the lines matching it in a window cross a count, change by a percentage against the same window a day or a week earlier, or stray from their usual level over the last week. **Alert on this query** in the web app's log explorer starts one from the current query.

## Results

Results come newest first, a page at a time: 100 lines by default, up to 500. Every line carries its project and source, so a search across projects reads as one stream. A search covers up to 64 projects and up to 366 days.

| Behaviour | What it means |
| --- | --- |
| Total | The count of every match, exact up to 100,000. Past that it says 100,000 and flags the count as capped. A client that only needs the page can skip the count. |
| Key terms | `name:value` terms are checked line by line after everything else has narrowed the range, under a budget of 50,000 lines read per search. If the budget runs out, the result is marked `partial`: the page and the total may be short. Add a word, a field or a shorter range to narrow first. |
| Deadline | A search that takes more than 10 seconds is stopped with `503` `the search took too long; narrow the time range or add filters`. |
| Template | A search can be narrowed to one [template](/docs/service/logtail/#templates) id, so only the lines that share an alert's shape come back. |

Each result also reports, per project, how many sources are keeping lines and how long they are kept, so a range older than retention can be labelled rather than shown empty.

## Counts by field

Beside the results, the Filters panel counts the matches by `source`, `env`, `host`, `run`, `project` or `level` (as its band, `400`). Each field shows its top values, five by default and up to 50, and how many matches fall outside them. A field's count ignores that field's own filter, so ticking one source still shows how many lines the others have. Counting with key terms uses the same scan budget as the search and can be `partial` too.

## Matches over time

The volume chart counts matches per time bucket, all together or split by level. The interval snaps to 1 minute, 5 or 15 minutes, 30 minutes, 1, 3, 6 or 12 hours, 1 day, 7 days or 30 days: the finest step that keeps the range within 200 buckets. Buckets with no matches are included, not skipped.

## A line in context

Open any line to see it with its neighbours: the lines just before and after it from the same project and source, 20 each side by default and up to 100. The neighbours ignore the query, so you see what the source was doing around the hit, not just other hits.

## Saved views

A view saves a search: its query, filters, relative time range (such as `15m`, `24h` or `7d`, at most 30 days) and the table's columns.

A view saved before `OR` existed keeps finding the same lines: a bare `or` in its query stays a word, written as `"or"`. A quoted `"or"` is always the literal word.

- **Private** views are seen, edited and deleted only by the member who saved them. Each member holds up to 100.
- **Shared** views are seen by the whole team. Only an owner or admin, or a [custom role](/docs/getting-started/concepts/#custom-roles) with `team_settings:write`, can share or unshare one. Its creator or any owner or admin can edit or delete it. A team holds up to 500.

A member [limited to some projects](/docs/getting-started/concepts/#per-project-roles) cannot save a view whose project filter or `project:` query terms name a project outside their grants. A view that already names one is hidden from them, and they cannot edit or delete it, even if they created it or are an admin.

Viewers can open views. Saving one needs a member, or `views:write`.

## Fields on every line

Besides `message`, `level`, `keys` and `tags`, every kept line has four indexed fields that search and the counts read directly. Send them as top-level fields on the [ingest payload](/docs/service/ingest/#payload). If a line has none, a key of the same name fills it, so clients that already log `env` or `host` as keys need no change.

| Field | Up to | Meaning |
| --- | --- | --- |
| `env` | 128 bytes | The environment, such as `prod` or `staging`. |
| `host` | 128 bytes | The machine, container or pod. |
| `run` | 128 bytes | A label you choose: a deploy number, a CI build, a job run. |
| `trace_id` | 64 bytes | A trace id, to pull one request's lines from every service. |

Pingtower attaches no meaning to any of them. There is no `service` field: a service is a source.

## Deleting lines

An owner or admin, or a custom role with `logs:write`, can delete kept lines: one line from its detail view, or all of one source's lines between two times. Both are recorded in the [audit log](/docs/service/team/#audit-log). Deleting lines removes them from search and the tail. It does not change alerts that already quoted them, or [secret findings](/docs/service/secrets/) they raised. To remove only a secret and keep the rest of the line, [block the value](/docs/service/secrets/#blocking-a-value) instead.
