feat: add more api logs (#1870)

Adds more detailed API logging using Pino
This commit is contained in:
David Nguyen
2025-06-30 19:46:32 +10:00
committed by GitHub
parent 0cc729e9bd
commit 7487399123
74 changed files with 1395 additions and 544 deletions

View File

@ -77,9 +77,15 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
};
}),
getDocument: authenticatedMiddleware(async (args, user, team) => {
getDocument: authenticatedMiddleware(async (args, user, team, { logger }) => {
const { id: documentId } = args.params;
logger.info({
input: {
id: documentId,
},
});
try {
const document = await getDocumentById({
documentId: Number(documentId),
@ -139,10 +145,16 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
downloadSignedDocument: authenticatedMiddleware(async (args, user, team) => {
downloadSignedDocument: authenticatedMiddleware(async (args, user, team, { logger }) => {
const { id: documentId } = args.params;
const { downloadOriginalDocument } = args.query;
logger.info({
input: {
id: documentId,
},
});
try {
if (process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT !== 's3') {
return {
@ -204,9 +216,15 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
deleteDocument: authenticatedMiddleware(async (args, user, team, { metadata }) => {
deleteDocument: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
const { id: documentId } = args.params;
logger.info({
input: {
id: documentId,
},
});
try {
const document = await getDocumentById({
documentId: Number(documentId),
@ -382,9 +400,15 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
deleteTemplate: authenticatedMiddleware(async (args, user, team) => {
deleteTemplate: authenticatedMiddleware(async (args, user, team, { logger }) => {
const { id: templateId } = args.params;
logger.info({
input: {
id: templateId,
},
});
try {
const deletedTemplate = await deleteTemplate({
id: Number(templateId),
@ -406,9 +430,15 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
getTemplate: authenticatedMiddleware(async (args, user, team) => {
getTemplate: authenticatedMiddleware(async (args, user, team, { logger }) => {
const { id: templateId } = args.params;
logger.info({
input: {
id: templateId,
},
});
try {
const template = await getTemplateById({
id: Number(templateId),
@ -463,202 +493,224 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
createDocumentFromTemplate: authenticatedMiddleware(async (args, user, team, { metadata }) => {
const { body, params } = args;
createDocumentFromTemplate: authenticatedMiddleware(
async (args, user, team, { logger, metadata }) => {
const { body, params } = args;
const { remaining } = await getServerLimits({ userId: user.id, teamId: team?.id });
if (remaining.documents <= 0) {
return {
status: 400,
body: {
message: 'You have reached the maximum number of documents allowed for this month',
logger.info({
input: {
templateId: params.templateId,
},
};
}
const templateId = Number(params.templateId);
const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`;
const document = await createDocumentFromTemplateLegacy({
templateId,
userId: user.id,
teamId: team?.id,
recipients: body.recipients,
});
let documentDataId = document.documentDataId;
if (body.formValues) {
const pdf = await getFileServerSide(document.documentData);
const prefilled = await insertFormValuesInPdf({
pdf: Buffer.from(pdf),
formValues: body.formValues,
});
const newDocumentData = await putPdfFileServerSide({
name: fileName,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(prefilled),
});
const { remaining } = await getServerLimits({ userId: user.id, teamId: team?.id });
documentDataId = newDocumentData.id;
}
await updateDocument({
documentId: document.id,
userId: user.id,
teamId: team?.id,
data: {
title: fileName,
externalId: body.externalId || null,
formValues: body.formValues,
documentData: {
connect: {
id: documentDataId,
if (remaining.documents <= 0) {
return {
status: 400,
body: {
message: 'You have reached the maximum number of documents allowed for this month',
},
},
},
});
};
}
if (body.meta) {
await upsertDocumentMeta({
documentId: document.id,
userId: user.id,
teamId: team?.id,
...body.meta,
requestMetadata: metadata,
});
}
const templateId = Number(params.templateId);
if (body.authOptions) {
await updateDocumentSettings({
documentId: document.id,
userId: user.id,
teamId: team?.id,
data: body.authOptions,
requestMetadata: metadata,
});
}
const fileName = body.title.endsWith('.pdf') ? body.title : `${body.title}.pdf`;
return {
status: 200,
body: {
documentId: document.id,
recipients: document.recipients.map((recipient) => ({
recipientId: recipient.id,
name: recipient.name,
email: recipient.email,
token: recipient.token,
role: recipient.role,
signingOrder: recipient.signingOrder,
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
})),
},
};
}),
generateDocumentFromTemplate: authenticatedMiddleware(async (args, user, team, { metadata }) => {
const { body, params } = args;
const { remaining } = await getServerLimits({ userId: user.id, teamId: team?.id });
if (remaining.documents <= 0) {
return {
status: 400,
body: {
message: 'You have reached the maximum number of documents allowed for this month',
},
};
}
const templateId = Number(params.templateId);
let document: Awaited<ReturnType<typeof createDocumentFromTemplate>> | null = null;
try {
document = await createDocumentFromTemplate({
const document = await createDocumentFromTemplateLegacy({
templateId,
externalId: body.externalId || null,
userId: user.id,
teamId: team?.id,
recipients: body.recipients,
prefillFields: body.prefillFields,
override: {
title: body.title,
...body.meta,
},
requestMetadata: metadata,
});
} catch (err) {
return AppError.toRestAPIError(err);
}
if (body.formValues) {
const fileName = document.title.endsWith('.pdf') ? document.title : `${document.title}.pdf`;
const pdf = await getFileServerSide(document.documentData);
const prefilled = await insertFormValuesInPdf({
pdf: Buffer.from(pdf),
formValues: body.formValues,
});
const newDocumentData = await putPdfFileServerSide({
name: fileName,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(prefilled),
});
let documentDataId = document.documentDataId;
if (body.formValues) {
const pdf = await getFileServerSide(document.documentData);
const prefilled = await insertFormValuesInPdf({
pdf: Buffer.from(pdf),
formValues: body.formValues,
});
const newDocumentData = await putPdfFileServerSide({
name: fileName,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(prefilled),
});
documentDataId = newDocumentData.id;
}
await updateDocument({
documentId: document.id,
userId: user.id,
teamId: team?.id,
data: {
title: fileName,
externalId: body.externalId || null,
formValues: body.formValues,
documentData: {
connect: {
id: newDocumentData.id,
id: documentDataId,
},
},
},
});
}
if (body.authOptions) {
await updateDocumentSettings({
documentId: document.id,
userId: user.id,
teamId: team?.id,
data: body.authOptions,
requestMetadata: metadata,
if (body.meta) {
await upsertDocumentMeta({
documentId: document.id,
userId: user.id,
teamId: team?.id,
...body.meta,
requestMetadata: metadata,
});
}
if (body.authOptions) {
await updateDocumentSettings({
documentId: document.id,
userId: user.id,
teamId: team?.id,
data: body.authOptions,
requestMetadata: metadata,
});
}
return {
status: 200,
body: {
documentId: document.id,
recipients: document.recipients.map((recipient) => ({
recipientId: recipient.id,
name: recipient.name,
email: recipient.email,
token: recipient.token,
role: recipient.role,
signingOrder: recipient.signingOrder,
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
})),
},
};
},
),
generateDocumentFromTemplate: authenticatedMiddleware(
async (args, user, team, { logger, metadata }) => {
const { body, params } = args;
logger.info({
input: {
templateId: params.templateId,
},
});
}
return {
status: 200,
body: {
documentId: document.id,
recipients: document.recipients.map((recipient) => ({
recipientId: recipient.id,
name: recipient.name,
email: recipient.email,
token: recipient.token,
role: recipient.role,
signingOrder: recipient.signingOrder,
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
})),
},
};
}),
const { remaining } = await getServerLimits({ userId: user.id, teamId: team?.id });
sendDocument: authenticatedMiddleware(async (args, user, team, { metadata }) => {
if (remaining.documents <= 0) {
return {
status: 400,
body: {
message: 'You have reached the maximum number of documents allowed for this month',
},
};
}
const templateId = Number(params.templateId);
let document: Awaited<ReturnType<typeof createDocumentFromTemplate>> | null = null;
try {
document = await createDocumentFromTemplate({
templateId,
externalId: body.externalId || null,
userId: user.id,
teamId: team?.id,
recipients: body.recipients,
prefillFields: body.prefillFields,
override: {
title: body.title,
...body.meta,
},
requestMetadata: metadata,
});
} catch (err) {
return AppError.toRestAPIError(err);
}
if (body.formValues) {
const fileName = document.title.endsWith('.pdf') ? document.title : `${document.title}.pdf`;
const pdf = await getFileServerSide(document.documentData);
const prefilled = await insertFormValuesInPdf({
pdf: Buffer.from(pdf),
formValues: body.formValues,
});
const newDocumentData = await putPdfFileServerSide({
name: fileName,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(prefilled),
});
await updateDocument({
documentId: document.id,
userId: user.id,
teamId: team?.id,
data: {
formValues: body.formValues,
documentData: {
connect: {
id: newDocumentData.id,
},
},
},
});
}
if (body.authOptions) {
await updateDocumentSettings({
documentId: document.id,
userId: user.id,
teamId: team?.id,
data: body.authOptions,
requestMetadata: metadata,
});
}
return {
status: 200,
body: {
documentId: document.id,
recipients: document.recipients.map((recipient) => ({
recipientId: recipient.id,
name: recipient.name,
email: recipient.email,
token: recipient.token,
role: recipient.role,
signingOrder: recipient.signingOrder,
signingUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`,
})),
},
};
},
),
sendDocument: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
const { id: documentId } = args.params;
const { sendEmail, sendCompletionEmails } = args.body;
logger.info({
input: {
id: documentId,
},
});
try {
const document = await getDocumentById({
documentId: Number(documentId),
@ -730,10 +782,16 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
resendDocument: authenticatedMiddleware(async (args, user, team, { metadata }) => {
resendDocument: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
const { id: documentId } = args.params;
const { recipients } = args.body;
logger.info({
input: {
id: documentId,
},
});
try {
await resendDocument({
userId: user.id,
@ -759,10 +817,16 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
createRecipient: authenticatedMiddleware(async (args, user, team, { metadata }) => {
createRecipient: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
const { id: documentId } = args.params;
const { name, email, role, authOptions, signingOrder } = args.body;
logger.info({
input: {
id: documentId,
},
});
const document = await getDocumentById({
documentId: Number(documentId),
userId: user.id,
@ -850,10 +914,17 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
updateRecipient: authenticatedMiddleware(async (args, user, team, { metadata }) => {
updateRecipient: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
const { id: documentId, recipientId } = args.params;
const { name, email, role, authOptions, signingOrder } = args.body;
logger.info({
input: {
id: documentId,
recipientId,
},
});
const document = await getDocumentById({
documentId: Number(documentId),
userId: user.id,
@ -916,9 +987,16 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
};
}),
deleteRecipient: authenticatedMiddleware(async (args, user, team, { metadata }) => {
deleteRecipient: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
const { id: documentId, recipientId } = args.params;
logger.info({
input: {
id: documentId,
recipientId,
},
});
const document = await getDocumentById({
documentId: Number(documentId),
userId: user.id,
@ -970,8 +1048,15 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
};
}),
createField: authenticatedMiddleware(async (args, user, team, { metadata }) => {
createField: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
const { id: documentId } = args.params;
logger.info({
input: {
id: documentId,
},
});
const fields = Array.isArray(args.body) ? args.body : [args.body];
const document = await prisma.document.findFirst({
@ -1131,11 +1216,18 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
}
}),
updateField: authenticatedMiddleware(async (args, user, team, { metadata }) => {
updateField: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
const { id: documentId, fieldId } = args.params;
const { recipientId, type, pageNumber, pageWidth, pageHeight, pageX, pageY, fieldMeta } =
args.body;
logger.info({
input: {
id: documentId,
fieldId,
},
});
const document = await getDocumentById({
documentId: Number(documentId),
userId: user.id,
@ -1222,9 +1314,16 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
};
}),
deleteField: authenticatedMiddleware(async (args, user, team, { metadata }) => {
deleteField: authenticatedMiddleware(async (args, user, team, { logger, metadata }) => {
const { id: documentId, fieldId } = args.params;
logger.info({
input: {
id: documentId,
fieldId,
},
});
const document = await getDocumentById({
documentId: Number(documentId),
userId: user.id,

View File

@ -1,10 +1,13 @@
import type { Team, User } from '@prisma/client';
import type { TsRestRequest } from '@ts-rest/serverless';
import type { Logger } from 'pino';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { getApiTokenByToken } from '@documenso/lib/server-only/public-api/get-api-token-by-token';
import type { BaseApiLog, RootApiLog } from '@documenso/lib/types/api-logs';
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { extractRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { nanoid } from '@documenso/lib/universal/id';
import { logger } from '@documenso/lib/utils/logger';
type B = {
@ -28,10 +31,24 @@ export const authenticatedMiddleware = <
args: T & { req: TsRestRequest },
user: Pick<User, 'id' | 'email' | 'name' | 'disabled'>,
team: Team,
options: { metadata: ApiRequestMetadata },
options: { metadata: ApiRequestMetadata; logger: Logger },
) => Promise<R>,
) => {
return async (args: T, { request }: B) => {
const requestMetadata = extractRequestMetadata(request);
const apiLogger = logger.child({
ipAddress: requestMetadata.ipAddress,
userAgent: requestMetadata.userAgent,
requestId: nanoid(),
} satisfies RootApiLog);
const infoToLog: BaseApiLog = {
auth: 'api',
source: 'apiV1',
path: request.url,
};
try {
const { authorization } = args.headers;
@ -52,8 +69,14 @@ export const authenticatedMiddleware = <
});
}
apiLogger.info({
...infoToLog,
userId: apiToken.user.id,
apiTokenId: apiToken.id,
} satisfies BaseApiLog);
const metadata: ApiRequestMetadata = {
requestMetadata: extractRequestMetadata(request),
requestMetadata,
source: 'apiV1',
auth: 'api',
auditUser: {
@ -63,17 +86,6 @@ export const authenticatedMiddleware = <
},
};
// Todo: Get from Hono context instead.
logger.info({
ipAddress: metadata.requestMetadata.ipAddress,
userAgent: metadata.requestMetadata.userAgent,
auth: 'api',
source: 'apiV1',
path: request.url,
userId: apiToken.user.id,
apiTokenId: apiToken.id,
});
return await handler(
{
...args,
@ -81,10 +93,12 @@ export const authenticatedMiddleware = <
},
apiToken.user,
apiToken.team,
{ metadata },
{ metadata, logger: apiLogger },
);
} catch (err) {
console.log({ err: err });
console.log({ err });
apiLogger.info(infoToLog);
let message = 'Unauthorized';