Error Logs

8 views Markdown

The seven endpoints that read caught errors, clear them and manage the logging settings.

Overview

These seven endpoints read the errors that were caught, clear them, and set which books are kept.

A row is not one event but one problem. The same error repeated a hundred times stays one row, with its counter raised and its last-seen time refreshed. That is why records are addressed by signature rather than by id.

The records are a history: a row does not say "this is broken", it says "this was broken then". Before setting out to fix one, it is worth measuring whether it still happens.

Reference

Listing the Error Records

get/api/v1/admin/tools/logs/errors
Tools/GetErrorLogs admin many filters

Returns the errors that were caught. Each row is not one event but one problem.

Query parameters — filters 14
searchstringA general search over the records.
typesstringFilters by record type.
levelsstringFilters by severity.
signaturestringNarrows to one signature.
filestringFilters by the file the error came from.
exception_classstringFilters by the class thrown.
request_uristringFilters by the request address.
http_methodstringFilters by the request method.
user_idintFilters by user.
ipstringFilters by address.
statusstringFilters by the record status.
min_occurrenceintOnly records repeated at least this many times. The easiest way to drop one-off noise.
date_rangestringFilters by date range.
messagestringSearches the error message.
Query parameters — order 5
hide_noiseboolHides records counted as noise.
orderstringThe sort field. It defaults to when it was last seen.
directionstringThe sort direction. It defaults to descending.
pageintDefaults to 1.
limitintDefaults to 25, maximum 100.
Response fields data[] — 15
idintThe row id.
signaturestringThe 32-character identity of this error. This is what the detail and the delete are addressed by.
typestringThe book it came from: system or database.
levelstringThe severity, such as error, warning or fatal.
countintHow many times this signature has been seen. A repeat raises this number instead of adding a row.
first_seenstringWhen it was seen for the first time.
last_seenstringWhen it was seen most recently.
filestringThe source file, given relative to the install root.
lineintThe source line.
message_previewstringA shortened message. The full body is not in the list; the detail carries it.
exception_classstringThe class thrown, when the error came from a throw.
request_uristringThe request path. Query values outside the allow-list are replaced.
request_methodstringThe request method.
user_idintThe account tied to the request. Zero when nobody was signed in.
ipstringThe address the request came from.
Meta 4
totalintTotal records matching the filter.
pageintThe page you are on.
limitintThe page size.
next_pageintThe next page. Zero means you are on the last one.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl -G 'https://panel.example.com/api/v1/admin/tools/logs/errors' \
  -H "Authorization: Bearer $API_KEY" \
  -d min_occurrence=5 \
  -d hide_noise=1
const url = new URL('https://panel.example.com/api/v1/admin/tools/logs/errors');
url.searchParams.set('min_occurrence', '5');
url.searchParams.set('hide_noise', '1');

const res  = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();
$url = 'https://panel.example.com/api/v1/admin/tools/logs/errors?' . http_build_query([
    'min_occurrence' => 5,
    'hide_noise'     => 1,
]);

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The list does not carry the BODY; read one record's detail to see the stack.
$errors = Api::Tools()->GetErrorLogs([], [
    'min_occurrence' => 5,
    'hide_noise'     => 1,
])['data'];

Error Record Detail

get/api/v1/admin/tools/logs/errors/{signature}
Tools/GetErrorLog admin addressed by signature

Returns one error together with its stack trace and the context of the request.

Query parameters 1
typestringWhich book: system or database. It defaults to system, so a database error has to be asked for.
Response fields data — 16
Every field of the list row, in the same shape as the listing endpoint.
payloadobjectThe kept context: the stack as shapes, the allow-listed request fields and a subset of the server values. It belongs to the first sighting, not the latest, because a repeat only moves the counter and the last-seen time.
Errors 3
signature_required422The signature is not a 32-character hex string.
not_found404No record under that signature. You may be looking in the wrong book.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/tools/logs/errors/a1b2c3d4e5f600112233445566778899' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/tools/logs/errors/a1b2c3d4e5f600112233445566778899', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/logs/errors/a1b2c3d4e5f600112233445566778899');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The context belongs to the FIRST sighting: later repeats raise the counter but leave the body.
$log = Api::Tools()->GetErrorLog(['signature' => 'a1b2c3d4e5f600112233445566778899'])['data'];

Deleting Error Records

delete/api/v1/admin/tools/logs/errors
Tools/DeleteErrorLogs admin by a list of signatures

Deletes the errors you name, by their signatures.

Body 1
signaturesstring[]requiredThe signatures to delete. Each has to be a 32-character hex string.
Response fields data — 2
deletedboolWhether the delete ran.
countintHow many records were deleted. It can be fewer than you sent: signatures that match nothing are skipped quietly.
Errors 2
signature_required422Not one valid signature was given.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/tools/logs/errors' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"signatures":["a1b2c3d4e5f600112233445566778899"]}'
const res = await fetch('https://panel.example.com/api/v1/admin/tools/logs/errors', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ signatures: ['a1b2c3d4e5f600112233445566778899'] }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/logs/errors');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['signatures' => ['a1b2c3d4e5f600112233445566778899']]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Compare the deleted count with what you sent: signatures matching nothing are skipped quietly.
$sent     = array_column($errors, 'signature');
$response = Api::Tools()->DeleteErrorLogs(['signatures' => $sent]);

