mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +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 { getServerById } from '/@/renderer/store';
|
||||||
import {
|
import {
|
||||||
controller as subsonicAdapter,
|
|
||||||
apiClient as subsonicApiClient,
|
apiClient as subsonicApiClient,
|
||||||
|
controller as subsonicBaseAdapter,
|
||||||
middleware as subsonicMiddleware,
|
middleware as subsonicMiddleware,
|
||||||
} from '/@/shared/api/subsonic/subsonic-controller';
|
} from '/@/shared/api/subsonic/subsonic-controller';
|
||||||
import { ApiController } from '/@/shared/types/adapter/api-controller-types';
|
import { ApiController } from '/@/shared/types/adapter/api-controller-types';
|
||||||
@@ -20,7 +21,7 @@ export const serverApi = {
|
|||||||
},
|
},
|
||||||
[ServerType.SUBSONIC]: {
|
[ServerType.SUBSONIC]: {
|
||||||
apiClient: subsonicApiClient,
|
apiClient: subsonicApiClient,
|
||||||
controller: subsonicAdapter,
|
controller: createLoggedApiController(subsonicBaseAdapter),
|
||||||
middleware: subsonicMiddleware,
|
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' }),
|
querySerializer: (params) => qs.stringify(params, { arrayFormat: 'repeat' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -56,8 +56,6 @@ type ErrorResponseArgs = {
|
|||||||
message?: string;
|
message?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SubsonicClient = Client<paths, `${string}/${string}`>;
|
|
||||||
|
|
||||||
function errorResponse(args: ErrorResponseArgs): [ApiControllerError, null] {
|
function errorResponse(args: ErrorResponseArgs): [ApiControllerError, null] {
|
||||||
const message = `${i18n.t('error.genericError', { postProcess: 'sentenceCase' }) as string}${
|
const message = `${i18n.t('error.genericError', { postProcess: 'sentenceCase' }) as string}${
|
||||||
args.message ? `: ${args.message}` : ''
|
args.message ? `: ${args.message}` : ''
|
||||||
@@ -131,7 +129,7 @@ function toHttpErrorCode(subsonicErrorCode: number): number {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const controller: ApiController = {
|
const baseController: ApiController = {
|
||||||
_utility: {
|
_utility: {
|
||||||
getImageUrl: (
|
getImageUrl: (
|
||||||
args: { id: string; size?: number; type: LibraryItem },
|
args: { id: string; size?: number; type: LibraryItem },
|
||||||
@@ -206,3 +204,5 @@ export const controller: ApiController = {
|
|||||||
// TODO: Implement user methods
|
// TODO: Implement user methods
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const controller = baseController;
|
||||||
|
|||||||
Reference in New Issue
Block a user