Refactor error handling and logging in API responses
This commit is contained in:
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/** @var list<string> */
|
||||
const QDB_API_LOG_ACTIVITIES = ['app_sync', 'app_change', 'web_change', 'web_export'];
|
||||
const QDB_API_LOG_ACTIVITIES = ['app_sync', 'app_change', 'web_change', 'web_export', 'api_error'];
|
||||
|
||||
function qdb_api_log_enabled(): bool
|
||||
{
|
||||
@ -150,7 +150,7 @@ function qdb_api_log_classify_activity(string $method, string $client, string $r
|
||||
$client = strtolower(trim($client));
|
||||
$route = strtolower(trim($route));
|
||||
|
||||
if ($method === 'OPTIONS' || in_array($route, ['health', 'activity-log', 'session'], true)) {
|
||||
if ($method === 'OPTIONS' || in_array($route, ['activity-log', 'session'], true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -191,37 +191,48 @@ function qdb_api_log_begin(string $method, string $route, array $context = []):
|
||||
|
||||
$client = trim((string)($_SERVER['HTTP_X_QDB_CLIENT'] ?? ''));
|
||||
$activity = qdb_api_log_classify_activity($method, $client, $route);
|
||||
if ($activity === null) {
|
||||
$GLOBALS['qdb_api_log_ctx'] = ['skip' => true];
|
||||
return;
|
||||
}
|
||||
|
||||
$GLOBALS['qdb_api_log_ctx'] = array_merge([
|
||||
'started_at' => microtime(true),
|
||||
'method' => strtoupper($method),
|
||||
'route' => $route,
|
||||
'activity' => $activity,
|
||||
'uri' => (string)($_SERVER['REQUEST_URI'] ?? ''),
|
||||
'ip' => qdb_api_log_client_ip(),
|
||||
'client' => $client,
|
||||
'user_agent' => trim((string)($_SERVER['HTTP_USER_AGENT'] ?? '')),
|
||||
], $context);
|
||||
|
||||
if ($activity !== null) {
|
||||
$GLOBALS['qdb_api_log_ctx']['activity'] = $activity;
|
||||
} else {
|
||||
$GLOBALS['qdb_api_log_ctx']['skip_success'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
function qdb_api_log_note_exception(Throwable $e): void
|
||||
{
|
||||
if (empty($GLOBALS['qdb_api_log_ctx']) || !empty($GLOBALS['qdb_api_log_ctx']['skip'])) {
|
||||
if (empty($GLOBALS['qdb_api_log_ctx'])) {
|
||||
return;
|
||||
}
|
||||
$GLOBALS['qdb_api_log_ctx']['exception'] = $e->getMessage();
|
||||
$GLOBALS['qdb_api_log_ctx']['exception_class'] = $e::class;
|
||||
}
|
||||
|
||||
function qdb_api_log_note_api_error(string $code, string $message, int $status): void
|
||||
{
|
||||
if (empty($GLOBALS['qdb_api_log_ctx'])) {
|
||||
return;
|
||||
}
|
||||
$GLOBALS['qdb_api_log_ctx']['api_error_code'] = $code;
|
||||
$GLOBALS['qdb_api_log_ctx']['api_error_message'] = $message;
|
||||
$GLOBALS['qdb_api_log_ctx']['api_error_status'] = $status;
|
||||
}
|
||||
|
||||
function qdb_api_log_finish(): void
|
||||
{
|
||||
$ctx = $GLOBALS['qdb_api_log_ctx'] ?? null;
|
||||
unset($GLOBALS['qdb_api_log_ctx']);
|
||||
if (!is_array($ctx) || !qdb_api_log_enabled() || !empty($ctx['skip'])) {
|
||||
if (!is_array($ctx) || !qdb_api_log_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -231,6 +242,14 @@ function qdb_api_log_finish(): void
|
||||
$status = 200;
|
||||
}
|
||||
|
||||
$isError = $status >= 400
|
||||
|| !empty($ctx['exception'])
|
||||
|| !empty($ctx['api_error_code']);
|
||||
|
||||
if (!empty($ctx['skip_success']) && !$isError) {
|
||||
return;
|
||||
}
|
||||
|
||||
$token = get_bearer_token();
|
||||
$auth = ['role' => null, 'userID' => null, 'token_hint' => null];
|
||||
if ($token !== null && $token !== '') {
|
||||
@ -256,9 +275,14 @@ function qdb_api_log_finish(): void
|
||||
$actorUsername = qdb_api_log_lookup_username($auth['userID'] ?? null);
|
||||
$targetUsername = qdb_api_log_extract_target_username($bodyLog, $queryLog);
|
||||
|
||||
$activity = $ctx['activity'] ?? null;
|
||||
if ($isError) {
|
||||
$activity = 'api_error';
|
||||
}
|
||||
|
||||
$entry = [
|
||||
'ts' => gmdate('c'),
|
||||
'activity' => $ctx['activity'] ?? null,
|
||||
'activity' => $activity,
|
||||
'method' => $ctx['method'] ?? '',
|
||||
'route' => $ctx['route'] ?? '',
|
||||
'status' => $status,
|
||||
@ -281,11 +305,19 @@ function qdb_api_log_finish(): void
|
||||
),
|
||||
];
|
||||
|
||||
if (!empty($ctx['api_error_code'])) {
|
||||
$entry['error_code'] = (string)$ctx['api_error_code'];
|
||||
$entry['error_message'] = (string)($ctx['api_error_message'] ?? '');
|
||||
$entry['error'] = $entry['error_code'] . ': ' . $entry['error_message'];
|
||||
}
|
||||
if (!empty($ctx['exception'])) {
|
||||
$entry['error'] = (string)$ctx['exception'];
|
||||
$entry['error_internal'] = (string)$ctx['exception'];
|
||||
if (!empty($ctx['exception_class'])) {
|
||||
$entry['error_class'] = (string)$ctx['exception_class'];
|
||||
}
|
||||
if (empty($entry['error'])) {
|
||||
$entry['error'] = 'SERVER_ERROR: ' . $entry['error_internal'];
|
||||
}
|
||||
}
|
||||
|
||||
if (($ctx['user_agent'] ?? '') !== '') {
|
||||
@ -563,13 +595,17 @@ function qdb_api_log_resolve_write_path(string $dir): string
|
||||
*
|
||||
* @return array{date: string, activity: string, entries: list<array<string, mixed>>, files: list<string>, truncated: bool}
|
||||
*/
|
||||
function qdb_api_log_read_entries(string $date, string $activityFilter = '', int $limit = 200): array
|
||||
function qdb_api_log_read_entries(string $date, string $activityFilter = '', int $limit = 200, bool $errorsOnly = false): array
|
||||
{
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
|
||||
$date = date('Y-m-d');
|
||||
}
|
||||
$limit = max(1, min(1000, $limit));
|
||||
$activityFilter = strtolower(trim($activityFilter));
|
||||
if ($activityFilter === 'errors') {
|
||||
$errorsOnly = true;
|
||||
$activityFilter = '';
|
||||
}
|
||||
if ($activityFilter !== '' && !in_array($activityFilter, QDB_API_LOG_ACTIVITIES, true)) {
|
||||
$activityFilter = '';
|
||||
}
|
||||
@ -591,6 +627,13 @@ function qdb_api_log_read_entries(string $date, string $activityFilter = '', int
|
||||
if ($activityFilter !== '' && ($row['activity'] ?? '') !== $activityFilter) {
|
||||
continue;
|
||||
}
|
||||
if ($errorsOnly) {
|
||||
$rowActivity = $row['activity'] ?? '';
|
||||
$rowStatus = (int)($row['status'] ?? 0);
|
||||
if ($rowActivity !== 'api_error' && $rowStatus < 400 && empty($row['error'])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$entries[] = qdb_api_log_entry_for_ui($row);
|
||||
}
|
||||
}
|
||||
@ -607,13 +650,14 @@ function qdb_api_log_read_entries(string $date, string $activityFilter = '', int
|
||||
$entries = qdb_api_log_enrich_entries_usernames($entries);
|
||||
|
||||
return [
|
||||
'date' => $date,
|
||||
'activity' => $activityFilter,
|
||||
'entries' => $entries,
|
||||
'files' => array_map('basename', $files),
|
||||
'truncated' => $truncated,
|
||||
'kinds' => QDB_API_LOG_ACTIVITIES,
|
||||
'logStatus' => qdb_api_log_status(),
|
||||
'date' => $date,
|
||||
'activity' => $activityFilter,
|
||||
'errorsOnly' => $errorsOnly,
|
||||
'entries' => $entries,
|
||||
'files' => array_map('basename', $files),
|
||||
'truncated' => $truncated,
|
||||
'kinds' => QDB_API_LOG_ACTIVITIES,
|
||||
'logStatus' => qdb_api_log_status(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -663,6 +707,8 @@ function qdb_api_log_entry_for_ui(array $row): array
|
||||
'changes' => $changes,
|
||||
'summary' => qdb_api_log_summarize_changes($changes, $targetUsername),
|
||||
'error' => $row['error'] ?? null,
|
||||
'error_code' => $row['error_code'] ?? null,
|
||||
'error_message' => $row['error_message'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user