Fetch log

The record of every automatic fetch: what a row means, success vs failure vs skipped, the expanded detail, filters, retention and how to debug a broken source.

Every automatic fetch Pulsaria runs is written to a log before anything else happens — successes, failures and deliberately skipped attempts alike. When a KPI stops updating or a number looks wrong, this is the screen that tells you why.

Where it is

Fetch Log is the fifth of the six top-level entries in the sidebar, between Reports and App Settings, on the route /logs. On a narrow window it is one of the six tabs in the bottom bar. Two things also link straight here: the red toast a failed scheduled fetch raises (“Auto-KPI failed” with a View details action), and the failure toast from a manual Run now (“Auto-KPI failed”, also with View details).

The heading is Fetch Log, and under it sits the subtitle “Recent auto-KPI fetches (Bash & HTTP). Max 500 entries.”

The subtitle says Bash and HTTP, but connector fetches are logged here too. That wording is out of date, not the behaviour.

What lands in the log

Only automatic sources write to it — Bash, HTTP and connector KPIs. Manual KPIs never appear, and neither does anything you type on the Daily Entry screen.

Every attempt from these three paths is recorded:

  • The background scheduler, when a KPI’s cron expression fires.
  • Run now on a KPI card or the KPI detail page.
  • Refresh in the Dashboard header, which runs all automatic KPIs at once.

Run test in the KPI dialog and Run test in the connector setup wizard are not logged. They run the same request and show you the same information in the result panel below the button, but they write nothing to disk.

What a row shows

Rows are newest first. Each collapsed row reads, left to right:

ElementWhat it shows
ChevronPoints right when collapsed, rotates down when the row is open
Status iconA green check when a value was stored, a red cross otherwise
KPI nameThe KPI’s name as it was at the moment of the fetch
Second lineOn success Value: 1234. On failure the error message, in red monospace
Source badgeBASH, HTTP or CONNECTOR
DurationFor example 412 ms — how long the whole attempt took
TimeRelative: “just now”, “5 minutes ago”, “3 hours ago”, “2 days ago”. Past seven days it switches to a plain date

The name is a snapshot. Rename a KPI and old rows keep the old name; delete a KPI and its rows stay in the log.

Success, failure and skipped

A row is green only when a number was parsed and written to the KPI’s history for today. There is no partial outcome: a failed fetch stores no value, leaves yesterday’s number in place, and does not touch the KPI’s status.

A row is red in two different situations that the screen does not distinguish:

Failure — the command exited non-zero, the API returned an error status, the response was not JSON, the JSONPath matched nothing, the output was not a number. Something went wrong.

Skipped — Pulsaria deliberately did not run the fetch. There are exactly two reasons, both about approval:

  • Command not approved — please confirm in KPI settings for a Bash KPI that has never been approved.
  • HTTP config not approved — please confirm in KPI settings for a custom HTTP KPI in the same state.

Skipped attempts are stored with their own flag, but the Fetch Log draws them with the same red cross and red text as a real failure, and the status filter counts them under Errors. The one place they get their own treatment is the summary toast after Refresh, which reads “3 successful · 1 failed · 2 skipped” and offers an Approve action. Skipped runs also do not raise the “Auto-KPI failed” toast — only real failures do.

If you see one of those two messages, nothing is broken. Open the KPI, press Run test, then Approve. See Shell commands and Custom HTTP sources for the approval model.

Expanding a row

Click anywhere on a row to open it. The detail area shows, in this order:

  1. The absolute date and time of the attempt, formatted for your language.
  2. HTTP Status: 200 — only for HTTP and connector fetches.
  3. A boxed, monospace stdout block — Bash only, and only when the command printed something.
  4. A boxed stderr block in red — Bash only, only when non-empty.
  5. A boxed body block — the response body of an HTTP or connector fetch.

Each box scrolls on its own once it gets tall, and long lines wrap. Several rows can be open at the same time; the state resets when you leave the screen.

What each source records

SourceRecorded in the file
Bashstdout, stderr, exit code, the command as executed, the shell invocation
HTTPStatus code, response body (first 4096 characters), the URL as dispatched, the method
ConnectorStatus code, response body (first 4096 characters), the resolved URL, the method

The expanded panel shows only the timestamp, the HTTP status, stdout, stderr and the body. The exit code, the executed command, the shell, the effective URL and the HTTP method are written to the file but are not displayed anywhere in the app. To read them, open fetch-log.json yourself or take a JSON export.

For a Bash failure the exit code is at least visible indirectly: the error message on the row itself reads Exit code 1. And if you need the exact command and shell while you are still building a KPI, the Run test result panel in the KPI dialog does show them.

The body is truncated twice on the way in: the native HTTP layer caps any response at 64 KB and gives up after 10 seconds, then Pulsaria stores the first 4096 characters of what came back. A large JSON response will be cut off mid-structure in the log. That is expected.

Filters

Three dropdowns sit above the list and combine with AND. The number of matching rows is shown at the right end of the same line, for example 37 entries.

FilterOptions
KPIAll KPIs, plus every non-manual KPI by name
SourceAll sources, Bash, HTTP
StatusAll, Success, Errors

Two gaps worth knowing:

  • The source filter has no Connector option. Connector rows carry a CONNECTOR badge but are only visible while the source filter is on All sources — picking Bash or HTTP hides them.
  • The status filter has no Skipped option. Skipped runs are matched by Errors.

The KPI dropdown is built from your current KPIs, so a KPI you have deleted has rows in the log but no entry in the filter. You will find them under All KPIs.

