Procházet zdrojové kódy

replace bunyan syntax with pino syntax

Yuki Takei před 1 týdnem
rodič
revize
2a4fe18c3b
48 změnil soubory, kde provedl 305 přidání a 239 odebrání
  1. 1 1
      .kiro/specs/migrate-logger-to-pino/spec.json
  2. 10 10
      .kiro/specs/migrate-logger-to-pino/tasks.md
  3. 6 8
      apps/app/src/client/components/PageEditor/PageEditor.tsx
  4. 1 1
      apps/app/src/client/components/RecentActivity/RecentActivity.tsx
  5. 2 2
      apps/app/src/client/components/StickyStretchableScroller.tsx
  6. 7 3
      apps/app/src/features/admin/states/socket-io.ts
  7. 2 2
      apps/app/src/features/comment/server/models/comment.ts
  8. 24 15
      apps/app/src/features/growi-plugin/server/services/growi-plugin/growi-plugin.ts
  9. 2 2
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/AiAssistantSidebar.tsx
  10. 2 2
      apps/app/src/features/openai/server/routes/edit/index.ts
  11. 22 13
      apps/app/src/features/openai/server/services/editor-assistant/llm-response-stream-processor.ts
  12. 49 31
      apps/app/src/features/openai/server/services/openai.ts
  13. 5 4
      apps/app/src/features/opentelemetry/server/custom-resource-attributes/application-resource-attributes.ts
  14. 1 1
      apps/app/src/features/opentelemetry/server/custom-resource-attributes/os-resource-attributes.ts
  15. 3 3
      apps/app/src/pages/common-props/commons.ts
  16. 3 3
      apps/app/src/pages/general-page/type-guards.ts
  17. 3 5
      apps/app/src/server/app.ts
  18. 2 2
      apps/app/src/server/events/user.ts
  19. 4 1
      apps/app/src/server/middlewares/access-token-parser/api-token.ts
  20. 3 3
      apps/app/src/server/middlewares/apiv1-form-validator.ts
  21. 3 3
      apps/app/src/server/middlewares/apiv3-form-validator.ts
  22. 16 10
      apps/app/src/server/middlewares/certify-shared-page-attachment/validate-referer/validate-referer.ts
  23. 1 1
      apps/app/src/server/middlewares/login-required.ts
  24. 6 3
      apps/app/src/server/middlewares/safe-redirect.ts
  25. 1 1
      apps/app/src/server/models/activity.ts
  26. 1 1
      apps/app/src/server/models/external-account.ts
  27. 2 2
      apps/app/src/server/models/user-group-relation.ts
  28. 2 2
      apps/app/src/server/routes/apiv3/bookmark-folder.ts
  29. 10 10
      apps/app/src/server/routes/apiv3/g2g-transfer.ts
  30. 18 12
      apps/app/src/server/routes/apiv3/page/update-page.ts
  31. 2 2
      apps/app/src/server/service/config-manager/config-loader.ts
  32. 1 1
      apps/app/src/server/service/external-account.ts
  33. 1 1
      apps/app/src/server/service/file-uploader/gridfs.ts
  34. 38 26
      apps/app/src/server/service/mail/mail.ts
  35. 1 1
      apps/app/src/server/service/mail/oauth2.ts
  36. 1 1
      apps/app/src/server/service/mail/ses.ts
  37. 2 2
      apps/app/src/server/service/mail/smtp.ts
  38. 3 3
      apps/app/src/server/service/page/events/seen.ts
  39. 6 9
      apps/app/src/server/service/page/index.ts
  40. 7 8
      apps/app/src/server/service/s2s-messaging/nchan.ts
  41. 4 4
      apps/app/src/server/service/search-delegator/elasticsearch.ts
  42. 4 4
      apps/app/src/server/service/slack-integration.ts
  43. 3 3
      apps/app/src/server/service/socket-io/socket-io.ts
  44. 2 2
      apps/app/src/server/service/yjs/create-mongodb-persistence.ts
  45. 9 6
      apps/app/src/server/service/yjs/upgrade-handler.ts
  46. 2 2
      apps/app/src/server/service/yjs/yjs.ts
  47. 4 4
      apps/app/src/server/util/slack-legacy.ts
  48. 3 3
      apps/app/src/states/socket-io/global-socket.ts

+ 1 - 1
.kiro/specs/migrate-logger-to-pino/spec.json

@@ -3,7 +3,7 @@
   "created_at": "2026-03-23T00:00:00.000Z",
   "updated_at": "2026-03-27T00:00:00.000Z",
   "language": "en",
