diff --git a/packages/toolpad-app/pages/api/rpc.ts b/packages/toolpad-app/pages/api/rpc.ts
index a0f56d9fe38..fe0d660d39e 100644
--- a/packages/toolpad-app/pages/api/rpc.ts
+++ b/packages/toolpad-app/pages/api/rpc.ts
@@ -24,7 +24,8 @@ import {
} from '../../src/server/data';
import { getLatestToolpadRelease } from '../../src/server/getLatestRelease';
import { hasOwnProperty } from '../../src/utils/collections';
-import { withRpcReqResLogs } from '../../src/server/logs/withLogs';
+import { errorFrom, serializeError } from '../../src/utils/errors';
+import logger from '../../src/server/logs/logger';
export interface Method
{
(...params: P): Promise;
@@ -72,6 +73,7 @@ function createRpcHandler(definition: Definition): NextApiHandler {
res.status(405).end();
return;
}
+
const { type, name, params } = req.body as RpcRequest;
if (!hasOwnProperty(definition, type) || !hasOwnProperty(definition[type], name)) {
@@ -82,20 +84,29 @@ function createRpcHandler(definition: Definition): NextApiHandler {
const method: MethodResolver = definition[type][name];
let rawResult;
+ let error: Error | null = null;
try {
rawResult = await method({ params, req, res });
- } catch (error) {
- console.error(error);
- if (error instanceof Error) {
- res.json({ error: { message: error.message, code: error.code, stack: error.stack } });
- } else {
- res.status(500).end();
- }
-
- return;
+ } catch (rawError) {
+ error = errorFrom(rawError);
}
- const responseData: RpcResponse = { result: superjson.stringify(rawResult) };
+
+ const responseData: RpcResponse = error
+ ? { error: serializeError(error) }
+ : { result: superjson.stringify(rawResult) };
+
res.json(responseData);
+
+ const logLevel = error ? 'warn' : 'trace';
+ logger[logLevel](
+ {
+ key: 'rpc',
+ type,
+ name,
+ error,
+ },
+ 'Handled RPC request',
+ );
};
}
@@ -182,4 +193,4 @@ const rpcServer = {
export type ServerDefinition = MethodsOf;
-export default withRpcReqResLogs(createRpcHandler(rpcServer));
+export default createRpcHandler(rpcServer);
diff --git a/packages/toolpad-app/src/server/config.ts b/packages/toolpad-app/src/server/config.ts
index 66147f068c5..58ba56a314e 100644
--- a/packages/toolpad-app/src/server/config.ts
+++ b/packages/toolpad-app/src/server/config.ts
@@ -17,7 +17,6 @@ export type ServerConfig = {
encryptionKeys: string[];
basicAuthUser?: string;
basicAuthPassword?: string;
- serverLogsEnabled: boolean;
recaptchaV2SecretKey?: string;
recaptchaV3SecretKey?: string;
ecsNodeUrl?: string;
@@ -55,7 +54,6 @@ function readConfig(): ServerConfig & typeof sharedConfig {
databaseUrl: process.env.TOOLPAD_DATABASE_URL,
googleSheetsClientId: process.env.TOOLPAD_DATASOURCE_GOOGLESHEETS_CLIENT_ID,
googleSheetsClientSecret: process.env.TOOLPAD_DATASOURCE_GOOGLESHEETS_CLIENT_SECRET,
- serverLogsEnabled: !!process.env.TOOLPAD_SERVER_LOGS_ENABLED,
recaptchaV2SecretKey: process.env.TOOLPAD_RECAPTCHA_V2_SECRET_KEY,
recaptchaV3SecretKey: process.env.TOOLPAD_RECAPTCHA_V3_SECRET_KEY,
ecsNodeUrl: process.env.TOOLPAD_ECS_NODE_URL,
diff --git a/packages/toolpad-app/src/server/logs/logInfo.ts b/packages/toolpad-app/src/server/logs/logInfo.ts
deleted file mode 100644
index 30f63500ccb..00000000000
--- a/packages/toolpad-app/src/server/logs/logInfo.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { NextApiRequest, NextApiResponse } from 'next';
-import logger from './logger';
-import type { RpcResponse } from '../../../pages/api/rpc';
-
-type ReqResLogPayload = {
- key: 'apiReqRes';
- req: NextApiRequest;
- res: NextApiResponse;
-};
-
-type RpcReqResLogPayload = {
- key: 'rpcReqRes';
- req: NextApiRequest;
- res: NextApiResponse;
- resErr?: RpcResponse['error'];
-};
-
-type LogPayload = ReqResLogPayload | RpcReqResLogPayload;
-
-function logInfo(payload: LogPayload, message?: string): void {
- logger.info(payload, message);
-}
-
-export default logInfo;
diff --git a/packages/toolpad-app/src/server/logs/logSerializers.ts b/packages/toolpad-app/src/server/logs/logSerializers.ts
index ed1e030b408..d245ed65288 100644
--- a/packages/toolpad-app/src/server/logs/logSerializers.ts
+++ b/packages/toolpad-app/src/server/logs/logSerializers.ts
@@ -1,4 +1,5 @@
import { NextApiRequest, NextApiResponse } from 'next';
+import { errorFrom } from '../../utils/errors';
function getReqLoggableIPAddress(req: NextApiRequest): string | null {
const forwardedHeader = req.headers['x-forwarded-for'];
@@ -38,9 +39,12 @@ export function resSerializer(res: NextApiResponse) {
};
}
-export function resErrSerializer(error: Error) {
+export function errSerializer(rawError: unknown) {
+ const error = errorFrom(rawError);
return {
message: error.message,
+ name: error.name,
+ stack: error.stack,
code: error.code,
};
}
diff --git a/packages/toolpad-app/src/server/logs/logger.ts b/packages/toolpad-app/src/server/logs/logger.ts
index 23f13e9a0bd..9cadc767d90 100644
--- a/packages/toolpad-app/src/server/logs/logger.ts
+++ b/packages/toolpad-app/src/server/logs/logger.ts
@@ -1,8 +1,8 @@
import pino from 'pino';
import ecsFormat from '@elastic/ecs-pino-format';
-
+import type { NextApiRequest, NextApiResponse } from 'next';
import config from '../config';
-import { reqSerializer, resSerializer, resErrSerializer } from './logSerializers';
+import { errSerializer, reqSerializer, resSerializer } from './logSerializers';
let transport;
if (config.ecsNodeUrl) {
@@ -21,17 +21,42 @@ if (config.ecsNodeUrl) {
const logger = pino(
{
- enabled: config.serverLogsEnabled,
level: process.env.LOG_LEVEL || 'info',
redact: { paths: [] },
serializers: {
+ err: errSerializer,
+ error: errSerializer,
req: reqSerializer,
res: resSerializer,
- resErr: resErrSerializer,
},
...(config.ecsNodeUrl ? ecsFormat() : {}),
},
transport,
);
-export default logger;
+interface ReqResLogPayload {
+ key: 'apiReqRes';
+ req: NextApiRequest;
+ res: NextApiResponse;
+}
+
+interface RpcReqResLogPayload {
+ key: 'rpc';
+ type: 'query' | 'mutation';
+ name: string;
+ error: Error | null;
+}
+
+type LogPayload = ReqResLogPayload | RpcReqResLogPayload;
+
+function logMethod(method: 'info' | 'trace' | 'error' | 'warn' | 'fatal') {
+ return (obj: LogPayload, msg: string) => logger[method](obj, msg);
+}
+
+export default {
+ info: logMethod('info'),
+ trace: logMethod('trace'),
+ error: logMethod('error'),
+ warn: logMethod('warn'),
+ fatal: logMethod('fatal'),
+};
diff --git a/packages/toolpad-app/src/server/logs/withLogs.ts b/packages/toolpad-app/src/server/logs/withLogs.ts
index baf16e5c201..b6efbc49b35 100644
--- a/packages/toolpad-app/src/server/logs/withLogs.ts
+++ b/packages/toolpad-app/src/server/logs/withLogs.ts
@@ -1,44 +1,10 @@
import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next';
-import logInfo from './logInfo';
-import type { RpcResponse } from '../../../pages/api/rpc';
-
-type RestArgs = [
- chunk: any,
- encoding: BufferEncoding,
- callback?: ((error?: Error | null) => void) | undefined,
-];
-
-const logWithResponseBody = (
- res: NextApiResponse,
- logHandler: (responseBody: Record) => void,
-): void => {
- const oldWrite = res.write;
- const oldEnd = res.end;
-
- const chunks: Buffer[] = [];
- res.write = (...restArgs: any[]) => {
- chunks.push(Buffer.from(restArgs[0]));
- return oldWrite.apply(res, restArgs as RestArgs);
- };
-
- res.end = (...restArgs: any[]) => {
- if (restArgs[0]) {
- chunks.push(Buffer.from(restArgs[0]));
- }
-
- const loggableResponseBody = JSON.parse(Buffer.concat(chunks).toString('utf8'), (key, value) =>
- key === 'stack' ? undefined : value,
- );
- logHandler(loggableResponseBody);
-
- return oldEnd.apply(res, restArgs as RestArgs);
- };
-};
+import logger from './logger';
export const withReqResLogs =
(apiHandler: NextApiHandler) =>
(req: NextApiRequest, res: NextApiResponse): unknown | Promise => {
- logInfo(
+ logger.info(
{
key: 'apiReqRes',
req,
@@ -49,23 +15,3 @@ export const withReqResLogs =
return apiHandler(req, res);
};
-
-export const withRpcReqResLogs =
- (apiHandler: NextApiHandler) =>
- async (req: NextApiRequest, res: NextApiResponse): Promise => {
- logWithResponseBody(res, (resBody) => {
- const error = (resBody as RpcResponse).error;
-
- logInfo(
- {
- key: 'rpcReqRes',
- req,
- res,
- ...(error ? { resErr: error } : {}),
- },
- 'Handled RPC request',
- );
- });
-
- return apiHandler(req, res);
- };