$missed = count($sent) - $response['data']['count'];

Clearing by Date

post/api/v1/admin/tools/logs/errors/cleanup
Tools/CleanupErrorLogs admin cannot be undone

Deletes error records older than the date you give, sweeping both books at once.

Body 1
beforestringrequiredThe cut-off date. Everything on and before it goes.
Response fields data — 3
clearedboolWhether the clear ran.
beforestringThe cut-off date that was used.
deletedobjectHow many were deleted per book: system and database.
Errors 2
invalid_date422No valid date was given.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/tools/logs/errors/cleanup' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"before":"2026-01-01"}'
const res = await fetch('https://panel.example.com/api/v1/admin/tools/logs/errors/cleanup', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ before: '2026-01-01' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/logs/errors/cleanup');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['before' => '2026-01-01']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Clearing settled history makes every record after it a REAL signal.
$response = Api::Tools()->CleanupErrorLogs(['before' => '2026-01-01']);

$system = $response['data']['deleted']['system'];

Importing the Leftover Files

post/api/v1/admin/tools/logs/errors/rebuild
Tools/RebuildErrorLogs admin a one-off migration

Reads the old error files left on disk and moves them into the records.

Body
No body is needed. There is nothing to narrow: whatever is left on disk is taken. Send an empty body.
Response fields data — 2
rebuiltboolWhether it ran.
countintHow many records were imported. Zero when no files are left to take.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/tools/logs/errors/rebuild' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/tools/logs/errors/rebuild', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/logs/errors/rebuild');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// This is not an index rebuild; it imports from the old file store.
// What comes in passes the same scrubbing as a fresh capture.
$response = Api::Tools()->RebuildErrorLogs();

Reading the Logging Settings

get/api/v1/admin/tools/logs/settings
Tools/GetLogSettings admin

Returns which books are being kept and whether debugging is on.

Response fields data — 6
error_logboolWhether errors are recorded.
error_debugboolWhether error detail is shown.
developmentboolWhether development mode is on.
module_logboolWhether module exchanges are recorded.
query_loggingboolWhether database queries are recorded.
query_logging_ipsstring[]The addresses query logging is limited to.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/tools/logs/settings' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/tools/logs/settings', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/logs/settings');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Tools()->GetLogSettings();

Writing the Logging Settings

put/api/v1/admin/tools/logs/settings
Tools/UpdateLogSettings admin careful in production

Writes which books to keep and whether debugging is on.

Body 6
error_logintTurns error recording on or off.
error_debugintShows the error detail. It leaks internals to visitors; keep it off in production.
developmentintTurns development mode on.
module_logintRecords module exchanges. It grows fast.
query_loggingintRecords database queries. It grows very fast; pair it with the address limit.
query_logging_ipsstring[] | stringThe addresses query logging is kept for. Give your own and only your requests are recorded.
Response fields data — 6
dataobjectThe settings as they now stand. Same shape as the read endpoint. Only the keys you send are changed, but the whole set comes back, so it tells you where the install ended up.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/tools/logs/settings' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"error_log":1,"module_log":0}'
const res = await fetch('https://panel.example.com/api/v1/admin/tools/logs/settings', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ error_log: 1, module_log: 0 }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/logs/settings');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'error_log'  => 1,
        'module_log' => 0,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Turn query logging on for your own address only: left open it fills the disk fast.
Api::Tools()->UpdateLogSettings([
    'query_logging'     => 1,
    'query_logging_ips' => ['203.0.113.5'],
]);

Pitfalls

A record is history, not the present

A long error list does not mean that many open problems: most rows may already be fixed. To tell whether one still happens, read its counter, send a few requests to the surface in question, and read the counter again. If it has not moved there is nothing to fix.

The context belongs to the first sighting

The stack trace, the address and the user on the detail come from the first time the error was caught; later repeats only update the counter and the last-seen time. So you may be reading today's repeat through a context from months ago.

There are two separate books

System errors and database errors are kept apart. The detail endpoint looks in the system book by default, so giving a database error's signature without naming the type finds nothing. The date-based clear, on the other hand, sweeps both.

Query logging fills the disk fast

Database query logging writes every request and grows fast when left on. Pair it with the address limit: give only your own address and it stays confined to your requests. In the same way, leaving error detail on in production shows internals to visitors.

The rebuild is not an index operation

Despite the name this endpoint rebuilds no index: it imports from the old file store left on disk. With nothing left to take it returns zero, which is not an error. The old bodies it takes in pass the same scrubbing as a fresh capture.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.