-  "phase": "tasks-generated",
+  "phase": "implementation-complete",
   "approvals": {
     "requirements": {
       "generated": true,

+ 10 - 10
.kiro/specs/migrate-logger-to-pino/tasks.md

@@ -123,49 +123,49 @@
   - Update unit tests in `logger-factory.spec.ts` to verify that calling `loggerFactory` for N distinct namespaces does not create N independent pino instances (all children share the root transport)
   - _Requirements: 11.1, 11.2, 11.3, 11.4_
 
-- [ ] 7. Migrate apps/app to @growi/logger (largest scope)
-- [ ] 7.1 Replace the logger factory module in apps/app
+- [x] 7. Migrate apps/app to @growi/logger (largest scope)
+- [x] 7.1 Replace the logger factory module in apps/app
   - Update the apps/app logger utility to import from `@growi/logger` instead of `universal-bunyan`
   - Call `initializeLoggerFactory` at application startup with the existing dev/prod config files (preserve current config content)
   - Re-export `loggerFactory` as the default export so all existing consumer imports continue to work unchanged
   - Add `@growi/logger` to apps/app dependencies and ensure pino-pretty is available for development formatting
   - _Requirements: 8.1, 2.2_
 
-- [ ] 7.2 Replace HTTP request logging middleware in apps/app
+- [x] 7.2 Replace HTTP request logging middleware in apps/app
   - Remove the morgan middleware (development mode) and express-bunyan-logger middleware (production mode) from the Express initialization
   - Add pino-http middleware configured with a logger from the factory using the `express` namespace
   - Configure route skipping to exclude `/_next/static/` paths in non-production mode
   - Verify the middleware produces log entries containing method, URL, status code, and response time
   - _Requirements: 6.1, 6.2, 6.3, 6.4_
 
-- [ ] 7.3 Update the OpenTelemetry diagnostic logger adapter
+- [x] 7.3 Update the OpenTelemetry diagnostic logger adapter
   - Rename the adapter class from `DiagLoggerBunyanAdapter` to `DiagLoggerPinoAdapter` and update the import to use pino types
   - Preserve the existing `parseMessage` helper logic that parses JSON strings and merges argument objects
   - Confirm the verbose-to-trace level mapping continues to work with pino's trace level
   - Update the OpenTelemetry SDK configuration to disable `@opentelemetry/instrumentation-pino` instead of `@opentelemetry/instrumentation-bunyan`
   - _Requirements: 7.1, 7.2, 7.3_
 
-- [ ] 7.4 Update all bunyan type references in apps/app source files
+- [x] 7.4 Update all bunyan type references in apps/app source files
   - Replace `import type Logger from 'bunyan'` with the Logger type exported from `@growi/logger` across all source files in apps/app
   - Verify that pino's Logger type is compatible with all existing usage patterns (info, debug, warn, error, trace, fatal method calls)
   - Run the TypeScript compiler to confirm no type errors
   - _Requirements: 10.1, 10.2, 10.3_
 
-- [ ] 8. Remove old logging dependencies and verify cleanup
-- [ ] 8.1 Remove bunyan-related packages from all package.json files
+- [x] 8. Remove old logging dependencies and verify cleanup
+- [x] 8.1 Remove bunyan-related packages from all package.json files
   - Remove `bunyan`, `universal-bunyan`, `bunyan-format`, `express-bunyan-logger`, `browser-bunyan`, `@browser-bunyan/console-formatted-stream`, `@types/bunyan` from every package.json in the monorepo
   - Remove `morgan` and `@types/morgan` from every package.json in the monorepo
   - Run `pnpm install` to update the lockfile and verify no broken peer dependency warnings
   - _Requirements: 9.1, 9.2_
 
-- [ ] 8.2 Verify no residual references to removed packages
+- [x] 8.2 Verify no residual references to removed packages
   - Search all source files for any remaining imports or requires of the removed packages (bunyan, universal-bunyan, browser-bunyan, express-bunyan-logger, morgan, bunyan-format)
   - Search all configuration and type definition files for stale bunyan references
   - Fix any remaining references found during the search
   - _Requirements: 9.3_
 
-- [ ] 9. Run full monorepo validation
-- [ ] 9.1 Execute lint, type-check, test, and build across the monorepo
+- [x] 9. Run full monorepo validation
+- [x] 9.1 Execute lint, type-check, test, and build across the monorepo
   - Run `turbo run lint --filter @growi/app` and fix any lint errors related to the migration
   - Run `turbo run test --filter @growi/app` and verify all existing tests pass
   - Run `turbo run build --filter @growi/app` and confirm the production build succeeds

+ 6 - 8
apps/app/src/client/components/PageEditor/PageEditor.tsx

@@ -221,10 +221,10 @@ export const PageEditorSubstance = (props: Props): JSX.Element => {
   const save: Save = useCallback(
     async (revisionId, markdown, opts, onConflict) => {
       if (pageId == null || selectedGrant == null) {
-        logger.error('Some materials to save are invalid', {
-          pageId,
-          selectedGrant,
-        });
+        logger.error(
+          { pageId, selectedGrant },
+          'Some materials to save are invalid',
+        );
         throw new Error('Some materials to save are invalid');
       }
 
@@ -251,7 +251,7 @@ export const PageEditorSubstance = (props: Props): JSX.Element => {
 
         return page;
       } catch (error) {
-        logger.error('failed to save', error);
+        logger.error({ err: error }, 'failed to save');
 
         const remoteRevisionData = extractRemoteRevisionDataFromErrorObj(error);
         if (remoteRevisionData != null) {
@@ -329,9 +329,7 @@ export const PageEditorSubstance = (props: Props): JSX.Element => {
   const uploadHandler = useCallback(
     (files: File[]) => {
       if (pageId == null) {
-        logger.error('pageId is invalid', {
-          pageId,
-        });
+        logger.error({ pageId }, 'pageId is invalid');
         throw new Error('pageId is invalid');
       }
 

+ 1 - 1
apps/app/src/client/components/RecentActivity/RecentActivity.tsx

@@ -54,7 +54,7 @@ export const RecentActivity = (props: RecentActivityProps): JSX.Element => {
 
   useEffect(() => {
     if (error) {
-      logger.error('Failed to fetch recent activity data', error);
+      logger.error({ err: error }, 'Failed to fetch recent activity data');
       toastError(error);
       return;
     }

+ 2 - 2
apps/app/src/client/components/StickyStretchableScroller.tsx

@@ -1,5 +1,5 @@
 import type { RefObject } from 'react';
-import React, {
+import {
   type JSX,
   useCallback,
   useEffect,
@@ -73,7 +73,7 @@ export const StickyStretchableScroller = (
     const scrollElement = simplebarRef.current.getScrollElement();
     const newHeight = calcViewHeight(scrollElement);
 
-    logger.debug('Set new height to simplebar', newHeight);
+    logger.debug({ newHeight }, 'Set new height to simplebar');
 
     // set new height
     setSimplebarMaxHeight(newHeight);

+ 7 - 3
apps/app/src/features/admin/states/socket-io.ts

@@ -27,12 +27,16 @@ export const useSetupAdminSocket = (): void => {
       .then(({ default: io }) => {
         if (cancelled) return;
         const newSocket = io('/admin', { transports: ['websocket'] });
-        newSocket.on('connect_error', (error) => logger.error('/admin', error));
-        newSocket.on('error', (error) => logger.error('/admin', error));
+        newSocket.on('connect_error', (error) =>
+          logger.error({ err: error }, '/admin'),
+        );
+        newSocket.on('error', (error) =>
+          logger.error({ err: error }, '/admin'),
+        );
         setSocket(newSocket);
       })
       .catch((error) =>
-        logger.error('Failed to initialize admin WebSocket:', error),
+        logger.error({ err: error }, 'Failed to initialize admin WebSocket'),
       );
 
     return () => {

+ 2 - 2
apps/app/src/features/comment/server/models/comment.ts

@@ -70,11 +70,11 @@ const add: Add = async function (
       commentPosition,
       replyTo,
     });
-    logger.debug('Comment saved.', data);
+    logger.debug({ data }, 'Comment saved.');
 
     return data;
   } catch (err) {
-    logger.debug('Error on saving comment.', err);
+    logger.debug({ err }, 'Error on saving comment.');
     throw err;
   }
 };

+ 24 - 15
apps/app/src/features/growi-plugin/server/services/growi-plugin/growi-plugin.ts

@@ -98,7 +98,7 @@ export class GrowiPluginService implements IGrowiPluginService {
             growiPlugin.organizationName,
           );
         } catch (err) {
-          logger.error(err);
+          logger.error({ err }, 'Plugin path validation failed');
           continue;
         }
         if (fs.existsSync(pluginPath)) {
@@ -135,12 +135,15 @@ export class GrowiPluginService implements IGrowiPluginService {
               await fs.promises.rm(unzippedReposPath, { recursive: true });
             if (fs.existsSync(pluginPath))
               await fs.promises.rm(pluginPath, { recursive: true });
-            logger.error(err);
+            logger.error({ err }, 'Failed to download plugin repository');
           }
         }
       }
     } catch (err) {
-      logger.error(err);
+      logger.error(
+        { err },
+        'Failed to download non-existent plugin repositories',
+      );
     }
   }
 
@@ -199,7 +202,7 @@ export class GrowiPluginService implements IGrowiPluginService {
       // move new repository from temporary path to storing path.
       fs.renameSync(temporaryReposPath, reposPath);
     } catch (err) {
-      logger.error(err);
+      logger.error({ err }, 'Failed to install plugin');
       throw err;
     } finally {
       // clean up
@@ -222,7 +225,7 @@ export class GrowiPluginService implements IGrowiPluginService {
         await fs.promises.rm(reposPath, { recursive: true });
       await this.deleteOldPluginDocument(installedPath);
 
-      logger.error(err);
+      logger.error({ err }, 'Failed to save plugin metadata');
       throw err;
     }
   }
@@ -253,7 +256,7 @@ export class GrowiPluginService implements IGrowiPluginService {
           }
         })
         .catch((err) => {
-          logger.error(err);
+          logger.error({ err }, 'Failed to download file');
           rejects('Failed to download file.');
         });
     });
@@ -270,7 +273,7 @@ export class GrowiPluginService implements IGrowiPluginService {
         unzipStream.Extract({ path: destPath.toString() }),
       );
     } catch (err) {
-      logger.error(err);
+      logger.error({ err }, 'Failed to unzip');
       throw new Error('Failed to unzip.');
     }
   }
@@ -345,7 +348,7 @@ export class GrowiPluginService implements IGrowiPluginService {
       plugin.meta = await generateTemplatePluginMeta(plugin, validationData);
     }
 
-    logger.info('Plugin detected => ', plugin);
+    logger.info({ plugin }, 'Plugin detected');
 
     return [plugin];
   }
@@ -371,7 +374,10 @@ export class GrowiPluginService implements IGrowiPluginService {
     try {
       await GrowiPlugin.deleteOne({ _id: pluginId });
     } catch (err) {
-      logger.error(err);
+      logger.error(
+        { err },
+        'Failed to delete plugin from GrowiPlugin documents',
+      );
       throw new Error('Failed to delete plugin from GrowiPlugin documents.');
     }
 
@@ -382,7 +388,7 @@ export class GrowiPluginService implements IGrowiPluginService {
         growiPlugins.installedPath,
       );
     } catch (err) {
-      logger.error(err);
+      logger.error({ err }, 'Invalid plugin installedPath');
       throw new Error(
         'The installedPath for the plugin is invalid, and the plugin has already been removed.',
       );
@@ -392,7 +398,7 @@ export class GrowiPluginService implements IGrowiPluginService {
       try {
         await deleteFolder(growiPluginsPath);
       } catch (err) {
-        logger.error(err);
+        logger.error({ err }, 'Failed to delete plugin repository');
         throw new Error('Failed to delete plugin repository.');
       }
     } else {
@@ -423,8 +429,8 @@ export class GrowiPluginService implements IGrowiPluginService {
       });
     } catch (e) {
       logger.error(
+        { err: e },
         `Could not find the theme '${theme}' from GrowiPlugin documents.`,
-        e,
       );
     }
 
@@ -440,7 +446,10 @@ export class GrowiPluginService implements IGrowiPluginService {
       }
       themeHref = `${PLUGIN_EXPRESS_STATIC_DIR}/${matchedPlugin.installedPath}/dist/${manifest[matchedThemeMetadata.manifestKey].file}`;
     } catch (e) {
-      logger.error(`Could not read manifest file for the theme '${theme}'`, e);
+      logger.error(
+        { err: e },
+        `Could not read manifest file for the theme '${theme}'`,
+      );
     }
 
     return {
@@ -479,11 +488,11 @@ export class GrowiPluginService implements IGrowiPluginService {
             entries.push([growiPlugin.installedPath, href]);
           }
         } catch (e) {
-          logger.warn(e);
+          logger.warn({ err: e }, 'Failed to retrieve plugin manifest');
         }
       });
     } catch (e) {
-      logger.error('Could not retrieve GrowiPlugin documents.', e);
+      logger.error({ err: e }, 'Could not retrieve GrowiPlugin documents.');
     }
 
     return entries;

