chore: update CHANGELOG.md to include Request ID tracing for debugging, enhancing request tracking across services, and update API client to propagate Request ID in headers

This commit is contained in:
Usman Baig
2026-02-27 17:26:08 +01:00
parent a928d2577b
commit 22bc18a7cc
4 changed files with 130 additions and 0 deletions

View File

@@ -1,8 +1,10 @@
/**
* HTTP client wrapper for API calls
* Includes Request ID propagation for debugging across services
*/
import { authMessageFromStatus, AUTH_ERROR_MESSAGES } from '@ciphera-net/ui'
import { generateRequestId, getRequestIdHeader, setLastRequestId } from '@/lib/utils/requestId'
/** Request timeout in ms; network errors surface as user-facing "Network error, please try again." */
const FETCH_TIMEOUT_MS = 30_000
@@ -180,8 +182,13 @@ async function apiRequest<T>(
? `${baseUrl}${endpoint}`
: `${baseUrl}/api/v1${endpoint}`
// * Generate and store request ID for tracing
const requestId = generateRequestId()
setLastRequestId(requestId)
const headers: Record<string, string> = {
'Content-Type': 'application/json',
[getRequestIdHeader()]: requestId,
}
// * Merge any additional headers from options

79
lib/utils/errorHandler.ts Normal file
View File

@@ -0,0 +1,79 @@
/**
* Error handling utilities with Request ID extraction
* Helps users report errors with traceable IDs for support
*/
import { getLastRequestId } from './requestId'
interface ApiErrorResponse {
error?: {
code?: string
message?: string
details?: unknown
request_id?: string
}
}
/**
* Extract request ID from error response or use last known request ID
*/
export function getRequestIdFromError(errorData?: ApiErrorResponse): string | null {
// * Try to get from error response body
if (errorData?.error?.request_id) {
return errorData.error.request_id
}
// * Fallback to last request ID stored during API call
return getLastRequestId()
}
/**
* Format error message for display with optional request ID
* Shows request ID in development or for specific error types
*/
export function formatErrorMessage(
message: string,
errorData?: ApiErrorResponse,
options: { showRequestId?: boolean } = {}
): string {
const requestId = getRequestIdFromError(errorData)
// * Always show request ID in development
const isDev = process.env.NODE_ENV === 'development'
if (requestId && (isDev || options.showRequestId)) {
return `${message}\n\nRequest ID: ${requestId}`
}
return message
}
/**
* Log error with request ID for debugging
*/
export function logErrorWithRequestId(
context: string,
error: unknown,
errorData?: ApiErrorResponse
): void {
const requestId = getRequestIdFromError(errorData)
if (requestId) {
console.error(`[${context}] Request ID: ${requestId}`, error)
} else {
console.error(`[${context}]`, error)
}
}
/**
* Get support message with request ID for user reports
*/
export function getSupportMessage(errorData?: ApiErrorResponse): string {
const requestId = getRequestIdFromError(errorData)
if (requestId) {
return `If this persists, contact support with Request ID: ${requestId}`
}
return 'If this persists, please contact support.'
}

43
lib/utils/requestId.ts Normal file
View File

@@ -0,0 +1,43 @@
/**
* Request ID utilities for tracing API calls across services
* Request IDs help debug issues by correlating logs across frontend and backends
*/
const REQUEST_ID_HEADER = 'X-Request-ID'
/**
* Generate a unique request ID
* Format: REQ<timestamp>_<random>
*/
export function generateRequestId(): string {
const timestamp = Date.now().toString(36)
const random = Math.random().toString(36).substring(2, 8)
return `REQ${timestamp}_${random}`
}
/**
* Get request ID header name
*/
export function getRequestIdHeader(): string {
return REQUEST_ID_HEADER
}
/**
* Store the last request ID for error reporting
*/
let lastRequestId: string | null = null
export function setLastRequestId(id: string): void {
lastRequestId = id
}
export function getLastRequestId(): string | null {
return lastRequestId
}
/**
* Clear the stored request ID
*/
export function clearLastRequestId(): void {
lastRequestId = null
}