With nothing logged at all the list reads “No fetch log entries yet.” When filters exclude everything, it reads “No entries match this filter.”

There is no free-text search, no date range, no paging and no sorting control.

How many entries are kept

The log is a ring buffer capped at 500 entries. A new entry is pushed on the front and the oldest one falls off the end. Nothing else prunes it — there is no age limit, so 500 entries can cover a week or a year depending on how many automatic KPIs you run and how often.

Rough arithmetic: five automatic KPIs on an hourly cron produce 120 rows a day and fill the buffer in about four days. The same five on a daily cron take three months.

Clearing the log

Clear sits at the top right of the screen and only appears when the log has at least one entry. It opens a native macOS dialog titled “Clear log” asking “Clear the entire fetch log?”. Confirm and the file is emptied.

Clearing cannot be undone, and the log is not part of any per-item backup. If you want to keep the evidence of a failure, take a JSON export first — see Backup, export & moving Macs.

Clearing has one side effect worth knowing. The scheduler decides whether a KPI is due by looking at its newest log entry; with the log empty it falls back to the KPI’s creation date instead. For a KPI that has existed for a while, that date is far in the past, so the next scheduler tick treats it as overdue and fetches it once. Expect a small burst of fresh rows within a minute of clearing. See Schedules & refresh.

Where the file lives

The log is one file, fetch-log.json, in your data folder. By default:

~/Library/Application Support/com.onepixelahead.pulsaria/pulsaria/fetch-log.json

If you have pointed Pulsaria at a different data folder, the log moves with it. It is a plain JSON array, newest first, and every field listed above is in there — including the ones the screen does not render.

The log is included in a JSON export but is deliberately never read back on import. Importing an export on a new Mac gives you your KPIs, entries, projects and connections, and an empty fetch log.

stdout, stderr and response bodies are stored verbatim. A command or an API that echoes a token or a customer name on failure puts that string into a file that also ends up in your exports. Keep secrets out of your output.

Debugging a broken source

The order that gets you to the answer fastest:

  1. Set the status filter to Errors and the KPI filter to the KPI in question.
  2. Look at the newest row. The message on the row is usually the whole story — Exit code 1, HTTP 401, Output is not a number.
  3. Expand the row. For Bash, stderr tells you what the command complained about. For HTTP and connectors, the body block holds the API’s own error response, which is almost always more specific than the status code.
  4. Fix the cause — re-approve the command, renew the credential, correct the JSONPath.
  5. Press Run now on the KPI and come back. A fresh green row at the top means it is fixed.

There is no automatic retry and no backoff. A failed fetch simply waits for the next firing of its cron expression, and that failed attempt is what the scheduler uses to decide when “next” is — so a broken KPI is retried on schedule, not every minute.

Connector error messages

Connector failures get translated, human-readable messages. Detail: is followed by the first 200 characters of the API’s response body.

MessageWhat to do
Unauthorized (401). The access token was rejected. Detail: …The credential is wrong, expired or revoked. Re-authorize the connection in App Settings → Connections
Access denied (403). Make sure the Service Account email is granted access to the property. Detail: …The credential is valid but has no access to that property or site. Grant it in Search Console or GA4
API not enabled in the Google Cloud project (403). Enable it and retry. Detail: …Enable the Search Console or Analytics Data API in the Cloud project the key belongs to
Bad request (400). Check parameters (site URL / property ID). Detail: …A connection parameter is wrong — a site URL that does not match the verified property, a mistyped property ID
Rate limit hit (429). Wait a minute and retry.Nothing to fix. This is the only message without a detail snippet
Server error (503). Try again later. Detail: …The provider’s problem. The next scheduled run will pick it up
Connection not found (deleted?)The KPI points at a connection that no longer exists. Edit the KPI and pick another
Credentials missing from keychain. Please re-authorize the connection.The Keychain item is gone. Re-authorize the connection
Connector KPI references no metric/connectionThe KPI was saved without a metric or a connection. Edit it and finish the setup
JSONPath "…" returned nothingThe API answered, but not in the expected shape. Read the body block

More background on each connector is on the Connectors page.

Bash and HTTP error messages

MessageMeaning
Command not approved — please confirm in KPI settingsSkipped, not failed. Approve the command
HTTP config not approved — please confirm in KPI settingsSkipped, not failed. Approve the HTTP config
Command changed — please re-approveThe command was edited after approval. Test and approve it again
HTTP config changed — please re-approveThe URL, headers or JSONPath changed after approval
Exit code 1The command failed. Read stderr
Output is not a numberThe command ran but printed something Pulsaria cannot parse. Read stdout
No command configuredThe Bash KPI has an empty command field
Shell commands are only available on macOSBash KPIs do not run on other platforms
HTTP 404A custom HTTP KPI got a non-2xx status. Read the body block
Response is not valid JSONThe endpoint answered with something other than JSON
JSONPath result not numericThe path matched, but the value is not a number
URL or JSONPath missingThe HTTP KPI is incomplete

Custom HTTP KPIs get the bare HTTP <status> message — the friendly 401/403/400/429 wording above exists only on the connector path. For your own endpoints, the body block in the expanded row is where the explanation is.

What this screen does not do

  • No export of the log from here. It travels only inside a full JSON export.
  • No search, no date filter, no paging.
  • No live tail — the list refreshes when data changes, not continuously.
  • No per-KPI retention or purge; Clear is all or nothing.
  • Nothing in the log is sent anywhere. It is a file on your Mac, like the rest of your data — see Privacy: what leaves your Mac.

Didn't find it?

Write to us — a real person reads every mail, usually the one who wrote the feature.

admin@one-pixel-ahead.com