+ 2 - 2
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/AiAssistantSidebar.tsx

@@ -373,10 +373,10 @@ const AiAssistantSidebarSubstance: React.FC<
                   mainMessages.push(data.appendedMessage);
                 },
                 onDetectedDiff: (data) => {
-                  logger.debug('sse diff', { data });
+                  logger.debug({ data }, 'sse diff');
                 },
                 onFinalized: (data) => {
-                  logger.debug('sse finalized', { data });
+                  logger.debug({ data }, 'sse finalized');
                 },
               });
             } else if (trimmedLine.startsWith('error:')) {

+ 2 - 2
apps/app/src/features/openai/server/routes/edit/index.ts

@@ -385,7 +385,7 @@ export const postMessageToEditHandlersFactory = (
 
         // Error handler
         stream.once('error', (err) => {
-          logger.error('Stream error:', err);
+          logger.error({ err }, 'Stream error');
 
           // Clean up
           streamProcessor.destroy();
@@ -409,7 +409,7 @@ export const postMessageToEditHandlersFactory = (
         });
       } catch (err) {
         // Clean up and respond on error
-        logger.error('Error in edit handler:', err);
+        logger.error({ err }, 'Error in edit handler');
         streamProcessor.destroy();
         return res.status(500).send(err.message);
       }

+ 22 - 13
apps/app/src/features/openai/server/services/editor-assistant/llm-response-stream-processor.ts

@@ -132,13 +132,16 @@ export class LlmResponseStreamProcessor {
             const validDiff = LlmEditorAssistantDiffSchema.safeParse(item);
             if (!validDiff.success) {
               // Phase 2B: Enhanced error logging for diff validation failures
-              logger.warn('Diff validation failed', {
-                errors: validDiff.error.errors,
-                item: JSON.stringify(item).substring(0, 200),
-                hasStartLine: 'startLine' in item,
-                hasSearch: 'search' in item,
-                hasReplace: 'replace' in item,
-              });
+              logger.warn(
+                {
+                  errors: validDiff.error.errors,
+                  item: JSON.stringify(item).substring(0, 200),
+                  hasStartLine: 'startLine' in item,
+                  hasSearch: 'search' in item,
+                  hasReplace: 'replace' in item,
+                },
+                'Diff validation failed',
+              );
               continue;
             }
 
@@ -146,10 +149,13 @@ export class LlmResponseStreamProcessor {
 
             // Phase 2B: Additional validation for required fields
             if (!diff.startLine) {
-              logger.error('startLine is required but missing in diff', {
-                search: diff.search?.substring(0, 50),
-                replace: diff.replace?.substring(0, 50),
-              });
+              logger.error(
+                {
+                  search: diff.search?.substring(0, 50),
+                  replace: diff.replace?.substring(0, 50),
+                },
+                'startLine is required but missing in diff',
+              );
               continue;
             }
 
@@ -187,7 +193,10 @@ export class LlmResponseStreamProcessor {
       }
     } catch (e) {
       // Ignore parse errors (expected for incomplete JSON)
-      logger.debug('JSON parsing error (expected for partial data):', e);
+      logger.debug(
+        { err: e },
+        'JSON parsing error (expected for partial data)',
+      );
     }
   }
 
@@ -254,7 +263,7 @@ export class LlmResponseStreamProcessor {
       const finalMessage = this.extractFinalMessage(rawBuffer);
       this.options?.dataFinalizedCallback?.(finalMessage, this.replacements);
     } catch (e) {
-      logger.debug('Failed to parse final JSON response:', e);
+      logger.debug({ err: e }, 'Failed to parse final JSON response');
 
       // Send final notification even on error
       const finalMessage = this.extractFinalMessage(rawBuffer);

+ 49 - 31
apps/app/src/features/openai/server/services/openai.ts

@@ -259,8 +259,8 @@ class OpenaiService implements IOpenaiService {
           })
           .catch((err) => {
             logger.error(
-              `Failed to generate thread title for threadId ${thread.id}:`,
-              err,
+              { err },
+              `Failed to generate thread title for threadId ${thread.id}`,
             );
           });
       }
@@ -282,9 +282,9 @@ class OpenaiService implements IOpenaiService {
           threadRelation.threadId,
           vectorStoreId,
         );
-        logger.debug('Update thread', updatedThreadResponse);
+        logger.debug({ data: updatedThreadResponse }, 'Update thread');
       } catch (err) {
-        logger.error(err);
+        logger.error({ err }, 'Failed to update thread');
       }
     }
   }
@@ -321,7 +321,7 @@ class OpenaiService implements IOpenaiService {
       const deletedThreadResponse = await this.client.deleteThread(
         threadRelation.threadId,
       );
-      logger.debug('Delete thread', deletedThreadResponse);
+      logger.debug({ data: deletedThreadResponse }, 'Delete thread');
       await threadRelation.remove();
     } catch (err) {
       await openaiApiErrorHandler(err, {
@@ -351,13 +351,13 @@ class OpenaiService implements IOpenaiService {
         const deleteThreadResponse = await this.client.deleteThread(
           expiredThreadRelation.threadId,
         );
-        logger.debug('Delete thread', deleteThreadResponse);
+        logger.debug({ data: deleteThreadResponse }, 'Delete thread');
         deletedThreadIds.push(expiredThreadRelation.threadId);
 
         // sleep
         await new Promise((resolve) => setTimeout(resolve, apiCallInterval));
       } catch (err) {
-        logger.error(err);
+        logger.error({ err }, 'Failed to delete expired thread');
       }
     }
 
@@ -509,7 +509,7 @@ class OpenaiService implements IOpenaiService {
       const deleteVectorStoreResponse = await this.client.deleteVectorStore(
         vectorStoreDocument.vectorStoreId,
       );
-      logger.debug('Delete vector store', deleteVectorStoreResponse);
+      logger.debug({ data: deleteVectorStoreResponse }, 'Delete vector store');
       await vectorStoreDocument.markAsDeleted();
     } catch (err) {
       await openaiApiErrorHandler(err, {
@@ -563,7 +563,7 @@ class OpenaiService implements IOpenaiService {
               attachment._id,
             );
           } catch (err) {
-            logger.error(err);
+            logger.error({ err }, 'Failed to upload attachment file');
           }
         }
         callback();
@@ -647,7 +647,7 @@ class OpenaiService implements IOpenaiService {
     const fileUploadResult = await Promise.allSettled(workers);
     fileUploadResult.forEach((result) => {
       if (result.status === 'rejected') {
-        logger.error(result.reason);
+        logger.error({ err: result.reason }, 'File upload failed');
       }
     });
 
@@ -677,14 +677,14 @@ class OpenaiService implements IOpenaiService {
           uploadedFileIds,
         );
       logger.debug(
+        { data: createVectorStoreFileBatchResponse },
         'Create vector store file',
-        createVectorStoreFileBatchResponse,
       );
 
       // Set isAttachedToVectorStore: true when the uploaded file is attached to VectorStore
       await VectorStoreFileRelationModel.markAsAttachedToVectorStore(pageIds);
     } catch (err) {
-      logger.error(err);
+      logger.error({ err }, 'Failed to create vector store file batch');
 
       // Delete all uploaded files if createVectorStoreFileBatch fails
       for await (const pageId of pageIds) {
@@ -742,8 +742,8 @@ class OpenaiService implements IOpenaiService {
       const fileId = vectorStoreFileRelation.fileIds[0];
       const deleteFileResponse = await this.client.deleteFile(fileId);
       logger.debug(
-        'Delete vector store file (attachment) ',
-        deleteFileResponse,
+        { data: deleteFileResponse },
+        'Delete vector store file (attachment)',
       );
 
       // Delete related VectorStoreFileRelation document
@@ -752,7 +752,10 @@ class OpenaiService implements IOpenaiService {
         await deleteAllAttachmentVectorStoreFileRelations();
       }
     } catch (err) {
-      logger.error(err);
+      logger.error(
+        { err },
+        'Failed to delete vector store file for attachment',
+      );
       await openaiApiErrorHandler(err, {
         notFoundError: () => deleteAllAttachmentVectorStoreFileRelations(),
       });
@@ -781,7 +784,10 @@ class OpenaiService implements IOpenaiService {
               vectorStoreFileRelation,
             );
           } catch (err) {
-            logger.error(err);
+            logger.error(
+              { err },
+              'Failed to delete vector store file for attachment',
+            );
           }
         }
       }
@@ -800,7 +806,7 @@ class OpenaiService implements IOpenaiService {
     for await (const fileId of vectorStoreFileRelation.fileIds) {
       try {
         const deleteFileResponse = await this.client.deleteFile(fileId);
-        logger.debug('Delete vector store file', deleteFileResponse);
+        logger.debug({ data: deleteFileResponse }, 'Delete vector store file');
         deletedFileIds.push(fileId);
         if (apiCallInterval != null) {
           // sleep
@@ -812,7 +818,7 @@ class OpenaiService implements IOpenaiService {
             deletedFileIds.push(fileId);
           },
         });
-        logger.error(err);
+        logger.error({ err }, 'Failed to delete file');
       }
     }
 
@@ -880,7 +886,7 @@ class OpenaiService implements IOpenaiService {
           apiCallInterval,
         );
       } catch (err) {
-        logger.error(err);
+        logger.error({ err }, 'Failed to delete vector store file');
       }
     }
   }
