From 1c17d601d83a1cffac3d33df697b2e1e3a28beae Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:07:06 +0100 Subject: [PATCH] feat(server/logging): add operations logging to comment mutations --- .../comments/graph/resolvers/comments.ts | 179 ++++++++++++++---- .../observability/domain/businessLogging.ts | 6 +- 2 files changed, 143 insertions(+), 42 deletions(-) diff --git a/packages/server/modules/comments/graph/resolvers/comments.ts b/packages/server/modules/comments/graph/resolvers/comments.ts index ac114092f..b278e6c90 100644 --- a/packages/server/modules/comments/graph/resolvers/comments.ts +++ b/packages/server/modules/comments/graph/resolvers/comments.ts @@ -91,6 +91,7 @@ import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector' import { Knex } from 'knex' import { getEventBus } from '@/modules/shared/services/eventBus' import { throwIfAuthNotOk } from '@/modules/shared/helpers/errorHelper' +import { withOperationLogging } from '@/observability/domain/businessLogging' // We can use the main DB for these const getStream = getStreamFactory({ db }) @@ -495,6 +496,11 @@ export = { }) throwIfAuthNotOk(canCreate) + const logger = ctx.log.child({ + projectId, + streamId: projectId //legacy + }) + const projectDb = await getProjectDbClient({ projectId }) const getViewerResourceItemsUngrouped = buildGetViewerResourceItemsUngrouped({ @@ -517,16 +523,28 @@ export = { emitEvent: getEventBus().emit }) - return await createCommentThreadAndNotify(args.input, ctx.userId!) + return await withOperationLogging( + async () => await createCommentThreadAndNotify(args.input, ctx.userId!), + { + operationName: 'createCommentThread', + operationDescription: 'Create comment thread', + logger + } + ) }, async reply(_parent, args, ctx) { + const projectId = args.input.projectId const canCreateComment = await ctx.authPolicies.project.comment.canCreate({ userId: ctx.userId, - projectId: args.input.projectId + projectId }) throwIfAuthNotOk(canCreateComment) + const logger = ctx.log.child({ + projectId, + streamId: projectId //legacy + }) - const projectDb = await getProjectDbClient({ projectId: args.input.projectId }) + const projectDb = await getProjectDbClient({ projectId }) const getComment = getCommentFactory({ db: projectDb }) const validateInputAttachments = validateInputAttachmentsFactory({ getBlobs: getBlobsFactory({ db: projectDb }) @@ -549,18 +567,33 @@ export = { }) }) - return await createCommentReplyAndNotify(args.input, ctx.userId!) + return await withOperationLogging( + async () => await createCommentReplyAndNotify(args.input, ctx.userId!), + { + operationName: 'replyToComment', + operationDescription: 'Reply to comment', + logger + } + ) }, async edit(_parent, args, ctx) { + const projectId = args.input.projectId + const commentId = args.input.commentId const canEditComment = await ctx.authPolicies.project.comment.canEdit({ - projectId: args.input.projectId, + projectId, userId: ctx.userId, - commentId: args.input.commentId + commentId }) throwIfAuthNotOk(canEditComment) + const logger = ctx.log.child({ + projectId, + streamId: projectId, //legacy + commentId + }) + const projectDb = await getProjectDbClient({ - projectId: args.input.projectId + projectId }) const getComment = getCommentFactory({ db: projectDb }) const validateInputAttachments = validateInputAttachmentsFactory({ @@ -575,18 +608,28 @@ export = { emitEvent: getEventBus().emit }) - return await editCommentAndNotify(args.input, ctx.userId!) + return await withOperationLogging( + async () => await editCommentAndNotify(args.input, ctx.userId!), + { logger, operationName: 'editComment', operationDescription: 'Edit comment' } + ) }, async archive(_parent, args, ctx) { + const projectId = args.input.projectId + const commentId = args.input.commentId const canArchive = await ctx.authPolicies.project.comment.canArchive({ userId: ctx.userId, - projectId: args.input.projectId, - commentId: args.input.commentId + projectId, + commentId }) throwIfAuthNotOk(canArchive) + const logger = ctx.log.child({ + projectId, + streamId: projectId, //legacy + commentId + }) const projectDb = await getProjectDbClient({ - projectId: args.input.projectId + projectId }) const getComment = getCommentFactory({ db: projectDb }) const getStream = getStreamFactory({ db: projectDb }) @@ -605,10 +648,14 @@ export = { emitEvent: getEventBus().emit }) - await archiveCommentAndNotify( - args.input.commentId, - ctx.userId!, - args.input.archived + await withOperationLogging( + async () => + await archiveCommentAndNotify(commentId, ctx.userId!, args.input.archived), + { + logger, + operationName: 'archiveComment', + operationDescription: 'Archive comment' + } ) return true } @@ -675,13 +722,19 @@ export = { }, async commentCreate(_parent, args, context) { + const projectId = args.input.streamId const canCreate = await context.authPolicies.project.comment.canCreate({ userId: context.userId, - projectId: args.input.streamId + projectId }) throwIfAuthNotOk(canCreate) - const projectDb = await getProjectDbClient({ projectId: args.input.streamId }) + const logger = context.log.child({ + projectId, + streamId: projectId //legacy + }) + + const projectDb = await getProjectDbClient({ projectId }) const getViewerResourcesFromLegacyIdentifiers = buildGetViewerResourcesFromLegacyIdentifiers({ db: projectDb }) @@ -699,23 +752,39 @@ export = { emitEvent: getEventBus().emit, getViewerResourcesFromLegacyIdentifiers }) - const comment = await createComment({ - userId: context.userId!, - input: args.input - }) + const comment = await withOperationLogging( + async () => + await createComment({ + userId: context.userId!, + input: args.input + }), + { + operationName: 'createComment', + operationDescription: 'Create comment', + logger + } + ) return comment.id }, async commentEdit(_parent, args, context) { + const projectId = args.input.streamId + const commentId = args.input.id const canEdit = await context.authPolicies.project.comment.canEdit({ userId: context.userId, - projectId: args.input.streamId, - commentId: args.input.id + projectId, + commentId }) throwIfAuthNotOk(canEdit) - const projectDb = await getProjectDbClient({ projectId: args.input.streamId }) + const logger = context.log.child({ + projectId, + streamId: projectId, //legacy + commentId + }) + + const projectDb = await getProjectDbClient({ projectId }) const editComment = editCommentFactory({ getComment: getCommentFactory({ db: projectDb }), validateInputAttachments: validateInputAttachmentsFactory({ @@ -725,7 +794,10 @@ export = { emitEvent: getEventBus().emit }) - await editComment({ userId: context.userId!, input: args.input }) + await withOperationLogging( + async () => await editComment({ userId: context.userId!, input: args.input }), + { operationName: 'editComment', operationDescription: 'Edit comment', logger } + ) return true }, @@ -745,38 +817,59 @@ export = { }, async commentArchive(_parent, args, context) { + const projectId = args.streamId + const commentId = args.commentId const canArchive = await context.authPolicies.project.comment.canArchive({ userId: context.userId, - projectId: args.streamId, - commentId: args.commentId + projectId, + commentId }) throwIfAuthNotOk(canArchive) - const projectDb = await getProjectDbClient({ projectId: args.streamId }) + const logger = context.log.child({ + projectId, + streamId: projectId, //legacy + commentId + }) + + const projectDb = await getProjectDbClient({ projectId }) const archiveComment = archiveCommentFactory({ getComment: getCommentFactory({ db: projectDb }), getStream, updateComment: updateCommentFactory({ db: projectDb }), emitEvent: getEventBus().emit }) - await archiveComment({ ...args, userId: context.userId! }) // NOTE: permissions check inside service + await withOperationLogging( + async () => await archiveComment({ ...args, userId: context.userId! }), // NOTE: permissions check inside service + { + logger, + operationName: 'archiveComment', + operationDescription: 'Archive comment' + } + ) return true }, async commentReply(_parent, args, context) { + const projectId = args.input.streamId if (!context.userId) throw new ForbiddenError('Only registered users can comment.') + const logger = context.log.child({ + projectId, + streamId: projectId //legacy + }) + const stream = await getStream({ - streamId: args.input.streamId, + streamId: projectId, userId: context.userId }) if (!stream?.allowPublicComments && !stream?.role) throw new ForbiddenError('You are not authorized.') - const projectDb = await getProjectDbClient({ projectId: args.input.streamId }) + const projectDb = await getProjectDbClient({ projectId }) const createCommentReply = createCommentReplyFactory({ validateInputAttachments: validateInputAttachmentsFactory({ @@ -796,14 +889,22 @@ export = { buildGetViewerResourcesFromLegacyIdentifiers({ db: projectDb }) }) }) - const reply = await createCommentReply({ - authorId: context.userId, - parentCommentId: args.input.parentComment, - streamId: args.input.streamId, - text: args.input.text as SmartTextEditorValueSchema, - data: args.input.data ?? null, - blobIds: args.input.blobIds - }) + const reply = await withOperationLogging( + async () => + await createCommentReply({ + authorId: context.userId, + parentCommentId: args.input.parentComment, + streamId: args.input.streamId, + text: args.input.text as SmartTextEditorValueSchema, + data: args.input.data ?? null, + blobIds: args.input.blobIds + }), + { + logger, + operationName: 'createCommentReply', + operationDescription: 'Create comment reply' + } + ) return reply.id } diff --git a/packages/server/observability/domain/businessLogging.ts b/packages/server/observability/domain/businessLogging.ts index 145f1f52a..1e545bc84 100644 --- a/packages/server/observability/domain/businessLogging.ts +++ b/packages/server/observability/domain/businessLogging.ts @@ -18,9 +18,9 @@ export const withOperationLogging = async ( logger: Logger operationName: string operationDescription?: string - errorHandler?: (err: unknown, logger: Logger) => MaybeAsync + errorHandler?: (err: unknown, logger: Logger) => MaybeAsync } -): Promise => { +): Promise => { const { operationName, operationDescription } = params const errorHandler = params.errorHandler || logErrorThenThrow const logger = params.logger.child(OperationName(operationName)) @@ -36,6 +36,6 @@ export const withOperationLogging = async ( logger.info(OperationStatus.success, OperationLogLinePrefix) return results } catch (err) { - await errorHandler(err, logger) + return await errorHandler(err, logger) } }