mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10:12 +02:00
add experimental request logger
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
import {
|
||||
ApiController,
|
||||
ApiControllerError,
|
||||
ApiControllerFn,
|
||||
} from '/@/shared/types/adapter/api-controller-types';
|
||||
import { ServerListItem } from '/@/shared/types/domain/server-domain-types';
|
||||
import { logger } from '/@/shared/utils/logger';
|
||||
|
||||
export interface LoggingOptions {
|
||||
logErrors?: boolean;
|
||||
logPerformance?: boolean;
|
||||
logRequests?: boolean;
|
||||
logResponses?: boolean;
|
||||
maxRequestSize?: number;
|
||||
maxResponseSize?: number;
|
||||
}
|
||||
|
||||
type LoggedApiControllerFn<TRequest, TResponse> = (
|
||||
request: TRequest,
|
||||
server: ServerListItem,
|
||||
options?: any,
|
||||
) => Promise<[ApiControllerError, null] | [null, TResponse]>;
|
||||
|
||||
export function createLoggedApiController(
|
||||
controller: ApiController,
|
||||
options: LoggingOptions = {},
|
||||
): ApiController {
|
||||
const loggedController: any = {};
|
||||
|
||||
// Log utility functions
|
||||
loggedController._utility = createLoggedUtility(controller._utility);
|
||||
|
||||
// Log all controller methods
|
||||
for (const [sectionKey, section] of Object.entries(controller)) {
|
||||
if (sectionKey === '_utility') continue;
|
||||
|
||||
loggedController[sectionKey] = {};
|
||||
|
||||
for (const [methodKey, method] of Object.entries(section as Record<string, any>)) {
|
||||
if (typeof method === 'function') {
|
||||
const functionName = `${sectionKey}.${methodKey}`;
|
||||
|
||||
if (methodKey === 'authenticate' || methodKey === 'getType') {
|
||||
// Special handling for non-standard API functions
|
||||
loggedController[sectionKey][methodKey] = (...args: any[]) => {
|
||||
logger.info(`[API] ${functionName} called`, {
|
||||
args: JSON.stringify(args, null, 2),
|
||||
});
|
||||
return method(...args);
|
||||
};
|
||||
} else {
|
||||
loggedController[sectionKey][methodKey] = createLoggedFunction(
|
||||
method as ApiControllerFn<any, any>,
|
||||
functionName,
|
||||
options,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
loggedController[sectionKey][methodKey] = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loggedController as ApiController;
|
||||
}
|
||||
|
||||
function createLoggedFunction<TRequest, TResponse>(
|
||||
originalFn: ApiControllerFn<TRequest, TResponse> | undefined,
|
||||
functionName: string,
|
||||
options: LoggingOptions = {},
|
||||
): LoggedApiControllerFn<TRequest, TResponse> | undefined {
|
||||
if (!originalFn) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return async (request: TRequest, server: ServerListItem, options?: any) => {
|
||||
const startTime = Date.now();
|
||||
const requestId = Math.random().toString(36).substring(2, 15);
|
||||
|
||||
const {
|
||||
logErrors = true,
|
||||
logPerformance = true,
|
||||
logRequests = true,
|
||||
logResponses = true,
|
||||
maxRequestSize = 1000,
|
||||
maxResponseSize = 1000,
|
||||
} = options;
|
||||
|
||||
if (logRequests) {
|
||||
const requestStr = JSON.stringify(request, null, 2);
|
||||
const truncatedRequest =
|
||||
requestStr.length > maxRequestSize
|
||||
? requestStr.substring(0, maxRequestSize) + '...'
|
||||
: requestStr;
|
||||
|
||||
logger.info(`[API] ${functionName} called`, {
|
||||
request: truncatedRequest,
|
||||
requestId,
|
||||
serverId: server.id,
|
||||
serverType: server.type,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await originalFn(request, server, options);
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
if (result[0]) {
|
||||
// Error response
|
||||
if (logErrors) {
|
||||
const error = result[0] as ApiControllerError;
|
||||
logger.error(`[API] ${functionName} failed`, {
|
||||
duration: logPerformance ? `${duration}ms` : undefined,
|
||||
error: {
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
},
|
||||
requestId,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Success response
|
||||
if (logResponses) {
|
||||
const response = result[1];
|
||||
const responseStr = JSON.stringify(response);
|
||||
const truncatedResponse =
|
||||
responseStr.length > maxResponseSize
|
||||
? responseStr.substring(0, maxResponseSize) + '...'
|
||||
: responseStr;
|
||||
|
||||
logger.info(`[API] ${functionName} succeeded`, {
|
||||
duration: logPerformance ? `${duration}ms` : undefined,
|
||||
requestId,
|
||||
response: truncatedResponse,
|
||||
responseSize: responseStr.length,
|
||||
responseType: typeof response,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
if (logErrors) {
|
||||
logger.error(`[API] ${functionName} threw exception`, {
|
||||
duration: logPerformance ? `${duration}ms` : undefined,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
requestId,
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createLoggedUtility<T extends Record<string, any>>(utility: T): T {
|
||||
const loggedUtility: any = {};
|
||||
|
||||
for (const [key, value] of Object.entries(utility)) {
|
||||
if (typeof value === 'function') {
|
||||
loggedUtility[key] = (...args: any[]) => {
|
||||
logger.debug(`[API] _utility.${key} called`, {
|
||||
args: JSON.stringify(args, null, 2),
|
||||
});
|
||||
return value(...args);
|
||||
};
|
||||
} else {
|
||||
loggedUtility[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return loggedUtility as T;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { createLoggedApiController } from '/@/renderer/api/api-controller-logger';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import {
|
||||
controller as subsonicAdapter,
|
||||
apiClient as subsonicApiClient,
|
||||
controller as subsonicBaseAdapter,
|
||||
middleware as subsonicMiddleware,
|
||||
} from '/@/shared/api/subsonic/subsonic-controller';
|
||||
import { ApiController } from '/@/shared/types/adapter/api-controller-types';
|
||||
@@ -20,7 +21,7 @@ export const serverApi = {
|
||||
},
|
||||
[ServerType.SUBSONIC]: {
|
||||
apiClient: subsonicApiClient,
|
||||
controller: subsonicAdapter,
|
||||
controller: createLoggedApiController(subsonicBaseAdapter),
|
||||
middleware: subsonicMiddleware,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ export const middleware: (server: ServerListItem) => Middleware = (server: Serve
|
||||
},
|
||||
});
|
||||
|
||||
const client: SubsonicClient = createClient<paths>({
|
||||
const client = createClient<paths>({
|
||||
querySerializer: (params) => qs.stringify(params, { arrayFormat: 'repeat' }),
|
||||
});
|
||||
|
||||
@@ -56,8 +56,6 @@ type ErrorResponseArgs = {
|
||||
message?: string;
|
||||
};
|
||||
|
||||
type SubsonicClient = Client<paths, `${string}/${string}`>;
|
||||
|
||||
function errorResponse(args: ErrorResponseArgs): [ApiControllerError, null] {
|
||||
const message = `${i18n.t('error.genericError', { postProcess: 'sentenceCase' }) as string}${
|
||||
args.message ? `: ${args.message}` : ''
|
||||
@@ -131,7 +129,7 @@ function toHttpErrorCode(subsonicErrorCode: number): number {
|
||||
}
|
||||
}
|
||||
|
||||
export const controller: ApiController = {
|
||||
const baseController: ApiController = {
|
||||
_utility: {
|
||||
getImageUrl: (
|
||||
args: { id: string; size?: number; type: LibraryItem },
|
||||
@@ -206,3 +204,5 @@ export const controller: ApiController = {
|
||||
// TODO: Implement user methods
|
||||
},
|
||||
};
|
||||
|
||||
export const controller = baseController;
|
||||
|
||||
Reference in New Issue
Block a user