@@ -896,7 +902,10 @@ class OpenaiService implements IOpenaiService {
     try {
       await this.deleteVectorStoreFileForAttachment(vectorStoreFileRelation);
     } catch (err) {
-      logger.error(err);
+      logger.error(
+        { err },
+        'Failed to delete vector store file on attachment delete',
+      );
     }
   }
 
@@ -1002,10 +1011,13 @@ class OpenaiService implements IOpenaiService {
       }
 
       logger.debug('--------- createVectorStoreFileOnPageCreate ---------');
-      logger.debug('AccessScopeType of aiAssistant: ', aiAssistant.accessScope);
       logger.debug(
-        'VectorStoreFile pagePath to be created: ',
-        pagesToVectorize.map((page) => page.path),
+        { accessScope: aiAssistant.accessScope },
+        'AccessScopeType of aiAssistant',
+      );
+      logger.debug(
+        { pagePaths: pagesToVectorize.map((page) => page.path) },
+        'VectorStoreFile pagePath to be created',
       );
       logger.debug('-----------------------------------------------------');
 
@@ -1038,11 +1050,17 @@ class OpenaiService implements IOpenaiService {
       }
 
       logger.debug('---------- updateVectorStoreOnPageUpdate ------------');
-      logger.debug('AccessScopeType of aiAssistant: ', aiAssistant.accessScope);
-      logger.debug('PagePath of VectorStoreFile to be deleted: ', page.path);
       logger.debug(
-        'pagePath of VectorStoreFile to be created: ',
-        pagesToVectorize.map((page) => page.path),
+        { accessScope: aiAssistant.accessScope },
+        'AccessScopeType of aiAssistant',
+      );
+      logger.debug(
+        { pagePath: page.path },
+        'PagePath of VectorStoreFile to be deleted',
+      );
+      logger.debug(
+        { pagePaths: pagesToVectorize.map((page) => page.path) },
+        'pagePath of VectorStoreFile to be created',
       );
       logger.debug('-----------------------------------------------------');
 
@@ -1089,7 +1107,7 @@ class OpenaiService implements IOpenaiService {
       undefined,
       file.path,
     );
