UNPKG

@smartsamurai/krapi-sdk

Version:

KRAPI TypeScript SDK - Easy-to-use client SDK for connecting to self-hosted KRAPI servers (like Appwrite SDK)

494 lines (451 loc) 13.5 kB
/** * Error Logger Utilities * * Standardizes error logging across the KRAPI SDK with consistent formatting * and context inclusion. */ import { Logger } from "../core"; import { KrapiError } from "../core/krapi-error"; import { HttpError } from "../http-clients/http-error"; /** * Log levels for error logging */ export type LogLevel = "error" | "warn" | "info" | "debug"; /** * Error log context interface * * Comprehensive context for error logging including: * - Operation details (what was being performed) * - Request data (what was sent) * - Response data (what was received) * - User/session context * - System state */ export interface ErrorLogContext { // Operation context operation?: string; service?: string; // Resource context userId?: string; projectId?: string; collectionName?: string; documentId?: string; // Request tracking requestId?: string; userAgent?: string; ipAddress?: string; sessionId?: string; // HTTP request context (if applicable) httpMethod?: string; httpUrl?: string; requestBody?: unknown; requestQuery?: Record<string, unknown>; requestHeaders?: Record<string, string>; // HTTP response context (if applicable) responseData?: unknown; responseHeaders?: Record<string, string>; httpStatus?: number; // Input parameters (what data was received) inputData?: Record<string, unknown>; parameters?: Record<string, unknown>; // Additional context [key: string]: unknown; } /** * Structured error log entry */ export interface ErrorLogEntry { timestamp: string; level: LogLevel; message: string; error: { code: string; message: string; status?: number; details?: Record<string, unknown>; }; context?: ErrorLogContext; stack?: string | undefined; } /** * Log an error with standardized formatting * * Formats and logs errors consistently across the SDK, including error codes, * context information, and appropriate log levels. * * @param logger - Logger instance to use * @param error - The error to log * @param context - Additional context for the error * @param level - Log level (defaults to 'error') */ export function logError( logger: Logger, error: unknown, context?: ErrorLogContext, level: LogLevel = "error" ): void { const logEntry = createErrorLogEntry(error, context); // Use appropriate logger method based on level switch (level) { case "error": logger.error(formatErrorMessage(logEntry), logEntry); break; case "warn": logger.warn(formatErrorMessage(logEntry), logEntry); break; case "info": logger.info(formatErrorMessage(logEntry), logEntry); break; case "debug": logger.debug(formatErrorMessage(logEntry), logEntry); break; } } /** * Create a structured error log entry with comprehensive context * * Extracts all available information from errors including: * - Request data (body, query, headers) * - Response data (body, headers, status) * - Operation context * - Full stack traces * - All error details * * @param error - The error to log * @param context - Additional context * @returns Structured error log entry with full context */ export function createErrorLogEntry( error: unknown, context?: ErrorLogContext ): ErrorLogEntry { const timestamp = new Date().toISOString(); // Handle HttpError - extract all HTTP context if (error instanceof HttpError) { const httpContext: ErrorLogContext = { ...(context || {}), ...(error.method ? { httpMethod: error.method } : {}), ...(error.url ? { httpUrl: error.url } : {}), ...(error.status !== undefined ? { httpStatus: error.status } : {}), ...(error.requestHeaders ? { requestHeaders: error.requestHeaders } : {}), ...(error.requestBody !== undefined ? { requestBody: error.requestBody } : {}), ...(error.requestQuery ? { requestQuery: error.requestQuery } : {}), ...(error.responseData !== undefined ? { responseData: error.responseData } : {}), ...(error.responseHeaders ? { responseHeaders: error.responseHeaders } : {}), }; const logEntry: ErrorLogEntry = { timestamp, level: "error", message: error.message, error: { code: error.code || "HTTP_ERROR", message: error.message, ...(error.status !== undefined ? { status: error.status } : {}), details: { isApiError: error.isApiError, isNetworkError: error.isNetworkError, isAuthError: error.isAuthError, isClientError: error.isClientError, isServerError: error.isServerError, originalError: error.originalError, }, }, stack: error.stack, context: httpContext, }; return logEntry; } // Handle KrapiError - extract all error details if (error instanceof KrapiError) { // Extract HTTP context from error details if it exists const httpErrorDetails = error.details?.httpError as { method?: string; url?: string; status?: number; requestBody?: unknown; requestQuery?: Record<string, unknown>; requestHeaders?: Record<string, string>; responseData?: unknown; responseHeaders?: Record<string, string>; } | undefined; const enhancedContext: ErrorLogContext = { ...(error.requestId ? { requestId: error.requestId } : {}), ...context, // Extract HTTP context from error details ...(httpErrorDetails ? { httpMethod: httpErrorDetails.method, httpUrl: httpErrorDetails.url, httpStatus: httpErrorDetails.status, requestBody: httpErrorDetails.requestBody, requestQuery: httpErrorDetails.requestQuery, requestHeaders: httpErrorDetails.requestHeaders, responseData: httpErrorDetails.responseData, responseHeaders: httpErrorDetails.responseHeaders, } : {}), // Include all error details ...(error.details ? { errorDetails: error.details } : {}), }; const logEntry: ErrorLogEntry = { timestamp, level: getLogLevelFromError(error), message: error.message, error: { code: error.code, message: error.message, ...(error.status !== undefined ? { status: error.status } : {}), ...(error.details ? { details: error.details } : {}), }, stack: error.stack, context: enhancedContext, }; return logEntry; } // Handle standard Error objects if (error instanceof Error) { const logEntry: ErrorLogEntry = { timestamp, level: "error", message: error.message, error: { code: "UNKNOWN_ERROR", message: error.message, details: { errorName: error.name, originalError: error, }, }, stack: error.stack, ...(context ? { context } : {}), }; return logEntry; } // Handle unknown error types const errorString = String(error); const logEntry: ErrorLogEntry = { timestamp, level: "error", message: errorString, error: { code: "UNKNOWN_ERROR", message: errorString, details: { errorType: typeof error, errorValue: error, }, }, ...(context ? { context } : {}), }; return logEntry; } /** * Format error message for logging * * @param entry - Error log entry * @returns Formatted error message */ export function formatErrorMessage(entry: ErrorLogEntry): string { const parts = [ `[${entry.error.code}]`, entry.message, ]; if (entry.error.status) { parts.push(`(HTTP ${entry.error.status})`); } if (entry.context?.operation) { parts.push(`[${entry.context.operation}]`); } if (entry.context?.requestId) { parts.push(`[Request: ${entry.context.requestId}]`); } return parts.join(" "); } /** * Get appropriate log level based on error type * * @param error - KrapiError instance * @returns Appropriate log level */ export function getLogLevelFromError(error: KrapiError): LogLevel { // Server errors should be logged as errors if (error.status && error.status >= 500) { return "error"; } // Client errors (4xx) are often expected and can be logged as warnings if (error.status && error.status >= 400 && error.status < 500) { // But some client errors like validation errors should be warnings if (error.code === "VALIDATION_ERROR" || error.code === "BAD_REQUEST") { return "warn"; } return "error"; } // Authentication errors if (error.code === "UNAUTHORIZED" || error.code === "FORBIDDEN") { return "warn"; // Often expected in normal operation } // Not found errors if (error.code === "NOT_FOUND") { return "info"; // Often expected (checking if resource exists) } // Default to error level return "error"; } /** * Log error with operation context * * Convenience function for logging errors with operation-specific context. * * @param logger - Logger instance * @param error - Error to log * @param operation - Operation name * @param additionalContext - Additional context */ export function logOperationError( logger: Logger, error: unknown, operation: string, additionalContext?: Omit<ErrorLogContext, "operation"> ): void { logError(logger, error, { operation, ...additionalContext, }); } /** * Log service-level errors with consistent formatting * * @param logger - Logger instance * @param error - Error to log * @param service - Service name * @param operation - Operation name * @param context - Additional context */ export function logServiceError( logger: Logger, error: unknown, service: string, operation: string, context?: ErrorLogContext ): void { const enhancedContext = { service, operation, ...context, }; logError(logger, error, enhancedContext); } /** * Log HTTP client errors with comprehensive request/response context * * Automatically extracts all HTTP context from HttpError instances. * * @param logger - Logger instance * @param error - HTTP error * @param method - HTTP method (if not in error) * @param url - Request URL (if not in error) * @param context - Additional context */ export function logHttpError( logger: Logger, error: unknown, method?: string, url?: string, context?: ErrorLogContext ): void { // If error is HttpError, it already contains all HTTP context // Just add any additional context provided const enhancedContext: ErrorLogContext = { ...(context || {}), ...(error instanceof HttpError ? {} : { ...(method ? { httpMethod: method } : {}), ...(url ? { httpUrl: url } : {}), }), }; logError(logger, error, enhancedContext); } /** * Log service operation errors with comprehensive context * * This is the main function services should use for error logging. * It captures: * - Operation name and service * - Input parameters (what data was received) * - Request data (if HTTP operation) * - Response data (if available) * - Full error details * - Stack traces * * @param logger - Logger instance * @param error - Error to log * @param service - Service name (e.g., "ProjectsService", "AuthService") * @param operation - Operation name (e.g., "createProject", "login") * @param inputData - Input parameters/data that was received * @param additionalContext - Additional context (userId, projectId, etc.) */ export function logServiceOperationError( logger: Logger, error: unknown, service: string, operation: string, inputData?: Record<string, unknown>, additionalContext?: Omit<ErrorLogContext, "service" | "operation" | "inputData"> ): void { const enhancedContext: ErrorLogContext = { service, operation, inputData: inputData || {}, ...(additionalContext || {}), }; logError(logger, error, enhancedContext); } /** * Create a child logger with error context * * Returns a logger function that automatically includes context in all logs. * * @param parentLogger - Parent logger * @param context - Context to include in all logs * @returns Contextualized logger */ export function createContextLogger( parentLogger: Logger, context: ErrorLogContext ): Logger { return { error: (message: string, ...args: unknown[]) => { const enhancedArgs = args.map(arg => { if (arg && typeof arg === "object" && !(arg instanceof Error)) { return { ...context, ...arg }; } return arg; }); parentLogger.error(message, ...enhancedArgs); }, warn: (message: string, ...args: unknown[]) => { const enhancedArgs = args.map(arg => { if (arg && typeof arg === "object" && !(arg instanceof Error)) { return { ...context, ...arg }; } return arg; }); parentLogger.warn(message, ...enhancedArgs); }, info: (message: string, ...args: unknown[]) => { const enhancedArgs = args.map(arg => { if (arg && typeof arg === "object" && !(arg instanceof Error)) { return { ...context, ...arg }; } return arg; }); parentLogger.info(message, ...enhancedArgs); }, debug: (message: string, ...args: unknown[]) => { const enhancedArgs = args.map(arg => { if (arg && typeof arg === "object" && !(arg instanceof Error)) { return { ...context, ...arg }; } return arg; }); parentLogger.debug(message, ...enhancedArgs); }, }; }