-    logger.debug('Uploaded file', uploadedFile);
+    logger.debug({ data: uploadedFile }, 'Uploaded file');
 
     for await (const aiAssistant of aiAssistants) {
       const pagesToVectorize = await this.filterPagesByAccessScope(
@@ -1152,8 +1170,8 @@ class OpenaiService implements IOpenaiService {
       ) {
         try {
           logger.debug(
-            'Target page path for VectorStoreFile generation: ',
-            chunk.map((page) => page.path),
+            { pagePaths: chunk.map((page) => page.path) },
+            'Target page path for VectorStoreFile generation',
           );
           await createVectorStoreFile(vectorStoreRelation, chunk);
           this.push(chunk);
@@ -1585,7 +1603,7 @@ class OpenaiService implements IOpenaiService {
       0,
     );
 
-    logger.debug('TotalPageCount: ', totalPageCount);
+    logger.debug({ totalPageCount }, 'TotalPageCount');
 
     const limitLearnablePageCountPerAssistant = configManager.getConfig(
       'openai:limitLearnablePageCountPerAssistant',

+ 5 - 4
apps/app/src/features/opentelemetry/server/custom-resource-attributes/application-resource-attributes.ts

@@ -33,13 +33,14 @@ export async function getApplicationResourceAttributes(): Promise<Attributes> {
         growiInfo.additionalInfo?.installedAtByOldestUser?.toISOString(),
     };
 
-    logger.info('Application resource attributes collected', { attributes });
+    logger.info({ attributes }, 'Application resource attributes collected');
 
     return attributes;
   } catch (error) {
-    logger.error('Failed to collect application resource attributes', {
-      error,
-    });
+    logger.error(
+      { err: error },
+      'Failed to collect application resource attributes',
+    );
     return {};
   }
 }

+ 1 - 1
apps/app/src/features/opentelemetry/server/custom-resource-attributes/os-resource-attributes.ts

@@ -28,7 +28,7 @@ export function getOsResourceAttributes(): Attributes {
     'os.totalmem': osInfo.totalmem,
   };
 
-  logger.info('OS resource attributes collected', { attributes });
+  logger.info({ attributes }, 'OS resource attributes collected');
 
   return attributes;
 }

+ 3 - 3
apps/app/src/pages/common-props/commons.ts

@@ -116,23 +116,23 @@ function isValidCommonEachRouteProps(
       p.nextjsRoutingPage !== undefined
     ) {
       logger.warn(
-        'isValidCommonEachRouteProps: nextjsRoutingPage is not a string or null',
         { nextjsRoutingPage: p.nextjsRoutingPage },
+        'isValidCommonEachRouteProps: nextjsRoutingPage is not a string or null',
       );
       return false;
     }
   }
   if (typeof p.currentPathname !== 'string') {
     logger.warn(
-      'isValidCommonEachRouteProps: currentPathname is not a string',
       { currentPathname: p.currentPathname },
+      'isValidCommonEachRouteProps: currentPathname is not a string',
     );
     return false;
   }
   if (typeof p.isMaintenanceMode !== 'boolean') {
     logger.warn(
-      'isValidCommonEachRouteProps: isMaintenanceMode is not a boolean',
       { isMaintenanceMode: p.isMaintenanceMode },
+      'isValidCommonEachRouteProps: isMaintenanceMode is not a boolean',
     );
     return false;
   }

+ 3 - 3
apps/app/src/pages/general-page/type-guards.ts

@@ -20,15 +20,15 @@ export function isValidGeneralPageInitialProps(
   // CommonPageInitialProps
   if (p.nextjsRoutingType === NextjsRoutingType.SAME_ROUTE) {
     logger.warn(
-      'isValidGeneralPageInitialProps: nextjsRoutingType must be equal to NextjsRoutingType.INITIAL or NextjsRoutingType.FROM_OUTSIDE',
       { nextjsRoutingType: p.nextjsRoutingType },
+      'isValidGeneralPageInitialProps: nextjsRoutingType must be equal to NextjsRoutingType.INITIAL or NextjsRoutingType.FROM_OUTSIDE',
     );
     return false;
   }
   if (typeof p.growiVersion !== 'string') {
     logger.warn(
-      'isValidGeneralPageInitialProps: growiVersion is not a string',
       { growiVersion: p.growiVersion },
+      'isValidGeneralPageInitialProps: growiVersion is not a string',
     );
     return false;
   }
@@ -37,8 +37,8 @@ export function isValidGeneralPageInitialProps(
   if (p.meta != null && typeof p.meta === 'object') {
     if (!isIPageInfo(p.meta)) {
       logger.warn(
-        'isValidGeneralPageInitialProps: meta is not a valid IPageInfo',
         { meta: p.meta },
+        'isValidGeneralPageInitialProps: meta is not a valid IPageInfo',
       );
       return false;
     }

+ 3 - 5
apps/app/src/server/app.ts

@@ -1,5 +1,3 @@
-import type Logger from 'bunyan';
-
 import {
   initInstrumentation,
   setupAdditionalResourceAttributes,
@@ -8,17 +6,17 @@ import {
 import loggerFactory from '~/utils/logger';
 import { hasProcessFlag } from '~/utils/process-utils';
 
-const logger: Logger = loggerFactory('growi');
+const logger = loggerFactory('growi');
 
 /** **********************************
  *          Main Process
  ********************************** */
 process.on('uncaughtException', (err?: Error) => {
-  logger.error('Uncaught Exception: ', err);
+  logger.error({ err }, 'Uncaught Exception');
 });
 
 process.on('unhandledRejection', (reason, p) => {
-  logger.error('Unhandled Rejection: Promise:', p, 'Reason:', reason);
+  logger.error({ reason, promise: p }, 'Unhandled Rejection');
 });
 
 async function main() {

+ 2 - 2
apps/app/src/server/events/user.ts

@@ -48,10 +48,10 @@ class UserEvent extends EventEmitter {
         const body = `# ${user.username}\nThis is ${user.username}'s page`;
 
         await this.crowi.pageService.create(userHomepagePath, body, user, {});
-        logger.debug('User page created', page);
+        logger.debug({ page }, 'User page created');
       }
     } catch (err) {
-      logger.error('Failed to create user page', err);
+      logger.error({ err }, 'Failed to create user page');
     }
   }
 }

+ 4 - 1
apps/app/src/server/middlewares/access-token-parser/api-token.ts

@@ -27,7 +27,10 @@ export const parserForApiToken = async (
     return;
   }
 
-  logger.debug('accessToken is', accessToken);
+  logger.debug(
+    { accessToken: `${accessToken.slice(0, 4)}...${accessToken.slice(-4)}` },
+    'accessToken is',
+  );
 
   const User = mongoose.model<HydratedDocument<IUser>, { findUserByApiToken }>(
     'User',

+ 3 - 3
apps/app/src/server/middlewares/apiv1-form-validator.ts

@@ -8,9 +8,9 @@ import ApiResponse from '../util/apiResponse';
 const logger = loggerFactory('growi:middlewares:ApiV1FormValidator');
 
 export default (req: Request, res: Response, next: NextFunction): void => {
-  logger.debug('req.query', req.query);
-  logger.debug('req.params', req.params);
-  logger.debug('req.body', req.body);
+  logger.debug({ query: req.query }, 'req.query');
+  logger.debug({ params: req.params }, 'req.params');
+  logger.debug({ body: req.body }, 'req.body');
 
   const errObjArray = validationResult(req);
   if (errObjArray.isEmpty()) {

+ 3 - 3
apps/app/src/server/middlewares/apiv3-form-validator.ts

@@ -11,9 +11,9 @@ export const apiV3FormValidator = (
   res: Response & { apiv3Err },
   next: NextFunction,
 ): void => {
-  logger.debug('req.query', req.query);
-  logger.debug('req.params', req.params);
-  logger.debug('req.body', req.body);
+  logger.debug({ query: req.query }, 'req.query');
+  logger.debug({ params: req.params }, 'req.params');
+  logger.debug({ body: req.body }, 'req.body');
 
   const errObjArray = validationResult(req);
   if (errObjArray.isEmpty()) {

+ 16 - 10
apps/app/src/server/middlewares/certify-shared-page-attachment/validate-referer/validate-referer.ts

@@ -38,16 +38,19 @@ export const validateReferer = (
     refererUrl.hostname !== siteUrl.hostname ||
     refererUrl.port !== siteUrl.port
   ) {
-    logger.warn('The hostname or port mismatched.', {
-      refererUrl: {
-        hostname: refererUrl.hostname,
-        port: refererUrl.port,
-      },
-      siteUrl: {
-        hostname: siteUrl.hostname,
-        port: siteUrl.port,
+    logger.warn(
+      {
+        refererUrl: {
+          hostname: refererUrl.hostname,
+          port: refererUrl.port,
+        },
+        siteUrl: {
+          hostname: siteUrl.hostname,
+          port: siteUrl.port,
+        },
       },
-    });
+      'The hostname or port mismatched.',
+    );
     return false;
   }
 
@@ -60,7 +63,10 @@ export const validateReferer = (
     return false;
   }
   if (match.groups?.shareLinkId == null) {
-    logger.warn(`The pathname ('${refererUrl.pathname}') is invalid.`, match);
+    logger.warn(
+      { match },
+      `The pathname ('${refererUrl.pathname}') is invalid.`,
+    );
     return false;
   }
 

+ 1 - 1
apps/app/src/server/middlewares/login-required.ts

@@ -49,7 +49,7 @@ const loginRequiredFactory = (
 
     // check the route config and ACL
     if (isGuestAllowed && crowi.aclService.isGuestAllowedToRead()) {
-      logger.debug('Allowed to read: ', req.path);
+      logger.debug({ path: req.path }, 'Allowed to read');
       return next();
     }
 

+ 6 - 3
apps/app/src/server/middlewares/safe-redirect.ts

@@ -61,17 +61,20 @@ const factory = (whitelistOfHosts: string[]) => {
         const isWhitelisted = isInWhitelist(whitelistOfHosts, redirectTo);
         if (isWhitelisted) {
           logger.debug(
+            { whitelist: whitelistOfHosts },
             `Requested redirect URL (${redirectTo}) is in whitelist.`,
-            `whitelist=${whitelistOfHosts}`,
           );
           return res.redirect(redirectTo);
         }
         logger.debug(
+          { whitelist: whitelistOfHosts },
           `Requested redirect URL (${redirectTo}) is NOT in whitelist.`,
-          `whitelist=${whitelistOfHosts}`,
         );
       } catch (err) {
-        logger.warn(`Requested redirect URL (${redirectTo}) is invalid.`, err);
+        logger.warn(
+          { err },
+          `Requested redirect URL (${redirectTo}) is invalid.`,
+        );
       }
 
       logger.warn(

+ 1 - 1
apps/app/src/server/models/activity.ts

@@ -100,7 +100,7 @@ activitySchema.index(
 activitySchema.plugin(mongoosePaginate);
 
 activitySchema.post('save', function () {
-  logger.debug('activity has been created', this);
+  logger.debug({ activity: this }, 'activity has been created');
 });
 
 activitySchema.statics.createByParameters = async function (

+ 1 - 1
apps/app/src/server/models/external-account.ts

@@ -80,7 +80,7 @@ schema.statics.findOrRegister = function (
   return this.findOne({ providerType, accountId }).then((account) => {
     // ExternalAccount is found
     if (account != null) {
-      logger.debug(`ExternalAccount '${accountId}' is found `, account);
+      logger.debug({ account }, `ExternalAccount '${accountId}' is found`);
       return account;
     }
 

+ 2 - 2
apps/app/src/server/models/user-group-relation.ts

@@ -104,7 +104,7 @@ schema.statics.findAllRelation = function () {
  * @memberof UserGroupRelation
  */
 schema.statics.findAllRelationForUserGroup = function (userGroup) {
-  logger.debug('findAllRelationForUserGroup is called', userGroup);
+  logger.debug({ userGroup }, 'findAllRelationForUserGroup is called');
   // biome-ignore lint/plugin: allow populate for backward compatibility
   return this.find({ relatedGroup: userGroup }).populate('relatedUser').exec();
 };
@@ -236,7 +236,7 @@ schema.statics.findUserByNotRelatedGroup = function (userGroup, queryOptions) {
       $or: searthField,
     };
 
-    logger.debug('findUserByNotRelatedGroup ', query);
+    logger.debug({ query }, 'findUserByNotRelatedGroup');
     return User.find(query).exec();
   });
 };

+ 2 - 2
apps/app/src/server/routes/apiv3/bookmark-folder.ts

@@ -188,7 +188,7 @@ module.exports = (crowi: Crowi) => {
 
       try {
         const bookmarkFolder = await BookmarkFolder.createByParameters(params);
-        logger.debug('bookmark folder created', bookmarkFolder);
+        logger.debug({ bookmarkFolder }, 'bookmark folder created');
         return res.apiv3({ bookmarkFolder });
       } catch (err) {
         logger.error(err);
@@ -467,7 +467,7 @@ module.exports = (crowi: Crowi) => {
             userId,
             folderId,
           );
-        logger.debug('bookmark added to folder', bookmarkFolder);
+        logger.debug({ bookmarkFolder }, 'bookmark added to folder');
         return res.apiv3({ bookmarkFolder });
       } catch (err) {
         logger.error(err);

+ 10 - 10
apps/app/src/server/routes/apiv3/g2g-transfer.ts

@@ -464,7 +464,7 @@ module.exports = (crowi: Crowi): Router => {
           fileName.length === 0 ||
           fileName.length > 256
         ) {
-          logger.warn('Invalid fileName in attachment metadata.', { fileName });
+          logger.warn({ fileName }, 'Invalid fileName in attachment metadata.');
           return res.apiv3Err(
             new ErrorV3(
               'Invalid fileName in attachment metadata.',
@@ -478,7 +478,7 @@ module.exports = (crowi: Crowi): Router => {
           !Number.isInteger(fileSize) ||
           fileSize < 0
         ) {
-          logger.warn('Invalid fileSize in attachment metadata.', { fileSize });
+          logger.warn({ fileSize }, 'Invalid fileSize in attachment metadata.');
           return res.apiv3Err(
             new ErrorV3(
               'Invalid fileSize in attachment metadata.',
@@ -489,10 +489,10 @@ module.exports = (crowi: Crowi): Router => {
         }
         const count = await Attachment.countDocuments({ fileName, fileSize });
         if (count === 0) {
-          logger.warn('Attachment not found in collection.', {
-            fileName,
-            fileSize,
-          });
+          logger.warn(
+            { fileName, fileSize },
+            'Attachment not found in collection.',
+          );
           return res.apiv3Err(
             new ErrorV3(
               'Attachment not found in collection.',
@@ -526,10 +526,10 @@ module.exports = (crowi: Crowi): Router => {
       // Normalize the path to prevent path traversal attacks
       const resolvedFilePath = path.resolve(file.path);
       if (!isPathWithinBase(resolvedFilePath, importService.baseDir)) {
-        logger.error('Path traversal attack detected', {
-          filePath: resolvedFilePath,
-          baseDir: importService.baseDir,
-        });
+        logger.error(
+          { filePath: resolvedFilePath, baseDir: importService.baseDir },
+          'Path traversal attack detected',
+        );
         return res.apiv3Err(
           new ErrorV3('Invalid file path.', 'invalid_path'),
           400,

+ 18 - 12
apps/app/src/server/routes/apiv3/page/update-page.ts

@@ -145,7 +145,7 @@ export const updatePageHandlersFactory = (crowi: Crowi): RequestHandler[] => {
         req.user,
       );
     } catch (err) {
-      logger.error('Edit notification failed', err);
+      logger.error({ err }, 'Edit notification failed');
     }
 
     // user notification
@@ -163,11 +163,14 @@ export const updatePageHandlersFactory = (crowi: Crowi): RequestHandler[] => {
         );
         for (const result of results) {
           if (result.status === 'rejected') {
-            logger.error('Create user notification failed', result.reason);
+            logger.error(
+              { err: result.reason },
+              'Create user notification failed',
+            );
           }
         }
       } catch (err) {
-        logger.error('Create user notification failed', err);
+        logger.error({ err }, 'Create user notification failed');
       }
     }
 
@@ -180,7 +183,7 @@ export const updatePageHandlersFactory = (crowi: Crowi): RequestHandler[] => {
         const openaiService = getOpenaiService();
         await openaiService?.updateVectorStoreFileOnPageUpdate(updatedPage);
       } catch (err) {
-        logger.error('Rebuild vector store failed', err);
+        logger.error({ err }, 'Rebuild vector store failed');
       }
     }
   }
@@ -305,11 +308,14 @@ export const updatePageHandlersFactory = (crowi: Crowi): RequestHandler[] => {
           try {
             previousRevision = await Revision.findById(sanitizeRevisionId);
           } catch (error) {
-            logger.error('Failed to fetch previousRevision by revisionId', {
-              revisionId: sanitizeRevisionId,
-              pageId: currentPage._id,
-              error,
-            });
+            logger.error(
+              {
+                revisionId: sanitizeRevisionId,
+                pageId: currentPage._id,
+                err: error,
+              },
+              'Failed to fetch previousRevision by revisionId',
+            );
           }
         }
 
@@ -319,12 +325,12 @@ export const updatePageHandlersFactory = (crowi: Crowi): RequestHandler[] => {
             previousRevision = await Revision.findById(currentPage.revision);
           } catch (error) {
             logger.error(
-              'Failed to fetch previousRevision by currentPage.revision',
               {
                 pageId: currentPage._id,
                 revisionId: currentPage.revision,
-                error,
+                err: error,
               },
+              'Failed to fetch previousRevision by currentPage.revision',
             );
           }
         }
@@ -339,7 +345,7 @@ export const updatePageHandlersFactory = (crowi: Crowi): RequestHandler[] => {
           options,
         );
       } catch (err) {
-        logger.error('Error occurred while updating a page.', err);
+        logger.error({ err }, 'Error occurred while updating a page.');
         return res.apiv3Err(err);
       }
 

+ 2 - 2
apps/app/src/server/service/config-manager/config-loader.ts

@@ -31,7 +31,7 @@ export class ConfigLoader implements IConfigLoader<ConfigKey, ConfigValues> {
       };
     }
 
-    logger.debug('loadFromEnv', envConfig);
+    logger.debug({ envConfig }, 'loadFromEnv');
 
     return envConfig;
   }
@@ -62,7 +62,7 @@ export class ConfigLoader implements IConfigLoader<ConfigKey, ConfigValues> {
       };
     }
 
-    logger.debug('loadFromDB', dbConfig);
+    logger.debug({ dbConfig }, 'loadFromDB');
     return dbConfig;
   }
 

+ 1 - 1
apps/app/src/server/service/external-account.ts

@@ -57,7 +57,7 @@ class ExternalAccountService {
           );
           return ExternalAccount.associate(providerId, userInfo.id, err.user);
         }
-        logger.error('provider-DuplicatedUsernameException', providerId);
+        logger.error({ providerId }, 'provider-DuplicatedUsernameException');
 
         throw new ErrorV3(
           'message.provider_duplicated_username_exception',

+ 1 - 1
apps/app/src/server/service/file-uploader/gridfs.ts

@@ -250,7 +250,7 @@ module.exports = (crowi: Crowi) => {
     try {
       // Add error handling to prevent resource leaks
       readable.on('error', (err) => {
-        logger.error('Readable stream error:', err);
+        logger.error({ err }, 'Readable stream error');
         readable.destroy();
         throw err;
       });

+ 38 - 26
apps/app/src/server/service/mail/mail.ts

@@ -82,8 +82,8 @@ class MailService implements S2sMessageHandlable {
         await s2sMessagingService.publish(s2sMessage);
       } catch (e) {
         logger.error(
-          'Failed to publish update message with S2sMessagingService: ',
-          e.message,
+          { err: e },
+          'Failed to publish update message with S2sMessagingService',
         );
       }
     }
@@ -161,14 +161,17 @@ class MailService implements S2sMessageHandlable {
     for (let attempt = 1; attempt <= maxRetries; attempt++) {
       try {
         const result = await this.mailer.sendMail(config);
-        logger.info('OAuth 2.0 email sent successfully', {
-          messageId: result.messageId,
-          from: config.from,
-          recipient: config.to,
-          attempt,
-          clientId: maskedClientId,
-          tag: 'oauth2_email_success',
-        });
+        logger.info(
+          {
+            messageId: result.messageId,
+            from: config.from,
+            recipient: config.to,
+            attempt,
+            clientId: maskedClientId,
+            tag: 'oauth2_email_success',
+          },
+          'OAuth 2.0 email sent successfully',
+        );
         return result;
       } catch (error: unknown) {
         const err = error as Error & { code?: string };
@@ -182,9 +185,8 @@ class MailService implements S2sMessageHandlable {
         }
 
         logger.error(
-          `OAuth 2.0 email send failed (attempt ${attempt}/${maxRetries})`,
           {
-            error: err.message,
+            err,
             code: err.code,
             user: config.from,
             recipient: config.to,
@@ -193,6 +195,7 @@ class MailService implements S2sMessageHandlable {
             timestamp: new Date().toISOString(),
             tag: monitoringTag,
           },
+          `OAuth 2.0 email send failed (attempt ${attempt}/${maxRetries})`,
         );
 
         if (attempt === maxRetries) {
@@ -232,17 +235,23 @@ class MailService implements S2sMessageHandlable {
 
       await FailedEmail.create(failedEmail);
 
-      logger.error('Failed email stored for manual review', {
-        recipient: config.to,
-        errorMessage: error.message,
-        errorCode: error.code,
-      });
+      logger.error(
+        {
+          recipient: config.to,
+          errorMessage: error.message,
+          errorCode: error.code,
+        },
+        'Failed email stored for manual review',
+      );
     } catch (err: unknown) {
       const storeError = err as Error;
-      logger.error('Failed to store failed email', {
-        error: storeError.message,
-        originalError: error.message,
-      });
+      logger.error(
+        {
+          err: storeError,
+          originalError: error.message,
+        },
+        'Failed to store failed email',
+      );
       throw new Error(`Failed to store failed email: ${storeError.message}`);
     }
   }
@@ -270,11 +279,14 @@ class MailService implements S2sMessageHandlable {
 
     // Use sendWithRetry for OAuth 2.0 to handle token refresh failures with exponential backoff
     if (transmissionMethod === 'oauth2') {
-      logger.debug('Sending email via OAuth2 with config:', {
-        from: mailConfig.from,
-        to: mailConfig.to,
-        subject: mailConfig.subject,
-      });
+      logger.debug(
+        {
+          from: mailConfig.from,
+          to: mailConfig.to,
+          subject: mailConfig.subject,
+        },
+        'Sending email via OAuth2 with config',
+      );
       return this.sendWithRetry(mailConfig as EmailConfig);
     }
 

+ 1 - 1
apps/app/src/server/service/mail/oauth2.ts

@@ -71,7 +71,7 @@ export function createOAuth2Client(
 
   const client = nodemailer.createTransport(option);
 
-  logger.debug('mailer set up for OAuth2', client);
+  logger.debug('mailer set up for OAuth2');
 
   return client;
 }

+ 1 - 1
apps/app/src/server/service/mail/ses.ts

@@ -39,7 +39,7 @@ export function createSESClient(
 
   const client = nodemailer.createTransport(ses(option));
 
-  logger.debug('mailer set up for SES', client);
+  logger.debug('mailer set up for SES');
 
   return client;
 }

+ 2 - 2
apps/app/src/server/service/mail/smtp.ts

@@ -23,7 +23,7 @@ export function createSMTPClient(
   configManager: IConfigManagerForApp,
   option?: SMTPTransport.Options,
 ): Transporter | null {
-  logger.debug('createSMTPClient option', option);
+  logger.debug('createSMTPClient called');
 
   let smtpOption: SMTPTransport.Options;
 
@@ -58,7 +58,7 @@ export function createSMTPClient(
 
   const client = nodemailer.createTransport(smtpOption);
 
-  logger.debug('mailer set up for SMTP', client);
+  logger.debug('mailer set up for SMTP');
 
   return client;
 }

+ 3 - 3
apps/app/src/server/service/page/events/seen.ts

@@ -24,13 +24,13 @@ export const onSeen = async (
     const page = await Page.findById(pageId);
 
     if (page == null) {
-      logger.warn('onSeen: page not found', { pageId });
+      logger.warn({ pageId }, 'onSeen: page not found');
       return;
     }
 
     await page.seen(user);
-    logger.debug('onSeen: successfully marked page as seen', { pageId });
+    logger.debug({ pageId }, 'onSeen: successfully marked page as seen');
   } catch (err) {
-    logger.error('onSeen: failed to mark page as seen', err);
+    logger.error({ err }, 'onSeen: failed to mark page as seen');
   }
 };

+ 6 - 9
apps/app/src/server/service/page/index.ts

@@ -2406,7 +2406,7 @@ class PageService implements IPageService {
     const ids = pages.map((page) => page._id);
     const paths = pages.map((page) => page.path);
 
-    logger.debug('Deleting completely', paths);
+    logger.debug({ paths }, 'Deleting completely');
 
     await this.deleteCompletelyOperation(ids, paths);
 
@@ -2461,7 +2461,7 @@ class PageService implements IPageService {
     const ids = [page._id];
     const paths = [page.path];
 
-    logger.debug('Deleting completely', paths);
+    logger.debug({ paths }, 'Deleting completely');
 
     const parameters = {
       ip: activityParameters.ip,
@@ -2598,7 +2598,7 @@ class PageService implements IPageService {
     const ids = [page._id];
     const paths = [page.path];
 
-    logger.debug('Deleting completely', paths);
+    logger.debug({ paths }, 'Deleting completely');
 
     await this.deleteCompletelyOperation(ids, paths);
 
@@ -3692,8 +3692,8 @@ class PageService implements IPageService {
         paths: nonNormalizablePagePaths,
       });
       logger.debug(
+        { paths: nonNormalizablePagePaths },
         'Some pages could not be converted.',
-        nonNormalizablePagePaths,
       );
     }
 
@@ -4219,8 +4219,8 @@ class PageService implements IPageService {
           // Throw if any error is found
           if (res.result.writeErrors.length > 0) {
             logger.error(
+              { writeErrors: res.result.writeErrors },
               'Failed to migrate some pages',
-              res.result.writeErrors,
             );
             socket?.emit(SocketEventName.PMEnded, { isSucceeded: false });
             throw Error('Failed to migrate some pages');
@@ -4230,11 +4230,8 @@ class PageService implements IPageService {
           if (res.result.nModified === 0 && res.result.nMatched === 0) {
             shouldContinue = false;
             logger.error(
+              { parentPaths, bulkWriteResult: res },
               'Migration is unable to continue',
-              'parentPaths:',
-              parentPaths,
-              'bulkWriteResult:',
-              res,
             );
             socket?.emit(SocketEventName.PMEnded, { isSucceeded: false });
           }

+ 7 - 8
apps/app/src/server/service/s2s-messaging/nchan.ts

@@ -67,7 +67,7 @@ class NchanDelegator extends AbstractS2sMessagingService {
 
     const url = this.constructUrl(this.publishPath).toString();
 
-    logger.debug('Publish message', s2sMessage, `to ${url}`);
+    logger.debug({ s2sMessage, url }, 'Publish message');
 
     return axios.post(url, s2sMessage);
   }
@@ -134,7 +134,7 @@ class NchanDelegator extends AbstractS2sMessagingService {
       logger.info('WebSocket client disconnected');
     });
     socket.addEventListener('error', (error) => {
-      logger.error('WebSocket error occured:', error.message);
+      logger.error({ err: error }, 'WebSocket error occured');
     });
 
     socket.addEventListener('open', () => {
@@ -163,8 +163,8 @@ class NchanDelegator extends AbstractS2sMessagingService {
       // check uid
       if (s2sMessage.publisherUid === this.uid) {
         logger.debug(
-          `Skip processing by ${handlable.constructor.name} because this message is sent by the publisher itself:`,
-          `from ${this.uid}`,
+          { publisherUid: this.uid },
+          `Skip processing by ${handlable.constructor.name} because this message is sent by the publisher itself`,
         );
         return;
       }
@@ -172,16 +172,15 @@ class NchanDelegator extends AbstractS2sMessagingService {
       // check shouldHandleS2sMessage
       const shouldHandle = handlable.shouldHandleS2sMessage(s2sMessage);
       logger.debug(
-        `${handlable.constructor.name}.shouldHandleS2sMessage(`,
-        s2sMessage,
-        `) => ${shouldHandle}`,
+        { s2sMessage, shouldHandle },
+        `${handlable.constructor.name}.shouldHandleS2sMessage`,
       );
 
       if (shouldHandle) {
         handlable.handleS2sMessage(s2sMessage);
       }
     } catch (err) {
-      logger.warn('Could not handle a message: ', err.message);
+      logger.warn({ err }, 'Could not handle a message');
     }
   }
 }

+ 4 - 4
apps/app/src/server/service/search-delegator/elasticsearch.ts

@@ -646,7 +646,7 @@ class ElasticsearchDelegator
       this.prepareBodyForDelete(body, page);
     });
 
-    logger.debug('deletePages(): Sending Request to ES', body);
+    logger.debug({ body }, 'deletePages(): Sending Request to ES');
     return this.client.bulk({
       body,
     });
@@ -664,7 +664,7 @@ class ElasticsearchDelegator
   ): Promise<ISearchResult<ISearchResultData>> {
     // for debug
     if (process.env.NODE_ENV === 'development') {
-      logger.debug('query: ', JSON.stringify(query, null, 2));
+      logger.debug({ query }, 'query');
 
       const validateQueryResponse = await (async () => {
         if (isES7ClientDelegator(this.client)) {
@@ -700,7 +700,7 @@ class ElasticsearchDelegator
       })();
 
       // for debug
-      logger.debug('ES result: ', validateQueryResponse);
+      logger.debug({ validateQueryResponse }, 'ES result');
     }
 
     const searchResponse = await (async () => {
@@ -1034,7 +1034,7 @@ class ElasticsearchDelegator
     const count = (await User.count({})) || 1;
 
     const minScore = queryString.length * 0.1 - 1; // increase with length
-    logger.debug('min_score: ', minScore);
+    logger.debug({ minScore }, 'min_score');
 
     query.body.query = {
       function_score: {

+ 4 - 4
apps/app/src/server/service/slack-integration.ts

@@ -250,8 +250,8 @@ export class SlackIntegrationService implements S2sMessageHandlable {
     try {
       await client.chat.postMessage(messageArgs);
     } catch (error) {
-      logger.debug('Post error', error);
-      logger.debug('Sent data to slack is:', messageArgs);
+      logger.debug({ err: error }, 'Post error');
+      logger.debug({ messageArgs }, 'Sent data to slack');
       throw error;
     }
   }
@@ -264,8 +264,8 @@ export class SlackIntegrationService implements S2sMessageHandlable {
     try {
       await slackLegacyUtil.postMessage(messageArgs);
     } catch (error) {
-      logger.debug('Post error', error);
-      logger.debug('Sent data to slack is:', messageArgs);
+      logger.debug({ err: error }, 'Post error');
+      logger.debug({ messageArgs }, 'Sent data to slack');
       throw error;
     }
   }

+ 3 - 3
apps/app/src/server/service/socket-io/socket-io.ts

@@ -178,7 +178,7 @@ export class SocketIoService {
       const clients = await this.getAdminSocket().fetchSockets();
       const clientsCount = clients.length;
 
-      logger.debug("Current count of clients for '/admin':", clientsCount);
+      logger.debug({ clientsCount }, "Current count of clients for '/admin'");
 
       const limit = configManager.getConfig(
         's2cMessagingPubsub:connectionsLimitForAdmin',
@@ -198,7 +198,7 @@ export class SocketIoService {
     if (socket.request.user == null) {
       const clientsCount = this.guestClients.size;
 
-      logger.debug('Current count of clients for guests:', clientsCount);
+      logger.debug({ clientsCount }, 'Current count of clients for guests');
 
       const limit = configManager.getConfig(
         's2cMessagingPubsub:connectionsLimitForGuest',
@@ -227,7 +227,7 @@ export class SocketIoService {
     const clients = await this.getDefaultSocket().fetchSockets();
     const clientsCount = clients.length;
 
-    logger.debug("Current count of clients for '/':", clientsCount);
+    logger.debug({ clientsCount }, "Current count of clients for '/'");
 
     const limit = configManager.getConfig(
       's2cMessagingPubsub:connectionsLimit',

+ 2 - 2
apps/app/src/server/service/yjs/create-mongodb-persistence.ts

@@ -33,7 +33,7 @@ export const createMongoDBPersistence = (
   const persistence: YWebsocketPersistence = {
     provider: mdb,
     bindState: async (docName: string, ydoc: WSSharedDoc) => {
-      logger.debug('bindState', { docName });
+      logger.debug({ docName }, 'bindState');
 
       const persistedYdoc = await mdb.getYDoc(docName);
 
@@ -93,7 +93,7 @@ export const createMongoDBPersistence = (
       });
     },
     writeState: async (docName: string) => {
-      logger.debug('writeState', { docName });
+      logger.debug({ docName }, 'writeState');
       // flush document on close to have the smallest possible database
       await mdb.flushDocument(docName);
     },

+ 9 - 6
apps/app/src/server/service/yjs/upgrade-handler.ts

@@ -89,7 +89,7 @@ export const createUpgradeHandler = (sessionConfig: SessionConfig) => {
   ): Promise<UpgradeResult> => {
     const pageId = extractPageId(request.url);
     if (pageId == null) {
-      logger.warn('Invalid URL path for Yjs upgrade', { url: request.url });
+      logger.warn({ url: request.url }, 'Invalid URL path for Yjs upgrade');
       writeErrorResponse(socket, 400, 'Bad Request');
       return { authorized: false, statusCode: 400 };
     }
@@ -100,7 +100,7 @@ export const createUpgradeHandler = (sessionConfig: SessionConfig) => {
       await runMiddleware(passportInit as ConnectMiddleware, request);
       await runMiddleware(passportSession as ConnectMiddleware, request);
     } catch (err) {
-      logger.warn('Session/passport middleware failed on upgrade', { err });
+      logger.warn({ err }, 'Session/passport middleware failed on upgrade');
       writeErrorResponse(socket, 401, 'Unauthorized');
       return { authorized: false, statusCode: 401 };
     }
@@ -114,10 +114,13 @@ export const createUpgradeHandler = (sessionConfig: SessionConfig) => {
     if (!isAccessible) {
       const statusCode = user == null ? 401 : 403;
       const message = user == null ? 'Unauthorized' : 'Forbidden';
-      logger.warn(`Yjs upgrade rejected: ${message}`, {
-        pageId,
-        userId: user?._id,
-      });
+      logger.warn(
+        {
+          pageId,
+          userId: user?._id,
+        },
+        `Yjs upgrade rejected: ${message}`,
+      );
       writeErrorResponse(socket, statusCode, message);
       return { authorized: false, statusCode };
     }

+ 2 - 2
apps/app/src/server/service/yjs/yjs.ts

@@ -102,7 +102,7 @@ class YjsService implements IYjsService {
       } catch (err) {
         guard.restore();
 
-        logger.error('Yjs upgrade handler failed unexpectedly', { url, err });
+        logger.error({ url, err }, 'Yjs upgrade handler failed unexpectedly');
         if (socket.writable) {
           socket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n');
         }
@@ -116,8 +116,8 @@ class YjsService implements IYjsService {
   public async getYDocStatus(pageId: string): Promise<YDocStatus> {
     const dumpLog = (status: YDocStatus, args?: { [key: string]: unknown }) => {
       logger.debug(
-        `getYDocStatus('${pageId}') detected '${status}'`,
         args ?? {},
+        `getYDocStatus('${pageId}') detected '${status}'`,
       );
     };
 

+ 4 - 4
apps/app/src/server/util/slack-legacy.ts

@@ -27,8 +27,8 @@ export const slackLegacyUtilFactory = (
     try {
       await webhook.send(messageObj);
     } catch (error) {
-      logger.debug('Post error', error);
-      logger.debug('Sent data to slack is:', messageObj);
+      logger.debug({ err: error }, 'Post error');
+      logger.debug({ messageObj }, 'Sent data to slack');
       throw error;
     }
   };
@@ -38,8 +38,8 @@ export const slackLegacyUtilFactory = (
     try {
       await client.chat.postMessage(messageObj);
     } catch (error) {
-      logger.debug('Post error', error);
-      logger.debug('Sent data to slack is:', messageObj);
+      logger.debug({ err: error }, 'Post error');
+      logger.debug({ messageObj }, 'Sent data to slack');
       throw error;
     }
   };

+ 3 - 3
apps/app/src/states/socket-io/global-socket.ts

@@ -42,16 +42,16 @@ export const useSetupGlobalSocket = (): void => {
 
       // Error handling
       newSocket.on('error', (err) => {
-        logger.error(err);
+        logger.error({ err }, 'Socket error');
       });
       newSocket.on('connect_error', (err) => {
-        logger.error('Failed to connect with websocket.', err);
+        logger.error({ err }, 'Failed to connect with websocket.');
       });
 
       // Store connection in atom
       setSocket(newSocket);
     } catch (error) {
-      logger.error('Failed to initialize WebSocket:', error);
+      logger.error({ err: error }, 'Failed to initialize WebSocket');
     }
   }, [setSocket]);