diff --git a/docs/technical/how-to-use-logger-en.md b/docs/technical/how-to-use-logger-en.md new file mode 100644 index 0000000000..5a6c9a4b17 --- /dev/null +++ b/docs/technical/how-to-use-logger-en.md @@ -0,0 +1,134 @@ +# How to use the LoggerService + +This is a developer document on how to use the logger. + +CherryStudio uses a unified logging service to print and record logs. **Unless there is a special reason, do not use `console.xxx` to print logs** + +The following are detailed instructions. + +## Usage in the `main` process + +### Importing + +```typescript +import { loggerService } from '@logger' +``` + +### Setting module information (Required by convention) + +After the import statements, set it up as follows: + +```typescript +const logger = loggerService.withContext('moduleName') +``` + +- `moduleName` is the name of the current file's module. It can be named after the filename, main class name, main function name, etc. The principle is to be clear and understandable. +- `moduleName` will be printed in the terminal and will also be present in the file log, making it easier to filter. + +### Setting `CONTEXT` information (Optional) + +In `withContext`, you can also set other `CONTEXT` information: + +```typescript +const logger = loggerService.withContext('moduleName', CONTEXT) +``` + +- `CONTEXT` is an object of the form `{ key: value, ... }`. +- `CONTEXT` information will not be printed in the terminal, but it will be recorded in the file log, making it easier to filter. + +### Logging + +In your code, you can call `logger` at any time to record logs. The supported methods are: `error`, `warn`, `info`, `verbose`, `debug`, `silly`. +For the meaning of each level, please refer to the section below. + +The following examples show how to use `logger.info` and `logger.error`. Other levels are used in the same way: +```typescript +logger.info('message', CONTEXT) +logger.info('message %s %d', 'hello', 123, CONTEXT) +logger.error('message', new Error('error message'), CONTEXT) +``` +- `message` is a required string. All other options are optional. +- `CONTEXT` as `{ key: value, ... }` is optional and will be recorded in the log file. +- If an `Error` type is passed, the error stack will be automatically recorded. + +### Log Levels + +- In the development environment, all log levels are printed to the terminal and recorded in the file log. +- In the production environment, the default log level is `info`. Logs are only recorded to the file and are not printed to the terminal. + +Changing the log level: +- You can change the log level with `logger.setLevel('newLevel')`. +- `logger.resetLevel()` resets it to the default level. +- `logger.getLevel()` gets the current log level. + +**Note:** Changing the log level has a global effect. Please do not change it arbitrarily in your code unless you are very clear about what you are doing. + +## Usage in the `renderer` process + +Usage in the `renderer` process for *importing*, *setting module information*, and *setting context information* is **exactly the same** as in the `main` process. +The following section focuses on the differences. + +### `initWindowSource` + +In the `renderer` process, there are different `window`s. Before starting to use the `logger`, we must set the `window` information: + +```typescript +loggerService.initWindowSource('windowName') +``` + +As a rule, we will set this in the `window`'s `entryPoint.tsx`. This ensures that `windowName` is set before it's used. +- An error will be thrown if `windowName` is not set, and the `logger` will not work. +- `windowName` can only be set once; subsequent attempts to set it will have no effect. +- `windowName` will not be printed in the `devTool`'s `console`, but it will be recorded in the `main` process terminal and the file log. + +### Log Levels + +- In the development environment, all log levels are printed to the `devTool`'s `console` by default. +- In the production environment, the default log level is `info`, and logs are printed to the `devTool`'s `console`. +- In both development and production environments, `warn` and `error` level logs are, by default, transmitted to the `main` process and recorded in the file log. + - In the development environment, the `main` process terminal will also print the logs transmitted from the renderer. + +#### Changing the Log Level + +Same as in the `main` process, you can manage the log level using `setLevel('level')`, `resetLevel()`, and `getLevel()`. +Similarly, changing the log level is a global adjustment. + +#### Changing the Level Transmitted to `main` + +Logs from the `renderer` are sent to `main` to be managed and recorded to a file centrally (according to `main`'s file logging level). By default, only `warn` and `error` level logs are transmitted to `main`. + +There are two ways to change the log level for transmission to `main`: + +##### Global Change + +The following methods can be used to set, reset, and get the log level for transmission to `main`, respectively. + +```typescript +logger.setLogToMainLevel('newLevel') +logger.resetLogToMainLevel() +logger.getLogToMainLevel() +``` +**Note:** This method has a global effect. Please do not change it arbitrarily in your code unless you are very clear about what you are doing. + + +##### Per-log Change + +By adding `{ logToMain: true }` at the end of the log call, you can force a single log entry to be transmitted to `main` (bypassing the global log level restriction), for example: + +```typescript +logger.info('message', { logToMain: true }) +``` + +## Log Level Usage Guidelines + +There are many log levels. The following are the guidelines that should be followed in CherryStudio for when to use each level: +(Arranged from highest to lowest log level) + +| Log Level | Core Definition & Use Case | Example | +| :------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`error`** | **Critical error causing the program to crash or core functionality to become unusable.**
This is the highest-priority log, usually requiring immediate reporting or user notification. | - Main or renderer process crash.
- Failure to read/write critical user data files (e.g., database, configuration files), preventing the application from running.
- All unhandled exceptions. | +| **`warn`** | **Potential issue or unexpected situation that does not affect the program's core functionality.**
The program can recover or use a fallback. | - Configuration file `settings.json` is missing; started with default settings.
- Auto-update check failed, but does not affect the use of the current version.
- A non-essential plugin failed to load. | +| **`info`** | **Records application lifecycle events and key user actions.**
This is the default level that should be recorded in a production release to trace the user's main operational path. | - Application start, exit.
- User successfully opens/saves a file.
- Main window created/closed.
- Starting an important task (e.g., "Start video export"). | +| **`verbose`** | **More detailed flow information than `info`, used for tracing specific features.**
Enabled when diagnosing issues with a specific feature to help understand the internal execution flow. | - Loading `Toolbar` module.
- IPC message `open-file-dialog` sent from the renderer process.
- Applying filter 'Sepia' to the image. | +| **`debug`** | **Detailed diagnostic information used during development and debugging.**
**Must not be enabled by default in production releases**, as it may contain sensitive data and impact performance. | - Parameters for function `renderImage`: `{ width: 800, ... }`.
- Specific data content received by IPC message `save-file`.
- Details of Redux/Vuex state changes in the renderer process. | +| **`silly`** | **The most detailed, low-level information, used only for extreme debugging.**
Rarely used in regular development; only for solving very difficult problems. | - Real-time mouse coordinates `(x: 150, y: 320)`.
- Size of each data chunk when reading a file.
- Time taken for each rendered frame. | \ No newline at end of file diff --git a/docs/technical/how-to-use-logger-zh.md b/docs/technical/how-to-use-logger-zh.md new file mode 100644 index 0000000000..6a58260bef --- /dev/null +++ b/docs/technical/how-to-use-logger-zh.md @@ -0,0 +1,135 @@ +# 如何使用日志 LoggerService + +这是关于如何使用日志的开发者文档。 + +CherryStudio使用统一的日志服务来打印和记录日志,**若无特殊原因,请勿使用`console.xxx`来打印日志** + +以下是详细说明 + + +## 在`main`进程中使用 + +### 引入 + +``` typescript +import { loggerService } from '@logger' +``` + +### 设置module信息(规范要求) + +在import头之后,设置: + +``` typescript +const logger = loggerService.withContext('moduleName') +``` + +- `moduleName`是当前文件模块的名称,命名可以以文件名、主类名、主函数名等,原则是清晰明了 +- `moduleName`会在终端中打印出来,也会在文件日志中提现,方便筛选 + +### 设置`CONTEXT`信息(可选) + +在`withContext`中,也可以设置其他`CONTEXT`信息: + +``` typescript +const logger = loggerService.withContext('moduleName', CONTEXT) +``` + +- `CONTEXT`为`{ key: value, ... }` +- `CONTEXT`信息不会在终端中打印出来,但是会在文件日志中记录,方便筛选 + +### 记录日志 + +在代码中,可以随时调用 `logger` 来记录日志,支持的方法有:`error`, `warn`, `info`, `verbose`, `debug`, `silly` +各级别的含义,请参考下面的章节。 + +以下以 `logger.info` 和 `logger.error` 举例如何使用,其他级别是一样的: +``` typescript +logger.info('message', CONTEXT) +logger.info('message %s %d', 'hello', 123, CONTEXT) +logger.error('message', new Error('error message'), CONTEXT) +``` +- `message` 是必填的,`string`类型,其他选项都是可选的 +- `CONTEXT`为`{ key: value, ...}` 是可选的,会在日志文件中记录 +- 如果传递了`Error`类型,会自动记录错误堆栈 + +### 记录级别 + +- 开发环境下,所有级别的日志都会打印到终端,并且记录到文件日志中 +- 生产环境下,默认记录级别为`info`,日志只会记录到文件,不会打印到终端 + +更改日志记录级别: +- 可以通过 `logger.setLevel('newLevel')` 来更改日志记录级别 +- `logger.resetLevel()` 可以重置为默认级别 +- `logger.getLevel()` 可以获取当前记录记录级别 + +**注意** 更改日志记录级别是全局生效的,请不要在代码中随意更改,除非你非常清楚自己在做什么 + +## 在`renderer`进程中使用 + +在`renderer`进程中使用,*引入方法*、*设置`module`信息*、*设置`context`信息的方法*和`main`进程中是**完全一样**的。 +下面着重讲一下不同之处。 + +### `initWindowSource` + +`renderer`进程中,有不同的`window`,在开始使用`logger`之前,我们必须设置`window`信息: + +```typescript +loggerService.initWindowSource('windowName') +``` + +原则上,我们将在`window`的`entryPoint.tsx`中进行设置,这可以保证`windowName`在开始使用前已经设置好了。 +- 未设置`windowName`会报错,`logger`将不起作用 +- `windowName`只能设置一次,重复设置将不生效 +- `windowName`不会在`devTool`的`console`中打印出来,但是会在`main`进程的终端和文件日志中记录 + +### 记录级别 + +- 开发环境下,默认所有级别的日志都会打印到`devTool`的`console` +- 生产环境下,默认记录级别为`info`,日志会打印到`devTool`的`console` +- 在开发和生产环境下,默认`warn`和`error`级别的日志,会传输给`main`进程,并记录到文件日志 + - 开发环境下,`main`进程终端中也会打印传输过来的日志 + +#### 更改日志记录级别 + +和`main`进程中一样,你可以通过`setLevel('level')`、`resetLevel()`和`getLevel()`来管理日志记录级别。 +同样,该日志记录级别也是全局调整的。 + +#### 更改传输到`main`的级别 + +将`renderer`的日志发送到`main`,并由`main`统一管理和记录到文件(根据`main`的记录到文件的级别),默认只有`warn`和`error`级别的日志会传输到`main` + +有以下两种方式,可以更改传输到`main`的日志级别: + +##### 全局更改 + +以下方法可以分别设置、重置和获取传输到`main`的日志级别 + +```typescript +logger.setLogToMainLevel('newLevel') +logger.resetLogToMainLevel() +logger.getLogToMainLevel() +``` +**注意** 该方法是全局生效的,请不要在代码中随意更改,除非你非常清楚自己在做什么 + + +##### 单条更改 + +在日志记录的最末尾,加上`{ logToMain: true }`,即可将本条日志传输到`main`(不受全局日志级别限制),例如: + +```typescript +logger.info('message', { logToMain: true }) +``` + +## 日志级别的使用规范 + +日志有很多级别,什么时候应该用哪个级别,下面是在CherryStudio中应该遵循的规范: +(按日志级别从高到低排列) + +| 日志级别 | 核心定义与使用场景 | 示例 | +| :------------ | :------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`error`** | **严重错误,导致程序崩溃或核心功能无法使用。**
这是最高优的日志,通常需要立即上报或提示用户。 | - 主进程或渲染进程崩溃。
- 无法读写用户关键数据文件(如数据库、配置文件),导致应用无法运行。
- 所有未捕获的异常。` | +| **`warn`** | **潜在问题或非预期情况,但不影响程序核心功能。**
程序可以从中恢复或使用备用方案。 | - 配置文件 `settings.json` 缺失,已使用默认配置启动。
- 自动更新检查失败,但不影响当前版本使用。
- 某个非核心插件加载失败。` | +| **`info`** | **记录应用生命周期和关键用户行为。**
这是发布版中默认应记录的级别,用于追踪用户的主要操作路径。 | - 应用启动、退出。
- 用户成功打开/保存文件。
- 主窗口创建/关闭。
- 开始执行一项重要任务(如“开始导出视频”)。` | +| **`verbose`** | **比 `info` 更详细的流程信息,用于追踪特定功能。**
在诊断特定功能问题时开启,帮助理解内部执行流程。 | - 正在加载 `Toolbar` 模块。
- IPC 消息 `open-file-dialog` 已从渲染进程发送。
- 正在应用滤镜 'Sepia' 到图像。` | +| **`debug`** | **开发和调试时使用的详细诊断信息。**
**严禁在发布版中默认开启**,因为它可能包含敏感数据并影响性能。 | - 函数 `renderImage` 的入参: `{ width: 800, ... }`。
- IPC 消息 `save-file` 收到的具体数据内容。
- 渲染进程中 Redux/Vuex 的 state 变更详情。` | +| **`silly`** | **最详尽的底层信息,仅用于极限调试。**
几乎不在常规开发中使用,仅为解决棘手问题。 | - 鼠标移动的实时坐标 `(x: 150, y: 320)`。
- 读取文件时每个数据块(chunk)的大小。
- 每一次渲染帧的耗时。 | \ No newline at end of file diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 7930b57f35..7c3af82973 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -18,7 +18,8 @@ export default defineConfig({ alias: { '@main': resolve('src/main'), '@types': resolve('src/renderer/src/types'), - '@shared': resolve('packages/shared') + '@shared': resolve('packages/shared'), + '@logger': resolve('src/main/services/LoggerService') } }, build: { @@ -68,7 +69,8 @@ export default defineConfig({ resolve: { alias: { '@renderer': resolve('src/renderer/src'), - '@shared': resolve('packages/shared') + '@shared': resolve('packages/shared'), + '@logger': resolve('src/renderer/src/services/LoggerService') } }, optimizeDeps: { diff --git a/package.json b/package.json index e7f6770477..9f74ab288c 100644 --- a/package.json +++ b/package.json @@ -163,7 +163,6 @@ "electron": "35.6.0", "electron-builder": "26.0.15", "electron-devtools-installer": "^3.2.0", - "electron-log": "^5.1.5", "electron-store": "^8.2.0", "electron-updater": "6.6.4", "electron-vite": "^3.1.0", @@ -236,6 +235,8 @@ "vite": "6.2.6", "vitest": "^3.1.4", "webdav": "^5.8.0", + "winston": "^3.17.0", + "winston-daily-rotate-file": "^5.0.0", "word-extractor": "^1.0.4", "zipread": "^1.3.3" }, diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 057e84ca76..4c2e823f31 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -31,6 +31,7 @@ export enum IpcChannel { App_GetBinaryPath = 'app:get-binary-path', App_InstallUvBinary = 'app:install-uv-binary', App_InstallBunBinary = 'app:install-bun-binary', + App_LogToMain = 'app:log-to-main', App_MacIsProcessTrusted = 'app:mac-is-process-trusted', App_MacRequestProcessTrust = 'app:mac-request-process-trust', diff --git a/packages/shared/config/types.ts b/packages/shared/config/types.ts index 28bb4acf65..cdb5d0ecab 100644 --- a/packages/shared/config/types.ts +++ b/packages/shared/config/types.ts @@ -9,3 +9,12 @@ export type LoaderReturn = { message?: string messageSource?: 'preprocess' | 'embedding' } + +export type LogSourceWithContext = { + process: 'main' | 'renderer' + window?: string // only for renderer process + module?: string + context?: Record +} + +export type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'verbose' | 'silly' diff --git a/src/main/index.ts b/src/main/index.ts index 97bd10bcf7..e1e05dbf30 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -5,13 +5,13 @@ import './bootstrap' import '@main/config' +import { loggerService } from '@logger' import { electronApp, optimizer } from '@electron-toolkit/utils' import { replaceDevtoolsFont } from '@main/utils/windowUtil' import { app } from 'electron' import installExtension, { REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } from 'electron-devtools-installer' -import Logger from 'electron-log' -import { isDev, isWin, isLinux } from './constant' +import { isDev, isLinux, isWin } from './constant' import { registerIpc } from './ipc' import { configManager } from './services/ConfigManager' import mcpService from './services/MCPService' @@ -26,7 +26,7 @@ import { registerShortcuts } from './services/ShortcutService' import { TrayService } from './services/TrayService' import { windowService } from './services/WindowService' -Logger.initialize() +const logger = loggerService.withContext('MainEntry') /** * Disable hardware acceleration if setting is enabled @@ -68,9 +68,9 @@ app.on('web-contents-created', (_, webContents) => { webContents.on('unresponsive', async () => { // Interrupt execution and collect call stack from unresponsive renderer - Logger.error('Renderer unresponsive start') + logger.error('Renderer unresponsive start') const callStack = await webContents.mainFrame.collectJavaScriptCallStack() - Logger.error('Renderer unresponsive js call stack\n', callStack) + logger.error('Renderer unresponsive js call stack\n', callStack) }) }) @@ -78,12 +78,12 @@ app.on('web-contents-created', (_, webContents) => { if (!isDev) { // handle uncaught exception process.on('uncaughtException', (error) => { - Logger.error('Uncaught Exception:', error) + logger.error('Uncaught Exception:', error) }) // handle unhandled rejection process.on('unhandledRejection', (reason, promise) => { - Logger.error('Unhandled Rejection at:', promise, 'reason:', reason) + logger.error('Unhandled Rejection at:', promise, 'reason:', reason) }) } @@ -181,8 +181,11 @@ if (!app.requestSingleInstanceLock()) { try { await mcpService.cleanup() } catch (error) { - Logger.error('Error cleaning up MCP service:', error) + logger.error('Error cleaning up MCP service:', error) } + + // finish the logger + logger.finish() }) // In this file you can include the rest of your app"s specific main process diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 37abf38dff..72ffa81a38 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -2,6 +2,7 @@ import fs from 'node:fs' import { arch } from 'node:os' import path from 'node:path' +import { loggerService } from '@logger' import { isLinux, isMac, isWin } from '@main/constant' import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process' import { handleZoomFactor } from '@main/utils/zoom' @@ -9,7 +10,6 @@ import { UpgradeChannel } from '@shared/config/constant' import { IpcChannel } from '@shared/IpcChannel' import { FileMetadata, Provider, Shortcut, ThemeMode } from '@types' import { BrowserWindow, dialog, ipcMain, ProxyConfig, session, shell, systemPreferences, webContents } from 'electron' -import log from 'electron-log' import { Notification } from 'src/renderer/src/types/notification' import appService from './services/AppService' @@ -43,6 +43,8 @@ import { decrypt, encrypt } from './utils/aes' import { getCacheDir, getConfigDir, getFilesDir, hasWritePermission, updateAppDataConfig } from './utils/file' import { compress, decompress } from './utils/zip' +const logger = loggerService.withContext('IPC') + const fileManager = new FileStorage() const backupManager = new BackupManager() const exportService = new ExportService(fileManager) @@ -66,7 +68,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { configPath: getConfigDir(), appDataPath: app.getPath('userData'), resourcesPath: getResourcePath(), - logsPath: log.transports.file.getFile().path, + logsPath: logger.getLogsDir(), arch: arch(), isPortable: isWin && 'PORTABLE_EXECUTABLE_DIR' in process.env, installPath: path.dirname(app.getPath('exe')) @@ -145,7 +147,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { }) ipcMain.handle(IpcChannel.App_SetTestPlan, async (_, isActive: boolean) => { - log.info('set test plan', isActive) + logger.info('set test plan', isActive) if (isActive !== configManager.getTestPlan()) { appUpdater.cancelDownload() configManager.setTestPlan(isActive) @@ -153,7 +155,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { }) ipcMain.handle(IpcChannel.App_SetTestChannel, async (_, channel: UpgradeChannel) => { - log.info('set test channel', channel) + logger.info('set test channel', channel) if (channel !== configManager.getTestChannel()) { appUpdater.cancelDownload() configManager.setTestChannel(channel) @@ -205,10 +207,12 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { }) ) await fileManager.clearTemp() - await fs.writeFileSync(log.transports.file.getFile().path, '') + // do not clear logs for now + // TODO clear logs + // await fs.writeFileSync(log.transports.file.getFile().path, '') return { success: true } } catch (error: any) { - log.error('Failed to clear cache:', error) + logger.error('Failed to clear cache:', error) return { success: false, error: error.message } } }) @@ -216,14 +220,14 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { // get cache size ipcMain.handle(IpcChannel.App_GetCacheSize, async () => { const cachePath = getCacheDir() - log.info(`Calculating cache size for path: ${cachePath}`) + logger.info(`Calculating cache size for path: ${cachePath}`) try { const sizeInBytes = await calculateDirectorySize(cachePath) const sizeInMB = (sizeInBytes / (1024 * 1024)).toFixed(2) return `${sizeInMB}` } catch (error: any) { - log.error(`Failed to calculate cache size for ${cachePath}: ${error.message}`) + logger.error(`Failed to calculate cache size for ${cachePath}: ${error.message}`) return '0' } }) @@ -260,7 +264,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { } return filePaths[0] } catch (error: any) { - log.error('Failed to select app data path:', error) + logger.error('Failed to select app data path:', error) return null } }) @@ -313,7 +317,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { }) return { success: true } } catch (error: any) { - log.error('Failed to copy user data:', error) + logger.error('Failed to copy user data:', error) return { success: false, error: error.message } } }) @@ -322,7 +326,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ipcMain.handle(IpcChannel.App_RelaunchApp, (_, options?: Electron.RelaunchOptions) => { // Fix for .AppImage if (isLinux && process.env.APPIMAGE) { - log.info('Relaunching app with options:', process.env.APPIMAGE, options) + logger.info('Relaunching app with options:', process.env.APPIMAGE, options) // On Linux, we need to use the APPIMAGE environment variable to relaunch // https://github.com/electron-userland/electron-builder/issues/1727#issuecomment-769896927 options = options || {} @@ -554,7 +558,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { // Process DXT file using the temporary path return await dxtService.uploadDxt(event, tempPath) } catch (error) { - log.error('[IPC] DXT upload error:', error) + logger.error('DXT upload error:', error) return { success: false, error: error instanceof Error ? error.message : 'Failed to upload DXT file' diff --git a/src/main/knowledage/ocr/MacSysOcrProvider.ts b/src/main/knowledage/ocr/MacSysOcrProvider.ts index df281eb60b..92e95cf716 100644 --- a/src/main/knowledage/ocr/MacSysOcrProvider.ts +++ b/src/main/knowledage/ocr/MacSysOcrProvider.ts @@ -1,12 +1,14 @@ +import { loggerService } from '@logger' import { isMac } from '@main/constant' import { FileMetadata, OcrProvider } from '@types' -import Logger from 'electron-log' import * as fs from 'fs' import * as path from 'path' import { TextItem } from 'pdfjs-dist/types/src/display/api' import BaseOcrProvider from './BaseOcrProvider' +const logger = loggerService.withContext('MacSysOcrProvider') + export default class MacSysOcrProvider extends BaseOcrProvider { private readonly MIN_TEXT_LENGTH = 1000 private MacOCR: any @@ -21,7 +23,7 @@ export default class MacSysOcrProvider extends BaseOcrProvider { const module = await import('@cherrystudio/mac-system-ocr') this.MacOCR = module.default } catch (error) { - Logger.error('[OCR] Failed to load mac-system-ocr:', error) + logger.error('Failed to load mac-system-ocr:', error) throw error } } @@ -83,7 +85,7 @@ export default class MacSysOcrProvider extends BaseOcrProvider { } public async parseFile(sourceId: string, file: FileMetadata): Promise<{ processedFile: FileMetadata }> { - Logger.info(`[OCR] Starting OCR process for file: ${file.name}`) + logger.info(`Starting OCR process for file: ${file.name}`) if (file.ext === '.pdf') { try { const { pdf } = await import('@cherrystudio/pdf-to-img-napi') @@ -103,7 +105,7 @@ export default class MacSysOcrProvider extends BaseOcrProvider { await new Promise((resolve, reject) => { writeStream.end(() => { - Logger.info(`[OCR] OCR process completed successfully for ${file.origin_name}`) + logger.info(`OCR process completed successfully for ${file.origin_name}`) resolve() }) writeStream.on('error', reject) @@ -119,7 +121,7 @@ export default class MacSysOcrProvider extends BaseOcrProvider { } } } catch (error) { - Logger.error('[OCR] Error during OCR process:', error) + logger.error('Error during OCR process:', error) throw error } } diff --git a/src/main/knowledage/ocr/OcrProviderFactory.ts b/src/main/knowledage/ocr/OcrProviderFactory.ts index 96d95a63ad..34b8fe6f1d 100644 --- a/src/main/knowledage/ocr/OcrProviderFactory.ts +++ b/src/main/knowledage/ocr/OcrProviderFactory.ts @@ -1,16 +1,19 @@ +import { loggerService } from '@logger' import { isMac } from '@main/constant' import { OcrProvider } from '@types' -import Logger from 'electron-log' import BaseOcrProvider from './BaseOcrProvider' import DefaultOcrProvider from './DefaultOcrProvider' import MacSysOcrProvider from './MacSysOcrProvider' + +const logger = loggerService.withContext('OcrProviderFactory') + export default class OcrProviderFactory { static create(provider: OcrProvider): BaseOcrProvider { switch (provider.id) { case 'system': if (!isMac) { - Logger.warn('[OCR] System OCR provider is only available on macOS') + logger.warn('System OCR provider is only available on macOS') } return new MacSysOcrProvider(provider) default: diff --git a/src/main/knowledage/preprocess/Doc2xPreprocessProvider.ts b/src/main/knowledage/preprocess/Doc2xPreprocessProvider.ts index 5851d33b86..f34b518c22 100644 --- a/src/main/knowledage/preprocess/Doc2xPreprocessProvider.ts +++ b/src/main/knowledage/preprocess/Doc2xPreprocessProvider.ts @@ -1,13 +1,15 @@ import fs from 'node:fs' import path from 'node:path' +import { loggerService } from '@logger' import { FileMetadata, PreprocessProvider } from '@types' import AdmZip from 'adm-zip' import axios, { AxiosRequestConfig } from 'axios' -import Logger from 'electron-log' import BasePreprocessProvider from './BasePreprocessProvider' +const logger = loggerService.withContext('Doc2xPreprocessProvider') + type ApiResponse = { code: string data: T @@ -52,11 +54,11 @@ export default class Doc2xPreprocessProvider extends BasePreprocessProvider { public async parseFile(sourceId: string, file: FileMetadata): Promise<{ processedFile: FileMetadata }> { try { - Logger.info(`Preprocess processing started: ${file.path}`) + logger.info(`Preprocess processing started: ${file.path}`) // 步骤1: 准备上传 const { uid, url } = await this.preupload() - Logger.info(`Preprocess preupload completed: uid=${uid}`) + logger.info(`Preprocess preupload completed: uid=${uid}`) await this.validateFile(file.path) @@ -65,7 +67,7 @@ export default class Doc2xPreprocessProvider extends BasePreprocessProvider { // 步骤3: 等待处理完成 await this.waitForProcessing(sourceId, uid) - Logger.info(`Preprocess parsing completed successfully for: ${file.path}`) + logger.info(`Preprocess parsing completed successfully for: ${file.path}`) // 步骤4: 导出文件 const { path: outputPath } = await this.exportFile(file, uid) @@ -75,7 +77,7 @@ export default class Doc2xPreprocessProvider extends BasePreprocessProvider { processedFile: this.createProcessedFileInfo(file, outputPath) } } catch (error) { - Logger.error( + logger.error( `Preprocess processing failed for ${file.path}: ${error instanceof Error ? error.message : String(error)}` ) throw error @@ -100,11 +102,11 @@ export default class Doc2xPreprocessProvider extends BasePreprocessProvider { * @returns 导出文件的路径 */ public async exportFile(file: FileMetadata, uid: string): Promise<{ path: string }> { - Logger.info(`Exporting file: ${file.path}`) + logger.info(`Exporting file: ${file.path}`) // 步骤1: 转换文件 await this.convertFile(uid, file.path) - Logger.info(`File conversion completed for: ${file.path}`) + logger.info(`File conversion completed for: ${file.path}`) // 步骤2: 等待导出并获取URL const exportUrl = await this.waitForExport(uid) @@ -123,7 +125,7 @@ export default class Doc2xPreprocessProvider extends BasePreprocessProvider { await this.delay(1000) const { status, progress } = await this.getStatus(uid) await this.sendPreprocessProgress(sourceId, progress) - Logger.info(`Preprocess processing status: ${status}, progress: ${progress}%`) + logger.info(`Preprocess processing status: ${status}, progress: ${progress}%`) if (status === 'success') { return @@ -142,7 +144,7 @@ export default class Doc2xPreprocessProvider extends BasePreprocessProvider { while (true) { await this.delay(1000) const { status, url } = await this.getParsedFile(uid) - Logger.info(`Export status: ${status}`) + logger.info(`Export status: ${status}`) if (status === 'success' && url) { return url @@ -169,7 +171,7 @@ export default class Doc2xPreprocessProvider extends BasePreprocessProvider { throw new Error(`API returned error: ${data.message || JSON.stringify(data)}`) } } catch (error) { - Logger.error(`Failed to get preupload URL: ${error instanceof Error ? error.message : String(error)}`) + logger.error(`Failed to get preupload URL: ${error instanceof Error ? error.message : String(error)}`) throw new Error('Failed to get preupload URL') } } @@ -188,7 +190,7 @@ export default class Doc2xPreprocessProvider extends BasePreprocessProvider { throw new Error(`HTTP status ${response.status}: ${response.statusText}`) } } catch (error) { - Logger.error(`Failed to upload file ${filePath}: ${error instanceof Error ? error.message : String(error)}`) + logger.error(`Failed to upload file ${filePath}: ${error instanceof Error ? error.message : String(error)}`) throw new Error('Failed to upload file') } } @@ -206,7 +208,7 @@ export default class Doc2xPreprocessProvider extends BasePreprocessProvider { throw new Error(`API returned error: ${response.data.message || JSON.stringify(response.data)}`) } } catch (error) { - Logger.error(`Failed to get status for uid ${uid}: ${error instanceof Error ? error.message : String(error)}`) + logger.error(`Failed to get status for uid ${uid}: ${error instanceof Error ? error.message : String(error)}`) throw new Error('Failed to get processing status') } } @@ -242,7 +244,7 @@ export default class Doc2xPreprocessProvider extends BasePreprocessProvider { throw new Error(`API returned error: ${response.data.message || JSON.stringify(response.data)}`) } } catch (error) { - Logger.error(`Failed to convert file ${filePath}: ${error instanceof Error ? error.message : String(error)}`) + logger.error(`Failed to convert file ${filePath}: ${error instanceof Error ? error.message : String(error)}`) throw new Error('Failed to convert file') } } @@ -265,7 +267,7 @@ export default class Doc2xPreprocessProvider extends BasePreprocessProvider { throw new Error(`HTTP status ${response.status}: ${response.statusText}`) } } catch (error) { - Logger.error( + logger.error( `Failed to get parsed file for uid ${uid}: ${error instanceof Error ? error.message : String(error)}` ) throw new Error('Failed to get parsed file information') @@ -288,7 +290,7 @@ export default class Doc2xPreprocessProvider extends BasePreprocessProvider { fs.mkdirSync(dirPath, { recursive: true }) fs.mkdirSync(extractPath, { recursive: true }) - Logger.info(`Downloading to export path: ${zipPath}`) + logger.info(`Downloading to export path: ${zipPath}`) try { // 下载文件 @@ -303,14 +305,14 @@ export default class Doc2xPreprocessProvider extends BasePreprocessProvider { // 解压文件 const zip = new AdmZip(zipPath) zip.extractAllTo(extractPath, true) - Logger.info(`Extracted files to: ${extractPath}`) + logger.info(`Extracted files to: ${extractPath}`) // 删除临时ZIP文件 fs.unlinkSync(zipPath) return { path: extractPath } } catch (error) { - Logger.error(`Failed to download and extract file: ${error instanceof Error ? error.message : String(error)}`) + logger.error(`Failed to download and extract file: ${error instanceof Error ? error.message : String(error)}`) throw new Error('Failed to download and extract file') } } diff --git a/src/main/knowledage/preprocess/MineruPreprocessProvider.ts b/src/main/knowledage/preprocess/MineruPreprocessProvider.ts index 58c0c00c23..1362ffa521 100644 --- a/src/main/knowledage/preprocess/MineruPreprocessProvider.ts +++ b/src/main/knowledage/preprocess/MineruPreprocessProvider.ts @@ -1,13 +1,15 @@ import fs from 'node:fs' import path from 'node:path' +import { loggerService } from '@logger' import { FileMetadata, PreprocessProvider } from '@types' import AdmZip from 'adm-zip' import axios from 'axios' -import Logger from 'electron-log' import BasePreprocessProvider from './BasePreprocessProvider' +const logger = loggerService.withContext('MineruPreprocessProvider') + type ApiResponse = { code: number data: T @@ -61,16 +63,16 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { file: FileMetadata ): Promise<{ processedFile: FileMetadata; quota: number }> { try { - Logger.info(`MinerU preprocess processing started: ${file.path}`) + logger.info(`MinerU preprocess processing started: ${file.path}`) await this.validateFile(file.path) // 1. 获取上传URL并上传文件 const batchId = await this.uploadFile(file) - Logger.info(`MinerU file upload completed: batch_id=${batchId}`) + logger.info(`MinerU file upload completed: batch_id=${batchId}`) // 2. 等待处理完成并获取结果 const extractResult = await this.waitForCompletion(sourceId, batchId, file.origin_name) - Logger.info(`MinerU processing completed for batch: ${batchId}`) + logger.info(`MinerU processing completed for batch: ${batchId}`) // 3. 下载并解压文件 const { path: outputPath } = await this.downloadAndExtractFile(extractResult.full_zip_url!, file) @@ -84,7 +86,7 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { quota } } catch (error: any) { - Logger.error(`MinerU preprocess processing failed for ${file.path}: ${error.message}`) + logger.error(`MinerU preprocess processing failed for ${file.path}: ${error.message}`) throw new Error(error.message) } } @@ -105,7 +107,7 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { const response: QuotaResponse = await quota.json() return response.data.user_left_quota } catch (error) { - console.error('Error checking quota:', error) + logger.error('Error checking quota:', error) throw error } } @@ -143,16 +145,16 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { try { fs.renameSync(originalMdPath, newMdPath) finalPath = newMdPath - Logger.info(`Renamed markdown file from ${mdFile} to ${finalName}`) + logger.info(`Renamed markdown file from ${mdFile} to ${finalName}`) } catch (renameError) { - Logger.warn(`Failed to rename file ${mdFile} to ${finalName}: ${renameError}`) + logger.warn(`Failed to rename file ${mdFile} to ${finalName}: ${renameError}`) // 如果重命名失败,使用原文件 finalPath = originalMdPath finalName = mdFile } } } catch (error) { - Logger.warn(`Failed to read output directory ${outputPath}: ${error}`) + logger.warn(`Failed to read output directory ${outputPath}: ${error}`) finalPath = path.join(outputPath, `${file.id}.md`) } @@ -171,13 +173,13 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { const zipPath = path.join(dirPath, `${file.id}.zip`) const extractPath = path.join(dirPath, `${file.id}`) - Logger.info(`Downloading MinerU result to: ${zipPath}`) + logger.info(`Downloading MinerU result to: ${zipPath}`) try { // 下载ZIP文件 const response = await axios.get(zipUrl, { responseType: 'arraybuffer' }) fs.writeFileSync(zipPath, response.data) - Logger.info(`Downloaded ZIP file: ${zipPath}`) + logger.info(`Downloaded ZIP file: ${zipPath}`) // 确保提取目录存在 if (!fs.existsSync(extractPath)) { @@ -187,14 +189,14 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { // 解压文件 const zip = new AdmZip(zipPath) zip.extractAllTo(extractPath, true) - Logger.info(`Extracted files to: ${extractPath}`) + logger.info(`Extracted files to: ${extractPath}`) // 删除临时ZIP文件 fs.unlinkSync(zipPath) return { path: extractPath } } catch (error: any) { - Logger.error(`Failed to download and extract file: ${error.message}`) + logger.error(`Failed to download and extract file: ${error.message}`) throw new Error(error.message) } } @@ -203,16 +205,16 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { try { // 步骤1: 获取上传URL const { batchId, fileUrls } = await this.getBatchUploadUrls(file) - Logger.info(`Got upload URLs for batch: ${batchId}`) + logger.info(`Got upload URLs for batch: ${batchId}`) console.log('batchId:', batchId, 'fileurls:', fileUrls) // 步骤2: 上传文件到获取的URL await this.putFileToUrl(file.path, fileUrls[0]) - Logger.info(`File uploaded successfully: ${file.path}`) + logger.info(`File uploaded successfully: ${file.path}`) return batchId } catch (error: any) { - Logger.error(`Failed to upload file ${file.path}: ${error.message}`) + logger.error(`Failed to upload file ${file.path}: ${error.message}`) throw new Error(error.message) } } @@ -260,7 +262,7 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { throw new Error(`HTTP ${response.status}: ${response.statusText}`) } } catch (error: any) { - Logger.error(`Failed to get batch upload URLs: ${error.message}`) + logger.error(`Failed to get batch upload URLs: ${error.message}`) throw new Error(error.message) } } @@ -296,16 +298,16 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { body: responseBody } - console.error('Response details:', errorInfo) + logger.error('Response details:', errorInfo) throw new Error(`Upload failed with status ${response.status}: ${responseBody}`) } catch (parseError) { throw new Error(`Upload failed with status ${response.status}. Could not parse response body.`) } } - Logger.info(`File uploaded successfully to: ${uploadUrl}`) + logger.info(`File uploaded successfully to: ${uploadUrl}`) } catch (error: any) { - Logger.error(`Failed to upload file to URL ${uploadUrl}: ${error}`) + logger.error(`Failed to upload file to URL ${uploadUrl}: ${error}`) throw new Error(error.message) } } @@ -334,7 +336,7 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { throw new Error(`HTTP ${response.status}: ${response.statusText}`) } } catch (error: any) { - Logger.error(`Failed to get extract results for batch ${batchId}: ${error.message}`) + logger.error(`Failed to get extract results for batch ${batchId}: ${error.message}`) throw new Error(error.message) } } @@ -360,7 +362,7 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { // 检查处理状态 if (fileResult.state === 'done' && fileResult.full_zip_url) { - Logger.info(`Processing completed for file: ${fileName}`) + logger.info(`Processing completed for file: ${fileName}`) return fileResult } else if (fileResult.state === 'failed') { throw new Error(`Processing failed for file: ${fileName}, error: ${fileResult.err_msg}`) @@ -371,15 +373,15 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { (fileResult.extract_progress.extracted_pages / fileResult.extract_progress.total_pages) * 100 ) await this.sendPreprocessProgress(sourceId, progress) - Logger.info(`File ${fileName} processing progress: ${progress}%`) + logger.info(`File ${fileName} processing progress: ${progress}%`) } else { // 如果没有具体进度信息,发送一个通用进度 await this.sendPreprocessProgress(sourceId, 50) - Logger.info(`File ${fileName} is still processing...`) + logger.info(`File ${fileName} is still processing...`) } } } catch (error) { - Logger.warn(`Failed to check status for batch ${batchId}, retry ${retries + 1}/${maxRetries}`) + logger.warn(`Failed to check status for batch ${batchId}, retry ${retries + 1}/${maxRetries}`) if (retries === maxRetries - 1) { throw error } diff --git a/src/main/knowledage/preprocess/MistralPreprocessProvider.ts b/src/main/knowledage/preprocess/MistralPreprocessProvider.ts index 3150162801..ece371ddb7 100644 --- a/src/main/knowledage/preprocess/MistralPreprocessProvider.ts +++ b/src/main/knowledage/preprocess/MistralPreprocessProvider.ts @@ -1,5 +1,6 @@ import fs from 'node:fs' +import { loggerService } from '@logger' import { MistralClientManager } from '@main/services/MistralClientManager' import { MistralService } from '@main/services/remotefile/MistralService' import { Mistral } from '@mistralai/mistralai' @@ -7,13 +8,14 @@ import { DocumentURLChunk } from '@mistralai/mistralai/models/components/documen import { ImageURLChunk } from '@mistralai/mistralai/models/components/imageurlchunk' import { OCRResponse } from '@mistralai/mistralai/models/components/ocrresponse' import { FileMetadata, FileTypes, PreprocessProvider, Provider } from '@types' -import Logger from 'electron-log' import path from 'path' import BasePreprocessProvider from './BasePreprocessProvider' type PreuploadResponse = DocumentURLChunk | ImageURLChunk +const logger = loggerService.withContext('MistralPreprocessProvider') + export default class MistralPreprocessProvider extends BasePreprocessProvider { private sdk: Mistral private fileService: MistralService @@ -36,20 +38,20 @@ export default class MistralPreprocessProvider extends BasePreprocessProvider { private async preupload(file: FileMetadata): Promise { let document: PreuploadResponse - Logger.info(`preprocess preupload started for local file: ${file.path}`) + logger.info(`preprocess preupload started for local file: ${file.path}`) if (file.ext.toLowerCase() === '.pdf') { const uploadResponse = await this.fileService.uploadFile(file) if (uploadResponse.status === 'failed') { - Logger.error('File upload failed:', uploadResponse) + logger.error('File upload failed:', uploadResponse) throw new Error('Failed to upload file: ' + uploadResponse.displayName) } await this.sendPreprocessProgress(file.id, 15) const fileUrl = await this.sdk.files.getSignedUrl({ fileId: uploadResponse.fileId }) - Logger.info('Got signed URL:', fileUrl) + logger.info('Got signed URL:', fileUrl) await this.sendPreprocessProgress(file.id, 20) document = { type: 'document_url', @@ -152,7 +154,7 @@ export default class MistralPreprocessProvider extends BasePreprocessProvider { counter++ } catch (error) { - Logger.error(`Failed to save image ${imageFileName}:`, error) + logger.error(`Failed to save image ${imageFileName}:`, error) } } }) diff --git a/src/main/knowledge/embeddings/VoyageEmbeddings.ts b/src/main/knowledge/embeddings/VoyageEmbeddings.ts index 61f23ad767..a8260a4ae9 100644 --- a/src/main/knowledge/embeddings/VoyageEmbeddings.ts +++ b/src/main/knowledge/embeddings/VoyageEmbeddings.ts @@ -1,8 +1,11 @@ import { BaseEmbeddings } from '@cherrystudio/embedjs-interfaces' import { VoyageEmbeddings as _VoyageEmbeddings } from '@langchain/community/embeddings/voyage' +import { loggerService } from '@logger' import { VOYAGE_SUPPORTED_DIM_MODELS } from './utils' +const logger = loggerService.withContext('VoyageEmbeddings') + /** * 支持设置嵌入维度的模型 */ @@ -16,7 +19,7 @@ export class VoyageEmbeddings extends BaseEmbeddings { if (!this.configuration.modelName) this.configuration.modelName = 'voyage-3' if (!VOYAGE_SUPPORTED_DIM_MODELS.includes(this.configuration.modelName) && this.configuration.outputDimension) { - console.error(`VoyageEmbeddings only supports ${VOYAGE_SUPPORTED_DIM_MODELS.join(', ')} to set outputDimension.`) + logger.error(`VoyageEmbeddings only supports ${VOYAGE_SUPPORTED_DIM_MODELS.join(', ')} to set outputDimension.`) this.model = new _VoyageEmbeddings({ ...this.configuration, outputDimension: undefined }) } else { this.model = new _VoyageEmbeddings(this.configuration) diff --git a/src/main/knowledge/loader/epubLoader.ts b/src/main/knowledge/loader/epubLoader.ts index bb62cba8cb..bd90a9c7fb 100644 --- a/src/main/knowledge/loader/epubLoader.ts +++ b/src/main/knowledge/loader/epubLoader.ts @@ -1,12 +1,14 @@ import { BaseLoader } from '@cherrystudio/embedjs-interfaces' import { cleanString } from '@cherrystudio/embedjs-utils' import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters' +import { loggerService } from '@logger' import { getTempDir } from '@main/utils/file' -import Logger from 'electron-log' import EPub from 'epub' import * as fs from 'fs' import path from 'path' +const logger = loggerService.withContext('EpubLoader') + /** * epub 加载器的配置选项 */ @@ -183,7 +185,7 @@ export class EpubLoader extends BaseLoader = { // 内置类型 @@ -75,7 +77,7 @@ export async function addFileLoader( // JSON类型处理 let jsonObject = {} let jsonParsed = true - Logger.info(`[KnowledgeBase] processing file ${file.path} as ${loaderType} type`) + logger.info(`[KnowledgeBase] processing file ${file.path} as ${loaderType} type`) switch (loaderType) { case 'common': // 内置类型处理 @@ -127,7 +129,7 @@ export async function addFileLoader( jsonObject = JSON.parse(await readTextFileWithAutoEncoding(file.path)) } catch (error) { jsonParsed = false - Logger.warn('[KnowledgeBase] failed parsing json file, falling back to text processing:', file.path, error) + logger.warn('[KnowledgeBase] failed parsing json file, falling back to text processing:', file.path, error) } if (jsonParsed) { diff --git a/src/main/knowledge/loader/odLoader.ts b/src/main/knowledge/loader/odLoader.ts index 7e13420b24..f2893d3982 100644 --- a/src/main/knowledge/loader/odLoader.ts +++ b/src/main/knowledge/loader/odLoader.ts @@ -1,9 +1,12 @@ import { BaseLoader } from '@cherrystudio/embedjs-interfaces' import { cleanString } from '@cherrystudio/embedjs-utils' import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters' +import { loggerService } from '@logger' import md5 from 'md5' import { OfficeParserConfig, parseOfficeAsync } from 'officeparser' +const logger = loggerService.withContext('OdLoader') + export enum OdType { OdtLoader = 'OdtLoader', OdsLoader = 'OdsLoader', @@ -42,7 +45,7 @@ export class OdLoader extends BaseLoader<{ type: string }> { try { this.extractedText = await parseOfficeAsync(this.filePath, this.config) } catch (err) { - console.error('odLoader error', err) + logger.error('odLoader error', err) throw err } } diff --git a/src/main/mcpServers/dify-knowledge.ts b/src/main/mcpServers/dify-knowledge.ts index 33c61820c0..db62f6cddd 100644 --- a/src/main/mcpServers/dify-knowledge.ts +++ b/src/main/mcpServers/dify-knowledge.ts @@ -1,9 +1,12 @@ // inspired by https://dify.ai/blog/turn-your-dify-app-into-an-mcp-server +import { loggerService } from '@logger' import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { CallToolRequestSchema, ListToolsRequestSchema, ToolSchema } from '@modelcontextprotocol/sdk/types.js' import { z } from 'zod' import { zodToJsonSchema } from 'zod-to-json-schema' +const logger = loggerService.withContext('DifyKnowledgeServer') + interface DifyKnowledgeServerConfig { difyKey: string apiHost: string @@ -168,7 +171,7 @@ class DifyKnowledgeServer { content: [{ type: 'text', text: formattedText }] } } catch (error) { - console.error('获取知识库列表时出错:', error) + logger.error('Error fetching knowledge list:', error) const errorMessage = error instanceof Error ? error.message : String(error) // 返回包含错误信息的 MCP 响应 return { @@ -247,7 +250,7 @@ class DifyKnowledgeServer { content: [{ type: 'text', text: formattedText }] } } catch (error) { - console.error('搜索知识库时出错:', error) + logger.error('Error searching knowledge:', error) const errorMessage = error instanceof Error ? error.message : String(error) return { content: [{ type: 'text', text: `Search Knowledge Error: ${errorMessage}` }], diff --git a/src/main/mcpServers/factory.ts b/src/main/mcpServers/factory.ts index 2376ef223e..fe1269cec2 100644 --- a/src/main/mcpServers/factory.ts +++ b/src/main/mcpServers/factory.ts @@ -1,5 +1,5 @@ +import { loggerService } from '@logger' import { Server } from '@modelcontextprotocol/sdk/server/index.js' -import Logger from 'electron-log' import BraveSearchServer from './brave-search' import DifyKnowledgeServer from './dify-knowledge' @@ -9,8 +9,10 @@ import MemoryServer from './memory' import PythonServer from './python' import ThinkingServer from './sequentialthinking' +const logger = loggerService.withContext('MCPFactory') + export function createInMemoryMCPServer(name: string, args: string[] = [], envs: Record = {}): Server { - Logger.info(`[MCP] Creating in-memory MCP server: ${name} with args: ${args} and envs: ${JSON.stringify(envs)}`) + logger.debug(`[MCP] Creating in-memory MCP server: ${name} with args: ${args} and envs: ${JSON.stringify(envs)}`) switch (name) { case '@cherry/memory': { const envPath = envs.MEMORY_FILE_PATH diff --git a/src/main/mcpServers/filesystem.ts b/src/main/mcpServers/filesystem.ts index 4d99507ba2..ff72973c29 100644 --- a/src/main/mcpServers/filesystem.ts +++ b/src/main/mcpServers/filesystem.ts @@ -1,5 +1,6 @@ // port https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/index.ts +import { loggerService } from '@logger' import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { CallToolRequestSchema, ListToolsRequestSchema, ToolSchema } from '@modelcontextprotocol/sdk/types.js' import { createTwoFilesPatch } from 'diff' @@ -10,6 +11,8 @@ import path from 'path' import { z } from 'zod' import { zodToJsonSchema } from 'zod-to-json-schema' +const logger = loggerService.withContext('MCP:FileSystemServer') + // Normalize all paths consistently function normalizePath(p: string): string { return path.normalize(p) @@ -294,7 +297,7 @@ class FileSystemServer { // Validate that all directories exist and are accessible this.validateDirs().catch((error) => { - console.error('Error validating allowed directories:', error) + logger.error('Error validating allowed directories:', error) throw new Error(`Error validating allowed directories: ${error}`) }) @@ -319,11 +322,11 @@ class FileSystemServer { try { const stats = await fs.stat(expandHome(dir)) if (!stats.isDirectory()) { - console.error(`Error: ${dir} is not a directory`) + logger.error(`Error: ${dir} is not a directory`) throw new Error(`Error: ${dir} is not a directory`) } } catch (error: any) { - console.error(`Error accessing directory ${dir}:`, error) + logger.error(`Error accessing directory ${dir}:`, error) throw new Error(`Error accessing directory ${dir}:`, error) } }) diff --git a/src/main/mcpServers/memory.ts b/src/main/mcpServers/memory.ts index 746670b36e..912999b6d8 100644 --- a/src/main/mcpServers/memory.ts +++ b/src/main/mcpServers/memory.ts @@ -1,11 +1,13 @@ +import { loggerService } from '@logger' import { getConfigDir } from '@main/utils/file' import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js' import { Mutex } from 'async-mutex' // 引入 Mutex -import Logger from 'electron-log' import { promises as fs } from 'fs' import path from 'path' +const logger = loggerService.withContext('MCPServer:Memory') + // Define memory file path const defaultMemoryPath = path.join(getConfigDir(), 'memory.json') @@ -61,7 +63,7 @@ class KnowledgeGraphManager { await fs.writeFile(this.memoryPath, JSON.stringify({ entities: [], relations: [] }, null, 2)) } } catch (error) { - console.error('Failed to ensure memory path exists:', error) + logger.error('Failed to ensure memory path exists:', error) // Propagate the error or handle it more gracefully depending on requirements throw new McpError( ErrorCode.InternalError, @@ -94,13 +96,13 @@ class KnowledgeGraphManager { this.relations = new Set() await this._persistGraph() // Create the file with empty structure } else if (error instanceof SyntaxError) { - console.error('Failed to parse memory.json, initializing with empty graph:', error) + logger.error('Failed to parse memory.json, initializing with empty graph:', error) // If JSON is invalid, start fresh and overwrite the corrupted file this.entities = new Map() this.relations = new Set() await this._persistGraph() } else { - console.error('Failed to load knowledge graph from disk:', error) + logger.error('Failed to load knowledge graph from disk:', error) throw new McpError( ErrorCode.InternalError, `Failed to load graph: ${error instanceof Error ? error.message : String(error)}` @@ -119,7 +121,7 @@ class KnowledgeGraphManager { } await fs.writeFile(this.memoryPath, JSON.stringify(graphData, null, 2)) } catch (error) { - console.error('Failed to save knowledge graph:', error) + logger.error('Failed to save knowledge graph:', error) // Decide how to handle write errors - potentially retry or notify throw new McpError( ErrorCode.InternalError, @@ -162,7 +164,7 @@ class KnowledgeGraphManager { relations.forEach((relation) => { // Ensure related entities exist before creating a relation if (!this.entities.has(relation.from) || !this.entities.has(relation.to)) { - console.warn(`Skipping relation creation: Entity not found for relation ${relation.from} -> ${relation.to}`) + logger.warn(`Skipping relation creation: Entity not found for relation ${relation.from} -> ${relation.to}`) return // Skip this relation } const relationStr = this._serializeRelation(relation) @@ -188,7 +190,7 @@ class KnowledgeGraphManager { // Option 1: Throw error throw new McpError(ErrorCode.InvalidParams, `Entity with name ${o.entityName} not found`) // Option 2: Skip and warn - // console.warn(`Entity with name ${o.entityName} not found when adding observations. Skipping.`); + // logger.warn(`Entity with name ${o.entityName} not found when adding observations. Skipping.`); // return; } // Ensure observations array exists @@ -356,9 +358,9 @@ class MemoryServer { private async _initializeManager(memoryPath: string): Promise { try { this.knowledgeGraphManager = await KnowledgeGraphManager.create(memoryPath) - Logger.log('KnowledgeGraphManager initialized successfully.') + logger.debug('KnowledgeGraphManager initialized successfully.') } catch (error) { - Logger.error('Failed to initialize KnowledgeGraphManager:', error) + logger.error('Failed to initialize KnowledgeGraphManager:', error) // Server might be unusable, consider how to handle this state // Maybe set a flag and return errors for all tool calls? this.knowledgeGraphManager = null // Ensure it's null if init fails @@ -385,7 +387,7 @@ class MemoryServer { await this._getManager() // Wait for initialization before confirming tools are available } catch (error) { // If manager failed to init, maybe return an empty tool list or throw? - console.error('Cannot list tools, manager initialization failed:', error) + logger.error('Cannot list tools, manager initialization failed:', error) return { tools: [] } // Return empty list if server is not ready } @@ -687,7 +689,7 @@ class MemoryServer { if (error instanceof McpError) { throw error // Re-throw McpErrors directly } - console.error(`Error executing tool ${name}:`, error) + logger.error(`Error executing tool ${name}:`, error) // Throw a generic internal error for unexpected issues throw new McpError( ErrorCode.InternalError, diff --git a/src/main/mcpServers/python.ts b/src/main/mcpServers/python.ts index 6fe0b80db1..b8a4208088 100644 --- a/src/main/mcpServers/python.ts +++ b/src/main/mcpServers/python.ts @@ -1,7 +1,9 @@ +import { loggerService } from '@logger' import { pythonService } from '@main/services/PythonService' import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js' -import Logger from 'electron-log' + +const logger = loggerService.withContext('MCPServer:Python') /** * Python MCP Server for executing Python code using Pyodide @@ -88,7 +90,7 @@ print('python code here')`, throw new McpError(ErrorCode.InvalidParams, 'Code parameter is required and must be a string') } - Logger.info('Executing Python code via Pyodide') + logger.debug('Executing Python code via Pyodide') const result = await pythonService.executeScript(code, context, timeout) @@ -102,7 +104,7 @@ print('python code here')`, } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) - Logger.error('Python execution error:', errorMessage) + logger.error('Python execution error:', errorMessage) throw new McpError(ErrorCode.InternalError, `Python execution failed: ${errorMessage}`) } diff --git a/src/main/mcpServers/sequentialthinking.ts b/src/main/mcpServers/sequentialthinking.ts index bcda96e192..90c1c329d5 100644 --- a/src/main/mcpServers/sequentialthinking.ts +++ b/src/main/mcpServers/sequentialthinking.ts @@ -1,11 +1,14 @@ // Sequential Thinking MCP Server // port https://github.com/modelcontextprotocol/servers/blob/main/src/sequentialthinking/index.ts +import { loggerService } from '@logger' import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { CallToolRequestSchema, ListToolsRequestSchema, Tool } from '@modelcontextprotocol/sdk/types.js' // Fixed chalk import for ESM import chalk from 'chalk' +const logger = loggerService.withContext('MCPServer:SequentialThinkingServer') + interface ThoughtData { thought: string thoughtNumber: number @@ -98,7 +101,7 @@ class SequentialThinkingServer { } const formattedThought = this.formatThought(validatedInput) - console.error(formattedThought) + logger.error(formattedThought) return { content: [ diff --git a/src/main/services/AppService.ts b/src/main/services/AppService.ts index f7dc5a7657..b95edda562 100644 --- a/src/main/services/AppService.ts +++ b/src/main/services/AppService.ts @@ -1,10 +1,12 @@ +import { loggerService } from '@logger' import { isDev, isLinux, isMac, isWin } from '@main/constant' import { app } from 'electron' -import log from 'electron-log' import fs from 'fs' import os from 'os' import path from 'path' +const logger = loggerService.withContext('AppService') + export class AppService { private static instance: AppService @@ -59,19 +61,19 @@ export class AppService { // Write desktop file await fs.promises.writeFile(desktopFile, desktopContent) - log.info('Created autostart desktop file for Linux') + logger.info('Created autostart desktop file for Linux') } else { // Remove desktop file try { await fs.promises.access(desktopFile) await fs.promises.unlink(desktopFile) - log.info('Removed autostart desktop file for Linux') + logger.info('Removed autostart desktop file for Linux') } catch { // File doesn't exist, no need to remove } } } catch (error) { - log.error('Failed to set launch on boot for Linux:', error) + logger.error('Failed to set launch on boot for Linux:', error) } } } diff --git a/src/main/services/AppUpdater.ts b/src/main/services/AppUpdater.ts index effcff00c5..ed67e5319c 100644 --- a/src/main/services/AppUpdater.ts +++ b/src/main/services/AppUpdater.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { isWin } from '@main/constant' import { locales } from '@main/utils/locales' import { generateUserAgent } from '@main/utils/systemInfo' @@ -5,13 +6,14 @@ import { FeedUrl, UpgradeChannel } from '@shared/config/constant' import { IpcChannel } from '@shared/IpcChannel' import { CancellationToken, UpdateInfo } from 'builder-util-runtime' import { app, BrowserWindow, dialog } from 'electron' -import logger from 'electron-log' import { AppUpdater as _AppUpdater, autoUpdater, NsisUpdater, UpdateCheckResult } from 'electron-updater' import path from 'path' import icon from '../../../build/icon.png?asset' import { configManager } from './ConfigManager' +const logger = loggerService.withContext('AppUpdater') + export default class AppUpdater { autoUpdater: _AppUpdater = autoUpdater private releaseInfo: UpdateInfo | undefined @@ -19,8 +21,6 @@ export default class AppUpdater { private updateCheckResult: UpdateCheckResult | null = null constructor(mainWindow: BrowserWindow) { - logger.transports.file.level = 'info' - autoUpdater.logger = logger autoUpdater.forceDevUpdateConfig = !app.isPackaged autoUpdater.autoDownload = configManager.getAutoUpdate() diff --git a/src/main/services/BackupManager.ts b/src/main/services/BackupManager.ts index 2b607cb51a..5e0f12d844 100644 --- a/src/main/services/BackupManager.ts +++ b/src/main/services/BackupManager.ts @@ -1,10 +1,10 @@ +import { loggerService } from '@logger' import { IpcChannel } from '@shared/IpcChannel' import { WebDavConfig } from '@types' import { S3Config } from '@types' import archiver from 'archiver' import { exec } from 'child_process' import { app } from 'electron' -import Logger from 'electron-log' import * as fs from 'fs-extra' import StreamZip from 'node-stream-zip' import * as path from 'path' @@ -15,6 +15,8 @@ import S3Storage from './S3Storage' import WebDav from './WebDav' import { windowService } from './WindowService' +const logger = loggerService.withContext('BackupManager') + class BackupManager { private tempDir = path.join(app.getPath('temp'), 'cherry-studio', 'backup', 'temp') private backupDir = path.join(app.getPath('temp'), 'cherry-studio', 'backup') @@ -58,7 +60,7 @@ class BackupManager { // 确保根目录权限 await this.forceSetWritable(dirPath) } catch (error) { - Logger.error(`权限设置失败:${dirPath}`, error) + logger.error(`权限设置失败:${dirPath}`, error) throw error } } @@ -81,7 +83,7 @@ class BackupManager { } } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { - Logger.warn(`权限设置警告:${targetPath}`, error) + logger.warn(`权限设置警告:${targetPath}`, error) } } } @@ -100,7 +102,7 @@ class BackupManager { // 只在关键阶段记录日志:开始、结束和主要阶段转换点 const logStages = ['preparing', 'writing_data', 'preparing_compression', 'completed'] if (logStages.includes(processData.stage) || processData.progress === 100) { - Logger.log('[BackupManager] backup progress', processData) + logger.debug('backup progress', processData) } } @@ -122,7 +124,7 @@ class BackupManager { onProgress({ stage: 'writing_data', progress: 20, total: 100 }) - Logger.log('[BackupManager IPC] ', skipBackupFile) + logger.debug('BackupManager IPC', skipBackupFile) if (!skipBackupFile) { // 复制 Data 目录到临时目录 @@ -143,7 +145,7 @@ class BackupManager { await this.setWritableRecursive(tempDataDir) onProgress({ stage: 'preparing_compression', progress: 50, total: 100 }) } else { - Logger.log('[BackupManager] Skip the backup of the file') + logger.debug('Skip the backup of the file') await fs.promises.mkdir(path.join(this.tempDir, 'Data')) // 不创建空 Data 目录会导致 restore 失败 } @@ -179,7 +181,7 @@ class BackupManager { } } catch (error) { // 仅在出错时记录日志 - Logger.error('[BackupManager] Error calculating totals:', error) + logger.error('[BackupManager] Error calculating totals:', error) } } @@ -218,7 +220,7 @@ class BackupManager { archive.on('error', reject) archive.on('warning', (err: any) => { if (err.code !== 'ENOENT') { - Logger.warn('[BackupManager] Archive warning:', err) + logger.warn('[BackupManager] Archive warning:', err) } }) @@ -236,10 +238,10 @@ class BackupManager { await fs.remove(this.tempDir) onProgress({ stage: 'completed', progress: 100, total: 100 }) - Logger.log('[BackupManager] Backup completed successfully') + logger.debug('Backup completed successfully') return backupedFilePath } catch (error) { - Logger.error('[BackupManager] Backup failed:', error) + logger.error('[BackupManager] Backup failed:', error) // 确保清理临时目录 await fs.remove(this.tempDir).catch(() => {}) throw error @@ -254,7 +256,7 @@ class BackupManager { // 只在关键阶段记录日志 const logStages = ['preparing', 'extracting', 'extracted', 'reading_data', 'completed'] if (logStages.includes(processData.stage) || processData.progress === 100) { - Logger.log('[BackupManager] restore progress', processData) + logger.debug('restore progress', processData) } } @@ -263,20 +265,20 @@ class BackupManager { await fs.ensureDir(this.tempDir) onProgress({ stage: 'preparing', progress: 0, total: 100 }) - Logger.log('[backup] step 1: unzip backup file', this.tempDir) + logger.debug('step 1: unzip backup file', this.tempDir) const zip = new StreamZip.async({ file: backupPath }) onProgress({ stage: 'extracting', progress: 15, total: 100 }) await zip.extract(null, this.tempDir) onProgress({ stage: 'extracted', progress: 25, total: 100 }) - Logger.log('[backup] step 2: read data.json') + logger.debug('step 2: read data.json') // 读取 data.json const dataPath = path.join(this.tempDir, 'data.json') const data = await fs.readFile(dataPath, 'utf-8') onProgress({ stage: 'reading_data', progress: 35, total: 100 }) - Logger.log('[backup] step 3: restore Data directory') + logger.debug('step 3: restore Data directory') // 恢复 Data 目录 const sourcePath = path.join(this.tempDir, 'Data') const destPath = getDataPath() @@ -299,20 +301,20 @@ class BackupManager { onProgress({ stage: 'copying_files', progress, total: 100 }) }) } else { - Logger.log('[backup] skipBackupFile is true, skip restoring Data directory') + logger.debug('skipBackupFile is true, skip restoring Data directory') } - Logger.log('[backup] step 4: clean up temp directory') + logger.debug('step 4: clean up temp directory') // 清理临时目录 await this.setWritableRecursive(this.tempDir) await fs.remove(this.tempDir) onProgress({ stage: 'completed', progress: 100, total: 100 }) - Logger.log('[backup] step 5: Restore completed successfully') + logger.debug('step 5: Restore completed successfully') return data } catch (error) { - Logger.error('[backup] Restore failed:', error) + logger.error('Restore failed:', error) await fs.remove(this.tempDir).catch(() => {}) throw error } @@ -369,7 +371,7 @@ class BackupManager { return await this.restore(_, backupedFilePath) } catch (error: any) { - Logger.error('[backup] Failed to restore from WebDAV:', error) + logger.error('Failed to restore from WebDAV:', error) throw new Error(error.message || 'Failed to restore backup file') } } @@ -389,7 +391,7 @@ class BackupManager { })) .sort((a, b) => new Date(b.modifiedTime).getTime() - new Date(a.modifiedTime).getTime()) } catch (error: any) { - Logger.error('Failed to list WebDAV files:', error) + logger.error('Failed to list WebDAV files:', error) throw new Error(error.message || 'Failed to list backup files') } } @@ -485,7 +487,7 @@ class BackupManager { const webdavClient = new WebDav(webdavConfig) return await webdavClient.deleteFile(fileName) } catch (error: any) { - Logger.error('Failed to delete WebDAV file:', error) + logger.error('Failed to delete WebDAV file:', error) throw new Error(error.message || 'Failed to delete backup file') } } @@ -507,7 +509,7 @@ class BackupManager { const backupedFilePath = await this.backup(_, fileName, data, backupDir, localConfig.skipBackupFile) return backupedFilePath } catch (error) { - Logger.error('[BackupManager] Local backup failed:', error) + logger.error('[BackupManager] Local backup failed:', error) throw error } } @@ -521,7 +523,7 @@ class BackupManager { .slice(0, 14) const filename = s3Config.fileName || `cherry-studio.backup.${deviceName}.${timestamp}.zip` - Logger.log(`[BackupManager] Starting S3 backup to ${filename}`) + logger.debug(`Starting S3 backup to ${filename}`) const backupedFilePath = await this.backup(_, filename, data, undefined, s3Config.skipBackupFile) const s3Client = new S3Storage(s3Config) @@ -530,10 +532,10 @@ class BackupManager { const result = await s3Client.putFileContents(filename, fileBuffer) await fs.remove(backupedFilePath) - Logger.log(`[BackupManager] S3 backup completed successfully: ${filename}`) + logger.debug(`S3 backup completed successfully: ${filename}`) return result } catch (error) { - Logger.error(`[BackupManager] S3 backup failed:`, error) + logger.error(`[BackupManager] S3 backup failed:`, error) await fs.remove(backupedFilePath) throw error } @@ -550,7 +552,7 @@ class BackupManager { return await this.restore(_, backupPath) } catch (error) { - Logger.error('[BackupManager] Local restore failed:', error) + logger.error('[BackupManager] Local restore failed:', error) throw error } } @@ -576,7 +578,7 @@ class BackupManager { // Sort by modified time, newest first return result.sort((a, b) => new Date(b.modifiedTime).getTime() - new Date(a.modifiedTime).getTime()) } catch (error) { - Logger.error('[BackupManager] List local backup files failed:', error) + logger.error('[BackupManager] List local backup files failed:', error) throw error } } @@ -592,7 +594,7 @@ class BackupManager { await fs.remove(filePath) return true } catch (error) { - Logger.error('[BackupManager] Delete local backup file failed:', error) + logger.error('[BackupManager] Delete local backup file failed:', error) throw error } } @@ -603,7 +605,7 @@ class BackupManager { await fs.ensureDir(dirPath) return true } catch (error) { - Logger.error('[BackupManager] Set local backup directory failed:', error) + logger.error('[BackupManager] Set local backup directory failed:', error) throw error } } @@ -611,7 +613,7 @@ class BackupManager { async restoreFromS3(_: Electron.IpcMainInvokeEvent, s3Config: S3Config) { const filename = s3Config.fileName || 'cherry-studio.backup.zip' - Logger.log(`[BackupManager] Starting restore from S3: ${filename}`) + logger.debug(`Starting restore from S3: ${filename}`) const s3Client = new S3Storage(s3Config) try { @@ -628,10 +630,10 @@ class BackupManager { writeStream.on('error', (error) => reject(error)) }) - Logger.log(`[BackupManager] S3 restore file downloaded successfully: ${filename}`) + logger.debug(`S3 restore file downloaded successfully: ${filename}`) return await this.restore(_, backupedFilePath) } catch (error: any) { - Logger.error('[BackupManager] Failed to restore from S3:', error) + logger.error('[BackupManager] Failed to restore from S3:', error) throw new Error(error.message || 'Failed to restore backup file') } } @@ -655,7 +657,7 @@ class BackupManager { return files.sort((a, b) => new Date(b.modifiedTime).getTime() - new Date(a.modifiedTime).getTime()) } catch (error: any) { - Logger.error('Failed to list S3 files:', error) + logger.error('Failed to list S3 files:', error) throw new Error(error.message || 'Failed to list backup files') } } @@ -665,7 +667,7 @@ class BackupManager { const s3Client = new S3Storage(s3Config) return await s3Client.deleteFile(fileName) } catch (error: any) { - Logger.error('Failed to delete S3 file:', error) + logger.error('Failed to delete S3 file:', error) throw new Error(error.message || 'Failed to delete backup file') } } diff --git a/src/main/services/CopilotService.ts b/src/main/services/CopilotService.ts index bc331a4468..56898d7c2a 100644 --- a/src/main/services/CopilotService.ts +++ b/src/main/services/CopilotService.ts @@ -1,10 +1,12 @@ +import { loggerService } from '@logger' import { AxiosRequestConfig } from 'axios' import axios from 'axios' import { app, safeStorage } from 'electron' -import Logger from 'electron-log' import fs from 'fs/promises' import path from 'path' +const logger = loggerService.withContext('CopilotService') + // 配置常量,集中管理 const CONFIG = { GITHUB_CLIENT_ID: 'Iv1.b507a08c87ecfe98', @@ -101,7 +103,7 @@ class CopilotService { avatar: response.data.avatar_url } } catch (error) { - console.error('Failed to get user information:', error) + logger.error('Failed to get user information:', error) throw new CopilotServiceError('无法获取GitHub用户信息', error) } } @@ -127,7 +129,7 @@ class CopilotService { return response.data } catch (error) { - console.error('Failed to get auth message:', error) + logger.error('Failed to get auth message:', error) throw new CopilotServiceError('无法获取GitHub授权信息', error) } } @@ -169,7 +171,7 @@ class CopilotService { // 仅在最后一次尝试失败时记录详细错误 const isLastAttempt = attempt === CONFIG.POLLING.MAX_ATTEMPTS - 1 if (isLastAttempt) { - console.error(`Token polling failed after ${CONFIG.POLLING.MAX_ATTEMPTS} attempts:`, error) + logger.error(`Token polling failed after ${CONFIG.POLLING.MAX_ATTEMPTS} attempts:`, error) } } } @@ -185,7 +187,7 @@ class CopilotService { const encryptedToken = safeStorage.encryptString(token) await fs.writeFile(this.tokenFilePath, encryptedToken) } catch (error) { - console.error('Failed to save token:', error) + logger.error('Failed to save token:', error) throw new CopilotServiceError('无法保存访问令牌', error) } } @@ -214,7 +216,7 @@ class CopilotService { return response.data } catch (error) { - console.error('Failed to get Copilot token:', error) + logger.error('Failed to get Copilot token:', error) throw new CopilotServiceError('无法获取Copilot令牌,请重新授权', error) } } @@ -227,13 +229,13 @@ class CopilotService { try { await fs.access(this.tokenFilePath) await fs.unlink(this.tokenFilePath) - Logger.log('Successfully logged out from Copilot') + logger.debug('Successfully logged out from Copilot') } catch (error) { // 文件不存在不是错误,只是记录一下 - Logger.log('Token file not found, nothing to delete') + logger.debug('Token file not found, nothing to delete') } } catch (error) { - console.error('Failed to logout:', error) + logger.error('Failed to logout:', error) throw new CopilotServiceError('无法完成退出登录操作', error) } } diff --git a/src/main/services/DxtService.ts b/src/main/services/DxtService.ts index f4324d25b4..22bbfaee31 100644 --- a/src/main/services/DxtService.ts +++ b/src/main/services/DxtService.ts @@ -1,11 +1,13 @@ +import { loggerService } from '@logger' import { getMcpDir, getTempDir } from '@main/utils/file' -import logger from 'electron-log' import * as fs from 'fs' import StreamZip from 'node-stream-zip' import * as os from 'os' import * as path from 'path' import { v4 as uuidv4 } from 'uuid' +const logger = loggerService.withContext('DxtService') + // Type definitions export interface DxtManifest { dxt_version: string @@ -174,7 +176,7 @@ class DxtService { fs.mkdirSync(this.mcpDir, { recursive: true }) } } catch (error) { - logger.error('[DxtService] Failed to create directories:', error) + logger.error('Failed to create directories:', error) } } @@ -184,7 +186,7 @@ class DxtService { fs.renameSync(source, destination) } catch (error) { // If rename fails (cross-filesystem), use copy + remove - logger.info('[DxtService] Cross-filesystem move detected, using copy + remove') + logger.debug('Cross-filesystem move detected, using copy + remove') // Ensure parent directory exists const parentDir = path.dirname(destination) @@ -230,7 +232,7 @@ class DxtService { } // Extract the DXT file (which is a ZIP archive) to a temporary directory - logger.info('[DxtService] Extracting DXT file:', filePath) + logger.debug('Extracting DXT file:', filePath) const zip = new StreamZip.async({ file: filePath }) await zip.extract(null, tempExtractDir) @@ -276,14 +278,14 @@ class DxtService { // Clean up any existing version of this server if (fs.existsSync(finalExtractDir)) { - logger.info('[DxtService] Removing existing server directory:', finalExtractDir) + logger.debug('Removing existing server directory:', finalExtractDir) fs.rmSync(finalExtractDir, { recursive: true, force: true }) } // Move the temporary directory to the final location // Use recursive copy + remove instead of rename to handle cross-filesystem moves await this.moveDirectory(tempExtractDir, finalExtractDir) - logger.info('[DxtService] DXT server extracted to:', finalExtractDir) + logger.debug('DXT server extracted to:', finalExtractDir) // Clean up the uploaded DXT file if it's in temp directory if (filePath.startsWith(this.tempDir)) { @@ -305,7 +307,7 @@ class DxtService { } const errorMessage = error instanceof Error ? error.message : 'Failed to process DXT file' - logger.error('[DxtService] DXT upload error:', error) + logger.error('DXT upload error:', error) return { success: false, @@ -322,7 +324,7 @@ class DxtService { // Read the manifest from the DXT server directory const manifestPath = path.join(dxtPath, 'manifest.json') if (!fs.existsSync(manifestPath)) { - logger.error('[DxtService] Manifest not found:', manifestPath) + logger.error('Manifest not found:', manifestPath) return null } @@ -330,14 +332,14 @@ class DxtService { const manifest: DxtManifest = JSON.parse(manifestContent) if (!manifest.server?.mcp_config) { - logger.error('[DxtService] No mcp_config found in manifest') + logger.error('No mcp_config found in manifest') return null } // Apply platform overrides and variable substitution const resolvedConfig = applyPlatformOverrides(manifest.server.mcp_config, dxtPath, userConfig) - logger.info('[DxtService] Resolved MCP config:', { + logger.debug('Resolved MCP config:', { command: resolvedConfig.command, args: resolvedConfig.args, env: resolvedConfig.env ? Object.keys(resolvedConfig.env) : undefined @@ -345,7 +347,7 @@ class DxtService { return resolvedConfig } catch (error) { - logger.error('[DxtService] Failed to resolve MCP config:', error) + logger.error('Failed to resolve MCP config:', error) return null } } @@ -360,7 +362,7 @@ class DxtService { // First try the sanitized path if (fs.existsSync(serverDir)) { - logger.info('[DxtService] Removing DXT server directory:', serverDir) + logger.debug('Removing DXT server directory:', serverDir) fs.rmSync(serverDir, { recursive: true, force: true }) return true } @@ -368,15 +370,15 @@ class DxtService { // Fallback: try with original name in case it was stored differently const originalServerDir = path.join(this.mcpDir, `server-${serverName}`) if (fs.existsSync(originalServerDir)) { - logger.info('[DxtService] Removing DXT server directory:', originalServerDir) + logger.debug('Removing DXT server directory:', originalServerDir) fs.rmSync(originalServerDir, { recursive: true, force: true }) return true } - logger.warn('[DxtService] Server directory not found:', serverDir) + logger.warn('Server directory not found:', serverDir) return false } catch (error) { - logger.error('[DxtService] Failed to cleanup DXT server:', error) + logger.error('Failed to cleanup DXT server:', error) return false } } @@ -388,7 +390,7 @@ class DxtService { fs.rmSync(this.tempDir, { recursive: true, force: true }) } } catch (error) { - logger.error('[DxtService] Cleanup error:', error) + logger.error('Cleanup error:', error) } } } diff --git a/src/main/services/ExportService.ts b/src/main/services/ExportService.ts index b17acc9bde..da6949dce9 100644 --- a/src/main/services/ExportService.ts +++ b/src/main/services/ExportService.ts @@ -1,6 +1,7 @@ /* eslint-disable no-case-declarations */ // ExportService +import { loggerService } from '@logger' import { AlignmentType, BorderStyle, @@ -18,11 +19,11 @@ import { WidthType } from 'docx' import { dialog } from 'electron' -import Logger from 'electron-log' import MarkdownIt from 'markdown-it' import FileStorage from './FileStorage' +const logger = loggerService.withContext('ExportService') export class ExportService { private fileManager: FileStorage private md: MarkdownIt @@ -399,10 +400,10 @@ export class ExportService { if (filePath) { await this.fileManager.writeFile(_, filePath, buffer) - Logger.info('[ExportService] Document exported successfully') + logger.debug('Document exported successfully') } } catch (error) { - Logger.error('[ExportService] Export to Word failed:', error) + logger.error('Export to Word failed:', error) throw error } } diff --git a/src/main/services/FileStorage.ts b/src/main/services/FileStorage.ts index 1adecd5733..c3ac8035ad 100644 --- a/src/main/services/FileStorage.ts +++ b/src/main/services/FileStorage.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { getFilesDir, getFileType, getTempDir, readTextFileWithAutoEncoding } from '@main/utils/file' import { documentExts, imageExts, MB } from '@shared/config/constant' import { FileMetadata } from '@types' @@ -10,7 +11,6 @@ import { SaveDialogReturnValue, shell } from 'electron' -import logger from 'electron-log' import * as fs from 'fs' import { writeFileSync } from 'fs' import { readFile } from 'fs/promises' @@ -21,6 +21,8 @@ import { chdir } from 'process' import { v4 as uuidv4 } from 'uuid' import WordExtractor from 'word-extractor' +const logger = loggerService.withContext('FileStorage') + class FileStorage { private storageDir = getFilesDir() private tempDir = getTempDir() @@ -38,7 +40,7 @@ class FileStorage { fs.mkdirSync(this.tempDir, { recursive: true }) } } catch (error) { - logger.error('[FileStorage] Failed to initialize storage directories:', error) + logger.error('Failed to initialize storage directories:', error) throw error } } @@ -136,9 +138,9 @@ class FileStorage { if (fileSizeInMB > 1) { try { await fs.promises.copyFile(sourcePath, destPath) - logger.info('[FileStorage] Image compressed successfully:', sourcePath) + logger.debug('Image compressed successfully:', sourcePath) } catch (jimpError) { - logger.error('[FileStorage] Image compression failed:', jimpError) + logger.error('Image compression failed:', jimpError) await fs.promises.copyFile(sourcePath, destPath) } } else { @@ -146,7 +148,7 @@ class FileStorage { await fs.promises.copyFile(sourcePath, destPath) } } catch (error) { - logger.error('[FileStorage] Image handling failed:', error) + logger.error('Image handling failed:', error) // 错误情况下直接复制原文件 await fs.promises.copyFile(sourcePath, destPath) } @@ -188,7 +190,7 @@ class FileStorage { count: 1 } - logger.info('[FileStorage] File uploaded:', fileMetadata) + logger.debug('File uploaded:', fileMetadata) return fileMetadata } @@ -257,7 +259,7 @@ class FileStorage { return data } catch (error) { chdir(originalCwd) - logger.error(error) + logger.error('Failed to read file:', error) throw error } } @@ -269,7 +271,7 @@ class FileStorage { return fs.readFileSync(filePath, 'utf-8') } } catch (error) { - logger.error(error) + logger.error('Failed to read file:', error) throw new Error(`Failed to read file: ${filePath}.`) } } @@ -319,7 +321,7 @@ class FileStorage { const ext = '.png' const destPath = path.join(this.storageDir, uuid + ext) - logger.info('[FileStorage] Saving base64 image:', { + logger.debug('Saving base64 image:', { storageDir: this.storageDir, destPath, bufferSize: buffer.length @@ -346,7 +348,7 @@ class FileStorage { return fileMetadata } catch (error) { - logger.error('[FileStorage] Failed to save base64 image:', error) + logger.error('Failed to save base64 image:', error) throw error } } @@ -560,7 +562,7 @@ class FileStorage { return fileMetadata } catch (error) { - logger.error('[FileStorage] Download file error:', error) + logger.error('Download file error:', error) throw error } } @@ -596,9 +598,9 @@ class FileStorage { // 复制文件 await fs.promises.copyFile(sourcePath, destPath) - logger.info('[FileStorage] File copied successfully:', { from: sourcePath, to: destPath }) + logger.debug('File copied successfully:', { from: sourcePath, to: destPath }) } catch (error) { - logger.error('[FileStorage] Copy file failed:', error) + logger.error('Copy file failed:', error) throw error } } @@ -606,18 +608,18 @@ class FileStorage { public writeFileWithId = async (_: Electron.IpcMainInvokeEvent, id: string, content: string): Promise => { try { const filePath = path.join(this.storageDir, id) - logger.info('[FileStorage] Writing file:', filePath) + logger.debug('Writing file:', filePath) // 确保目录存在 if (!fs.existsSync(this.storageDir)) { - logger.info('[FileStorage] Creating storage directory:', this.storageDir) + logger.debug('Creating storage directory:', this.storageDir) fs.mkdirSync(this.storageDir, { recursive: true }) } await fs.promises.writeFile(filePath, content, 'utf8') - logger.info('[FileStorage] File written successfully:', filePath) + logger.debug('File written successfully:', filePath) } catch (error) { - logger.error('[FileStorage] Failed to write file:', error) + logger.error('Failed to write file:', error) throw error } } diff --git a/src/main/services/KnowledgeService.ts b/src/main/services/KnowledgeService.ts index 736934cd1a..ef6115afec 100644 --- a/src/main/services/KnowledgeService.ts +++ b/src/main/services/KnowledgeService.ts @@ -21,6 +21,7 @@ import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' import { LibSqlDb } from '@cherrystudio/embedjs-libsql' import { SitemapLoader } from '@cherrystudio/embedjs-loader-sitemap' import { WebLoader } from '@cherrystudio/embedjs-loader-web' +import { loggerService } from '@logger' import OcrProvider from '@main/knowledage/ocr/OcrProvider' import PreprocessProvider from '@main/knowledage/preprocess/PreprocessProvider' import Embeddings from '@main/knowledge/embeddings/Embeddings' @@ -34,9 +35,10 @@ import { MB } from '@shared/config/constant' import type { LoaderReturn } from '@shared/config/types' import { IpcChannel } from '@shared/IpcChannel' import { FileMetadata, KnowledgeBaseParams, KnowledgeItem } from '@types' -import Logger from 'electron-log' import { v4 as uuidv4 } from 'uuid' +const logger = loggerService.withContext('KnowledgeService') + export interface KnowledgeBaseAddItemOptions { base: KnowledgeBaseParams item: KnowledgeItem @@ -137,7 +139,7 @@ class KnowledgeService { .setSearchResultCount(documentCount || 30) .build() } catch (e) { - Logger.error(e) + logger.error('Failed to create RAGApplication:', e) throw new Error(`Failed to create RAGApplication: ${e}`) } @@ -190,7 +192,7 @@ class KnowledgeService { return result }) .catch((e) => { - Logger.error(`Error in addFileLoader for ${file.name}: ${e}`) + logger.error(`Error in addFileLoader for ${file.name}: ${e}`) const errorResult: LoaderReturn = { ...KnowledgeService.ERROR_LOADER_RETURN, message: e.message, @@ -200,7 +202,7 @@ class KnowledgeService { return errorResult }) } catch (e: any) { - Logger.error(`Preprocessing failed for ${file.name}: ${e}`) + logger.error(`Preprocessing failed for ${file.name}: ${e}`) const errorResult: LoaderReturn = { ...KnowledgeService.ERROR_LOADER_RETURN, message: e.message, @@ -256,7 +258,7 @@ class KnowledgeService { return result }) .catch((err) => { - Logger.error(err) + logger.error('Failed to add dir loader:', err) return { ...KnowledgeService.ERROR_LOADER_RETURN, message: `Failed to add dir loader: ${err.message}`, @@ -306,7 +308,7 @@ class KnowledgeService { return result }) .catch((err) => { - Logger.error(err) + logger.error('Failed to add url loader:', err) return { ...KnowledgeService.ERROR_LOADER_RETURN, message: `Failed to add url loader: ${err.message}`, @@ -350,7 +352,7 @@ class KnowledgeService { return result }) .catch((err) => { - Logger.error(err) + logger.error('Failed to add sitemap loader:', err) return { ...KnowledgeService.ERROR_LOADER_RETURN, message: `Failed to add sitemap loader: ${err.message}`, @@ -400,7 +402,7 @@ class KnowledgeService { } }) .catch((err) => { - Logger.error(err) + logger.error('Failed to add note loader:', err) return { ...KnowledgeService.ERROR_LOADER_RETURN, message: `Failed to add note loader: ${err.message}`, @@ -508,7 +510,7 @@ class KnowledgeService { } }) .catch((err) => { - Logger.error(err) + logger.error('Failed to add item:', err) resolve({ ...KnowledgeService.ERROR_LOADER_RETURN, message: `Failed to add item: ${err.message}`, @@ -523,7 +525,7 @@ class KnowledgeService { { uniqueId, uniqueIds, base }: { uniqueId: string; uniqueIds: string[]; base: KnowledgeBaseParams } ): Promise => { const ragApplication = await this.getRagApplication(base) - Logger.log(`[ KnowledgeService Remove Item UniqueId: ${uniqueId}]`) + logger.debug(`Remove Item UniqueId: ${uniqueId}`) for (const id of uniqueIds) { await ragApplication.deleteLoader(id) } @@ -569,12 +571,12 @@ class KnowledgeService { // 首先检查文件是否已经被预处理过 const alreadyProcessed = await provider.checkIfAlreadyProcessed(file) if (alreadyProcessed) { - Logger.info(`File already preprocess processed, using cached result: ${file.path}`) + logger.debug(`File already preprocess processed, using cached result: ${file.path}`) return alreadyProcessed } // 执行预处理 - Logger.info(`Starting preprocess processing for scanned PDF: ${file.path}`) + logger.debug(`Starting preprocess processing for scanned PDF: ${file.path}`) const { processedFile, quota } = await provider.parseFile(item.id, file) fileToProcess = processedFile const mainWindow = windowService.getMainWindow() @@ -583,7 +585,7 @@ class KnowledgeService { quota: quota }) } catch (err) { - Logger.error(`Preprocess processing failed: ${err}`) + logger.error(`Preprocess processing failed: ${err}`) // 如果预处理失败,使用原始文件 // fileToProcess = file throw new Error(`Preprocess processing failed: ${err}`) @@ -605,7 +607,7 @@ class KnowledgeService { } throw new Error('No preprocess provider configured') } catch (err) { - Logger.error(`Failed to check quota: ${err}`) + logger.error(`Failed to check quota: ${err}`) throw new Error(`Failed to check quota: ${err}`) } } diff --git a/src/main/services/LoggerService.ts b/src/main/services/LoggerService.ts new file mode 100644 index 0000000000..6bf6e25b57 --- /dev/null +++ b/src/main/services/LoggerService.ts @@ -0,0 +1,272 @@ +import type { LogLevel, LogSourceWithContext } from '@shared/config/types' +import { IpcChannel } from '@shared/IpcChannel' +import { app, ipcMain } from 'electron' +import os from 'os' +import path from 'path' +import winston from 'winston' +import DailyRotateFile from 'winston-daily-rotate-file' + +import { isDev } from '../constant' + +const ANSICOLORS = { + RED: '\x1b[31m', + GREEN: '\x1b[32m', + YELLOW: '\x1b[33m', + BLUE: '\x1b[34m', + MAGENTA: '\x1b[35m', + CYAN: '\x1b[36m', + END: '\x1b[0m', + BOLD: '\x1b[1m', + ITALIC: '\x1b[3m', + UNDERLINE: '\x1b[4m' +} +function colorText(text: string, color: string) { + return ANSICOLORS[color] + text + ANSICOLORS.END +} + +const SYSTEM_INFO = { + os: `${os.platform()}-${os.arch()} / ${os.version()}`, + hw: `${os.cpus()[0]?.model || 'Unknown CPU'} / ${(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)}GB` +} +const APP_VERSION = `v${app?.getVersion?.() || 'unknown'}` + +const DEFAULT_LEVEL = isDev ? 'silly' : 'info' + +/** + * IMPORTANT: How to use LoggerService + * please refer to + * English: `docs/technical/how-to-use-logger-en.md` + * Chinese: `docs/technical/how-to-use-logger-zh.md` + */ +export class LoggerService { + private static instance: LoggerService + private logger: winston.Logger + + private logsDir: string = '' + + private module: string = '' + private context: Record = {} + + private constructor() { + // Create logs directory path + this.logsDir = path.join(app.getPath('userData'), 'logs') + + // Configure transports based on environment + const transports: winston.transport[] = [] + + //TODO remove when debug is done + // transports.push(new winston.transports.Console()) + + // Daily rotate file transport for general logs + transports.push( + new DailyRotateFile({ + filename: path.join(this.logsDir, 'app.%DATE%.log'), + datePattern: 'YYYY-MM-DD', + maxSize: '10m', + maxFiles: '30d' + }) + ) + + // Daily rotate file transport for error logs + transports.push( + new DailyRotateFile({ + level: 'warn', + filename: path.join(this.logsDir, 'app-error.%DATE%.log'), + datePattern: 'YYYY-MM-DD', + maxSize: '10m', + maxFiles: '60d' + }) + ) + + // Configure Winston logger + this.logger = winston.createLogger({ + level: DEFAULT_LEVEL, // Development: all levels, Production: info and above + format: winston.format.combine( + winston.format.splat(), + winston.format.timestamp({ + format: 'YYYY-MM-DD HH:mm:ss' + }), + winston.format.errors({ stack: true }), + winston.format.json() + ), + exitOnError: false, + transports + }) + + // Handle transport events + this.logger.on('error', (error) => { + console.error('LoggerService fatal error:', error) + }) + + //register ipc handler, for renderer process to log to main process + this.registerIpcHandler() + } + + public static getInstance(): LoggerService { + if (!LoggerService.instance) { + LoggerService.instance = new LoggerService() + } + return LoggerService.instance + } + + public withContext(module: string, context?: Record): LoggerService { + const newLogger = Object.create(this) + + // Copy all properties from the base logger + newLogger.logger = this.logger + newLogger.module = module + newLogger.context = { ...this.context, ...context } + + return newLogger + } + + public finish() { + this.logger.end() + } + + private processLog(source: LogSourceWithContext, level: LogLevel, message: string, meta: any[]): void { + if (isDev) { + const datetimeColored = colorText( + new Date().toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + fractionalSecondDigits: 3, + hour12: false + }), + 'CYAN' + ) + + console.log('processLog', source.process, this.module, this.context) + + let moduleString = '' + if (source.process === 'main') { + moduleString = this.module ? ` [${colorText(this.module, 'UNDERLINE')}] ` : ' ' + } else { + const combineString = `${source.window}:${source.module}` + moduleString = ` [${colorText(combineString, 'UNDERLINE')}] ` + } + + switch (level) { + case 'error': + console.error( + `${datetimeColored} ${colorText(colorText('', 'RED'), 'BOLD')}${moduleString}${message}`, + ...meta + ) + break + case 'warn': + console.warn( + `${datetimeColored} ${colorText(colorText('', 'YELLOW'), 'BOLD')}${moduleString}${message}`, + ...meta + ) + break + case 'info': + console.info( + `${datetimeColored} ${colorText(colorText('', 'GREEN'), 'BOLD')}${moduleString}${message}`, + ...meta + ) + break + case 'debug': + console.debug( + `${datetimeColored} ${colorText(colorText('', 'BLUE'), 'BOLD')}${moduleString}${message}`, + ...meta + ) + break + case 'verbose': + console.log(`${datetimeColored} ${colorText('', 'BOLD')}${moduleString}${message}`, ...meta) + break + case 'silly': + console.log(`${datetimeColored} ${colorText('', 'BOLD')}${moduleString}${message}`, ...meta) + break + } + } + + // add source information to meta + // renderer process has its own module and context, do not use this.module and this.context + const sourceWithContext: LogSourceWithContext = source + if (source.process === 'main') { + sourceWithContext.module = this.module + if (Object.keys(this.context).length > 0) { + sourceWithContext.context = this.context + } + } + meta.push(sourceWithContext) + + // add extra system information for error and warn levels + if (level === 'error' || level === 'warn') { + const extra = { + sys: SYSTEM_INFO, + appver: APP_VERSION + } + + meta.push(extra) + } + + this.logger.log(level, message, ...meta) + } + + public error(message: string, ...data: any[]): void { + this.processMainLog('error', message, data) + } + public warn(message: string, ...data: any[]): void { + this.processMainLog('warn', message, data) + } + public info(message: string, ...data: any[]): void { + this.processMainLog('info', message, data) + } + public verbose(message: string, ...data: any[]): void { + this.processMainLog('verbose', message, data) + } + public debug(message: string, ...data: any[]): void { + this.processMainLog('debug', message, data) + } + public silly(message: string, ...data: any[]): void { + this.processMainLog('silly', message, data) + } + + private processMainLog(level: LogLevel, message: string, data: any[]): void { + this.processLog({ process: 'main' }, level, message, data) + } + + // bind original this to become a callback function + private processRendererLog = (source: LogSourceWithContext, level: LogLevel, message: string, data: any[]): void => { + this.processLog(source, level, message, data) + } + + // Additional utility methods + public setLevel(level: string): void { + this.logger.level = level + } + + public getLevel(): string { + return this.logger.level + } + + // Method to reset log level to environment default + public resetLevel(): void { + this.setLevel(DEFAULT_LEVEL) + } + + // Method to get the underlying Winston logger instance + public getBaseLogger(): winston.Logger { + return this.logger + } + + public getLogsDir(): string { + return this.logsDir + } + + private registerIpcHandler(): void { + ipcMain.handle( + IpcChannel.App_LogToMain, + (_, source: LogSourceWithContext, level: LogLevel, message: string, data: any[]) => { + this.processRendererLog(source, level, message, data) + } + ) + } +} + +export const loggerService = LoggerService.getInstance() diff --git a/src/main/services/MCPService.ts b/src/main/services/MCPService.ts index 61959f1676..4e93664d7d 100644 --- a/src/main/services/MCPService.ts +++ b/src/main/services/MCPService.ts @@ -2,6 +2,7 @@ import crypto from 'node:crypto' import os from 'node:os' import path from 'node:path' +import { loggerService } from '@logger' import { createInMemoryMCPServer } from '@main/mcpServers/factory' import { makeSureDirExists } from '@main/utils' import { buildFunctionCallToolName } from '@main/utils/mcp' @@ -35,7 +36,6 @@ import { MCPTool } from '@types' import { app } from 'electron' -import Logger from 'electron-log' import { EventEmitter } from 'events' import { memoize } from 'lodash' import { v4 as uuidv4 } from 'uuid' @@ -49,6 +49,8 @@ import getLoginShellEnvironment from './mcp/shell-env' // Generic type for caching wrapped functions type CachedFunction = (...args: T) => Promise +const logger = loggerService.withContext('MCPService') + /** * Higher-order function to add caching capability to any async function * @param fn The original function to be wrapped with caching @@ -67,7 +69,7 @@ function withCache( const cacheKey = getCacheKey(...args) if (CacheService.has(cacheKey)) { - Logger.info(`${logPrefix} loaded from cache`) + logger.debug(`${logPrefix} loaded from cache`) const cachedData = CacheService.get(cacheKey) if (cachedData) { return cachedData @@ -130,7 +132,7 @@ class McpService { try { // Check if the existing client is still connected const pingResult = await existingClient.ping() - Logger.info(`[MCP] Ping result for ${server.name}:`, pingResult) + logger.debug(`Ping result for ${server.name}:`, pingResult) // If the ping fails, remove the client from the cache // and create a new one if (!pingResult) { @@ -139,7 +141,7 @@ class McpService { return existingClient } } catch (error: any) { - Logger.error(`[MCP] Error pinging server ${server.name}:`, error?.message) + logger.error(`Error pinging server ${server.name}:`, error?.message) this.clients.delete(serverKey) } } @@ -165,15 +167,15 @@ class McpService { > => { // Create appropriate transport based on configuration if (server.type === 'inMemory') { - Logger.info(`[MCP] Using in-memory transport for server: ${server.name}`) + logger.debug(`Using in-memory transport for server: ${server.name}`) const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair() // start the in-memory server with the given name and environment variables const inMemoryServer = createInMemoryMCPServer(server.name, args, server.env || {}) try { await inMemoryServer.connect(serverTransport) - Logger.info(`[MCP] In-memory server started: ${server.name}`) + logger.debug(`In-memory server started: ${server.name}`) } catch (error: Error | any) { - Logger.error(`[MCP] Error starting in-memory server: ${error}`) + logger.error(`Error starting in-memory server: ${error}`) throw new Error(`Failed to start in-memory server: ${error.message}`) } // set the client transport to the client @@ -201,7 +203,7 @@ class McpService { headers['Authorization'] = `Bearer ${tokens.access_token}` } } catch (error) { - Logger.error('Failed to fetch tokens:', error) + logger.error('Failed to fetch tokens:', error) } } @@ -231,15 +233,15 @@ class McpService { ...server.env, ...resolvedConfig.env } - Logger.info(`[MCP] Using resolved DXT config - command: ${cmd}, args: ${args?.join(' ')}`) + logger.debug(`Using resolved DXT config - command: ${cmd}, args: ${args?.join(' ')}`) } else { - Logger.warn(`[MCP] Failed to resolve DXT config for ${server.name}, falling back to manifest values`) + logger.warn(`Failed to resolve DXT config for ${server.name}, falling back to manifest values`) } } if (server.command === 'npx') { cmd = await getBinaryPath('bun') - Logger.info(`[MCP] Using command: ${cmd}`) + logger.debug(`Using command: ${cmd}`) // add -x to args if args exist if (args && args.length > 0) { @@ -274,7 +276,7 @@ class McpService { } } - Logger.info(`[MCP] Starting server with command: ${cmd} ${args ? args.join(' ') : ''}`) + logger.debug(`Starting server with command: ${cmd} ${args ? args.join(' ') : ''}`) // Logger.info(`[MCP] Environment variables for server:`, server.env) const loginShellEnv = await this.getLoginShellEnv() @@ -296,12 +298,12 @@ class McpService { // For DXT servers, set the working directory to the extracted path if (server.dxtPath) { transportOptions.cwd = server.dxtPath - Logger.info(`[MCP] Setting working directory for DXT server: ${server.dxtPath}`) + logger.debug(`Setting working directory for DXT server: ${server.dxtPath}`) } const stdioTransport = new StdioClientTransport(transportOptions) stdioTransport.stderr?.on('data', (data) => - Logger.info(`[MCP] Stdio stderr for server: ${server.name} `, data.toString()) + logger.debug(`Stdio stderr for server: ${server.name}` + data.toString()) ) return stdioTransport } else { @@ -310,7 +312,7 @@ class McpService { } const handleAuth = async (client: Client, transport: SSEClientTransport | StreamableHTTPClientTransport) => { - Logger.info(`[MCP] Starting OAuth flow for server: ${server.name}`) + logger.debug(`Starting OAuth flow for server: ${server.name}`) // Create an event emitter for the OAuth callback const events = new EventEmitter() @@ -323,27 +325,27 @@ class McpService { // Set a timeout to close the callback server const timeoutId = setTimeout(() => { - Logger.warn(`[MCP] OAuth flow timed out for server: ${server.name}`) + logger.warn(`OAuth flow timed out for server: ${server.name}`) callbackServer.close() }, 300000) // 5 minutes timeout try { // Wait for the authorization code const authCode = await callbackServer.waitForAuthCode() - Logger.info(`[MCP] Received auth code: ${authCode}`) + logger.debug(`Received auth code: ${authCode}`) // Complete the OAuth flow await transport.finishAuth(authCode) - Logger.info(`[MCP] OAuth flow completed for server: ${server.name}`) + logger.debug(`OAuth flow completed for server: ${server.name}`) const newTransport = await initTransport() // Try to connect again await client.connect(newTransport) - Logger.info(`[MCP] Successfully authenticated with server: ${server.name}`) + logger.debug(`Successfully authenticated with server: ${server.name}`) } catch (oauthError) { - Logger.error(`[MCP] OAuth authentication failed for server ${server.name}:`, oauthError) + logger.error(`OAuth authentication failed for server ${server.name}:`, oauthError) throw new Error( `OAuth authentication failed: ${oauthError instanceof Error ? oauthError.message : String(oauthError)}` ) @@ -363,7 +365,7 @@ class McpService { error instanceof Error && (error.name === 'UnauthorizedError' || error.message.includes('Unauthorized')) ) { - Logger.info(`[MCP] Authentication required for server: ${server.name}`) + logger.debug(`Authentication required for server: ${server.name}`) await handleAuth(client, transport as SSEClientTransport | StreamableHTTPClientTransport) } else { throw error @@ -379,10 +381,16 @@ class McpService { // Clear existing cache to ensure fresh data this.clearServerCache(serverKey) - Logger.info(`[MCP] Activated server: ${server.name}`) + // Set up notification handlers + this.setupNotificationHandlers(client, server) + + // Clear existing cache to ensure fresh data + this.clearServerCache(serverKey) + + logger.debug(`Activated server: ${server.name}`) return client } catch (error: any) { - Logger.error(`[MCP] Error activating server ${server.name}:`, error?.message) + logger.error(`Error activating server ${server.name}:`, error?.message) throw new Error(`[MCP] Error activating server ${server.name}: ${error.message}`) } } finally { @@ -406,50 +414,50 @@ class McpService { try { // Set up tools list changed notification handler client.setNotificationHandler(ToolListChangedNotificationSchema, async () => { - Logger.info(`[MCP] Tools list changed for server: ${server.name}`) + logger.debug(`Tools list changed for server: ${server.name}`) // Clear tools cache CacheService.remove(`mcp:list_tool:${serverKey}`) }) // Set up resources list changed notification handler client.setNotificationHandler(ResourceListChangedNotificationSchema, async () => { - Logger.info(`[MCP] Resources list changed for server: ${server.name}`) + logger.debug(`Resources list changed for server: ${server.name}`) // Clear resources cache CacheService.remove(`mcp:list_resources:${serverKey}`) }) // Set up prompts list changed notification handler client.setNotificationHandler(PromptListChangedNotificationSchema, async () => { - Logger.info(`[MCP] Prompts list changed for server: ${server.name}`) + logger.debug(`Prompts list changed for server: ${server.name}`) // Clear prompts cache CacheService.remove(`mcp:list_prompts:${serverKey}`) }) // Set up resource updated notification handler client.setNotificationHandler(ResourceUpdatedNotificationSchema, async () => { - Logger.info(`[MCP] Resource updated for server: ${server.name}`) + logger.debug(`Resource updated for server: ${server.name}`) // Clear resource-specific caches this.clearResourceCaches(serverKey) }) // Set up progress notification handler client.setNotificationHandler(ProgressNotificationSchema, async (notification) => { - Logger.info(`[MCP] Progress notification received for server: ${server.name}`, notification.params) + logger.debug(`Progress notification received for server: ${server.name}`, notification.params) }) // Set up cancelled notification handler client.setNotificationHandler(CancelledNotificationSchema, async (notification) => { - Logger.info(`[MCP] Operation cancelled for server: ${server.name}`, notification.params) + logger.debug(`Operation cancelled for server: ${server.name}`, notification.params) }) // Set up logging message notification handler client.setNotificationHandler(LoggingMessageNotificationSchema, async (notification) => { - Logger.info(`[MCP] Message from server ${server.name}:`, notification.params) + logger.debug(`Message from server ${server.name}:`, notification.params) }) - Logger.info(`[MCP] Set up notification handlers for server: ${server.name}`) + logger.debug(`Set up notification handlers for server: ${server.name}`) } catch (error) { - Logger.error(`[MCP] Failed to set up notification handlers for server ${server.name}:`, error) + logger.error(`Failed to set up notification handlers for server ${server.name}:`, error) } } @@ -467,7 +475,7 @@ class McpService { CacheService.remove(`mcp:list_tool:${serverKey}`) CacheService.remove(`mcp:list_prompts:${serverKey}`) CacheService.remove(`mcp:list_resources:${serverKey}`) - Logger.info(`[MCP] Cleared all caches for server: ${serverKey}`) + logger.debug(`Cleared all caches for server: ${serverKey}`) } async closeClient(serverKey: string) { @@ -475,18 +483,18 @@ class McpService { if (client) { // Remove the client from the cache await client.close() - Logger.info(`[MCP] Closed server: ${serverKey}`) + logger.debug(`Closed server: ${serverKey}`) this.clients.delete(serverKey) // Clear all caches for this server this.clearServerCache(serverKey) } else { - Logger.warn(`[MCP] No client found for server: ${serverKey}`) + logger.warn(`No client found for server: ${serverKey}`) } } async stopServer(_: Electron.IpcMainInvokeEvent, server: MCPServer) { const serverKey = this.getServerKey(server) - Logger.info(`[MCP] Stopping server: ${server.name}`) + logger.debug(`Stopping server: ${server.name}`) await this.closeClient(serverKey) } @@ -502,16 +510,16 @@ class McpService { try { const cleaned = this.dxtService.cleanupDxtServer(server.name) if (cleaned) { - Logger.info(`[MCP] Cleaned up DXT server directory for: ${server.name}`) + logger.debug(`Cleaned up DXT server directory for: ${server.name}`) } } catch (error) { - Logger.error(`[MCP] Failed to cleanup DXT server: ${server.name}`, error) + logger.error(`Failed to cleanup DXT server: ${server.name}`, error) } } } async restartServer(_: Electron.IpcMainInvokeEvent, server: MCPServer) { - Logger.info(`[MCP] Restarting server: ${server.name}`) + logger.debug(`Restarting server: ${server.name}`) const serverKey = this.getServerKey(server) await this.closeClient(serverKey) // Clear cache before restarting to ensure fresh data @@ -524,7 +532,7 @@ class McpService { try { await this.closeClient(key) } catch (error: any) { - Logger.error(`[MCP] Failed to close client: ${error?.message}`) + logger.error(`Failed to close client: ${error?.message}`) } } } @@ -533,9 +541,9 @@ class McpService { * Check connectivity for an MCP server */ public async checkMcpConnectivity(_: Electron.IpcMainInvokeEvent, server: MCPServer): Promise { - Logger.info(`[MCP] Checking connectivity for server: ${server.name}`) + logger.debug(`Checking connectivity for server: ${server.name}`) try { - Logger.info(`[MCP] About to call initClient for server: ${server.name}`, { hasInitClient: !!this.initClient }) + logger.debug(`About to call initClient for server: ${server.name}`, { hasInitClient: !!this.initClient }) if (!this.initClient) { throw new Error('initClient method is not available') @@ -544,10 +552,10 @@ class McpService { const client = await this.initClient(server) // Attempt to list tools as a way to check connectivity await client.listTools() - Logger.info(`[MCP] Connectivity check successful for server: ${server.name}`) + logger.debug(`Connectivity check successful for server: ${server.name}`) return true } catch (error) { - Logger.error(`[MCP] Connectivity check failed for server: ${server.name}`, error) + logger.error(`Connectivity check failed for server: ${server.name}`, error) // Close the client if connectivity check fails to ensure a clean state for the next attempt const serverKey = this.getServerKey(server) await this.closeClient(serverKey) @@ -556,7 +564,7 @@ class McpService { } private async listToolsImpl(server: MCPServer): Promise { - Logger.info(`[MCP] Listing tools for server: ${server.name}`) + logger.debug(`Listing tools for server: ${server.name}`) const client = await this.initClient(server) try { const { tools } = await client.listTools() @@ -572,7 +580,7 @@ class McpService { }) return serverTools } catch (error: any) { - Logger.error(`[MCP] Failed to list tools for server: ${server.name}`, error?.message) + logger.error(`Failed to list tools for server: ${server.name}`, error?.message) return [] } } @@ -603,12 +611,12 @@ class McpService { this.activeToolCalls.set(toolCallId, abortController) try { - Logger.info('[MCP] Calling:', server.name, name, args, 'callId:', toolCallId) + logger.debug('Calling:', server.name, name, args, 'callId:', toolCallId) if (typeof args === 'string') { try { args = JSON.parse(args) } catch (e) { - Logger.error('[MCP] args parse error', args) + logger.error('args parse error', args) } } const client = await this.initClient(server) @@ -622,7 +630,7 @@ class McpService { }) return result as MCPCallToolResponse } catch (error) { - Logger.error(`[MCP] Error calling tool ${name} on ${server.name}:`, error) + logger.error(`Error calling tool ${name} on ${server.name}:`, error) throw error } finally { this.activeToolCalls.delete(toolCallId) @@ -643,7 +651,7 @@ class McpService { */ private async listPromptsImpl(server: MCPServer): Promise { const client = await this.initClient(server) - Logger.info(`[MCP] Listing prompts for server: ${server.name}`) + logger.debug(`Listing prompts for server: ${server.name}`) try { const { prompts } = await client.listPrompts() return prompts.map((prompt: any) => ({ @@ -655,7 +663,7 @@ class McpService { } catch (error: any) { // -32601 is the code for the method not found if (error?.code !== -32601) { - Logger.error(`[MCP] Failed to list prompts for server: ${server.name}`, error?.message) + logger.error(`Failed to list prompts for server: ${server.name}`, error?.message) } return [] } @@ -685,7 +693,7 @@ class McpService { name: string, args?: Record ): Promise { - Logger.info(`[MCP] Getting prompt ${name} from server: ${server.name}`) + logger.debug(`Getting prompt ${name} from server: ${server.name}`) const client = await this.initClient(server) return await client.getPrompt({ name, arguments: args }) } @@ -715,7 +723,7 @@ class McpService { */ private async listResourcesImpl(server: MCPServer): Promise { const client = await this.initClient(server) - Logger.info(`[MCP] Listing resources for server: ${server.name}`) + logger.debug(`Listing resources for server: ${server.name}`) try { const result = await client.listResources() const resources = result.resources || [] @@ -727,7 +735,7 @@ class McpService { } catch (error: any) { // -32601 is the code for the method not found if (error?.code !== -32601) { - Logger.error(`[MCP] Failed to list resources for server: ${server.name}`, error?.message) + logger.error(`Failed to list resources for server: ${server.name}`, error?.message) } return [] } @@ -753,7 +761,7 @@ class McpService { * Get a specific resource from an MCP server (implementation) */ private async getResourceImpl(server: MCPServer, uri: string): Promise { - Logger.info(`[MCP] Getting resource ${uri} from server: ${server.name}`) + logger.debug(`Getting resource ${uri} from server: ${server.name}`) const client = await this.initClient(server) try { const result = await client.readResource({ uri: uri }) @@ -771,7 +779,7 @@ class McpService { contents: contents } } catch (error: Error | any) { - Logger.error(`[MCP] Failed to get resource ${uri} from server: ${server.name}`, error.message) + logger.error(`Failed to get resource ${uri} from server: ${server.name}`, error.message) throw new Error(`Failed to get resource ${uri} from server: ${server.name}: ${error.message}`) } } @@ -801,10 +809,10 @@ class McpService { const pathSeparator = process.platform === 'win32' ? ';' : ':' const cherryBinPath = path.join(os.homedir(), '.cherrystudio', 'bin') loginEnv.PATH = `${loginEnv.PATH}${pathSeparator}${cherryBinPath}` - Logger.info('[MCP] Successfully fetched login shell environment variables:') + logger.debug('Successfully fetched login shell environment variables:') return loginEnv } catch (error) { - Logger.error('[MCP] Failed to fetch login shell environment variables:', error) + logger.error('Failed to fetch login shell environment variables:', error) return {} } }) @@ -823,10 +831,10 @@ class McpService { if (activeToolCall) { activeToolCall.abort() this.activeToolCalls.delete(callId) - Logger.info(`[MCP] Aborted tool call: ${callId}`) + logger.debug(`Aborted tool call: ${callId}`) return true } else { - Logger.warn(`[MCP] No active tool call found for callId: ${callId}`) + logger.warn(`No active tool call found for callId: ${callId}`) return false } } @@ -836,22 +844,22 @@ class McpService { */ public async getServerVersion(_: Electron.IpcMainInvokeEvent, server: MCPServer): Promise { try { - Logger.info(`[MCP] Getting server version for: ${server.name}`) + logger.debug(`Getting server version for: ${server.name}`) const client = await this.initClient(server) // Try to get server information which may include version const serverInfo = client.getServerVersion() - Logger.info(`[MCP] Server info for ${server.name}:`, serverInfo) + logger.debug(`Server info for ${server.name}:`, serverInfo) if (serverInfo && serverInfo.version) { - Logger.info(`[MCP] Server version for ${server.name}: ${serverInfo.version}`) + logger.debug(`Server version for ${server.name}: ${serverInfo.version}`) return serverInfo.version } - Logger.warn(`[MCP] No version information available for server: ${server.name}`) + logger.warn(`No version information available for server: ${server.name}`) return null } catch (error: any) { - Logger.error(`[MCP] Failed to get server version for ${server.name}:`, error?.message) + logger.error(`Failed to get server version for ${server.name}:`, error?.message) return null } } diff --git a/src/main/services/NutstoreService.ts b/src/main/services/NutstoreService.ts index 5f256f52c3..ac8cb3b70d 100644 --- a/src/main/services/NutstoreService.ts +++ b/src/main/services/NutstoreService.ts @@ -1,5 +1,6 @@ import path from 'node:path' +import { loggerService } from '@logger' import { NUTSTORE_HOST } from '@shared/config/nutstore' import { XMLParser } from 'fast-xml-parser' import { isNil, partial } from 'lodash' @@ -7,6 +8,8 @@ import { type FileStat } from 'webdav' import { createOAuthUrl, decryptSecret } from '../integration/nutstore/sso/lib/index.mjs' +const logger = loggerService.withContext('NutstoreService') + interface OAuthResponse { username: string userid: string @@ -45,7 +48,7 @@ export async function decryptToken(token: string) { }) return JSON.parse(decrypted) as OAuthResponse } catch (error) { - console.error('解密失败:', error) + logger.error('Failed to decrypt token:', error) return null } } diff --git a/src/main/services/ObsidianVaultService.ts b/src/main/services/ObsidianVaultService.ts index 0f9b33c475..894bdc8864 100644 --- a/src/main/services/ObsidianVaultService.ts +++ b/src/main/services/ObsidianVaultService.ts @@ -1,8 +1,9 @@ +import { loggerService } from '@logger' import { app } from 'electron' -import Logger from 'electron-log' import fs from 'fs' import path from 'path' +const logger = loggerService.withContext('ObsidianVaultService') interface VaultInfo { path: string name: string @@ -56,7 +57,7 @@ class ObsidianVaultService { name: vault.name || path.basename(vault.path) })) } catch (error) { - console.error('获取Obsidian Vault失败:', error) + logger.error('Failed to get Obsidian Vault:', error) return [] } } @@ -70,20 +71,20 @@ class ObsidianVaultService { try { // 检查vault路径是否存在 if (!fs.existsSync(vaultPath)) { - console.error('Vault路径不存在:', vaultPath) + logger.error('Vault path does not exist:', vaultPath) return [] } // 检查是否是目录 const stats = fs.statSync(vaultPath) if (!stats.isDirectory()) { - console.error('Vault路径不是一个目录:', vaultPath) + logger.error('Vault path is not a directory:', vaultPath) return [] } this.traverseDirectory(vaultPath, '', results) } catch (error) { - console.error('读取Vault文件夹结构失败:', error) + logger.error('Failed to read Vault folder structure:', error) } return results @@ -105,7 +106,7 @@ class ObsidianVaultService { // 确保目录存在且可访问 if (!fs.existsSync(dirPath)) { - console.error('目录不存在:', dirPath) + logger.error('Directory does not exist:', dirPath) return } @@ -113,7 +114,7 @@ class ObsidianVaultService { try { items = fs.readdirSync(dirPath, { withFileTypes: true }) } catch (err) { - console.error(`无法读取目录 ${dirPath}:`, err) + logger.error(`Failed to read directory ${dirPath}:`, err) return } @@ -138,7 +139,7 @@ class ObsidianVaultService { } } } catch (error) { - console.error(`遍历目录出错 ${dirPath}:`, error) + logger.error(`Failed to traverse directory ${dirPath}:`, error) } } @@ -152,14 +153,14 @@ class ObsidianVaultService { const vault = vaults.find((v) => v.name === vaultName) if (!vault) { - console.error('未找到指定名称的Vault:', vaultName) + logger.error('Vault not found:', vaultName) return [] } - Logger.log('获取Vault文件结构:', vault.name, vault.path) + logger.debug('Get Vault file structure:', vault.name, vault.path) return this.getVaultStructure(vault.path) } catch (error) { - console.error('获取Vault文件结构时发生错误:', error) + logger.error('Failed to get Vault file structure:', error) return [] } } diff --git a/src/main/services/ProtocolClient.ts b/src/main/services/ProtocolClient.ts index cac0983fd6..086f51b154 100644 --- a/src/main/services/ProtocolClient.ts +++ b/src/main/services/ProtocolClient.ts @@ -3,13 +3,15 @@ import fs from 'node:fs/promises' import path from 'node:path' import { promisify } from 'node:util' +import { loggerService } from '@logger' import { app } from 'electron' -import Logger from 'electron-log' import { handleProvidersProtocolUrl } from './urlschema/handle-providers' import { handleMcpProtocolUrl } from './urlschema/mcp-install' import { windowService } from './WindowService' +const logger = loggerService.withContext('ProtocolClient') + export const CHERRY_STUDIO_PROTOCOL = 'cherrystudio' export function registerProtocolClient(app: Electron.App) { @@ -65,12 +67,12 @@ export async function setupAppImageDeepLink(): Promise { return } - Logger.info('AppImage environment detected on Linux, setting up deep link.') + logger.debug('AppImage environment detected on Linux, setting up deep link.') try { const appPath = app.getPath('exe') if (!appPath) { - Logger.error('Could not determine App path.') + logger.error('Could not determine App path.') return } @@ -95,24 +97,24 @@ NoDisplay=true // Write the .desktop file (overwrite if exists) await fs.writeFile(desktopFilePath, desktopFileContent, 'utf-8') - Logger.info(`Created/Updated desktop file: ${desktopFilePath}`) + logger.debug(`Created/Updated desktop file: ${desktopFilePath}`) // Update the desktop database // It's important to update the database for the changes to take effect try { const { stdout, stderr } = await execAsync(`update-desktop-database ${escapePathForExec(applicationsDir)}`) if (stderr) { - Logger.warn(`update-desktop-database stderr: ${stderr}`) + logger.warn(`update-desktop-database stderr: ${stderr}`) } - Logger.info(`update-desktop-database stdout: ${stdout}`) - Logger.info('Desktop database updated successfully.') + logger.debug(`update-desktop-database stdout: ${stdout}`) + logger.debug('Desktop database updated successfully.') } catch (updateError) { - Logger.error('Failed to update desktop database:', updateError) + logger.error('Failed to update desktop database:', updateError) // Continue even if update fails, as the file is still created. } } catch (error) { // Log the error but don't prevent the app from starting - Logger.error('Failed to setup AppImage deep link:', error) + logger.error('Failed to setup AppImage deep link:', error) } } diff --git a/src/main/services/ProxyManager.ts b/src/main/services/ProxyManager.ts index a2936e37dc..60bef773c4 100644 --- a/src/main/services/ProxyManager.ts +++ b/src/main/services/ProxyManager.ts @@ -1,6 +1,6 @@ +import { loggerService } from '@logger' import axios from 'axios' import { app, ProxyConfig, session } from 'electron' -import Logger from 'electron-log' import { socksDispatcher } from 'fetch-socks' import http from 'http' import https from 'https' @@ -8,6 +8,8 @@ import { getSystemProxy } from 'os-proxy-config' import { ProxyAgent } from 'proxy-agent' import { Dispatcher, EnvHttpProxyAgent, getGlobalDispatcher, setGlobalDispatcher } from 'undici' +const logger = loggerService.withContext('ProxyManager') + export class ProxyManager { private config: ProxyConfig = { mode: 'direct' } private systemProxyInterval: NodeJS.Timeout | null = null @@ -59,7 +61,7 @@ export class ProxyManager { } async configureProxy(config: ProxyConfig): Promise { - Logger.info('configureProxy', config.mode, config.proxyRules) + logger.info('configureProxy', config.mode, config.proxyRules) if (this.isSettingProxy) { return } @@ -68,7 +70,7 @@ export class ProxyManager { try { if (config?.mode === this.config?.mode && config?.proxyRules === this.config?.proxyRules) { - Logger.info('proxy config is the same, skip configure') + logger.info('proxy config is the same, skip configure') return } @@ -77,7 +79,7 @@ export class ProxyManager { if (config.mode === 'system') { const currentProxy = await getSystemProxy() if (currentProxy) { - Logger.info('current system proxy', currentProxy.proxyUrl) + logger.info('current system proxy', currentProxy.proxyUrl) this.config.proxyRules = currentProxy.proxyUrl.toLowerCase() this.monitorSystemProxy() } else { @@ -88,7 +90,7 @@ export class ProxyManager { this.setGlobalProxy() } catch (error) { - Logger.error('Failed to config proxy:', error) + logger.error('Failed to config proxy:', error) throw error } finally { this.isSettingProxy = false diff --git a/src/main/services/ReduxService.ts b/src/main/services/ReduxService.ts index 3cddd0e947..44ff02c0dc 100644 --- a/src/main/services/ReduxService.ts +++ b/src/main/services/ReduxService.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { IpcChannel } from '@shared/IpcChannel' import { ipcMain } from 'electron' import { EventEmitter } from 'events' @@ -7,6 +8,8 @@ import { windowService } from './WindowService' type StoreValue = any type Unsubscribe = () => void +const logger = loggerService.withContext('ReduxService') + export class ReduxService extends EventEmitter { private stateCache: any = {} private isReady = false @@ -65,7 +68,7 @@ export class ReduxService extends EventEmitter { const selectorFn = new Function('state', `return ${selector}`) return selectorFn(this.stateCache) } catch (error) { - console.error('Failed to select from cache:', error) + logger.error('Failed to select from cache:', error) return undefined } } @@ -94,7 +97,7 @@ export class ReduxService extends EventEmitter { })() `) } catch (error) { - console.error('Failed to select store value:', error) + logger.error('Failed to select store value:', error) throw error } } @@ -111,7 +114,7 @@ export class ReduxService extends EventEmitter { window.store.dispatch(${JSON.stringify(action)}) `) } catch (error) { - console.error('Failed to dispatch action:', error) + logger.error('Failed to dispatch action:', error) throw error } } @@ -149,7 +152,7 @@ export class ReduxService extends EventEmitter { const newValue = await this.select(selector) callback(newValue) } catch (error) { - console.error('Error in subscription handler:', error) + logger.error('Error in subscription handler:', error) } } @@ -171,7 +174,7 @@ export class ReduxService extends EventEmitter { window.store.getState() `) } catch (error) { - console.error('Failed to get state:', error) + logger.error('Failed to get state:', error) throw error } } @@ -191,7 +194,7 @@ export const reduxService = new ReduxService() try { // 读取状态 const settings = await reduxService.select('state.settings') - Logger.log('settings', settings) + logger.log('settings', settings) // 派发 action await reduxService.dispatch({ @@ -201,7 +204,7 @@ export const reduxService = new ReduxService() // 订阅状态变化 const unsubscribe = await reduxService.subscribe('state.settings.apiKey', (newValue) => { - Logger.log('API key changed:', newValue) + logger.log('API key changed:', newValue) }) // 批量执行 actions @@ -212,16 +215,16 @@ export const reduxService = new ReduxService() // 同步方法虽然可能不是最新的数据,但响应更快 const apiKey = reduxService.selectSync('state.settings.apiKey') - Logger.log('apiKey', apiKey) + logger.log('apiKey', apiKey) // 处理保证是最新的数据 const apiKey1 = await reduxService.select('state.settings.apiKey') - Logger.log('apiKey1', apiKey1) + logger.log('apiKey1', apiKey1) // 取消订阅 unsubscribe() } catch (error) { - Logger.error('Error:', error) + logger.error('Error:', error) } } */ diff --git a/src/main/services/S3Storage.ts b/src/main/services/S3Storage.ts index 0b45bb0387..6ce9cb6e23 100644 --- a/src/main/services/S3Storage.ts +++ b/src/main/services/S3Storage.ts @@ -6,11 +6,13 @@ import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3' +import { loggerService } from '@logger' import type { S3Config } from '@types' -import Logger from 'electron-log' import * as net from 'net' import { Readable } from 'stream' +const logger = loggerService.withContext('S3Storage') + /** * 将可读流转换为 Buffer */ @@ -50,7 +52,7 @@ export default class S3Storage { const isInWhiteList = VIRTUAL_HOST_SUFFIXES.some((suffix) => hostname.endsWith(suffix)) return !isInWhiteList } catch (e) { - Logger.warn('[S3Storage] Failed to parse endpoint, fallback to Path-Style:', endpoint, e) + logger.warn('[S3Storage] Failed to parse endpoint, fallback to Path-Style:', endpoint, e) return true } })() @@ -96,7 +98,7 @@ export default class S3Storage { }) ) } catch (error) { - Logger.error('[S3Storage] Error putting object:', error) + logger.error('[S3Storage] Error putting object:', error) throw error } } @@ -109,7 +111,7 @@ export default class S3Storage { } return await streamToBuffer(res.Body as Readable) } catch (error) { - Logger.error('[S3Storage] Error getting object:', error) + logger.error('[S3Storage] Error getting object:', error) throw error } } @@ -126,7 +128,7 @@ export default class S3Storage { } } } catch (error) { - Logger.error('[S3Storage] Error deleting object:', error) + logger.error('[S3Storage] Error deleting object:', error) throw error } } @@ -163,7 +165,7 @@ export default class S3Storage { return files } catch (error) { - Logger.error('[S3Storage] Error listing objects:', error) + logger.error('[S3Storage] Error listing objects:', error) throw error } } @@ -176,7 +178,7 @@ export default class S3Storage { await this.client.send(new HeadBucketCommand({ Bucket: this.bucket })) return true } catch (error) { - Logger.error('[S3Storage] Error checking connection:', error) + logger.error('[S3Storage] Error checking connection:', error) throw error } } diff --git a/src/main/services/SelectionService.ts b/src/main/services/SelectionService.ts index 21520a3735..f20dc067d2 100644 --- a/src/main/services/SelectionService.ts +++ b/src/main/services/SelectionService.ts @@ -1,8 +1,8 @@ +import { loggerService } from '@logger' import { SELECTION_FINETUNED_LIST, SELECTION_PREDEFINED_BLACKLIST } from '@main/configs/SelectionConfig' import { isDev, isMac, isWin } from '@main/constant' import { IpcChannel } from '@shared/IpcChannel' import { app, BrowserWindow, ipcMain, screen, systemPreferences } from 'electron' -import Logger from 'electron-log' import { join } from 'path' import type { KeyboardEventData, @@ -16,6 +16,8 @@ import type { ActionItem } from '../../renderer/src/types/selectionTypes' import { ConfigKeys, configManager } from './ConfigManager' import storeSyncService from './StoreSyncService' +const logger = loggerService.withContext('SelectionService') + const isSupportedOS = isWin || isMac let SelectionHook: SelectionHookConstructor | null = null @@ -25,7 +27,7 @@ try { SelectionHook = require('selection-hook') } } catch (error) { - Logger.error('Failed to load selection-hook:', error) + logger.error('Failed to load selection-hook:', error) } // Type definitions @@ -1504,12 +1506,12 @@ export class SelectionService { private logInfo(message: string, forceShow: boolean = false): void { if (isDev || forceShow) { - Logger.info('[SelectionService] Info: ', message) + logger.info(message) } } private logError(...args: [...string[], Error]): void { - Logger.error('[SelectionService] Error: ', ...args) + logger.error('[SelectionService] Error: ', ...args) } } @@ -1525,7 +1527,7 @@ export function initSelectionService(): boolean { //avoid closure const ss = SelectionService.getInstance() if (!ss) { - Logger.error('SelectionService not initialized: instance is null') + logger.error('SelectionService not initialized: instance is null') return } @@ -1540,7 +1542,7 @@ export function initSelectionService(): boolean { const ss = SelectionService.getInstance() if (!ss) { - Logger.error('SelectionService not initialized: instance is null') + logger.error('SelectionService not initialized: instance is null') return false } diff --git a/src/main/services/ShortcutService.ts b/src/main/services/ShortcutService.ts index 92544e17c0..12f786d797 100644 --- a/src/main/services/ShortcutService.ts +++ b/src/main/services/ShortcutService.ts @@ -1,12 +1,14 @@ +import { loggerService } from '@logger' import { handleZoomFactor } from '@main/utils/zoom' import { Shortcut } from '@types' import { BrowserWindow, globalShortcut } from 'electron' -import Logger from 'electron-log' import { configManager } from './ConfigManager' import selectionService from './SelectionService' import { windowService } from './WindowService' +const logger = loggerService.withContext('ShortcutService') + let showAppAccelerator: string | null = null let showMiniWindowAccelerator: string | null = null let selectionAssistantToggleAccelerator: string | null = null @@ -222,7 +224,7 @@ export function registerShortcuts(window: BrowserWindow) { globalShortcut.register(accelerator, () => handler(window)) } catch (error) { - Logger.error(`[ShortcutService] Failed to register shortcut ${shortcut.key}`) + logger.warn(`Failed to register shortcut ${shortcut.key}`) } }) } @@ -257,7 +259,7 @@ export function registerShortcuts(window: BrowserWindow) { handler && globalShortcut.register(accelerator, () => handler(window)) } } catch (error) { - Logger.error('[ShortcutService] Failed to unregister shortcuts') + logger.warn('Failed to unregister shortcuts') } } @@ -290,6 +292,6 @@ export function unregisterAllShortcuts() { windowOnHandlers.clear() globalShortcut.unregisterAll() } catch (error) { - Logger.error('[ShortcutService] Failed to unregister all shortcuts') + logger.warn('Failed to unregister all shortcuts') } } diff --git a/src/main/services/WebDav.ts b/src/main/services/WebDav.ts index 76996140e0..f055d16fbb 100644 --- a/src/main/services/WebDav.ts +++ b/src/main/services/WebDav.ts @@ -1,5 +1,5 @@ +import { loggerService } from '@logger' import { WebDavConfig } from '@types' -import Logger from 'electron-log' import https from 'https' import path from 'path' import Stream from 'stream' @@ -11,6 +11,9 @@ import { PutFileContentsOptions, WebDAVClient } from 'webdav' + +const logger = loggerService.withContext('WebDav') + export default class WebDav { public instance: WebDAVClient | undefined private webdavPath: string @@ -50,7 +53,7 @@ export default class WebDav { }) } } catch (error) { - Logger.error('[WebDAV] Error creating directory on WebDAV:', error) + logger.error('Error creating directory on WebDAV:', error) throw error } @@ -59,7 +62,7 @@ export default class WebDav { try { return await this.instance.putFileContents(remoteFilePath, data, options) } catch (error) { - Logger.error('[WebDAV] Error putting file contents on WebDAV:', error) + logger.error('Error putting file contents on WebDAV:', error) throw error } } @@ -74,7 +77,7 @@ export default class WebDav { try { return await this.instance.getFileContents(remoteFilePath, options) } catch (error) { - Logger.error('[WebDAV] Error getting file contents on WebDAV:', error) + logger.error('Error getting file contents on WebDAV:', error) throw error } } @@ -87,7 +90,7 @@ export default class WebDav { try { return await this.instance.getDirectoryContents(this.webdavPath) } catch (error) { - Logger.error('[WebDAV] Error getting directory contents on WebDAV:', error) + logger.error('Error getting directory contents on WebDAV:', error) throw error } } @@ -100,7 +103,7 @@ export default class WebDav { try { return await this.instance.exists('/') } catch (error) { - Logger.error('[WebDAV] Error checking connection:', error) + logger.error('Error checking connection:', error) throw error } } @@ -113,7 +116,7 @@ export default class WebDav { try { return await this.instance.createDirectory(path, options) } catch (error) { - Logger.error('[WebDAV] Error creating directory on WebDAV:', error) + logger.error('Error creating directory on WebDAV:', error) throw error } } @@ -128,7 +131,7 @@ export default class WebDav { try { return await this.instance.deleteFile(remoteFilePath) } catch (error) { - Logger.error('[WebDAV] Error deleting file on WebDAV:', error) + logger.error('Error deleting file on WebDAV:', error) throw error } } diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index fd2a3c9c84..2b81bc8050 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -2,11 +2,11 @@ import './ThemeService' import { is } from '@electron-toolkit/utils' +import { loggerService } from '@logger' import { isDev, isLinux, isMac, isWin } from '@main/constant' import { getFilesDir } from '@main/utils/file' import { IpcChannel } from '@shared/IpcChannel' import { app, BrowserWindow, nativeTheme, screen, shell } from 'electron' -import Logger from 'electron-log' import windowStateKeeper from 'electron-window-state' import { join } from 'path' @@ -19,6 +19,9 @@ import { initSessionUserAgent } from './WebviewService' const DEFAULT_MINIWINDOW_WIDTH = 550 const DEFAULT_MINIWINDOW_HEIGHT = 400 +// const logger = loggerService.withContext('WindowService') +const logger = loggerService.withContext('WindowService') + export class WindowService { private static instance: WindowService | null = null private mainWindow: BrowserWindow | null = null @@ -118,14 +121,14 @@ export class WindowService { const spellCheckLanguages = configManager.get('spellCheckLanguages', []) as string[] spellCheckLanguages.length > 0 && mainWindow.webContents.session.setSpellCheckerLanguages(spellCheckLanguages) } catch (error) { - Logger.error('Failed to set spell check languages:', error as Error) + logger.error('Failed to set spell check languages:', error as Error) } } } private setupMainWindowMonitor(mainWindow: BrowserWindow) { mainWindow.webContents.on('render-process-gone', (_, details) => { - Logger.error(`Renderer process crashed with: ${JSON.stringify(details)}`) + logger.error(`Renderer process crashed with: ${JSON.stringify(details)}`) const currentTime = Date.now() const lastCrashTime = this.lastRendererProcessCrashTime this.lastRendererProcessCrashTime = currentTime @@ -272,7 +275,7 @@ export class WindowService { const fileName = url.replace('http://file/', '') const storageDir = getFilesDir() const filePath = storageDir + '/' + fileName - shell.openPath(filePath).catch((err) => Logger.error('Failed to open file:', err)) + shell.openPath(filePath).catch((err) => logger.error('Failed to open file:', err)) } else { shell.openExternal(details.url) } @@ -625,7 +628,7 @@ export class WindowService { }, 100) } } catch (error) { - Logger.error('Failed to quote to main window:', error as Error) + logger.error('Failed to quote to main window:', error as Error) } } } diff --git a/src/main/services/mcp/oauth/callback.ts b/src/main/services/mcp/oauth/callback.ts index db70827d00..71a4460fa2 100644 --- a/src/main/services/mcp/oauth/callback.ts +++ b/src/main/services/mcp/oauth/callback.ts @@ -1,10 +1,12 @@ -import Logger from 'electron-log' +import { loggerService } from '@logger' import EventEmitter from 'events' import http from 'http' import { URL } from 'url' import { OAuthCallbackServerOptions } from './types' +const logger = loggerService.withContext('MCP:OAuthCallbackServer') + export class CallBackServer { private server: Promise private events: EventEmitter @@ -28,7 +30,7 @@ export class CallBackServer { this.events.emit('auth-code-received', code) } } catch (error) { - Logger.error('Error processing OAuth callback:', error) + logger.error('Error processing OAuth callback:', error) res.writeHead(500, { 'Content-Type': 'text/plain' }) res.end('Internal Server Error') } @@ -41,12 +43,12 @@ export class CallBackServer { // Handle server errors server.on('error', (error) => { - Logger.error('OAuth callback server error:', error) + logger.error('OAuth callback server error:', error) }) return new Promise((resolve, reject) => { server.listen(port, () => { - Logger.info(`OAuth callback server listening on port ${port}`) + logger.info(`OAuth callback server listening on port ${port}`) resolve(server) }) diff --git a/src/main/services/mcp/oauth/provider.ts b/src/main/services/mcp/oauth/provider.ts index a2a47fc15e..037ba70b75 100644 --- a/src/main/services/mcp/oauth/provider.ts +++ b/src/main/services/mcp/oauth/provider.ts @@ -1,14 +1,16 @@ import path from 'node:path' +import { loggerService } from '@logger' import { getConfigDir } from '@main/utils/file' import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth' import { OAuthClientInformation, OAuthClientInformationFull, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth' -import Logger from 'electron-log' import open from 'open' import { JsonFileStorage } from './storage' import { OAuthProviderOptions } from './types' +const logger = loggerService.withContext('MCP:OAuthClientProvider') + export class McpOAuthClientProvider implements OAuthClientProvider { private storage: JsonFileStorage public readonly config: Required @@ -61,9 +63,9 @@ export class McpOAuthClientProvider implements OAuthClientProvider { try { // Open the browser to the authorization URL await open(authorizationUrl.toString()) - Logger.info('Browser opened automatically.') + logger.debug('Browser opened automatically.') } catch (error) { - Logger.error('Could not open browser automatically.') + logger.error('Could not open browser automatically.') throw error // Let caller handle the error } } diff --git a/src/main/services/mcp/oauth/storage.ts b/src/main/services/mcp/oauth/storage.ts index 349fcf8bf1..95223119d2 100644 --- a/src/main/services/mcp/oauth/storage.ts +++ b/src/main/services/mcp/oauth/storage.ts @@ -1,14 +1,16 @@ +import { loggerService } from '@logger' import { OAuthClientInformation, OAuthClientInformationFull, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js' -import Logger from 'electron-log' import fs from 'fs/promises' import path from 'path' import { IOAuthStorage, OAuthStorageData, OAuthStorageSchema } from './types' +const logger = loggerService.withContext('MCP:OAuthStorage') + export class JsonFileStorage implements IOAuthStorage { private readonly filePath: string private cache: OAuthStorageData | null = null @@ -38,7 +40,7 @@ export class JsonFileStorage implements IOAuthStorage { await this.writeStorage(initial) return initial } - Logger.error('Error reading OAuth storage:', error) + logger.error('Error reading OAuth storage:', error) throw new Error(`Failed to read OAuth storage: ${error instanceof Error ? error.message : String(error)}`) } } @@ -59,7 +61,7 @@ export class JsonFileStorage implements IOAuthStorage { // Update cache this.cache = data } catch (error) { - Logger.error('Error writing OAuth storage:', error) + logger.error('Error writing OAuth storage:', error) throw new Error(`Failed to write OAuth storage: ${error instanceof Error ? error.message : String(error)}`) } } @@ -112,7 +114,7 @@ export class JsonFileStorage implements IOAuthStorage { this.cache = null } catch (error) { if (error instanceof Error && 'code' in error && error.code !== 'ENOENT') { - Logger.error('Error clearing OAuth storage:', error) + logger.error('Error clearing OAuth storage:', error) throw new Error(`Failed to clear OAuth storage: ${error instanceof Error ? error.message : String(error)}`) } } diff --git a/src/main/services/mcp/shell-env.ts b/src/main/services/mcp/shell-env.ts index a4128b3651..e5de39c2e4 100644 --- a/src/main/services/mcp/shell-env.ts +++ b/src/main/services/mcp/shell-env.ts @@ -1,7 +1,9 @@ +import { loggerService } from '@logger' import { spawn } from 'child_process' -import Logger from 'electron-log' import os from 'os' +const logger = loggerService.withContext('ShellEnv') + /** * Spawns a login shell in the user's home directory to capture its environment variables. * @returns {Promise} A promise that resolves with an object containing @@ -35,7 +37,7 @@ function getLoginShellEnvironment(): Promise> { // Defaulting to bash, but this might not be the user's actual login shell. // A more robust solution might involve checking /etc/passwd or similar, // but that's more complex and often requires higher privileges or native modules. - Logger.warn("process.env.SHELL is not set. Defaulting to /bin/bash. This might not be the user's login shell.") + logger.warn("process.env.SHELL is not set. Defaulting to /bin/bash. This might not be the user's login shell.") shellPath = '/bin/bash' // A common default } // -l: Make it a login shell. This sources profile files like .profile, .bash_profile, .zprofile etc. @@ -47,7 +49,7 @@ function getLoginShellEnvironment(): Promise> { commandArgs = ['-ilc', shellCommandToGetEnv] // -i for interactive, -l for login, -c to execute command } - Logger.log(`[ShellEnv] Spawning shell: ${shellPath} with args: ${commandArgs.join(' ')} in ${homeDirectory}`) + logger.debug(`Spawning shell: ${shellPath} with args: ${commandArgs.join(' ')} in ${homeDirectory}`) const child = spawn(shellPath, commandArgs, { cwd: homeDirectory, // Run the command in the user's home directory @@ -68,21 +70,21 @@ function getLoginShellEnvironment(): Promise> { }) child.on('error', (error) => { - Logger.error(`Failed to start shell process: ${shellPath}`, error) + logger.error(`Failed to start shell process: ${shellPath}`, error) reject(new Error(`Failed to start shell: ${error.message}`)) }) child.on('close', (code) => { if (code !== 0) { const errorMessage = `Shell process exited with code ${code}. Shell: ${shellPath}. Args: ${commandArgs.join(' ')}. CWD: ${homeDirectory}. Stderr: ${errorOutput.trim()}` - Logger.error(errorMessage) + logger.error(errorMessage) return reject(new Error(errorMessage)) } if (errorOutput.trim()) { // Some shells might output warnings or non-fatal errors to stderr // during profile loading. Log it, but proceed if exit code is 0. - Logger.warn(`Shell process stderr output (even with exit code 0):\n${errorOutput.trim()}`) + logger.warn(`Shell process stderr output (even with exit code 0):\n${errorOutput.trim()}`) } const env: Record = {} @@ -104,10 +106,10 @@ function getLoginShellEnvironment(): Promise> { if (Object.keys(env).length === 0 && output.length < 100) { // Arbitrary small length check // This might indicate an issue if no env vars were parsed or output was minimal - Logger.warn( + logger.warn( 'Parsed environment is empty or output was very short. This might indicate an issue with shell execution or environment variable retrieval.' ) - Logger.warn('Raw output from shell:\n', output) + logger.warn('Raw output from shell:\n', output) } env.PATH = env.Path || env.PATH || '' diff --git a/src/main/services/memory/MemoryService.ts b/src/main/services/memory/MemoryService.ts index 07f0932525..3b2e557307 100644 --- a/src/main/services/memory/MemoryService.ts +++ b/src/main/services/memory/MemoryService.ts @@ -1,4 +1,5 @@ import { Client, createClient } from '@libsql/client' +import { loggerService } from '@logger' import Embeddings from '@main/knowledge/embeddings/Embeddings' import type { AddMemoryOptions, @@ -11,11 +12,12 @@ import type { } from '@types' import crypto from 'crypto' import { app } from 'electron' -import Logger from 'electron-log' import path from 'path' import { MemoryQueries } from './queries' +const logger = loggerService.withContext('MemoryService') + export interface EmbeddingOptions { model: string provider: string @@ -88,9 +90,9 @@ export class MemoryService { // Create tables await this.createTables() this.isInitialized = true - Logger.info('Memory database initialized successfully') + logger.debug('Memory database initialized successfully') } catch (error) { - Logger.error('Failed to initialize memory database:', error) + logger.error('Failed to initialize memory database:', error) throw new Error( `Memory database initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}` ) @@ -118,7 +120,7 @@ export class MemoryService { await this.db.execute(MemoryQueries.createIndexes.vector) } catch (error) { // Vector index might not be supported in all versions - Logger.warn('Failed to create vector index, falling back to non-indexed search:', error) + logger.warn('Failed to create vector index, falling back to non-indexed search:', error) } } @@ -157,11 +159,11 @@ export class MemoryService { if (!isDeleted) { // Active record exists, skip insertion - Logger.info(`Memory already exists with hash: ${hash}`) + logger.debug(`Memory already exists with hash: ${hash}`) continue } else { // Deleted record exists, restore it instead of inserting new one - Logger.info(`Restoring deleted memory with hash: ${hash}`) + logger.debug(`Restoring deleted memory with hash: ${hash}`) // Generate embedding if model is configured let embedding: number[] | null = null @@ -169,11 +171,11 @@ export class MemoryService { if (embedderApiClient) { try { embedding = await this.generateEmbedding(trimmedMemory) - Logger.info( + logger.debug( `Generated embedding for restored memory with dimension: ${embedding.length} (target: ${this.config?.embedderDimensions || MemoryService.UNIFIED_DIMENSION})` ) } catch (error) { - Logger.error('Failed to generate embedding for restored memory:', error) + logger.error('Failed to generate embedding for restored memory:', error) } } @@ -211,7 +213,7 @@ export class MemoryService { if (this.config?.embedderApiClient) { try { embedding = await this.generateEmbedding(trimmedMemory) - Logger.info( + logger.debug( `Generated embedding with dimension: ${embedding.length} (target: ${this.config?.embedderDimensions || MemoryService.UNIFIED_DIMENSION})` ) @@ -227,15 +229,15 @@ export class MemoryService { if (similarMemories.memories.length > 0) { const highestSimilarity = Math.max(...similarMemories.memories.map((m) => m.score || 0)) if (highestSimilarity >= MemoryService.SIMILARITY_THRESHOLD) { - Logger.info( + logger.debug( `Skipping memory addition due to high similarity: ${highestSimilarity.toFixed(3)} >= ${MemoryService.SIMILARITY_THRESHOLD}` ) - Logger.info(`Similar memory found: "${similarMemories.memories[0].memory}"`) + logger.debug(`Similar memory found: "${similarMemories.memories[0].memory}"`) continue } } } catch (error) { - Logger.error('Failed to generate embedding:', error) + logger.error('Failed to generate embedding:', error) } } @@ -277,7 +279,7 @@ export class MemoryService { count: addedMemories.length } } catch (error) { - Logger.error('Failed to add memories:', error) + logger.error('Failed to add memories:', error) return { memories: [], count: 0, @@ -302,7 +304,7 @@ export class MemoryService { const queryEmbedding = await this.generateEmbedding(query) return await this.hybridSearch(query, queryEmbedding, { limit, userId, agentId, filters }) } catch (error) { - Logger.error('Vector search failed, falling back to text search:', error) + logger.error('Vector search failed, falling back to text search:', error) } } @@ -357,7 +359,7 @@ export class MemoryService { count: memories.length } } catch (error) { - Logger.error('Search failed:', error) + logger.error('Search failed:', error) return { memories: [], count: 0, @@ -422,7 +424,7 @@ export class MemoryService { count: totalCount } } catch (error) { - Logger.error('List failed:', error) + logger.error('List failed:', error) return { memories: [], count: 0, @@ -460,9 +462,9 @@ export class MemoryService { // Add to history await this.addHistory(id, currentMemory, null, 'DELETE') - Logger.info(`Memory deleted: ${id}`) + logger.debug(`Memory deleted: ${id}`) } catch (error) { - Logger.error('Delete failed:', error) + logger.error('Delete failed:', error) throw new Error(`Failed to delete memory: ${error instanceof Error ? error.message : 'Unknown error'}`) } } @@ -497,11 +499,11 @@ export class MemoryService { if (this.config?.embedderApiClient) { try { embedding = await this.generateEmbedding(memory) - Logger.info( + logger.debug( `Updated embedding with dimension: ${embedding.length} (target: ${this.config?.embedderDimensions || MemoryService.UNIFIED_DIMENSION})` ) } catch (error) { - Logger.error('Failed to generate embedding for update:', error) + logger.error('Failed to generate embedding for update:', error) } } @@ -524,9 +526,9 @@ export class MemoryService { // Add to history await this.addHistory(id, previousMemory, memory, 'UPDATE') - Logger.info(`Memory updated: ${id}`) + logger.debug(`Memory updated: ${id}`) } catch (error) { - Logger.error('Update failed:', error) + logger.error('Update failed:', error) throw new Error(`Failed to update memory: ${error instanceof Error ? error.message : 'Unknown error'}`) } } @@ -555,7 +557,7 @@ export class MemoryService { isDeleted: row.is_deleted === 1 })) } catch (error) { - Logger.error('Get history failed:', error) + logger.error('Get history failed:', error) throw new Error(`Failed to get memory history: ${error instanceof Error ? error.message : 'Unknown error'}`) } } @@ -591,9 +593,9 @@ export class MemoryService { args: [userId] }) - Logger.info(`Reset all memories for user ${userId} (${totalCount} memories deleted)`) + logger.debug(`Reset all memories for user ${userId} (${totalCount} memories deleted)`) } catch (error) { - Logger.error('Reset user memories failed:', error) + logger.error('Reset user memories failed:', error) throw new Error(`Failed to reset user memories: ${error instanceof Error ? error.message : 'Unknown error'}`) } } @@ -633,9 +635,9 @@ export class MemoryService { args: [userId] }) - Logger.info(`Deleted user ${userId} and ${totalCount} memories`) + logger.debug(`Deleted user ${userId} and ${totalCount} memories`) } catch (error) { - Logger.error('Delete user failed:', error) + logger.error('Delete user failed:', error) throw new Error(`Failed to delete user: ${error instanceof Error ? error.message : 'Unknown error'}`) } } @@ -659,7 +661,7 @@ export class MemoryService { lastMemoryDate: row.last_memory_date as string })) } catch (error) { - Logger.error('Get users list failed:', error) + logger.error('Get users list failed:', error) throw new Error(`Failed to get users list: ${error instanceof Error ? error.message : 'Unknown error'}`) } } @@ -730,7 +732,7 @@ export class MemoryService { // Normalize to unified dimension return this.normalizeEmbedding(embedding) } catch (error) { - Logger.error('Embedding generation failed:', error) + logger.error('Embedding generation failed:', error) throw new Error(`Failed to generate embedding: ${error instanceof Error ? error.message : 'Unknown error'}`) } } @@ -800,7 +802,7 @@ export class MemoryService { count: memories.length } } catch (error) { - Logger.error('Hybrid search failed:', error) + logger.error('Hybrid search failed:', error) throw new Error(`Hybrid search failed: ${error instanceof Error ? error.message : 'Unknown error'}`) } } diff --git a/src/main/services/remotefile/GeminiService.ts b/src/main/services/remotefile/GeminiService.ts index 82178f5c14..05838d8a6d 100644 --- a/src/main/services/remotefile/GeminiService.ts +++ b/src/main/services/remotefile/GeminiService.ts @@ -1,11 +1,13 @@ import { File, Files, FileState, GoogleGenAI } from '@google/genai' +import { loggerService } from '@logger' import { FileListResponse, FileMetadata, FileUploadResponse, Provider } from '@types' -import Logger from 'electron-log' import { v4 as uuidv4 } from 'uuid' import { CacheService } from '../CacheService' import { BaseFileService } from './BaseFileService' +const logger = loggerService.withContext('GeminiService') + export class GeminiService extends BaseFileService { private static readonly FILE_LIST_CACHE_KEY = 'gemini_file_list' private static readonly FILE_CACHE_DURATION = 48 * 60 * 60 * 1000 @@ -69,7 +71,7 @@ export class GeminiService extends BaseFileService { return response } catch (error) { - Logger.error('Error uploading file to Gemini:', error) + logger.error('Error uploading file to Gemini:', error) return { fileId: '', displayName: file.origin_name, @@ -82,7 +84,7 @@ export class GeminiService extends BaseFileService { async retrieveFile(fileId: string): Promise { try { const cachedResponse = CacheService.get(`${GeminiService.FILE_LIST_CACHE_KEY}_${fileId}`) - Logger.info('[GeminiService] cachedResponse', cachedResponse) + logger.debug('[GeminiService] cachedResponse', cachedResponse) if (cachedResponse) { return cachedResponse } @@ -91,11 +93,11 @@ export class GeminiService extends BaseFileService { for await (const f of await this.fileManager.list()) { files.push(f) } - Logger.info('[GeminiService] files', files) + logger.debug('files', files) const file = files .filter((file) => file.state === FileState.ACTIVE) .find((file) => file.name?.substring(6) === fileId) // 去掉 files/ 前缀 - Logger.info('[GeminiService] file', file) + logger.debug('file', file) if (file) { return { fileId: fileId, @@ -115,7 +117,7 @@ export class GeminiService extends BaseFileService { originalFile: undefined } } catch (error) { - Logger.error('Error retrieving file from Gemini:', error) + logger.error('Error retrieving file from Gemini:', error) return { fileId: fileId, displayName: '', @@ -173,7 +175,7 @@ export class GeminiService extends BaseFileService { CacheService.set(GeminiService.FILE_LIST_CACHE_KEY, fileList, GeminiService.LIST_CACHE_DURATION) return fileList } catch (error) { - Logger.error('Error listing files from Gemini:', error) + logger.error('Error listing files from Gemini:', error) return { files: [] } } } @@ -181,9 +183,9 @@ export class GeminiService extends BaseFileService { async deleteFile(fileId: string): Promise { try { await this.fileManager.delete({ name: fileId }) - Logger.info(`File ${fileId} deleted from Gemini`) + logger.debug(`File ${fileId} deleted from Gemini`) } catch (error) { - Logger.error('Error deleting file from Gemini:', error) + logger.error('Error deleting file from Gemini:', error) throw error } } diff --git a/src/main/services/remotefile/MistralService.ts b/src/main/services/remotefile/MistralService.ts index 3964871ce4..b70ca84d6a 100644 --- a/src/main/services/remotefile/MistralService.ts +++ b/src/main/services/remotefile/MistralService.ts @@ -1,12 +1,14 @@ import fs from 'node:fs/promises' +import { loggerService } from '@logger' import { Mistral } from '@mistralai/mistralai' import { FileListResponse, FileMetadata, FileUploadResponse, Provider } from '@types' -import Logger from 'electron-log' import { MistralClientManager } from '../MistralClientManager' import { BaseFileService } from './BaseFileService' +const logger = loggerService.withContext('MistralService') + export class MistralService extends BaseFileService { private readonly client: Mistral @@ -38,7 +40,7 @@ export class MistralService extends BaseFileService { } } } catch (error) { - Logger.error('Error uploading file:', error) + logger.error('Error uploading file:', error) return { fileId: '', displayName: file.origin_name, @@ -63,7 +65,7 @@ export class MistralService extends BaseFileService { })) } } catch (error) { - Logger.error('Error listing files:', error) + logger.error('Error listing files:', error) return { files: [] } } } @@ -73,9 +75,9 @@ export class MistralService extends BaseFileService { await this.client.files.delete({ fileId }) - Logger.info(`File ${fileId} deleted`) + logger.debug(`File ${fileId} deleted`) } catch (error) { - Logger.error('Error deleting file:', error) + logger.error('Error deleting file:', error) throw error } } @@ -92,7 +94,7 @@ export class MistralService extends BaseFileService { status: 'success' // Retrieved files are always processed } } catch (error) { - Logger.error('Error retrieving file:', error) + logger.error('Error retrieving file:', error) return { fileId: fileId, displayName: '', diff --git a/src/main/services/urlschema/handle-providers.ts b/src/main/services/urlschema/handle-providers.ts index f8b0661370..cf7cd605b9 100644 --- a/src/main/services/urlschema/handle-providers.ts +++ b/src/main/services/urlschema/handle-providers.ts @@ -1,7 +1,8 @@ +import { loggerService } from '@logger' import { isMac } from '@main/constant' -import Logger from 'electron-log' import { windowService } from '../WindowService' +const logger = loggerService.withContext('URLSchema:handleProvidersProtocolUrl') function ParseData(data: string) { try { @@ -9,7 +10,7 @@ function ParseData(data: string) { return JSON.stringify(result) } catch (error) { - Logger.error('ParseData error:', { error }) + logger.error('ParseData error:', error) return null } } @@ -33,7 +34,7 @@ export async function handleProvidersProtocolUrl(url: URL) { const data = ParseData(params.get('data')?.replaceAll('_', '+').replaceAll('-', '/') || '') if (!data) { - Logger.error('handleProvidersProtocolUrl data is null or invalid') + logger.error('handleProvidersProtocolUrl data is null or invalid') return } @@ -41,7 +42,7 @@ export async function handleProvidersProtocolUrl(url: URL) { const version = params.get('v') if (version == '1') { // TODO: handle different version - Logger.info('handleProvidersProtocolUrl', { data, version }) + logger.debug('handleProvidersProtocolUrl', { data, version }) } // add check there is window.navigate function in mainWindow @@ -59,14 +60,14 @@ export async function handleProvidersProtocolUrl(url: URL) { } } else { setTimeout(() => { - Logger.info('handleProvidersProtocolUrl timeout', { data, version }) + logger.debug('handleProvidersProtocolUrl timeout', { data, version }) handleProvidersProtocolUrl(url) }, 1000) } break } default: - Logger.error(`Unknown MCP protocol URL: ${url}`) + logger.error(`Unknown MCP protocol URL: ${url}`) break } } diff --git a/src/main/services/urlschema/mcp-install.ts b/src/main/services/urlschema/mcp-install.ts index f2e58eef2a..5db183ef82 100644 --- a/src/main/services/urlschema/mcp-install.ts +++ b/src/main/services/urlschema/mcp-install.ts @@ -1,10 +1,12 @@ +import { loggerService } from '@logger' import { nanoid } from '@reduxjs/toolkit' import { IpcChannel } from '@shared/IpcChannel' import { MCPServer } from '@types' -import Logger from 'electron-log' import { windowService } from '../WindowService' +const logger = loggerService.withContext('URLSchema:handleMcpProtocolUrl') + function installMCPServer(server: MCPServer) { const mainWindow = windowService.getMainWindow() @@ -49,9 +51,9 @@ export function handleMcpProtocolUrl(url: URL) { if (data) { const stringify = Buffer.from(data, 'base64').toString('utf8') - Logger.info('install MCP servers from urlschema: ', stringify) + logger.debug('install MCP servers from urlschema: ', stringify) const jsonConfig = JSON.parse(stringify) - Logger.info('install MCP servers from urlschema: ', jsonConfig) + logger.debug('install MCP servers from urlschema: ', jsonConfig) // support both {mcpServers: [servers]}, [servers] and {server} if (jsonConfig.mcpServers) { @@ -70,7 +72,7 @@ export function handleMcpProtocolUrl(url: URL) { break } default: - console.error(`Unknown MCP protocol URL: ${url}`) + logger.error(`Unknown MCP protocol URL: ${url}`) break } } diff --git a/src/main/utils/file.ts b/src/main/utils/file.ts index d03af3ad72..4f67fd6504 100644 --- a/src/main/utils/file.ts +++ b/src/main/utils/file.ts @@ -3,15 +3,17 @@ import { open, readFile } from 'node:fs/promises' import os from 'node:os' import path from 'node:path' +import { loggerService } from '@logger' import { isLinux, isPortable } from '@main/constant' import { audioExts, documentExts, imageExts, MB, textExts, videoExts } from '@shared/config/constant' import { FileMetadata, FileTypes } from '@types' import { app } from 'electron' -import Logger from 'electron-log' import iconv from 'iconv-lite' import * as jschardet from 'jschardet' import { v4 as uuidv4 } from 'uuid' +const logger = loggerService.withContext('Utils:File') + export function initAppDataDir() { const appDataPath = getAppDataPathFromConfig() if (appDataPath) { @@ -234,7 +236,7 @@ export async function readTextFileWithAutoEncoding(filePath: string): Promise { return new Promise((resolve, reject) => { const installScriptPath = path.join(getResourcePath(), 'scripts', scriptPath) - log.info(`Running script at: ${installScriptPath}`) + logger.info(`Running script at: ${installScriptPath}`) const nodeProcess = spawn(process.execPath, [installScriptPath], { env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' } }) nodeProcess.stdout.on('data', (data) => { - log.info(`Script output: ${data}`) + logger.debug(`Script output: ${data}`) }) nodeProcess.stderr.on('data', (data) => { - log.error(`Script error: ${data}`) + logger.error(`Script error: ${data}`) }) nodeProcess.on('close', (code) => { if (code === 0) { - log.info('Script completed successfully') + logger.debug('Script completed successfully') resolve() } else { - log.error(`Script exited with code ${code}`) + logger.warn(`Script exited with code ${code}`) reject(new Error(`Process exited with code ${code}`)) } }) diff --git a/src/main/utils/zip.ts b/src/main/utils/zip.ts index b2762f7a98..95e7595046 100644 --- a/src/main/utils/zip.ts +++ b/src/main/utils/zip.ts @@ -1,7 +1,9 @@ import util from 'node:util' import zlib from 'node:zlib' -import logger from 'electron-log' +import { loggerService } from '@logger' + +const logger = loggerService.withContext('Utils:Zip') // 将 zlib 的 gzip 和 gunzip 方法转换为 Promise 版本 const gzipPromise = util.promisify(zlib.gzip) diff --git a/src/preload/index.ts b/src/preload/index.ts index ab27e37ebc..163631c802 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1,6 +1,7 @@ import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' import { electronAPI } from '@electron-toolkit/preload' import { UpgradeChannel } from '@shared/config/constant' +import type { LogLevel, LogSourceWithContext } from '@shared/config/types' import { IpcChannel } from '@shared/IpcChannel' import { AddMemoryOptions, @@ -59,6 +60,8 @@ const api = { openWebsite: (url: string) => ipcRenderer.invoke(IpcChannel.Open_Website, url), getCacheSize: () => ipcRenderer.invoke(IpcChannel.App_GetCacheSize), clearCache: () => ipcRenderer.invoke(IpcChannel.App_ClearCache), + logToMain: (source: LogSourceWithContext, level: LogLevel, message: string, data: any[]) => + ipcRenderer.invoke(IpcChannel.App_LogToMain, source, level, message, data), mac: { isProcessTrusted: (): Promise => ipcRenderer.invoke(IpcChannel.App_MacIsProcessTrusted), requestProcessTrust: (): Promise => ipcRenderer.invoke(IpcChannel.App_MacRequestProcessTrust) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index b46910cd65..c8a5d41db4 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1,5 +1,6 @@ import '@renderer/databases' +import { loggerService } from '@logger' import store, { persistor } from '@renderer/store' import { Provider } from 'react-redux' import { HashRouter, Route, Routes } from 'react-router-dom' @@ -22,7 +23,11 @@ import PaintingsRoutePage from './pages/paintings/PaintingsRoutePage' import SettingsPage from './pages/settings/SettingsPage' import TranslatePage from './pages/translate/TranslatePage' +const logger = loggerService.withContext('App.tsx') + function App(): React.ReactElement { + logger.error('App initialized') + return ( diff --git a/src/renderer/src/aiCore/clients/BaseApiClient.ts b/src/renderer/src/aiCore/clients/BaseApiClient.ts index 3b904685d1..2a174ecacc 100644 --- a/src/renderer/src/aiCore/clients/BaseApiClient.ts +++ b/src/renderer/src/aiCore/clients/BaseApiClient.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { isFunctionCallingModel, isNotSupportTemperatureAndTopP, @@ -40,12 +41,13 @@ import { isJSON, parseJSON } from '@renderer/utils' import { addAbortController, removeAbortController } from '@renderer/utils/abortController' import { findFileBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find' import { defaultTimeout } from '@shared/config/constant' -import Logger from 'electron-log/renderer' import { isEmpty } from 'lodash' import { CompletionsContext } from '../middleware/types' import { ApiClient, RequestTransformer, ResponseChunkTransformer } from './types' +const logger = loggerService.withContext('BaseApiClient') + /** * Abstract base class for API clients. * Provides common functionality and structure for specific client implementations. @@ -228,7 +230,7 @@ export abstract class BaseApiClient< const allReferences = [...webSearchReferences, ...reindexedKnowledgeReferences, ...memoryReferences] - Logger.log(`Found ${allReferences.length} references for ID: ${message.id}`, allReferences) + logger.debug(`Found ${allReferences.length} references for ID: ${message.id}`, allReferences) if (!isEmpty(allReferences)) { const referenceContent = `\`\`\`json\n${JSON.stringify(allReferences, null, 2)}\n\`\`\`` @@ -317,10 +319,10 @@ export abstract class BaseApiClient< if (!isEmpty(knowledgeReferences)) { window.keyv.remove(`knowledge-search-${message.id}`) - // Logger.log(`Found ${knowledgeReferences.length} knowledge base references in cache for ID: ${message.id}`) + logger.debug(`Found ${knowledgeReferences.length} knowledge base references in cache for ID: ${message.id}`) return knowledgeReferences } - // Logger.log(`No knowledge base references found in cache for ID: ${message.id}`) + logger.debug(`No knowledge base references found in cache for ID: ${message.id}`) return [] } diff --git a/src/renderer/src/aiCore/clients/NewAPIClient.ts b/src/renderer/src/aiCore/clients/NewAPIClient.ts index 769ca90acf..f029ce2c38 100644 --- a/src/renderer/src/aiCore/clients/NewAPIClient.ts +++ b/src/renderer/src/aiCore/clients/NewAPIClient.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { isSupportedModel } from '@renderer/config/models' import { GenerateImageParams, @@ -28,6 +29,8 @@ import { OpenAIAPIClient } from './openai/OpenAIApiClient' import { OpenAIResponseAPIClient } from './openai/OpenAIResponseAPIClient' import { RequestTransformer, ResponseChunkTransformer } from './types' +const logger = loggerService.withContext('NewAPIClient') + export class NewAPIClient extends BaseApiClient { // 使用联合类型而不是any,保持类型安全 private clients: Map = @@ -176,7 +179,7 @@ export class NewAPIClient extends BaseApiClient { return models.filter(isSupportedModel) } catch (error) { - console.error('Error listing models:', error) + logger.error('Error listing models:', error) return [] } } diff --git a/src/renderer/src/aiCore/clients/anthropic/AnthropicAPIClient.ts b/src/renderer/src/aiCore/clients/anthropic/AnthropicAPIClient.ts index e18b20889a..de50955fa2 100644 --- a/src/renderer/src/aiCore/clients/anthropic/AnthropicAPIClient.ts +++ b/src/renderer/src/aiCore/clients/anthropic/AnthropicAPIClient.ts @@ -24,9 +24,9 @@ import { WebSearchToolResultError } from '@anthropic-ai/sdk/resources/messages' import { MessageStream } from '@anthropic-ai/sdk/resources/messages/messages' +import { loggerService } from '@logger' import { GenericChunk } from '@renderer/aiCore/middleware/schemas' import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant' -import Logger from '@renderer/config/logger' import { findTokenLimit, isClaudeReasoningModel, isReasoningModel, isWebSearchModel } from '@renderer/config/models' import { getAssistantSettings } from '@renderer/services/AssistantService' import FileManager from '@renderer/services/FileManager' @@ -74,6 +74,8 @@ import { buildSystemPrompt } from '@renderer/utils/prompt' import { BaseApiClient } from '../BaseApiClient' import { AnthropicStreamListener, RawStreamListener, RequestTransformer, ResponseChunkTransformer } from '../types' +const logger = loggerService.withContext('AnthropicAPIClient') + export class AnthropicAPIClient extends BaseApiClient< Anthropic, AnthropicSdkParams, @@ -374,12 +376,12 @@ export class AnthropicAPIClient extends BaseApiClient< rawOutput: AnthropicSdkRawOutput, listener: RawStreamListener ): AnthropicSdkRawOutput { - console.log(`[AnthropicApiClient] 附加流监听器到原始输出`) + logger.debug(`Attaching stream listener to raw output`) // 专用的Anthropic事件处理 const anthropicListener = listener as AnthropicStreamListener // 检查是否为MessageStream if (rawOutput instanceof MessageStream) { - console.log(`[AnthropicApiClient] 检测到 Anthropic MessageStream,附加专用监听器`) + logger.debug(`Detected Anthropic MessageStream, attaching specialized listener`) if (listener.onStart) { listener.onStart() @@ -679,13 +681,13 @@ export class AnthropicAPIClient extends BaseApiClient< if (toolCall) { try { toolCall.input = JSON.parse(accumulatedJson) - Logger.debug(`Tool call id: ${toolCall.id}, accumulated json: ${accumulatedJson}`) + logger.debug(`Tool call id: ${toolCall.id}, accumulated json: ${accumulatedJson}`) controller.enqueue({ type: ChunkType.MCP_TOOL_CREATED, tool_calls: [toolCall] } as MCPToolCreatedChunk) } catch (error) { - Logger.error(`Error parsing tool call input: ${error}`) + logger.error(`Error parsing tool call input: ${error}`) } } break diff --git a/src/renderer/src/aiCore/clients/gemini/GeminiAPIClient.ts b/src/renderer/src/aiCore/clients/gemini/GeminiAPIClient.ts index 30d497c21d..0cafcc406a 100644 --- a/src/renderer/src/aiCore/clients/gemini/GeminiAPIClient.ts +++ b/src/renderer/src/aiCore/clients/gemini/GeminiAPIClient.ts @@ -16,6 +16,7 @@ import { ThinkingConfig, Tool } from '@google/genai' +import { loggerService } from '@logger' import { nanoid } from '@reduxjs/toolkit' import { GenericChunk } from '@renderer/aiCore/middleware/schemas' import { @@ -64,6 +65,8 @@ import { defaultTimeout, MB } from '@shared/config/constant' import { BaseApiClient } from '../BaseApiClient' import { RequestTransformer, ResponseChunkTransformer } from '../types' +const logger = loggerService.withContext('GeminiAPIClient') + export class GeminiAPIClient extends BaseApiClient< GoogleGenAI, GeminiSdkParams, @@ -139,7 +142,7 @@ export class GeminiAPIClient extends BaseApiClient< // console.log(response?.generatedImages?.[0]?.image?.imageBytes); return images } catch (error) { - console.error('[generateImage] error:', error) + logger.error('[generateImage] error:', error) throw error } } diff --git a/src/renderer/src/aiCore/clients/gemini/VertexAPIClient.ts b/src/renderer/src/aiCore/clients/gemini/VertexAPIClient.ts index 713d2585d3..aecd6d0a08 100644 --- a/src/renderer/src/aiCore/clients/gemini/VertexAPIClient.ts +++ b/src/renderer/src/aiCore/clients/gemini/VertexAPIClient.ts @@ -1,9 +1,11 @@ import { GoogleGenAI } from '@google/genai' +import { loggerService } from '@logger' import { getVertexAILocation, getVertexAIProjectId, getVertexAIServiceAccount } from '@renderer/hooks/useVertexAI' import { Provider } from '@renderer/types' import { GeminiAPIClient } from './GeminiAPIClient' +const logger = loggerService.withContext('VertexAPIClient') export class VertexAPIClient extends GeminiAPIClient { private authHeaders?: Record private authHeadersExpiry?: number @@ -73,7 +75,7 @@ export class VertexAPIClient extends GeminiAPIClient { return this.authHeaders } catch (error: any) { - console.error('Failed to get auth headers:', error) + logger.error('Failed to get auth headers:', error) throw new Error(`Service Account authentication failed: ${error.message}`) } } diff --git a/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts b/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts index 80a611493d..b4e5805724 100644 --- a/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts +++ b/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts @@ -1,5 +1,5 @@ +import { loggerService } from '@logger' import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant' -import Logger from '@renderer/config/logger' import { findTokenLimit, GEMINI_FLASH_MODEL_REGEX, @@ -58,6 +58,8 @@ import { GenericChunk } from '../../middleware/schemas' import { RequestTransformer, ResponseChunkTransformer, ResponseChunkTransformerContext } from '../types' import { OpenAIBaseClient } from './OpenAIBaseClient' +const logger = loggerService.withContext('OpenAIApiClient') + export class OpenAIAPIClient extends OpenAIBaseClient< OpenAI | AzureOpenAI, OpenAISdkParams, @@ -790,7 +792,7 @@ export class OpenAIAPIClient extends OpenAIBaseClient< // 处理finish_reason,发送流结束信号 if ('finish_reason' in choice && choice.finish_reason) { - Logger.debug(`[OpenAIApiClient] Stream finished with reason: ${choice.finish_reason}`) + logger.debug(`Stream finished with reason: ${choice.finish_reason}`) const webSearchData = collectWebSearchData(chunk, contentSource, context) if (webSearchData) { controller.enqueue({ @@ -808,7 +810,7 @@ export class OpenAIAPIClient extends OpenAIBaseClient< flush(controller) { if (isFinished) return - Logger.debug('[OpenAIApiClient] Stream ended without finish_reason, emitting fallback completion signals') + logger.debug('Stream ended without finish_reason, emitting fallback completion signals') emitCompletionSignals(controller) } }) diff --git a/src/renderer/src/aiCore/clients/openai/OpenAIBaseClient.ts b/src/renderer/src/aiCore/clients/openai/OpenAIBaseClient.ts index 95ddcbedd0..74ae7f39b6 100644 --- a/src/renderer/src/aiCore/clients/openai/OpenAIBaseClient.ts +++ b/src/renderer/src/aiCore/clients/openai/OpenAIBaseClient.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { isClaudeReasoningModel, isNotSupportTemperatureAndTopP, @@ -28,6 +29,8 @@ import OpenAI, { AzureOpenAI } from 'openai' import { BaseApiClient } from '../BaseApiClient' +const logger = loggerService.withContext('OpenAIBaseClient') + /** * 抽象的OpenAI基础客户端类,包含两个OpenAI客户端之间的共享功能 */ @@ -125,7 +128,7 @@ export abstract class OpenAIBaseClient< return models.filter(isSupportedModel) } catch (error) { - console.error('Error listing models:', error) + logger.error('Error listing models:', error) return [] } } diff --git a/src/renderer/src/aiCore/clients/ppio/PPIOAPIClient.ts b/src/renderer/src/aiCore/clients/ppio/PPIOAPIClient.ts index 2b8dec332d..c881357ec3 100644 --- a/src/renderer/src/aiCore/clients/ppio/PPIOAPIClient.ts +++ b/src/renderer/src/aiCore/clients/ppio/PPIOAPIClient.ts @@ -1,9 +1,11 @@ +import { loggerService } from '@logger' import { isSupportedModel } from '@renderer/config/models' import { Provider } from '@renderer/types' import OpenAI from 'openai' import { OpenAIAPIClient } from '../openai/OpenAIApiClient' +const logger = loggerService.withContext('PPIOAPIClient') export class PPIOAPIClient extends OpenAIAPIClient { constructor(provider: Provider) { super(provider) @@ -58,7 +60,7 @@ export class PPIOAPIClient extends OpenAIAPIClient { return processedModels.filter(isSupportedModel) } catch (error) { - console.error('Error listing PPIO models:', error) + logger.error('Error listing PPIO models:', error) return [] } } diff --git a/src/renderer/src/aiCore/index.ts b/src/renderer/src/aiCore/index.ts index f9caa80f81..3ff0bc5b85 100644 --- a/src/renderer/src/aiCore/index.ts +++ b/src/renderer/src/aiCore/index.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { ApiClientFactory } from '@renderer/aiCore/clients/ApiClientFactory' import { BaseApiClient } from '@renderer/aiCore/clients/BaseApiClient' import { isDedicatedImageGenerationModel, isFunctionCallingModel } from '@renderer/config/models' @@ -25,6 +26,8 @@ import { MIDDLEWARE_NAME as ToolUseExtractionMiddlewareName } from './middleware import { MiddlewareRegistry } from './middleware/register' import { CompletionsParams, CompletionsResult } from './middleware/schemas' +const logger = loggerService.withContext('AiProvider') + export default class AiProvider { private apiClient: BaseApiClient @@ -124,7 +127,7 @@ export default class AiProvider { const dimensions = await this.apiClient.getEmbeddingDimensions(model) return dimensions } catch (error) { - console.error('Error getting embedding dimensions:', error) + logger.error('Error getting embedding dimensions:', error) throw error } } diff --git a/src/renderer/src/aiCore/middleware/builder.ts b/src/renderer/src/aiCore/middleware/builder.ts index e76b59c2bd..2ea20d4937 100644 --- a/src/renderer/src/aiCore/middleware/builder.ts +++ b/src/renderer/src/aiCore/middleware/builder.ts @@ -1,6 +1,10 @@ +import { loggerService } from '@logger' + import { DefaultCompletionsNamedMiddlewares } from './register' import { BaseContext, CompletionsMiddleware, MethodMiddleware } from './types' +const logger = loggerService.withContext('aiCore:MiddlewareBuilder') + /** * 带有名称标识的中间件接口 */ @@ -66,7 +70,7 @@ export class MiddlewareBuilder { if (index !== -1) { this.middlewares.splice(index + 1, 0, middlewareToInsert) } else { - console.warn(`MiddlewareBuilder: 未找到名为 '${targetName}' 的中间件,无法插入`) + logger.warn(`未找到名为 '${targetName}' 的中间件,无法插入`) } return this } @@ -82,7 +86,7 @@ export class MiddlewareBuilder { if (index !== -1) { this.middlewares.splice(index, 0, middlewareToInsert) } else { - console.warn(`MiddlewareBuilder: 未找到名为 '${targetName}' 的中间件,无法插入`) + logger.warn(`未找到名为 '${targetName}' 的中间件,无法插入`) } return this } @@ -98,7 +102,7 @@ export class MiddlewareBuilder { if (index !== -1) { this.middlewares[index] = newMiddleware } else { - console.warn(`MiddlewareBuilder: 未找到名为 '${targetName}' 的中间件,无法替换`) + logger.warn(`未找到名为 '${targetName}' 的中间件,无法替换`) } return this } diff --git a/src/renderer/src/aiCore/middleware/common/AbortHandlerMiddleware.ts b/src/renderer/src/aiCore/middleware/common/AbortHandlerMiddleware.ts index 2acf553533..c1d3102ed9 100644 --- a/src/renderer/src/aiCore/middleware/common/AbortHandlerMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/common/AbortHandlerMiddleware.ts @@ -1,9 +1,12 @@ +import { loggerService } from '@logger' import { Chunk, ChunkType, ErrorChunk } from '@renderer/types/chunk' import { addAbortController, removeAbortController } from '@renderer/utils/abortController' import { CompletionsParams, CompletionsResult } from '../schemas' import type { CompletionsContext, CompletionsMiddleware } from '../types' +const logger = loggerService.withContext('aiCore:AbortHandlerMiddleware') + export const MIDDLEWARE_NAME = 'AbortHandlerMiddleware' export const AbortHandlerMiddleware: CompletionsMiddleware = @@ -31,7 +34,7 @@ export const AbortHandlerMiddleware: CompletionsMiddleware = } if (!messageId) { - console.warn(`[${MIDDLEWARE_NAME}] No messageId found, abort functionality will not be available.`) + logger.warn(`No messageId found, abort functionality will not be available.`) return next(ctx, params) } diff --git a/src/renderer/src/aiCore/middleware/common/FinalChunkConsumerMiddleware.ts b/src/renderer/src/aiCore/middleware/common/FinalChunkConsumerMiddleware.ts index 80e0cdc5e6..eb06f0d4a8 100644 --- a/src/renderer/src/aiCore/middleware/common/FinalChunkConsumerMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/common/FinalChunkConsumerMiddleware.ts @@ -1,4 +1,4 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import { Usage } from '@renderer/types' import type { Chunk } from '@renderer/types/chunk' import { ChunkType } from '@renderer/types/chunk' @@ -8,6 +8,8 @@ import { CompletionsContext, CompletionsMiddleware } from '../types' export const MIDDLEWARE_NAME = 'FinalChunkConsumerAndNotifierMiddleware' +const logger = loggerService.withContext('FinalChunkConsumerMiddleware') + /** * 最终Chunk消费和通知中间件 * @@ -63,7 +65,7 @@ const FinalChunkConsumerMiddleware: CompletionsMiddleware = while (true) { const { done, value: chunk } = await reader.read() if (done) { - Logger.debug(`[${MIDDLEWARE_NAME}] Input stream finished.`) + logger.debug(`Input stream finished.`) break } @@ -79,11 +81,11 @@ const FinalChunkConsumerMiddleware: CompletionsMiddleware = if (!shouldSkipChunk) params.onChunk?.(genericChunk) } else { - Logger.warn(`[${MIDDLEWARE_NAME}] Received undefined chunk before stream was done.`) + logger.warn(`Received undefined chunk before stream was done.`) } } } catch (error) { - Logger.error(`[${MIDDLEWARE_NAME}] Error consuming stream:`, error) + logger.error(`Error consuming stream:`, error) throw error } finally { if (params.onChunk && !isRecursiveCall) { @@ -115,7 +117,7 @@ const FinalChunkConsumerMiddleware: CompletionsMiddleware = return modifiedResult } else { - Logger.debug(`[${MIDDLEWARE_NAME}] No GenericChunk stream to process.`) + logger.debug(`No GenericChunk stream to process.`) } } @@ -133,7 +135,7 @@ function extractAndAccumulateUsageMetrics(ctx: CompletionsContext, chunk: Generi try { if (ctx._internal.customState && !ctx._internal.customState?.firstTokenTimestamp) { ctx._internal.customState.firstTokenTimestamp = Date.now() - Logger.debug(`[${MIDDLEWARE_NAME}] First token timestamp: ${ctx._internal.customState.firstTokenTimestamp}`) + logger.debug(`First token timestamp: ${ctx._internal.customState.firstTokenTimestamp}`) } if (chunk.type === ChunkType.LLM_RESPONSE_COMPLETE) { // 从LLM_RESPONSE_COMPLETE chunk中提取usage数据 @@ -157,7 +159,7 @@ function extractAndAccumulateUsageMetrics(ctx: CompletionsContext, chunk: Generi ) } } catch (error) { - console.error(`[${MIDDLEWARE_NAME}] Error extracting usage/metrics from chunk:`, error) + logger.error(`Error extracting usage/metrics from chunk:`, error) } } diff --git a/src/renderer/src/aiCore/middleware/common/LoggingMiddleware.ts b/src/renderer/src/aiCore/middleware/common/LoggingMiddleware.ts index 361eea3119..f391e08c3f 100644 --- a/src/renderer/src/aiCore/middleware/common/LoggingMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/common/LoggingMiddleware.ts @@ -1,5 +1,9 @@ +import { loggerService } from '@logger' + import { BaseContext, MethodMiddleware, MiddlewareAPI } from '../types' +const logger = loggerService.withContext('LoggingMiddleware') + export const MIDDLEWARE_NAME = 'GenericLoggingMiddlewares' /** @@ -44,20 +48,20 @@ export const createGenericLoggingMiddleware: () => MethodMiddleware = () => { return (_: MiddlewareAPI) => (next) => async (ctx, args) => { const methodName = ctx.methodName const logPrefix = `[${middlewareName} (${methodName})]` - console.log(`${logPrefix} Initiating. Args:`, stringifyArgsForLogging(args)) + logger.debug(`${logPrefix} Initiating. Args:`, stringifyArgsForLogging(args)) const startTime = Date.now() try { const result = await next(ctx, args) const duration = Date.now() - startTime // Log successful completion of the method call with duration. / // 记录方法调用成功完成及其持续时间。 - console.log(`${logPrefix} Successful. Duration: ${duration}ms`) + logger.debug(`${logPrefix} Successful. Duration: ${duration}ms`) return result } catch (error) { const duration = Date.now() - startTime // Log failure of the method call with duration and error information. / // 记录方法调用失败及其持续时间和错误信息。 - console.error(`${logPrefix} Failed. Duration: ${duration}ms`, error) + logger.error(`${logPrefix} Failed. Duration: ${duration}ms`, error) throw error // Re-throw the error to be handled by subsequent layers or the caller / 重新抛出错误,由后续层或调用者处理 } } diff --git a/src/renderer/src/aiCore/middleware/core/McpToolChunkMiddleware.ts b/src/renderer/src/aiCore/middleware/core/McpToolChunkMiddleware.ts index b74c4895dc..a14ce323b8 100644 --- a/src/renderer/src/aiCore/middleware/core/McpToolChunkMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/core/McpToolChunkMiddleware.ts @@ -1,4 +1,4 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import { MCPTool, MCPToolResponse, Model, ToolCallResponse } from '@renderer/types' import { ChunkType, MCPToolCreatedChunk } from '@renderer/types/chunk' import { SdkMessageParam, SdkRawOutput, SdkToolCall } from '@renderer/types/sdk' @@ -10,6 +10,8 @@ import { CompletionsContext, CompletionsMiddleware } from '../types' export const MIDDLEWARE_NAME = 'McpToolChunkMiddleware' const MAX_TOOL_RECURSION_DEPTH = 20 // 防止无限递归 +const logger = loggerService.withContext('McpToolChunkMiddleware') + /** * MCP工具处理中间件 * @@ -32,7 +34,7 @@ export const McpToolChunkMiddleware: CompletionsMiddleware = const executeWithToolHandling = async (currentParams: CompletionsParams, depth = 0): Promise => { if (depth >= MAX_TOOL_RECURSION_DEPTH) { - Logger.error(`🔧 [${MIDDLEWARE_NAME}] Maximum recursion depth ${MAX_TOOL_RECURSION_DEPTH} exceeded`) + logger.error(`Maximum recursion depth ${MAX_TOOL_RECURSION_DEPTH} exceeded`) throw new Error(`Maximum tool recursion depth ${MAX_TOOL_RECURSION_DEPTH} exceeded`) } @@ -43,7 +45,7 @@ export const McpToolChunkMiddleware: CompletionsMiddleware = } else { const enhancedCompletions = ctx._internal.enhancedDispatch if (!enhancedCompletions) { - Logger.error(`🔧 [${MIDDLEWARE_NAME}] Enhanced completions method not found, cannot perform recursive call`) + logger.error(`Enhanced completions method not found, cannot perform recursive call`) throw new Error('Enhanced completions method not found') } @@ -54,7 +56,7 @@ export const McpToolChunkMiddleware: CompletionsMiddleware = } if (!result.stream) { - Logger.error(`🔧 [${MIDDLEWARE_NAME}] No stream returned from enhanced completions`) + logger.error(`No stream returned from enhanced completions`) throw new Error('No stream returned from enhanced completions') } @@ -123,7 +125,7 @@ function createToolHandlingTransform( executedToolResults.push(...result.toolResults) executedToolCalls.push(...result.confirmedToolCalls) } catch (error) { - console.error(`🔧 [${MIDDLEWARE_NAME}] Error executing tool call asynchronously:`, error) + logger.error(`Error executing tool call asynchronously:`, error) } })() @@ -150,7 +152,7 @@ function createToolHandlingTransform( // 缓存执行结果 executedToolResults.push(...result.toolResults) } catch (error) { - console.error(`🔧 [${MIDDLEWARE_NAME}] Error executing tool use response asynchronously:`, error) + logger.error(`Error executing tool use response asynchronously:`, error) // 错误时不影响其他工具的执行 } })() @@ -162,7 +164,7 @@ function createToolHandlingTransform( controller.enqueue(chunk) } } catch (error) { - console.error(`🔧 [${MIDDLEWARE_NAME}] Error processing chunk:`, error) + logger.error(`Error processing chunk:`, error) controller.error(error) } }, @@ -194,7 +196,7 @@ function createToolHandlingTransform( await executeWithToolHandling(newParams, depth + 1) } } catch (error) { - Logger.error(`🔧 [${MIDDLEWARE_NAME}] Error in tool processing:`, error) + logger.error(`Error in tool processing:`, error) controller.error(error) } finally { hasToolCalls = false @@ -227,7 +229,7 @@ async function executeToolCalls( .filter((t): t is ToolCallResponse => typeof t !== 'undefined') if (mcpToolResponses.length === 0) { - console.warn(`🔧 [${MIDDLEWARE_NAME}] No valid MCP tool responses to execute`) + logger.warn(`No valid MCP tool responses to execute`) return { toolResults: [], confirmedToolCalls: [] } } @@ -325,7 +327,7 @@ function buildParamsWithToolResults( ctx._internal.observer.usage.total_tokens += additionalTokens } } catch (error) { - Logger.error(`🔧 [${MIDDLEWARE_NAME}] Error estimating token usage for new messages:`, error) + logger.error(`Error estimating token usage for new messages:`, error) } } diff --git a/src/renderer/src/aiCore/middleware/core/ResponseTransformMiddleware.ts b/src/renderer/src/aiCore/middleware/core/ResponseTransformMiddleware.ts index 5477aa6557..e0cd2f0d81 100644 --- a/src/renderer/src/aiCore/middleware/core/ResponseTransformMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/core/ResponseTransformMiddleware.ts @@ -1,4 +1,4 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import { SdkRawChunk } from '@renderer/types/sdk' import { ResponseChunkTransformerContext } from '../../clients/types' @@ -7,6 +7,8 @@ import { CompletionsContext, CompletionsMiddleware } from '../types' export const MIDDLEWARE_NAME = 'ResponseTransformMiddleware' +const logger = loggerService.withContext('ResponseTransformMiddleware') + /** * 响应转换中间件 * @@ -32,14 +34,14 @@ export const ResponseTransformMiddleware: CompletionsMiddleware = if (adaptedStream instanceof ReadableStream) { const apiClient = ctx.apiClientInstance if (!apiClient) { - console.error(`[${MIDDLEWARE_NAME}] ApiClient instance not found in context`) + logger.error(`ApiClient instance not found in context`) throw new Error('ApiClient instance not found in context') } // 获取响应转换器 const responseChunkTransformer = apiClient.getResponseChunkTransformer(ctx) if (!responseChunkTransformer) { - Logger.warn(`[${MIDDLEWARE_NAME}] No ResponseChunkTransformer available, skipping transformation`) + logger.warn(`No ResponseChunkTransformer available, skipping transformation`) return result } @@ -47,7 +49,7 @@ export const ResponseTransformMiddleware: CompletionsMiddleware = const model = assistant?.model if (!assistant || !model) { - console.error(`[${MIDDLEWARE_NAME}] Assistant or Model not found for transformation`) + logger.error(`Assistant or Model not found for transformation`) throw new Error('Assistant or Model not found for transformation') } @@ -61,7 +63,7 @@ export const ResponseTransformMiddleware: CompletionsMiddleware = provider: ctx.apiClientInstance?.provider } - console.log(`[${MIDDLEWARE_NAME}] Transforming raw SDK chunks with context:`, transformerContext) + logger.debug(`Transforming raw SDK chunks with context:`, transformerContext) try { // 创建转换后的流 @@ -75,7 +77,7 @@ export const ResponseTransformMiddleware: CompletionsMiddleware = stream: genericChunkTransformStream } } catch (error) { - Logger.error(`[${MIDDLEWARE_NAME}] Error during chunk transformation:`, error) + logger.error(`Error during chunk transformation:`, error) throw error } } diff --git a/src/renderer/src/aiCore/middleware/core/TextChunkMiddleware.ts b/src/renderer/src/aiCore/middleware/core/TextChunkMiddleware.ts index cfaf70299f..03035f257c 100644 --- a/src/renderer/src/aiCore/middleware/core/TextChunkMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/core/TextChunkMiddleware.ts @@ -1,4 +1,4 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import { ChunkType } from '@renderer/types/chunk' import { CompletionsParams, CompletionsResult, GenericChunk } from '../schemas' @@ -6,6 +6,8 @@ import { CompletionsContext, CompletionsMiddleware } from '../types' export const MIDDLEWARE_NAME = 'TextChunkMiddleware' +const logger = loggerService.withContext('TextChunkMiddleware') + /** * 文本块处理中间件 * @@ -32,7 +34,7 @@ export const TextChunkMiddleware: CompletionsMiddleware = const model = params.assistant?.model if (!assistant || !model) { - Logger.warn(`[${MIDDLEWARE_NAME}] Missing assistant or model information, skipping text processing`) + logger.warn(`Missing assistant or model information, skipping text processing`) return result } @@ -92,7 +94,7 @@ export const TextChunkMiddleware: CompletionsMiddleware = stream: enhancedTextStream } } else { - Logger.warn(`[${MIDDLEWARE_NAME}] No stream to process or not a ReadableStream. Returning original result.`) + logger.warn(`No stream to process or not a ReadableStream. Returning original result.`) } } diff --git a/src/renderer/src/aiCore/middleware/core/ThinkChunkMiddleware.ts b/src/renderer/src/aiCore/middleware/core/ThinkChunkMiddleware.ts index 957b925400..5ec8fc20d5 100644 --- a/src/renderer/src/aiCore/middleware/core/ThinkChunkMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/core/ThinkChunkMiddleware.ts @@ -1,4 +1,4 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import { ChunkType, ThinkingCompleteChunk, ThinkingDeltaChunk } from '@renderer/types/chunk' import { CompletionsParams, CompletionsResult, GenericChunk } from '../schemas' @@ -6,6 +6,8 @@ import { CompletionsContext, CompletionsMiddleware } from '../types' export const MIDDLEWARE_NAME = 'ThinkChunkMiddleware' +const logger = loggerService.withContext('ThinkChunkMiddleware') + /** * 处理思考内容的中间件 * @@ -94,7 +96,7 @@ export const ThinkChunkMiddleware: CompletionsMiddleware = stream: processedStream } } else { - Logger.warn(`[${MIDDLEWARE_NAME}] No generic chunk stream to process or not a ReadableStream.`) + logger.warn(`No generic chunk stream to process or not a ReadableStream.`) } } diff --git a/src/renderer/src/aiCore/middleware/core/TransformCoreToSdkParamsMiddleware.ts b/src/renderer/src/aiCore/middleware/core/TransformCoreToSdkParamsMiddleware.ts index 0ff536d418..51471ba1df 100644 --- a/src/renderer/src/aiCore/middleware/core/TransformCoreToSdkParamsMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/core/TransformCoreToSdkParamsMiddleware.ts @@ -1,4 +1,4 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import { ChunkType } from '@renderer/types/chunk' import { CompletionsParams, CompletionsResult } from '../schemas' @@ -6,6 +6,8 @@ import { CompletionsContext, CompletionsMiddleware } from '../types' export const MIDDLEWARE_NAME = 'TransformCoreToSdkParamsMiddleware' +const logger = loggerService.withContext('TransformCoreToSdkParamsMiddleware') + /** * 中间件:将CoreCompletionsRequest转换为SDK特定的参数 * 使用上下文中ApiClient实例的requestTransformer进行转换 @@ -23,16 +25,14 @@ export const TransformCoreToSdkParamsMiddleware: CompletionsMiddleware = const apiClient = ctx.apiClientInstance if (!apiClient) { - Logger.error(`🔄 [${MIDDLEWARE_NAME}] ApiClient instance not found in context.`) + logger.error(`ApiClient instance not found in context.`) throw new Error('ApiClient instance not found in context') } // 检查是否有requestTransformer方法 const requestTransformer = apiClient.getRequestTransformer() if (!requestTransformer) { - Logger.warn( - `🔄 [${MIDDLEWARE_NAME}] ApiClient does not have getRequestTransformer method, skipping transformation` - ) + logger.warn(`ApiClient does not have getRequestTransformer method, skipping transformation`) const result = await next(ctx, params) return result } @@ -42,7 +42,7 @@ export const TransformCoreToSdkParamsMiddleware: CompletionsMiddleware = const model = params.assistant.model if (!assistant || !model) { - console.error(`🔄 [${MIDDLEWARE_NAME}] Assistant or Model not found for transformation.`) + logger.error(`Assistant or Model not found for transformation.`) throw new Error('Assistant or Model not found for transformation') } @@ -74,7 +74,7 @@ export const TransformCoreToSdkParamsMiddleware: CompletionsMiddleware = } return next(ctx, params) } catch (error) { - Logger.error(`🔄 [${MIDDLEWARE_NAME}] Error during request transformation:`, error) + logger.error(`Error during request transformation:`, error) // 让错误向上传播,或者可以在这里进行特定的错误处理 throw error } diff --git a/src/renderer/src/aiCore/middleware/feat/ThinkingTagExtractionMiddleware.ts b/src/renderer/src/aiCore/middleware/feat/ThinkingTagExtractionMiddleware.ts index 425f29c705..46a4ec3e85 100644 --- a/src/renderer/src/aiCore/middleware/feat/ThinkingTagExtractionMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/feat/ThinkingTagExtractionMiddleware.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { Model } from '@renderer/types' import { ChunkType, @@ -7,11 +8,12 @@ import { ThinkingStartChunk } from '@renderer/types/chunk' import { TagConfig, TagExtractor } from '@renderer/utils/tagExtraction' -import Logger from 'electron-log/renderer' import { CompletionsParams, CompletionsResult, GenericChunk } from '../schemas' import { CompletionsContext, CompletionsMiddleware } from '../types' +const logger = loggerService.withContext('ThinkingTagExtractionMiddleware') + export const MIDDLEWARE_NAME = 'ThinkingTagExtractionMiddleware' // 不同模型的思考标签配置 @@ -151,7 +153,7 @@ export const ThinkingTagExtractionMiddleware: CompletionsMiddleware = stream: processedStream } } else { - Logger.warn(`[${MIDDLEWARE_NAME}] No generic chunk stream to process or not a ReadableStream.`) + logger.warn(`[${MIDDLEWARE_NAME}] No generic chunk stream to process or not a ReadableStream.`) } } return result diff --git a/src/renderer/src/aiCore/middleware/feat/ToolUseExtractionMiddleware.ts b/src/renderer/src/aiCore/middleware/feat/ToolUseExtractionMiddleware.ts index b53d7348f1..933f4b8060 100644 --- a/src/renderer/src/aiCore/middleware/feat/ToolUseExtractionMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/feat/ToolUseExtractionMiddleware.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { MCPTool } from '@renderer/types' import { ChunkType, MCPToolCreatedChunk, TextDeltaChunk } from '@renderer/types/chunk' import { parseToolUse } from '@renderer/utils/mcp-tools' @@ -8,6 +9,8 @@ import { CompletionsContext, CompletionsMiddleware } from '../types' export const MIDDLEWARE_NAME = 'ToolUseExtractionMiddleware' +const logger = loggerService.withContext('ToolUseExtractionMiddleware') + // 工具使用标签配置 const TOOL_USE_TAG_CONFIG: TagConfig = { openingTag: '', @@ -106,7 +109,7 @@ function createToolUseExtractionTransform( // 转发其他所有chunk controller.enqueue(chunk) } catch (error) { - console.error(`🔧 [${MIDDLEWARE_NAME}] Error processing chunk:`, error) + logger.error(`Error processing chunk:`, error) controller.error(error) } }, diff --git a/src/renderer/src/components/CodeBlockView/HtmlArtifactsCard.tsx b/src/renderer/src/components/CodeBlockView/HtmlArtifactsCard.tsx index b3389570c1..c7a5a99d52 100644 --- a/src/renderer/src/components/CodeBlockView/HtmlArtifactsCard.tsx +++ b/src/renderer/src/components/CodeBlockView/HtmlArtifactsCard.tsx @@ -1,4 +1,5 @@ import { CodeOutlined, LinkOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { useTheme } from '@renderer/context/ThemeProvider' import { ThemeMode } from '@renderer/types' import { extractTitle } from '@renderer/utils/formats' @@ -11,6 +12,8 @@ import styled, { keyframes } from 'styled-components' import HtmlArtifactsPopup from './HtmlArtifactsPopup' +const logger = loggerService.withContext('HtmlArtifactsCard') + const HTML_VOID_ELEMENTS = new Set([ 'area', 'base', @@ -123,7 +126,7 @@ const HtmlArtifactsCard: FC = ({ html }) => { if (window.api.shell?.openExternal) { window.api.shell.openExternal(filePath) } else { - console.error(t('artifacts.preview.openExternal.error.content')) + logger.error(t('artifacts.preview.openExternal.error.content')) } } diff --git a/src/renderer/src/components/CodeBlockView/view.tsx b/src/renderer/src/components/CodeBlockView/view.tsx index 95fb6ddf30..28e371feb5 100644 --- a/src/renderer/src/components/CodeBlockView/view.tsx +++ b/src/renderer/src/components/CodeBlockView/view.tsx @@ -1,4 +1,5 @@ import { LoadingOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import CodeEditor from '@renderer/components/CodeEditor' import { CodeTool, CodeToolbar, TOOL_SPECS, useCodeTool } from '@renderer/components/CodeToolbar' import { useSettings } from '@renderer/hooks/useSettings' @@ -17,6 +18,8 @@ import HtmlArtifactsCard from './HtmlArtifactsCard' import StatusBar from './StatusBar' import { ViewMode } from './types' +const logger = loggerService.withContext('CodeBlockView') + interface Props { children: string language: string @@ -92,7 +95,7 @@ export const CodeBlockView: React.FC = memo(({ children, language, onSave setOutput(formattedOutput) }) .catch((error) => { - console.error('Unexpected error:', error) + logger.error('Unexpected error:', error) setOutput(`Unexpected error: ${error.message || 'Unknown error'}`) }) .finally(() => { diff --git a/src/renderer/src/components/CodeToolbar/usePreviewTools.tsx b/src/renderer/src/components/CodeToolbar/usePreviewTools.tsx index c9fb904fc7..73aabe4aa4 100644 --- a/src/renderer/src/components/CodeToolbar/usePreviewTools.tsx +++ b/src/renderer/src/components/CodeToolbar/usePreviewTools.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { download } from '@renderer/utils/download' import { FileImage, ZoomIn, ZoomOut } from 'lucide-react' import { RefObject, useCallback, useEffect, useRef, useState } from 'react' @@ -8,6 +9,8 @@ import { TOOL_SPECS } from './constants' import { useCodeTool } from './hook' import { CodeTool } from './types' +const logger = loggerService.withContext('usePreviewToolHandlers') + // 预编译正则表达式用于查询位置 const TRANSFORM_REGEX = /translate\((-?\d+\.?\d*)px,\s*(-?\d+\.?\d*)px\)/ @@ -205,7 +208,7 @@ export const usePreviewToolHandlers = ( } img.src = svgBase64 } catch (error) { - console.error('Copy failed:', error) + logger.error('Copy failed:', error) window.message.error(t('message.copy.failed')) } }, [getImgElement, t]) @@ -265,7 +268,7 @@ export const usePreviewToolHandlers = ( img.src = svgBase64 } } catch (error) { - console.error('Download failed:', error) + logger.error('Download failed:', error) } }, [getImgElement, prefix, customDownloader] diff --git a/src/renderer/src/components/Icons/FallbackFavicon.tsx b/src/renderer/src/components/Icons/FallbackFavicon.tsx index df45673215..d62b0a884f 100644 --- a/src/renderer/src/components/Icons/FallbackFavicon.tsx +++ b/src/renderer/src/components/Icons/FallbackFavicon.tsx @@ -1,7 +1,9 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import { useEffect, useState } from 'react' import styled from 'styled-components' +const logger = loggerService.withContext('FallbackFavicon') + // 记录失败的URL的缓存键前缀 const FAILED_FAVICON_CACHE_PREFIX = 'failed_favicon_' // 失败URL的缓存时间 (24小时) @@ -121,7 +123,7 @@ const FallbackFavicon: React.FC = ({ hostname, alt }) => { setFaviconState({ status: 'loaded', src: url }) }) .catch((error) => { - Logger.log('All favicon requests failed:', error) + logger.error('All favicon requests failed:', error) setFaviconState({ status: 'loaded', src: faviconUrls[0] }) }) diff --git a/src/renderer/src/components/ImageViewer.tsx b/src/renderer/src/components/ImageViewer.tsx index e9f9be1691..a64379543e 100644 --- a/src/renderer/src/components/ImageViewer.tsx +++ b/src/renderer/src/components/ImageViewer.tsx @@ -9,6 +9,7 @@ import { ZoomInOutlined, ZoomOutOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { download } from '@renderer/utils/download' import { Dropdown, Image as AntImage, ImageProps as AntImageProps, Space } from 'antd' import { Base64 } from 'js-base64' @@ -21,6 +22,8 @@ interface ImageViewerProps extends AntImageProps { src: string } +const logger = loggerService.withContext('ImageViewer') + const ImageViewer: React.FC = ({ src, style, ...props }) => { const { t } = useTranslation() @@ -59,7 +62,7 @@ const ImageViewer: React.FC = ({ src, style, ...props }) => { window.message.success(t('message.copy.success')) } catch (error) { - console.error('复制图片失败:', error) + logger.error('复制图片失败:', error) window.message.error(t('message.copy.failed')) } } diff --git a/src/renderer/src/components/LocalBackupModals.tsx b/src/renderer/src/components/LocalBackupModals.tsx index 420c9c2f37..aa2848eaa3 100644 --- a/src/renderer/src/components/LocalBackupModals.tsx +++ b/src/renderer/src/components/LocalBackupModals.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { backupToLocal } from '@renderer/services/BackupService' import { Button, Input, Modal } from 'antd' import dayjs from 'dayjs' @@ -13,6 +14,8 @@ interface LocalBackupModalProps { setCustomFileName: (value: string) => void } +const logger = loggerService.withContext('LocalBackupModal') + export function LocalBackupModal({ isModalVisible, handleBackup, @@ -80,7 +83,7 @@ export function useLocalBackupModal(localBackupDir: string | undefined) { }) setIsModalVisible(false) } catch (error) { - console.error('[LocalBackupModal] Backup failed:', error) + logger.error('Backup failed:', error) } finally { setBackuping(false) } diff --git a/src/renderer/src/components/NutstorePathSelector.tsx b/src/renderer/src/components/NutstorePathSelector.tsx index 8bdebc42bf..b25b76a0c4 100644 --- a/src/renderer/src/components/NutstorePathSelector.tsx +++ b/src/renderer/src/components/NutstorePathSelector.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { FolderIcon as NutstoreFolderIcon } from '@renderer/components/Icons/NutstoreIcons' import { Button, Input } from 'antd' import { useCallback, useEffect, useState } from 'react' @@ -12,6 +13,8 @@ interface NewFolderProps { className?: string } +const logger = loggerService.withContext('NutstorePathSelector') + const NewFolderContainer = styled.div` display: flex; align-items: center; @@ -95,7 +98,7 @@ function FileList(props: FileListProps) { setFiles(items) } catch (error) { if (error instanceof Error) { - console.error(error) + logger.error('Error fetching files:', error) window.modal.error({ content: error.message, centered: true diff --git a/src/renderer/src/components/ObsidianExportDialog.tsx b/src/renderer/src/components/ObsidianExportDialog.tsx index dfc4cc09e0..d45dde0856 100644 --- a/src/renderer/src/components/ObsidianExportDialog.tsx +++ b/src/renderer/src/components/ObsidianExportDialog.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import i18n from '@renderer/i18n' import store from '@renderer/store' import type { Topic } from '@renderer/types' @@ -12,6 +13,8 @@ import { import { Alert, Empty, Form, Input, Modal, Select, Spin, Switch, TreeSelect } from 'antd' import React, { useEffect, useState } from 'react' +const logger = loggerService.withContext('ObsidianExportDialog') + const { Option } = Select interface FileInfo { @@ -192,7 +195,7 @@ const PopupContainer: React.FC = ({ setFiles(filesData) } } catch (error) { - console.error('获取Obsidian Vault失败:', error) + logger.error('获取Obsidian Vault失败:', error) setError(i18n.t('chat.topics.export.obsidian_fetch_error')) } finally { setLoading(false) @@ -210,7 +213,7 @@ const PopupContainer: React.FC = ({ const filesData = await window.obsidian.getFiles(selectedVault) setFiles(filesData) } catch (error) { - console.error('获取Obsidian文件失败:', error) + logger.error('获取Obsidian文件失败:', error) setError(i18n.t('chat.topics.export.obsidian_fetch_folders_error')) } finally { setLoading(false) diff --git a/src/renderer/src/components/Popups/ApiKeyListPopup/hook.ts b/src/renderer/src/components/Popups/ApiKeyListPopup/hook.ts index 5bd9072d12..10497520b6 100644 --- a/src/renderer/src/components/Popups/ApiKeyListPopup/hook.ts +++ b/src/renderer/src/components/Popups/ApiKeyListPopup/hook.ts @@ -1,4 +1,4 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import { isEmbeddingModel, isRerankModel } from '@renderer/config/models' import SelectProviderModelPopup from '@renderer/pages/settings/ProviderSettings/SelectProviderModelPopup' import { checkApi } from '@renderer/services/ApiService' @@ -19,6 +19,8 @@ interface UseApiKeysProps { providerKind: ApiProviderKind } +const logger = loggerService.withContext('ApiKeyListPopup') + /** * API Keys 管理 hook */ @@ -116,7 +118,7 @@ export function useApiKeys({ provider, updateProvider, providerKind }: UseApiKey const updateKey = useCallback( (index: number, key: string): ApiKeyValidity => { if (index < 0 || index >= keys.length) { - Logger.error('[ApiKeyList] invalid key index', { index }) + logger.error('invalid key index', { index }) return { isValid: false, error: 'Invalid index' } } @@ -220,7 +222,7 @@ export function useApiKeys({ provider, updateProvider, providerKind }: UseApiKey latency: undefined }) - Logger.error('[ApiKeyList] failed to validate the connectivity of the api key', error) + logger.error('failed to validate the connectivity of the api key', error) } }, [keys, connectivityStates, updateConnectivityState, provider, providerKind] @@ -301,7 +303,7 @@ async function getModelForCheck(provider: Provider, t: TFunction): Promise void } @@ -35,7 +37,7 @@ const PopupContainer: React.FC = ({ resolve }) => { }, []) const onOk = async () => { - Logger.log('[BackupManager] ', skipBackupFile) + logger.debug('skipBackupFile', skipBackupFile) await backup(skipBackupFile) setOpen(false) } diff --git a/src/renderer/src/components/Popups/SaveToKnowledgePopup.tsx b/src/renderer/src/components/Popups/SaveToKnowledgePopup.tsx index b6f0577c4f..c7738cd96c 100644 --- a/src/renderer/src/components/Popups/SaveToKnowledgePopup.tsx +++ b/src/renderer/src/components/Popups/SaveToKnowledgePopup.tsx @@ -1,6 +1,6 @@ +import { loggerService } from '@logger' import CustomTag from '@renderer/components/CustomTag' import { TopView } from '@renderer/components/TopView' -import Logger from '@renderer/config/logger' import { useKnowledge, useKnowledgeBases } from '@renderer/hooks/useKnowledge' import { Message } from '@renderer/types/newMessage' import { @@ -16,6 +16,8 @@ import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' +const logger = loggerService.withContext('SaveToKnowledgePopup') + const { Text } = Typography // 内容类型配置 @@ -201,7 +203,7 @@ const PopupContainer: React.FC = ({ message, title, resolve }) => { setOpen(false) resolve({ success: true, savedCount }) } catch (error) { - Logger.error('[SaveToKnowledgePopup] save failed:', error) + logger.error('save failed:', error) window.message.error(t('chat.save.knowledge.error.save_failed')) setLoading(false) } diff --git a/src/renderer/src/components/Popups/TextEditPopup.tsx b/src/renderer/src/components/Popups/TextEditPopup.tsx index ab7bf40cb6..1edd35215c 100644 --- a/src/renderer/src/components/Popups/TextEditPopup.tsx +++ b/src/renderer/src/components/Popups/TextEditPopup.tsx @@ -1,4 +1,5 @@ import { LoadingOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { useDefaultModel } from '@renderer/hooks/useAssistant' import { useSettings } from '@renderer/hooks/useSettings' import { fetchTranslate } from '@renderer/services/ApiService' @@ -15,6 +16,8 @@ import styled from 'styled-components' import { TopView } from '../TopView' +const logger = loggerService.withContext('TextEditPopup') + interface ShowParams { text: string textareaProps?: TextAreaProps @@ -118,7 +121,7 @@ const PopupContainer: React.FC = ({ setTextValue(translatedText) } } catch (error) { - console.error('Translation failed:', error) + logger.error('Translation failed:', error) window.message.error({ content: t('translate.error.failed'), key: 'translate-message' diff --git a/src/renderer/src/components/TranslateButton.tsx b/src/renderer/src/components/TranslateButton.tsx index 41a29a6fa4..516b8057b4 100644 --- a/src/renderer/src/components/TranslateButton.tsx +++ b/src/renderer/src/components/TranslateButton.tsx @@ -1,4 +1,5 @@ import { LoadingOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { useDefaultModel } from '@renderer/hooks/useAssistant' import { useSettings } from '@renderer/hooks/useSettings' import { fetchTranslate } from '@renderer/services/ApiService' @@ -18,6 +19,8 @@ interface Props { isLoading?: boolean } +const logger = loggerService.withContext('TranslateButton') + const TranslateButton: FC = ({ text, onTranslated, disabled, style, isLoading }) => { const { t } = useTranslation() const { translateModel } = useDefaultModel() @@ -59,7 +62,7 @@ const TranslateButton: FC = ({ text, onTranslated, disabled, style, isLoa const translatedText = await fetchTranslate({ content: text, assistant }) onTranslated(translatedText) } catch (error) { - console.error('Translation failed:', error) + logger.error('Translation failed:', error) window.message.error({ content: t('translate.error.failed'), key: 'translate-message' diff --git a/src/renderer/src/config/logger.ts b/src/renderer/src/config/logger.ts deleted file mode 100644 index 2101e24857..0000000000 --- a/src/renderer/src/config/logger.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Logger from 'electron-log/renderer' - -// 设置渲染进程的日志级别 -Logger.transports.console.level = 'info' - -export default Logger diff --git a/src/renderer/src/config/minapps.ts b/src/renderer/src/config/minapps.ts index 6990e6d157..b363eac9e9 100644 --- a/src/renderer/src/config/minapps.ts +++ b/src/renderer/src/config/minapps.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import ThreeMinTopAppLogo from '@renderer/assets/images/apps/3mintop.png?url' import AbacusLogo from '@renderer/assets/images/apps/abacus.webp?url' import AIStudioLogo from '@renderer/assets/images/apps/aistudio.svg?url' @@ -57,6 +58,8 @@ import OpenAiProviderLogo from '@renderer/assets/images/providers/openai.png?url import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png?url' import { MinAppType } from '@renderer/types' +const logger = loggerService.withContext('Config:minapps') + // 加载自定义小应用 const loadCustomMiniApp = async (): Promise => { try { @@ -79,7 +82,7 @@ const loadCustomMiniApp = async (): Promise => { addTime: app.addTime || now })) } catch (error) { - console.error('Failed to load custom mini apps:', error) + logger.error('Failed to load custom mini apps:', error) return [] } } diff --git a/src/renderer/src/databases/upgrades.ts b/src/renderer/src/databases/upgrades.ts index 49c886aedc..f94d45e059 100644 --- a/src/renderer/src/databases/upgrades.ts +++ b/src/renderer/src/databases/upgrades.ts @@ -1,4 +1,4 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import { LanguagesEnum } from '@renderer/config/translate' import type { LanguageCode, LegacyMessage as OldMessage, Topic } from '@renderer/types' import { FileTypes, WebSearchSource } from '@renderer/types' // Import FileTypes enum @@ -23,6 +23,8 @@ import { createTranslationBlock } from '../utils/messageUtils/create' +const logger = loggerService.withContext('Database:Upgrades') + export async function upgradeToV5(tx: Transaction): Promise { const topics = await tx.table('topics').toArray() const files = await tx.table('files').toArray() @@ -91,7 +93,7 @@ function mapOldStatusToNewMessageStatus(oldStatus: OldMessage['status']): NewMes // --- UPDATED UPGRADE FUNCTION for Version 7 --- export async function upgradeToV7(tx: Transaction): Promise { - Logger.info('Starting DB migration to version 7: Normalizing messages and blocks...') + logger.info('Starting DB migration to version 7: Normalizing messages and blocks...') const oldTopicsTable = tx.table('topics') const newBlocksTable = tx.table('message_blocks') @@ -102,7 +104,7 @@ export async function upgradeToV7(tx: Transaction): Promise { const blocksToCreate: MessageBlock[] = [] if (!oldTopic.messages || !Array.isArray(oldTopic.messages)) { - console.warn(`Topic ${oldTopic.id} has no valid messages array, skipping.`) + logger.warn(`Topic ${oldTopic.id} has no valid messages array, skipping.`) topicUpdates[oldTopic.id] = { messages: [] } return } @@ -303,14 +305,14 @@ export async function upgradeToV7(tx: Transaction): Promise { const updateOperations = Object.entries(topicUpdates).map(([id, data]) => ({ key: id, changes: data })) if (updateOperations.length > 0) { await oldTopicsTable.bulkUpdate(updateOperations) - Logger.log(`Updated message references for ${updateOperations.length} topics.`) + logger.info(`Updated message references for ${updateOperations.length} topics.`) } - Logger.log('DB migration to version 7 finished successfully.') + logger.info('DB migration to version 7 finished successfully.') } export async function upgradeToV8(tx: Transaction): Promise { - Logger.log('DB migration to version 8 started') + logger.info('DB migration to version 8 started') const langMap: Record = { english: 'en-us', @@ -340,7 +342,7 @@ export async function upgradeToV8(tx: Transaction): Promise { const originTarget = (await settingsTable.get('translate:target:language'))?.value const originPair = (await settingsTable.get('translate:bidirectional:pair'))?.value let newSource, newTarget, newPair - Logger.log('originSource: %o', originSource) + logger.info('originSource: %o', originSource) if (originSource === 'auto') { newSource = 'auto' } else { @@ -350,20 +352,20 @@ export async function upgradeToV8(tx: Transaction): Promise { } } - Logger.log('originTarget: %o', originTarget) + logger.info('originTarget: %o', originTarget) newTarget = langMap[originTarget] if (!newTarget) { newTarget = LanguagesEnum.zhCN.langCode } - Logger.log('originPair: %o', originPair) + logger.info('originPair: %o', originPair) if (!originPair || !originPair[0] || !originPair[1]) { newPair = defaultPair } else { newPair = [langMap[originPair[0]], langMap[originPair[1]]] } - Logger.log('DB migration to version 8: %o', { newSource, newTarget, newPair }) + logger.info('DB migration to version 8: %o', { newSource, newTarget, newPair }) await settingsTable.put({ id: 'translate:bidirectional:pair', value: newPair }) await settingsTable.put({ id: 'translate:source:language', value: newSource }) @@ -379,8 +381,8 @@ export async function upgradeToV8(tx: Transaction): Promise { targetLanguage: langMap[history.targetLanguage] }) } catch (error) { - console.error('Error upgrading history:', error) + logger.error('Error upgrading history:', error) } } - Logger.log('DB migration to version 8 finished.') + logger.info('DB migration to version 8 finished.') } diff --git a/src/renderer/src/hooks/useAppInit.ts b/src/renderer/src/hooks/useAppInit.ts index 140219ad78..ee8096a943 100644 --- a/src/renderer/src/hooks/useAppInit.ts +++ b/src/renderer/src/hooks/useAppInit.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { isMac } from '@renderer/config/constant' import { isLocalAi } from '@renderer/config/env' import { useTheme } from '@renderer/context/ThemeProvider' @@ -20,6 +21,8 @@ import { useRuntime } from './useRuntime' import { useSettings } from './useSettings' import useUpdateHandler from './useUpdateHandler' +const logger = loggerService.withContext('useAppInit') + export function useAppInit() { const dispatch = useAppDispatch() const { proxyUrl, language, windowStyle, autoCheckUpdate, proxyMode, customCss, enableDataCollection } = useSettings() @@ -133,7 +136,7 @@ export function useAppInit() { useEffect(() => { const memoryService = MemoryService.getInstance() memoryService.updateConfig().catch((error) => { - console.error('Failed to update memory config:', error) + logger.error('Failed to update memory config:', error) }) }, [memoryConfig]) } diff --git a/src/renderer/src/hooks/useChatContext.ts b/src/renderer/src/hooks/useChatContext.ts index 2771037d33..43577972fe 100644 --- a/src/renderer/src/hooks/useChatContext.ts +++ b/src/renderer/src/hooks/useChatContext.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { useMessageOperations } from '@renderer/hooks/useMessageOperations' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { RootState } from '@renderer/store' @@ -9,6 +10,8 @@ import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector, useStore } from 'react-redux' +const logger = loggerService.withContext('useChatContext') + export const useChatContext = (activeTopic: Topic) => { const { t } = useTranslation() const dispatch = useDispatch() @@ -115,7 +118,7 @@ export const useChatContext = (activeTopic: Topic) => { window.message.success(t('message.delete.success')) handleToggleMultiSelectMode(false) } catch (error) { - console.error('Failed to delete messages:', error) + logger.error('Failed to delete messages:', error) window.message.error(t('message.delete.failed')) } } diff --git a/src/renderer/src/hooks/useMessageOperations.ts b/src/renderer/src/hooks/useMessageOperations.ts index d8ac8aac60..331d887065 100644 --- a/src/renderer/src/hooks/useMessageOperations.ts +++ b/src/renderer/src/hooks/useMessageOperations.ts @@ -1,5 +1,5 @@ +import { loggerService } from '@logger' import { createSelector } from '@reduxjs/toolkit' -import Logger from '@renderer/config/logger' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { estimateUserPromptUsage } from '@renderer/services/TokenService' import store, { type RootState, useAppDispatch, useAppSelector } from '@renderer/store' @@ -26,6 +26,8 @@ import { abortCompletion } from '@renderer/utils/abortController' import { throttle } from 'lodash' import { useCallback } from 'react' +const logger = loggerService.withContext('UseMessageOperations') + const selectMessagesState = (state: RootState) => state.messages export const selectNewTopicLoading = createSelector( @@ -75,7 +77,7 @@ export function useMessageOperations(topic: Topic) { const editMessage = useCallback( async (messageId: string, updates: Partial>) => { if (!topic?.id) { - console.error('[editMessage] Topic prop is not valid.') + logger.error('[editMessage] Topic prop is not valid.') return } @@ -157,7 +159,7 @@ export function useMessageOperations(topic: Topic) { const regenerateAssistantMessage = useCallback( async (message: Message, assistant: Assistant) => { if (message.role !== 'assistant') { - console.warn('regenerateAssistantMessage should only be called for assistant messages.') + logger.warn('regenerateAssistantMessage should only be called for assistant messages.') return } await dispatch(regenerateAssistantResponseThunk(topic.id, message, assistant)) @@ -172,11 +174,11 @@ export function useMessageOperations(topic: Topic) { const appendAssistantResponse = useCallback( async (existingAssistantMessage: Message, newModel: Model, assistant: Assistant) => { if (existingAssistantMessage.role !== 'assistant') { - console.error('appendAssistantResponse should only be called for an existing assistant message.') + logger.error('appendAssistantResponse should only be called for an existing assistant message.') return } if (!existingAssistantMessage.askId) { - console.error('Cannot append response: The existing assistant message is missing its askId.') + logger.error('Cannot append response: The existing assistant message is missing its askId.') return } await dispatch(appendAssistantResponseThunk(topic.id, existingAssistantMessage.id, newModel, assistant)) @@ -204,7 +206,7 @@ export function useMessageOperations(topic: Topic) { const state = store.getState() const message = state.messages.entities[messageId] if (!message) { - console.error('[getTranslationUpdater] cannot find message:', messageId) + logger.error('[getTranslationUpdater] cannot find message:', messageId) return null } @@ -240,7 +242,7 @@ export function useMessageOperations(topic: Topic) { } if (!blockId) { - console.error('[getTranslationUpdater] Failed to create translation block.') + logger.error('[getTranslationUpdater] Failed to create translation block.') return null } @@ -265,7 +267,7 @@ export function useMessageOperations(topic: Topic) { */ const createTopicBranch = useCallback( (sourceTopicId: string, branchPointIndex: number, newTopic: Topic) => { - Logger.log(`Cloning messages from topic ${sourceTopicId} to new topic ${newTopic.id}`) + logger.info(`Cloning messages from topic ${sourceTopicId} to new topic ${newTopic.id}`) return dispatch(cloneMessagesToNewTopicThunk(sourceTopicId, branchPointIndex, newTopic)) }, [dispatch] @@ -280,7 +282,7 @@ export function useMessageOperations(topic: Topic) { const editMessageBlocks = useCallback( async (messageId: string, editedBlocks: MessageBlock[]) => { if (!topic?.id) { - console.error('[editMessageBlocks] Topic prop is not valid.') + logger.error('[editMessageBlocks] Topic prop is not valid.') return } @@ -289,7 +291,7 @@ export function useMessageOperations(topic: Topic) { const state = store.getState() const message = state.messages.entities[messageId] if (!message) { - console.error('[editMessageBlocks] Message not found:', messageId) + logger.error('[editMessageBlocks] Message not found:', messageId) return } @@ -353,7 +355,7 @@ export function useMessageOperations(topic: Topic) { await dispatch(removeBlocksThunk(topic.id, messageId, blockIdsToRemove)) } } catch (error) { - console.error('[editMessageBlocks] Failed to update message blocks:', error) + logger.error('[editMessageBlocks] Failed to update message blocks:', error) } }, [dispatch, topic?.id] @@ -369,7 +371,7 @@ export function useMessageOperations(topic: Topic) { const mainTextBlock = editedBlocks.find((block) => block.type === MessageBlockType.MAIN_TEXT) if (!mainTextBlock) { - console.error('[resendUserMessageWithEdit] Main text block not found in edited blocks') + logger.error('[resendUserMessageWithEdit] Main text block not found in edited blocks') return } @@ -401,14 +403,14 @@ export function useMessageOperations(topic: Topic) { const removeMessageBlock = useCallback( async (messageId: string, blockIdToRemove: string) => { if (!topic?.id) { - console.error('[removeMessageBlock] Topic prop is not valid.') + logger.error('[removeMessageBlock] Topic prop is not valid.') return } const state = store.getState() const message = state.messages.entities[messageId] if (!message || !message.blocks) { - console.error('[removeMessageBlock] Message not found or has no blocks:', messageId) + logger.error('[removeMessageBlock] Message not found or has no blocks:', messageId) return } diff --git a/src/renderer/src/hooks/useNutstoreSSO.ts b/src/renderer/src/hooks/useNutstoreSSO.ts index 229615a2cd..8be86257ac 100644 --- a/src/renderer/src/hooks/useNutstoreSSO.ts +++ b/src/renderer/src/hooks/useNutstoreSSO.ts @@ -1,5 +1,8 @@ +import { loggerService } from '@logger' import { useCallback } from 'react' +const logger = loggerService.withContext('useNutstoreSSO') + export function useNutstoreSSO() { const nutstoreSSOHandler = useCallback(() => { return new Promise((resolve, reject) => { @@ -11,7 +14,7 @@ export function useNutstoreSSO() { if (!encryptedToken) return reject(null) resolve(encryptedToken) } catch (error) { - console.error('解析URL失败:', error) + logger.error('解析URL失败:', error) reject(null) } finally { removeListener() diff --git a/src/renderer/src/hooks/usePinnedModels.ts b/src/renderer/src/hooks/usePinnedModels.ts index 74337872a4..ae513f2fd8 100644 --- a/src/renderer/src/hooks/usePinnedModels.ts +++ b/src/renderer/src/hooks/usePinnedModels.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import db from '@renderer/databases' import { getModelUniqId } from '@renderer/services/ModelService' import { sortBy } from 'lodash' @@ -5,6 +6,8 @@ import { useCallback, useEffect, useState } from 'react' import { useProviders } from './useProvider' +const logger = loggerService.withContext('usePinnedModels') + export const usePinnedModels = () => { const [pinnedModels, setPinnedModels] = useState([]) const [loading, setLoading] = useState(true) @@ -30,7 +33,7 @@ export const usePinnedModels = () => { } loadPinnedModels().catch((error) => { - console.error('Failed to load pinned models', error) + logger.error('Failed to load pinned models', error) setPinnedModels([]) setLoading(false) }) @@ -53,7 +56,7 @@ export const usePinnedModels = () => { : [...pinnedModels, modelId] await updatePinnedModels(newPinnedModels) } catch (error) { - console.error('Failed to toggle pinned model', error) + logger.error('Failed to toggle pinned model', error) } }, [pinnedModels, updatePinnedModels] diff --git a/src/renderer/src/init.ts b/src/renderer/src/init.ts index 6ce10c16b8..de8d6eabc8 100644 --- a/src/renderer/src/init.ts +++ b/src/renderer/src/init.ts @@ -1,10 +1,13 @@ import KeyvStorage from '@kangfenmao/keyv-storage' +import { loggerService } from '@logger' import { startAutoSync } from './services/BackupService' import { startNutstoreAutoSync } from './services/NutstoreService' import storeSyncService from './services/StoreSyncService' import store from './store' +loggerService.initWindowSource('mainWindow') + function initKeyv() { window.keyv = new KeyvStorage() window.keyv.init() diff --git a/src/renderer/src/pages/agents/components/AddAgentPopup.tsx b/src/renderer/src/pages/agents/components/AddAgentPopup.tsx index c213614d35..a4184d3c96 100644 --- a/src/renderer/src/pages/agents/components/AddAgentPopup.tsx +++ b/src/renderer/src/pages/agents/components/AddAgentPopup.tsx @@ -1,6 +1,7 @@ import 'emoji-picker-element' import { CheckOutlined, LoadingOutlined, RollbackOutlined, ThunderboltOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import EmojiPicker from '@renderer/components/EmojiPicker' import { TopView } from '@renderer/components/TopView' import { AGENT_PROMPT } from '@renderer/config/prompts' @@ -30,6 +31,8 @@ type FieldType = { knowledge_base_ids: string[] } +const logger = loggerService.withContext('AddAgentPopup') + const PopupContainer: React.FC = ({ resolve }) => { const [open, setOpen] = useState(true) const [form] = Form.useForm() @@ -140,7 +143,7 @@ const PopupContainer: React.FC = ({ resolve }) => { setOriginalPrompt(content) setHasUnsavedChanges(true) } catch (error) { - console.error('Error fetching data:', error) + logger.error('Error fetching data:', error) } setLoading(false) diff --git a/src/renderer/src/pages/agents/index.ts b/src/renderer/src/pages/agents/index.ts index 48a87f9792..f83b903700 100644 --- a/src/renderer/src/pages/agents/index.ts +++ b/src/renderer/src/pages/agents/index.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { useRuntime } from '@renderer/hooks/useRuntime' import { useSettings } from '@renderer/hooks/useSettings' import store from '@renderer/store' @@ -5,6 +6,8 @@ import { Agent } from '@renderer/types' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' +const logger = loggerService.withContext('useSystemAgents') + let _agents: Agent[] = [] export const getAgentsFromSystemAgents = (systemAgents: any) => { @@ -41,7 +44,7 @@ export function useSystemAgents() { setAgents(agentsData) return } catch (error) { - console.error('Failed to load remote agents:', error) + logger.error('Failed to load remote agents:', error) // 远程加载失败,继续尝试加载本地数据 } } @@ -66,7 +69,7 @@ export function useSystemAgents() { setAgents(_agents) } catch (error) { - console.error('Failed to load agents:', error) + logger.error('Failed to load agents:', error) // 发生错误时使用已加载的本地 agents setAgents(_agents) } diff --git a/src/renderer/src/pages/apps/App.tsx b/src/renderer/src/pages/apps/App.tsx index 9fc48086da..a56baa3b24 100644 --- a/src/renderer/src/pages/apps/App.tsx +++ b/src/renderer/src/pages/apps/App.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import MinAppIcon from '@renderer/components/Icons/MinAppIcon' import { loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateDefaultMinApps } from '@renderer/config/minapps' import { useMinappPopup } from '@renderer/hooks/useMinappPopup' @@ -16,6 +17,8 @@ interface Props { isLast?: boolean } +const logger = loggerService.withContext('App') + const App: FC = ({ app, onClick, size = 60, isLast }) => { const { openMinappKeepAlive } = useMinappPopup() const { t } = useTranslation() @@ -69,7 +72,7 @@ const App: FC = ({ app, onClick, size = 60, isLast }) => { updateDisabledMinapps(disabled.filter((item) => item.id !== app.id)) } catch (error) { window.message.error(t('settings.miniapps.custom.remove_error')) - console.error('Failed to remove custom mini app:', error) + logger.error('Failed to remove custom mini app:', error) } } } diff --git a/src/renderer/src/pages/apps/NewAppButton.tsx b/src/renderer/src/pages/apps/NewAppButton.tsx index af76a25ee3..d63cf8b1e7 100644 --- a/src/renderer/src/pages/apps/NewAppButton.tsx +++ b/src/renderer/src/pages/apps/NewAppButton.tsx @@ -1,4 +1,5 @@ import { PlusOutlined, UploadOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateDefaultMinApps } from '@renderer/config/minapps' import { useMinapps } from '@renderer/hooks/useMinapps' import { MinAppType } from '@renderer/types' @@ -12,6 +13,8 @@ interface Props { size?: number } +const logger = loggerService.withContext('NewAppButton') + const NewAppButton: FC = ({ size = 60 }) => { const { t } = useTranslation() const [isModalVisible, setIsModalVisible] = useState(false) @@ -60,7 +63,7 @@ const NewAppButton: FC = ({ size = 60 }) => { updateMinapps([...minapps, newApp]) } catch (error) { window.message.error(t('settings.miniapps.custom.save_error')) - console.error('Failed to save custom mini app:', error) + logger.error('Failed to save custom mini app:', error) } } @@ -80,7 +83,7 @@ const NewAppButton: FC = ({ size = 60 }) => { } reader.readAsDataURL(file) } catch (error) { - console.error('Failed to read file:', error) + logger.error('Failed to read file:', error) window.message.error(t('settings.miniapps.custom.logo_upload_error')) } } diff --git a/src/renderer/src/pages/home/Chat.tsx b/src/renderer/src/pages/home/Chat.tsx index ac8483fc73..3aca1f856b 100644 --- a/src/renderer/src/pages/home/Chat.tsx +++ b/src/renderer/src/pages/home/Chat.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { ContentSearch, ContentSearchRef } from '@renderer/components/ContentSearch' import MultiSelectActionPopup from '@renderer/components/Popups/MultiSelectionPopup' import { QuickPanelProvider } from '@renderer/components/QuickPanel' @@ -18,6 +19,8 @@ import Inputbar from './Inputbar/Inputbar' import Messages from './Messages/Messages' import Tabs from './Tabs' +const logger = loggerService.withContext('Chat') + interface Props { assistant: Assistant activeTopic: Topic @@ -51,7 +54,7 @@ const Chat: FC = (props) => { const selectedText = window.getSelection()?.toString().trim() contentSearchRef.current?.enable(selectedText) } catch (error) { - console.error('Error enabling content search:', error) + logger.error('Error enabling content search:', error) } }) diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 94dcd72ee8..120f4ae882 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -1,7 +1,7 @@ import { HolderOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { QuickPanelView, useQuickPanel } from '@renderer/components/QuickPanel' import TranslateButton from '@renderer/components/TranslateButton' -import Logger from '@renderer/config/logger' import { isGenerateImageModel, isGenerateImageModels, @@ -57,6 +57,8 @@ import MentionModelsInput from './MentionModelsInput' import SendMessageButton from './SendMessageButton' import TokenCount from './TokenCount' +const logger = loggerService.withContext('Inputbar') + interface Props { assistant: Assistant setActiveTopic: (topic: Topic) => void @@ -205,7 +207,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = return } - Logger.log('[DEBUG] Starting to send message') + logger.info('Starting to send message') EventEmitter.emit(EVENT_NAMES.SEND_MESSAGE) @@ -214,7 +216,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = const uploadedFiles = await FileManager.uploadFiles(files) const baseUserMessage: MessageInputBaseParams = { assistant, topic, content: text } - Logger.log('baseUserMessage', baseUserMessage) + logger.info('baseUserMessage', baseUserMessage) // getUserMessage() if (uploadedFiles) { @@ -243,7 +245,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = setTimeout(() => resizeTextArea(true), 0) setExpend(false) } catch (error) { - console.error('Failed to send message:', error) + logger.warn('Failed to send message:', error) } }, [assistant, dispatch, files, inputEmpty, loading, mentionedModels, resizeTextArea, text, topic]) @@ -258,7 +260,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = translatedText && setText(translatedText) setTimeout(() => resizeTextArea(), 0) } catch (error) { - console.error('Translation failed:', error) + logger.warn('Translation failed:', error) } finally { setIsTranslating(false) } @@ -371,7 +373,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = }, 200) if (spaceClickCount === 2) { - Logger.log('Triple space detected - trigger translation') + logger.info('Triple space detected - trigger translation') setSpaceClickCount(0) setIsTranslating(true) translate() @@ -559,7 +561,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = setIsFileDragging(false) const files = await getFilesFromDropEvent(e).catch((err) => { - Logger.error('[Inputbar] handleDrop:', err) + logger.error('handleDrop:', err) return null }) @@ -771,7 +773,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = return exists ? prev.filter((m) => getModelUniqId(m) !== modelId) : [...prev, model] }) } else { - console.error('在已上传图片时,不能添加非视觉模型') + logger.error('Cannot add non-vision model when images are uploaded') } }, [couldMentionNotVisionModel] diff --git a/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx index b97892cd9a..461f42c185 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx @@ -1,4 +1,5 @@ import { CheckOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import ThinkingEffect from '@renderer/components/ThinkingEffect' import { useSettings } from '@renderer/hooks/useSettings' import { MessageBlockStatus, type ThinkingMessageBlock } from '@renderer/types/newMessage' @@ -9,6 +10,7 @@ import styled from 'styled-components' import Markdown from '../../Markdown/Markdown' +const logger = loggerService.withContext('ThinkingBlock') interface Props { block: ThinkingMessageBlock } @@ -39,7 +41,7 @@ const ThinkingBlock: React.FC = ({ block }) => { setTimeout(() => setCopied(false), 2000) }) .catch((error) => { - console.error('Failed to copy text:', error) + logger.error('Failed to copy text:', error) antdMessage.error({ content: t('message.copy.failed'), key: 'copy-message-error' }) }) } diff --git a/src/renderer/src/pages/home/Messages/Blocks/index.tsx b/src/renderer/src/pages/home/Messages/Blocks/index.tsx index ed6d742e8e..3da83aeec7 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/index.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/index.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import type { RootState } from '@renderer/store' import { messageBlocksSelectors } from '@renderer/store/messageBlock' import type { ImageMessageBlock, MainTextMessageBlock, Message, MessageBlock } from '@renderer/types/newMessage' @@ -17,6 +18,8 @@ import ThinkingBlock from './ThinkingBlock' import ToolBlock from './ToolBlock' import TranslationBlock from './TranslationBlock' +const logger = loggerService.withContext('MessageBlockRenderer') + interface AnimatedBlockWrapperProps { children: React.ReactNode enableAnimation: boolean @@ -144,7 +147,7 @@ const MessageBlockRenderer: React.FC = ({ blocks, message }) => { blockComponent = break default: - console.warn('Unsupported block type in MessageBlockRenderer:', (block as any).type, block) + logger.warn('Unsupported block type in MessageBlockRenderer:', (block as any).type, block) break } diff --git a/src/renderer/src/pages/home/Messages/Message.tsx b/src/renderer/src/pages/home/Messages/Message.tsx index 14052ad7c3..06fc7c33b3 100644 --- a/src/renderer/src/pages/home/Messages/Message.tsx +++ b/src/renderer/src/pages/home/Messages/Message.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import Scrollbar from '@renderer/components/Scrollbar' import { useMessageEditing } from '@renderer/context/MessageEditingContext' import { useAssistant } from '@renderer/hooks/useAssistant' @@ -35,6 +36,8 @@ interface Props { onSetMessages?: Dispatch> } +const logger = loggerService.withContext('MessageItem') + const MessageItem: FC = ({ message, topic, @@ -70,7 +73,7 @@ const MessageItem: FC = ({ editMessage(message.id, { usage: usage }) stopEditing() } catch (error) { - console.error('Failed to save message blocks:', error) + logger.error('Failed to save message blocks:', error) } }, [message, editMessageBlocks, stopEditing, editMessage] @@ -85,7 +88,7 @@ const MessageItem: FC = ({ await resendUserMessageWithEdit(message, blocks, assistantWithTopicPrompt) stopEditing() } catch (error) { - console.error('Failed to resend message:', error) + logger.error('Failed to resend message:', error) } }, [message, resendUserMessageWithEdit, assistant, stopEditing, topic.prompt] diff --git a/src/renderer/src/pages/home/Messages/MessageEditor.tsx b/src/renderer/src/pages/home/Messages/MessageEditor.tsx index b70077b54e..f3392b6c2a 100644 --- a/src/renderer/src/pages/home/Messages/MessageEditor.tsx +++ b/src/renderer/src/pages/home/Messages/MessageEditor.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import CustomTag from '@renderer/components/CustomTag' import TranslateButton from '@renderer/components/TranslateButton' import { isGenerateImageModel, isVisionModel } from '@renderer/config/models' @@ -33,6 +34,8 @@ interface Props { onCancel: () => void } +const logger = loggerService.withContext('MessageBlockEditor') + const MessageBlockEditor: FC = ({ message, topicId, onSave, onResend, onCancel }) => { const allBlocks = findAllBlocks(message) const [editedBlocks, setEditedBlocks] = useState(allBlocks) @@ -161,7 +164,7 @@ const MessageBlockEditor: FC = ({ message, topicId, onSave, onResend, onC setIsFileDragging(false) const files = await getFilesFromDropEvent(e).catch((err) => { - console.error('[src/renderer/src/pages/home/Inputbar/Inputbar.tsx] handleDrop:', err) + logger.error('[src/renderer/src/pages/home/Inputbar/Inputbar.tsx] handleDrop:', err) return null }) if (files) { diff --git a/src/renderer/src/pages/home/Messages/MessageImage.tsx b/src/renderer/src/pages/home/Messages/MessageImage.tsx index 7198066259..692b13495f 100644 --- a/src/renderer/src/pages/home/Messages/MessageImage.tsx +++ b/src/renderer/src/pages/home/Messages/MessageImage.tsx @@ -8,6 +8,7 @@ import { ZoomInOutlined, ZoomOutOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import type { ImageMessageBlock } from '@renderer/types/newMessage' import { Image as AntdImage, Space } from 'antd' import { FC } from 'react' @@ -18,6 +19,8 @@ interface Props { block: ImageMessageBlock } +const logger = loggerService.withContext('MessageImage') + const MessageImage: FC = ({ block }) => { const { t } = useTranslation() @@ -31,7 +34,7 @@ const MessageImage: FC = ({ block }) => { document.body.removeChild(link) window.message.success(t('message.download.success')) } catch (error) { - console.error('下载图片失败:', error) + logger.error('下载图片失败:', error) window.message.error(t('message.download.failed')) } } @@ -83,7 +86,7 @@ const MessageImage: FC = ({ block }) => { window.message.success(t('message.copy.success')) } catch (error) { - console.error('复制图片失败:', error) + logger.error('复制图片失败:', error) window.message.error(t('message.copy.failed')) } } diff --git a/src/renderer/src/pages/home/Messages/MessageTools.tsx b/src/renderer/src/pages/home/Messages/MessageTools.tsx index 82503c83ef..5db7def434 100644 --- a/src/renderer/src/pages/home/Messages/MessageTools.tsx +++ b/src/renderer/src/pages/home/Messages/MessageTools.tsx @@ -1,4 +1,5 @@ import { CheckOutlined, CloseOutlined, ExpandOutlined, LoadingOutlined, WarningOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { useCodeStyle } from '@renderer/context/CodeStyleProvider' import { useMCPServers } from '@renderer/hooks/useMCPServers' import { useSettings } from '@renderer/hooks/useSettings' @@ -7,7 +8,6 @@ import { isToolAutoApproved } from '@renderer/utils/mcp-tools' import { cancelToolAction, confirmToolAction } from '@renderer/utils/userConfirmation' import { Button, Collapse, ConfigProvider, Dropdown, Flex, message as antdMessage, Modal, Tabs, Tooltip } from 'antd' import { message } from 'antd' -import Logger from 'electron-log/renderer' import { ChevronDown, ChevronRight, CirclePlay, CircleX, PauseCircle, ShieldCheck } from 'lucide-react' import { FC, memo, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -17,6 +17,8 @@ interface Props { block: ToolMessageBlock } +const logger = loggerService.withContext('MessageTools') + const COUNTDOWN_TIME = 30 const MessageTools: FC = ({ block }) => { @@ -42,7 +44,7 @@ const MessageTools: FC = ({ block }) => { if (countdown > 0) { timer.current = setTimeout(() => { - console.log('countdown', countdown) + logger.debug('countdown', countdown) setCountdown((prev) => prev - 1) }, 1000) } else if (countdown === 0) { @@ -119,7 +121,7 @@ const MessageTools: FC = ({ block }) => { message.error({ content: t('message.tools.abort_failed'), key: 'abort-tool' }) } } catch (error) { - Logger.error('Failed to abort tool:', error) + logger.error('Failed to abort tool:', error) message.error({ content: t('message.tools.abort_failed'), key: 'abort-tool' }) } } @@ -292,7 +294,7 @@ const MessageTools: FC = ({ block }) => { return } } catch (e) { - console.error('failed to render the preview of mcp results:', e) + logger.error('failed to render the preview of mcp results:', e) return } } diff --git a/src/renderer/src/pages/home/Messages/Messages.tsx b/src/renderer/src/pages/home/Messages/Messages.tsx index 03434a0cd1..aef377f940 100644 --- a/src/renderer/src/pages/home/Messages/Messages.tsx +++ b/src/renderer/src/pages/home/Messages/Messages.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import ContextMenu from '@renderer/components/ContextMenu' import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring' import Scrollbar from '@renderer/components/Scrollbar' @@ -49,6 +50,8 @@ interface MessagesProps { onFirstUpdate?(): void } +const logger = loggerService.withContext('Messages') + const Messages: React.FC = ({ assistant, topic, setActiveTopic, onComponentUpdate, onFirstUpdate }) => { const { containerRef: scrollContainerRef, handleScroll: handleScrollPosition } = useScrollPosition( `topic-${topic.id}` @@ -177,7 +180,7 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic, o const currentMessages = messagesRef.current if (index < 0 || index > currentMessages.length) { - console.error(`[NEW_BRANCH] Invalid branch index: ${index}`) + logger.error(`[NEW_BRANCH] Invalid branch index: ${index}`) return } @@ -196,7 +199,7 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic, o // Optional: Handle cloning failure (e.g., show an error message) // You might want to remove the added topic if cloning fails // removeTopic(newTopic.id); // Assuming you have a removeTopic function - console.error(`[NEW_BRANCH] Failed to create topic branch for topic ${newTopic.id}`) + logger.error(`[NEW_BRANCH] Failed to create topic branch for topic ${newTopic.id}`) window.message.error(t('message.branch.error')) // Example error message } }), @@ -222,11 +225,11 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic, o window.message.success({ content: t('code_block.edit.save.success'), key: 'save-code' }) } catch (error) { - console.error(`Failed to save code block ${codeBlockId} content to message block ${msgBlockId}:`, error) + logger.error(`Failed to save code block ${codeBlockId} content to message block ${msgBlockId}:`, error) window.message.error({ content: t('code_block.edit.save.failed'), key: 'save-code-failed' }) } } else { - console.error( + logger.error( `Failed to save code block ${codeBlockId} content to message block ${msgBlockId}: no such message block or the block doesn't have a content field` ) window.message.error({ content: t('code_block.edit.save.failed'), key: 'save-code-failed' }) diff --git a/src/renderer/src/pages/knowledge/components/AddKnowledgePopup.tsx b/src/renderer/src/pages/knowledge/components/AddKnowledgePopup.tsx index d85ee87ed9..a1742dcb2a 100644 --- a/src/renderer/src/pages/knowledge/components/AddKnowledgePopup.tsx +++ b/src/renderer/src/pages/knowledge/components/AddKnowledgePopup.tsx @@ -1,4 +1,5 @@ import { InfoCircleOutlined, WarningOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import AiProvider from '@renderer/aiCore' import { HStack } from '@renderer/components/Layout' import { TopView } from '@renderer/components/TopView' @@ -30,6 +31,8 @@ interface Props extends ShowParams { resolve: (data: any) => void } +const logger = loggerService.withContext('AddKnowledgePopup') + const PopupContainer: React.FC = ({ title, resolve }) => { const [open, setOpen] = useState(true) const [loading, setLoading] = useState(false) @@ -146,7 +149,7 @@ const PopupContainer: React.FC = ({ title, resolve }) => { setDimensions(finalDimensions) } catch (error) { - console.error('Error getting embedding dimensions:', error) + logger.error('Error getting embedding dimensions:', error) window.message.error(t('message.error.get_embedding_dimensions') + '\n' + getErrorMessage(error)) setLoading(false) return @@ -176,7 +179,7 @@ const PopupContainer: React.FC = ({ title, resolve }) => { resolve(_newBase) } } catch (error) { - console.error('Validation failed:', error) + logger.error('Validation failed:', error) } } const onCancel = () => { diff --git a/src/renderer/src/pages/knowledge/components/KnowledgeSearchPopup.tsx b/src/renderer/src/pages/knowledge/components/KnowledgeSearchPopup.tsx index 78397a8648..ac05b4c9ef 100644 --- a/src/renderer/src/pages/knowledge/components/KnowledgeSearchPopup.tsx +++ b/src/renderer/src/pages/knowledge/components/KnowledgeSearchPopup.tsx @@ -1,5 +1,6 @@ import { CopyOutlined } from '@ant-design/icons' import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' +import { loggerService } from '@logger' import { HStack } from '@renderer/components/Layout' import { TopView } from '@renderer/components/TopView' import { searchKnowledgeBase } from '@renderer/services/KnowledgeService' @@ -20,6 +21,8 @@ interface Props extends ShowParams { resolve: (data: any) => void } +const logger = loggerService.withContext('KnowledgeSearchPopup') + const PopupContainer: React.FC = ({ base, resolve }) => { const [open, setOpen] = useState(true) const [loading, setLoading] = useState(false) @@ -41,7 +44,7 @@ const PopupContainer: React.FC = ({ base, resolve }) => { const searchResults = await searchKnowledgeBase(value, base) setResults(searchResults) } catch (error) { - console.error(`Failed to search knowledge base ${base.name}:`, error) + logger.error(`Failed to search knowledge base ${base.name}:`, error) setResults([]) } finally { setLoading(false) @@ -79,7 +82,7 @@ const PopupContainer: React.FC = ({ base, resolve }) => { await navigator.clipboard.writeText(text) message.success(t('message.copied')) } catch (error) { - console.error('Failed to copy text:', error) + logger.error('Failed to copy text:', error) window.message.error(t('message.copyError') || 'Failed to copy text') } } diff --git a/src/renderer/src/pages/knowledge/components/KnowledgeSettings.tsx b/src/renderer/src/pages/knowledge/components/KnowledgeSettings.tsx index 008ea4a7cf..7af6692abc 100644 --- a/src/renderer/src/pages/knowledge/components/KnowledgeSettings.tsx +++ b/src/renderer/src/pages/knowledge/components/KnowledgeSettings.tsx @@ -1,4 +1,5 @@ import { InfoCircleOutlined, WarningOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { HStack } from '@renderer/components/Layout' import { TopView } from '@renderer/components/TopView' import { DEFAULT_KNOWLEDGE_DOCUMENT_COUNT, isMac } from '@renderer/config/constant' @@ -24,6 +25,8 @@ interface Props extends ShowParams { resolve: (data: any) => void } +const logger = loggerService.withContext('KnowledgeSettings') + const PopupContainer: React.FC = ({ base: _base, resolve }) => { const { preprocessProviders } = usePreprocessProviders() const { ocrProviders } = useOcrProviders() @@ -98,7 +101,7 @@ const PopupContainer: React.FC = ({ base: _base, resolve }) => { setOpen(false) resolve(newBase) } catch (error) { - console.error('Validation failed:', error) + logger.error('Validation failed:', error) } } diff --git a/src/renderer/src/pages/knowledge/components/QuotaTag.tsx b/src/renderer/src/pages/knowledge/components/QuotaTag.tsx index fb3f60fe54..4edd67df37 100644 --- a/src/renderer/src/pages/knowledge/components/QuotaTag.tsx +++ b/src/renderer/src/pages/knowledge/components/QuotaTag.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { usePreprocessProvider } from '@renderer/hooks/usePreprocess' import { getStoreSetting } from '@renderer/hooks/useSettings' import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService' @@ -6,6 +7,8 @@ import { Tag } from 'antd' import { FC, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' +const logger = loggerService.withContext('QuotaTag') + const QuotaTag: FC<{ base: KnowledgeBase; providerId: string; quota?: number }> = ({ base, providerId, @@ -34,7 +37,7 @@ const QuotaTag: FC<{ base: KnowledgeBase; providerId: string; quota?: number }> }) setQuota(response) } catch (error) { - console.error('[KnowledgeContent] Error checking quota:', error) + logger.error('[KnowledgeContent] Error checking quota:', error) } } } diff --git a/src/renderer/src/pages/knowledge/items/KnowledgeDirectories.tsx b/src/renderer/src/pages/knowledge/items/KnowledgeDirectories.tsx index 3cdd480c04..2044199930 100644 --- a/src/renderer/src/pages/knowledge/items/KnowledgeDirectories.tsx +++ b/src/renderer/src/pages/knowledge/items/KnowledgeDirectories.tsx @@ -1,7 +1,7 @@ import { DeleteOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import Ellipsis from '@renderer/components/Ellipsis' import Scrollbar from '@renderer/components/Scrollbar' -import Logger from '@renderer/config/logger' import { useKnowledge } from '@renderer/hooks/useKnowledge' import FileItem from '@renderer/pages/files/FileItem' import { getProviderName } from '@renderer/services/ProviderService' @@ -24,6 +24,8 @@ import { StatusIconWrapper } from '../KnowledgeContent' +const logger = loggerService.withContext('KnowledgeDirectories') + interface KnowledgeContentProps { selectedBase: KnowledgeBase progressMap: Map @@ -54,7 +56,7 @@ const KnowledgeDirectories: FC = ({ selectedBase, progres } const path = await window.api.file.selectFolder() - Logger.log('[KnowledgeContent] Selected directory:', path) + logger.info('Selected directory:', path) path && addDirectory(path) } diff --git a/src/renderer/src/pages/knowledge/items/KnowledgeSitemaps.tsx b/src/renderer/src/pages/knowledge/items/KnowledgeSitemaps.tsx index 37b8a1ee4b..a7e8a04802 100644 --- a/src/renderer/src/pages/knowledge/items/KnowledgeSitemaps.tsx +++ b/src/renderer/src/pages/knowledge/items/KnowledgeSitemaps.tsx @@ -1,4 +1,5 @@ import { DeleteOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import Ellipsis from '@renderer/components/Ellipsis' import PromptPopup from '@renderer/components/Popups/PromptPopup' import Scrollbar from '@renderer/components/Scrollbar' @@ -28,6 +29,8 @@ interface KnowledgeContentProps { selectedBase: KnowledgeBase } +const logger = loggerService.withContext('KnowledgeSitemaps') + const getDisplayTime = (item: KnowledgeItem) => { const timestamp = item.updated_at && item.updated_at > item.created_at ? item.updated_at : item.created_at return dayjs(timestamp).format('MM-DD HH:mm') @@ -71,7 +74,7 @@ const KnowledgeSitemaps: FC = ({ selectedBase }) => { } addSitemap(url) } catch (e) { - console.error('Invalid Sitemap URL:', url) + logger.error('Invalid Sitemap URL:', url) } } } diff --git a/src/renderer/src/pages/memory/index.tsx b/src/renderer/src/pages/memory/index.tsx index e4ea9020ea..eeafcf475b 100644 --- a/src/renderer/src/pages/memory/index.tsx +++ b/src/renderer/src/pages/memory/index.tsx @@ -11,6 +11,7 @@ import { UserDeleteOutlined, UserOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { Center } from '@renderer/components/Layout' import Scrollbar from '@renderer/components/Scrollbar' @@ -48,6 +49,8 @@ import styled from 'styled-components' import { SettingDivider, SettingRow, SettingRowTitle, SettingTitle } from '../settings' import MemoriesSettingsModal from './settings-modal' +const logger = loggerService.withContext('MemoryPage') + dayjs.extend(relativeTime) const DEFAULT_USER_ID = 'default-user' @@ -327,7 +330,7 @@ const MemoriesPage = () => { const users = usersList.map((user) => user.userId) setUniqueUsers(users) } catch (error) { - console.error('Failed to load users list:', error) + logger.error('Failed to load users list:', error) } }, [memoryService]) @@ -349,7 +352,7 @@ const MemoriesPage = () => { console.log('Loaded memories for user:', targetUser, 'count:', result.results?.length || 0) setAllMemories(result.results || []) } catch (error) { - console.error('Failed to load memories:', error) + logger.error('Failed to load memories:', error) window.message.error(t('memory.load_failed')) } finally { setLoading(false) @@ -415,7 +418,7 @@ const MemoriesPage = () => { setCurrentPage(1) await loadMemories(currentUser) } catch (error) { - console.error('Failed to add memory:', error) + logger.error('Failed to add memory:', error) window.message.error(t('memory.add_failed')) } } @@ -427,7 +430,7 @@ const MemoriesPage = () => { // Reload all memories await loadMemories(currentUser) } catch (error) { - console.error('Failed to delete memory:', error) + logger.error('Failed to delete memory:', error) window.message.error(t('memory.delete_failed')) } } @@ -444,7 +447,7 @@ const MemoriesPage = () => { // Reload all memories await loadMemories(currentUser) } catch (error) { - console.error('Failed to update memory:', error) + logger.error('Failed to update memory:', error) window.message.error(t('memory.update_failed')) } } @@ -469,7 +472,7 @@ const MemoriesPage = () => { t('memory.user_switched', { user: userId === DEFAULT_USER_ID ? t('memory.default_user') : userId }) ) } catch (error) { - console.error('Failed to switch user:', error) + logger.error('Failed to switch user:', error) window.message.error(t('memory.user_switch_failed')) } } @@ -489,7 +492,7 @@ const MemoriesPage = () => { window.message.success(t('memory.user_created', { user: userId })) setAddUserModalVisible(false) } catch (error) { - console.error('Failed to add user:', error) + logger.error('Failed to add user:', error) window.message.error(t('memory.add_user_failed')) } } @@ -521,7 +524,7 @@ const MemoriesPage = () => { // Reload memories to show the empty state await loadMemories(currentUser) } catch (error) { - console.error('Failed to reset memories:', error) + logger.error('Failed to reset memories:', error) window.message.error(t('memory.reset_memories_failed')) } } @@ -555,7 +558,7 @@ const MemoriesPage = () => { await loadMemories(currentUser) } } catch (error) { - console.error('Failed to delete user:', error) + logger.error('Failed to delete user:', error) window.message.error(t('memory.delete_user_failed')) } } diff --git a/src/renderer/src/pages/memory/settings-modal.tsx b/src/renderer/src/pages/memory/settings-modal.tsx index 215df7f4b4..629de6fac6 100644 --- a/src/renderer/src/pages/memory/settings-modal.tsx +++ b/src/renderer/src/pages/memory/settings-modal.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import AiProvider from '@renderer/aiCore' import { isEmbeddingModel, isRerankModel } from '@renderer/config/models' import { useModel } from '@renderer/hooks/useModel' @@ -25,6 +26,8 @@ type formValue = { autoDims: boolean } +const logger = loggerService.withContext('MemoriesSettingsModal') + const MemoriesSettingsModal: FC = ({ visible, onSubmit, onCancel, form }) => { const { providers } = useProviders() const dispatch = useDispatch() @@ -82,7 +85,7 @@ const MemoriesSettingsModal: FC = ({ visible, onSubm const aiProvider = new AiProvider(provider) finalDimensions = await aiProvider.getEmbeddingDimensions(embedderModel) } catch (error) { - console.error('Error getting embedding dimensions:', error) + logger.error('Error getting embedding dimensions:', error) window.message.error(t('message.error.get_embedding_dimensions') + '\n' + getErrorMessage(error)) setLoading(false) return @@ -121,7 +124,7 @@ const MemoriesSettingsModal: FC = ({ visible, onSubm setLoading(false) } } catch (error) { - console.error('Error submitting form:', error) + logger.error('Error submitting form:', error) setLoading(false) } } diff --git a/src/renderer/src/pages/paintings/AihubmixPage.tsx b/src/renderer/src/pages/paintings/AihubmixPage.tsx index af57c21f47..eefed99133 100644 --- a/src/renderer/src/pages/paintings/AihubmixPage.tsx +++ b/src/renderer/src/pages/paintings/AihubmixPage.tsx @@ -1,4 +1,5 @@ import { PlusOutlined, RedoOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import AiProvider from '@renderer/aiCore' import IcImageUp from '@renderer/assets/images/paintings/ic_ImageUp.svg' import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar' @@ -35,6 +36,8 @@ import Artboard from './components/Artboard' import PaintingsList from './components/PaintingsList' import { type ConfigItem, createModeConfigs, DEFAULT_PAINTING } from './config/aihubmixConfig' +const logger = loggerService.withContext('AihubmixPage') + // 使用函数创建配置项 const modeConfigs = createModeConfigs() @@ -104,7 +107,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { urls.map(async (url) => { try { if (!url?.trim()) { - console.error('图像URL为空,可能是提示词违禁') + logger.error('图像URL为空,可能是提示词违禁') window.message.warning({ content: t('message.empty_url'), key: 'empty-url-warning' @@ -113,7 +116,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { } return await window.api.file.download(url) } catch (error) { - console.error('下载图像失败:', error) + logger.error('下载图像失败:', error) if ( error instanceof Error && (error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL')) @@ -267,7 +270,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { if (!response.ok) { const errorData = await response.json() - console.error('V3 API错误:', errorData) + logger.error('V3 API错误:', errorData) throw new Error(errorData.error?.message || '生成图像失败') } @@ -383,7 +386,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { if (!response.ok) { const errorData = await response.json() - console.error('V3 Remix API错误:', errorData) + logger.error('V3 Remix API错误:', errorData) throw new Error(errorData.error?.message || '图像混合失败') } @@ -453,7 +456,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { if (!response.ok) { const errorData = await response.json() - console.error('通用API错误:', errorData) + logger.error('通用API错误:', errorData) throw new Error(errorData.error?.message || '生成图像失败') } @@ -547,7 +550,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { const translatedText = await translateText(painting.prompt, LanguagesEnum.enUS) updatePaintingState({ prompt: translatedText }) } catch (error) { - console.error('Translation failed:', error) + logger.error('Translation failed:', error) } finally { setIsTranslating(false) } diff --git a/src/renderer/src/pages/paintings/NewApiPage.tsx b/src/renderer/src/pages/paintings/NewApiPage.tsx index 544622fab2..5d6a8561b1 100644 --- a/src/renderer/src/pages/paintings/NewApiPage.tsx +++ b/src/renderer/src/pages/paintings/NewApiPage.tsx @@ -1,4 +1,5 @@ import { PlusOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import AiProvider from '@renderer/aiCore' import IcImageUp from '@renderer/assets/images/paintings/ic_ImageUp.svg' import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar' @@ -33,6 +34,8 @@ import SendMessageButton from '../home/Inputbar/SendMessageButton' import { SettingHelpLink, SettingTitle } from '../settings' import Artboard from './components/Artboard' +const logger = loggerService.withContext('NewApiPage') + const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => { const [mode, setMode] = useState('openai_image_generate') const { addPainting, removePainting, updatePainting, newApiPaintings } = usePaintings() @@ -171,7 +174,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => { urls.map(async (url) => { try { if (!url?.trim()) { - console.error('图像URL为空') + logger.error('图像URL为空') window.message.warning({ content: t('message.empty_url'), key: 'empty-url-warning' @@ -180,7 +183,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => { } return await window.api.file.download(url) } catch (error) { - console.error('下载图像失败:', error) + logger.error('下载图像失败:', error) if ( error instanceof Error && (error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL')) @@ -393,7 +396,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => { const translatedText = await translateText(painting.prompt, LanguagesEnum.enUS) updatePaintingState({ prompt: translatedText }) } catch (error) { - console.error('Translation failed:', error) + logger.error('Translation failed:', error) } finally { setIsTranslating(false) } diff --git a/src/renderer/src/pages/paintings/SiliconPage.tsx b/src/renderer/src/pages/paintings/SiliconPage.tsx index f595ef76de..bf4f1fb5b3 100644 --- a/src/renderer/src/pages/paintings/SiliconPage.tsx +++ b/src/renderer/src/pages/paintings/SiliconPage.tsx @@ -1,4 +1,5 @@ import { PlusOutlined, RedoOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import AiProvider from '@renderer/aiCore' import ImageSize1_1 from '@renderer/assets/images/paintings/image-size-1-1.svg' import ImageSize1_2 from '@renderer/assets/images/paintings/image-size-1-2.svg' @@ -39,6 +40,8 @@ import { SettingTitle } from '../settings' import Artboard from './components/Artboard' import PaintingsList from './components/PaintingsList' +const logger = loggerService.withContext('SiliconPage') + const IMAGE_SIZES = [ { label: '1:1', @@ -206,7 +209,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { urls.map(async (url) => { try { if (!url || url.trim() === '') { - console.error('图像URL为空,可能是提示词违禁') + logger.error('图像URL为空,可能是提示词违禁') window.message.warning({ content: t('message.empty_url'), key: 'empty-url-warning' @@ -215,7 +218,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { } return await window.api.file.download(url) } catch (error) { - console.error('Failed to download image:', error) + logger.error('Failed to download image:', error) if ( error instanceof Error && (error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL')) @@ -306,7 +309,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { const translatedText = await translateText(painting.prompt, LanguagesEnum.enUS) updatePaintingState({ prompt: translatedText }) } catch (error) { - console.error('Translation failed:', error) + logger.error('Translation failed:', error) } finally { setIsTranslating(false) } diff --git a/src/renderer/src/pages/paintings/TokenFluxPage.tsx b/src/renderer/src/pages/paintings/TokenFluxPage.tsx index 40e1c2f2c0..0f40556a3c 100644 --- a/src/renderer/src/pages/paintings/TokenFluxPage.tsx +++ b/src/renderer/src/pages/paintings/TokenFluxPage.tsx @@ -1,4 +1,5 @@ import { PlusOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar' import Scrollbar from '@renderer/components/Scrollbar' import TranslateButton from '@renderer/components/TranslateButton' @@ -32,6 +33,8 @@ import PaintingsList from './components/PaintingsList' import { DEFAULT_TOKENFLUX_PAINTING, type TokenFluxModel } from './config/tokenFluxConfig' import TokenFluxService from './utils/TokenFluxService' +const logger = loggerService.withContext('TokenFluxPage') + const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => { const [models, setModels] = useState([]) const [selectedModel, setSelectedModel] = useState(null) @@ -259,7 +262,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => { const translatedText = await translateText(painting.prompt, LanguagesEnum.enUS) updatePaintingState({ prompt: translatedText }) } catch (error) { - console.error('Translation failed:', error) + logger.error('Translation failed:', error) } finally { setIsTranslating(false) } @@ -320,7 +323,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => { const readI18nContext = (property: Record, key: string): string => { const lang = i18n.language.split('-')[0] // Get the base language code (e.g., 'en' from 'en-US') - console.log('readI18nContext', { property, key, lang }) + logger.debug('readI18nContext', { property, key, lang }) return property[`${key}_${lang}`] || property[key] } @@ -346,7 +349,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => { tokenFluxService .pollGenerationResult(painting.generationId, { onStatusUpdate: (updates) => { - console.log('Polling status update:', updates) + logger.debug('Polling status update:', updates) updatePaintingState(updates) } }) @@ -360,7 +363,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => { } }) .catch((error) => { - console.error('Polling failed:', error) + logger.error('Polling failed:', error) updatePaintingState({ status: 'failed' }) }) } diff --git a/src/renderer/src/pages/paintings/components/DynamicFormRender.tsx b/src/renderer/src/pages/paintings/components/DynamicFormRender.tsx index bea262b689..03fe70ec43 100644 --- a/src/renderer/src/pages/paintings/components/DynamicFormRender.tsx +++ b/src/renderer/src/pages/paintings/components/DynamicFormRender.tsx @@ -1,4 +1,5 @@ import { CloseOutlined, LinkOutlined, RedoOutlined, UploadOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { convertToBase64 } from '@renderer/utils' import { Button, Input, InputNumber, Select, Switch, Upload } from 'antd' import TextArea from 'antd/es/input/TextArea' @@ -11,6 +12,8 @@ interface DynamicFormRenderProps { onChange: (field: string, value: any) => void } +const logger = loggerService.withContext('DynamicFormRender') + export const DynamicFormRender: React.FC = ({ schemaProperty, propertyName, @@ -39,11 +42,11 @@ export const DynamicFormRender: React.FC = ({ if (typeof base64Image === 'string') { onChange(propertyName, base64Image) } else { - console.error('Failed to convert image to base64') + logger.error('Failed to convert image to base64') } } } catch (error) { - console.error('Error processing image:', error) + logger.error('Error processing image:', error) } }, [] diff --git a/src/renderer/src/pages/paintings/utils/TokenFluxService.ts b/src/renderer/src/pages/paintings/utils/TokenFluxService.ts index 1c55215ec9..c3b94717ef 100644 --- a/src/renderer/src/pages/paintings/utils/TokenFluxService.ts +++ b/src/renderer/src/pages/paintings/utils/TokenFluxService.ts @@ -1,8 +1,11 @@ +import { loggerService } from '@logger' import { CacheService } from '@renderer/services/CacheService' import { FileMetadata, TokenFluxPainting } from '@renderer/types' import type { TokenFluxModel } from '../config/tokenFluxConfig' +const logger = loggerService.withContext('TokenFluxService') + export interface TokenFluxGenerationRequest { model: string input: { @@ -171,7 +174,7 @@ export class TokenFluxService { // Continue polling for other statuses (processing, queued, etc.) setTimeout(poll, intervalMs) } catch (error) { - console.error('Polling error:', error) + logger.error('Polling error:', error) retryCount++ if (retryCount >= maxRetries) { @@ -215,7 +218,7 @@ export class TokenFluxService { urls.map(async (url) => { try { if (!url?.trim()) { - console.error('Image URL is empty') + logger.error('Image URL is empty') window.message.warning({ content: 'Image URL is empty', key: 'empty-url-warning' @@ -224,7 +227,7 @@ export class TokenFluxService { } return await window.api.file.download(url) } catch (error) { - console.error('Failed to download image:', error) + logger.error('Failed to download image:', error) return null } }) diff --git a/src/renderer/src/pages/settings/AssistantSettings/AssistantMemorySettings.tsx b/src/renderer/src/pages/settings/AssistantSettings/AssistantMemorySettings.tsx index 6fb3e30ef2..c5d70e8b3c 100644 --- a/src/renderer/src/pages/settings/AssistantSettings/AssistantMemorySettings.tsx +++ b/src/renderer/src/pages/settings/AssistantSettings/AssistantMemorySettings.tsx @@ -1,4 +1,5 @@ import { InfoCircleOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { Box } from '@renderer/components/Layout' import MemoryService from '@renderer/services/MemoryService' import { selectGlobalMemoryEnabled, selectMemoryConfig } from '@renderer/store/memory' @@ -13,6 +14,8 @@ import styled from 'styled-components' import MemoriesSettingsModal from '../../memory/settings-modal' +const logger = loggerService.withContext('AssistantMemorySettings') + const { Text } = Typography interface Props { @@ -44,7 +47,7 @@ const AssistantMemorySettings: React.FC = ({ assistant, updateAssistant, }) setMemoryStats({ count: result.results.length, loading: false }) } catch (error) { - console.error('Failed to load memory stats:', error) + logger.error('Failed to load memory stats:', error) setMemoryStats({ count: 0, loading: false }) } }, [assistant.id, memoryService]) diff --git a/src/renderer/src/pages/settings/DataSettings/LocalBackupSettings.tsx b/src/renderer/src/pages/settings/DataSettings/LocalBackupSettings.tsx index ef579ec49d..6ae3a59906 100644 --- a/src/renderer/src/pages/settings/DataSettings/LocalBackupSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/LocalBackupSettings.tsx @@ -1,4 +1,5 @@ import { DeleteOutlined, FolderOpenOutlined, SaveOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { HStack } from '@renderer/components/Layout' import { LocalBackupManager } from '@renderer/components/LocalBackupManager' import { LocalBackupModal, useLocalBackupModal } from '@renderer/components/LocalBackupModals' @@ -22,6 +23,8 @@ import { useTranslation } from 'react-i18next' import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..' +const logger = loggerService.withContext('LocalBackupSettings') + const LocalBackupSettings: React.FC = () => { const dispatch = useAppDispatch() @@ -133,7 +136,7 @@ const LocalBackupSettings: React.FC = () => { handleLocalBackupDirChange(newLocalBackupDir) } catch (error) { - console.error('Failed to select directory:', error) + logger.error('Failed to select directory:', error) } } diff --git a/src/renderer/src/pages/settings/DataSettings/ObsidianSettings.tsx b/src/renderer/src/pages/settings/DataSettings/ObsidianSettings.tsx index 8b72f508ec..72aa6bf153 100644 --- a/src/renderer/src/pages/settings/DataSettings/ObsidianSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/ObsidianSettings.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { HStack } from '@renderer/components/Layout' import { useSettings } from '@renderer/hooks/useSettings' import { useAppDispatch } from '@renderer/store' @@ -8,6 +9,8 @@ import { useTranslation } from 'react-i18next' import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..' +const logger = loggerService.withContext('ObsidianSettings') + const { Option } = Select const ObsidianSettings: FC = () => { @@ -40,7 +43,7 @@ const ObsidianSettings: FC = () => { dispatch(setDefaultObsidianVault(vaultsData[0].name)) } } catch (error) { - console.error('获取Obsidian Vault失败:', error) + logger.error('获取Obsidian Vault失败:', error) setError(t('settings.data.obsidian.default_vault_fetch_error')) } finally { setLoading(false) diff --git a/src/renderer/src/pages/settings/DataSettings/SiyuanSettings.tsx b/src/renderer/src/pages/settings/DataSettings/SiyuanSettings.tsx index 7a51edc70f..7b5edbba11 100644 --- a/src/renderer/src/pages/settings/DataSettings/SiyuanSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/SiyuanSettings.tsx @@ -1,4 +1,5 @@ import { InfoCircleOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { HStack } from '@renderer/components/Layout' import { useTheme } from '@renderer/context/ThemeProvider' import { useMinappPopup } from '@renderer/hooks/useMinappPopup' @@ -12,6 +13,8 @@ import { useSelector } from 'react-redux' import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..' +const logger = loggerService.withContext('SiyuanSettings') + const SiyuanSettings: FC = () => { const { openMinapp } = useMinappPopup() const { t } = useTranslation() @@ -75,7 +78,7 @@ const SiyuanSettings: FC = () => { window.message.success(t('settings.data.siyuan.check.success')) } catch (error) { - console.error('Check Siyuan connection failed:', error) + logger.error('Check Siyuan connection failed:', error) window.message.error(t('settings.data.siyuan.check.error')) } } diff --git a/src/renderer/src/pages/settings/MCPSettings/AddMcpServerModal.tsx b/src/renderer/src/pages/settings/MCPSettings/AddMcpServerModal.tsx index d7fcdba06c..d6f0638a94 100644 --- a/src/renderer/src/pages/settings/MCPSettings/AddMcpServerModal.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/AddMcpServerModal.tsx @@ -1,4 +1,5 @@ import { UploadOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { nanoid } from '@reduxjs/toolkit' import CodeEditor from '@renderer/components/CodeEditor' import { useAppDispatch } from '@renderer/store' @@ -8,6 +9,8 @@ import { Button, Form, Modal, Upload } from 'antd' import { FC, useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' +const logger = loggerService.withContext('AddMcpServerModal') + interface AddMcpServerModalProps { visible: boolean onClose: () => void @@ -127,7 +130,7 @@ const AddMcpServerModal: FC = ({ }) .filter((arg) => arg.trim() !== '' && arg !== '--' && arg !== '=' && !arg.startsWith('--=')) - console.log('Processed DXT args:', processedArgs) + logger.debug('Processed DXT args:', processedArgs) // Create MCPServer from DXT manifest const newServer: MCPServer = { @@ -160,19 +163,19 @@ const AddMcpServerModal: FC = ({ window.api.mcp .checkMcpConnectivity(newServer) .then((isConnected) => { - console.log(`Connectivity check for ${newServer.name}: ${isConnected}`) + logger.debug(`Connectivity check for ${newServer.name}: ${isConnected}`) dispatch(setMCPServerActive({ id: newServer.id, isActive: isConnected })) }) .catch((connError: any) => { - console.error(`Connectivity check failed for ${newServer.name}:`, connError) + logger.error(`Connectivity check failed for ${newServer.name}:`, connError) // Don't show error for DXT servers as they might need additional setup - console.warn( + logger.warn( `DXT server ${newServer.name} connectivity check failed, this is normal for servers requiring additional configuration` ) }) }, 1000) // Delay to ensure server is properly added to store } catch (error) { - console.error('DXT processing error:', error) + logger.error('DXT processing error:', error) window.message.error({ content: t('settings.mcp.addServer.importFrom.dxtProcessFailed'), key: 'mcp-dxt-error' @@ -236,11 +239,11 @@ const AddMcpServerModal: FC = ({ window.api.mcp .checkMcpConnectivity(newServer) .then((isConnected) => { - console.log(`Connectivity check for ${newServer.name}: ${isConnected}`) + logger.debug(`Connectivity check for ${newServer.name}: ${isConnected}`) dispatch(setMCPServerActive({ id: newServer.id, isActive: isConnected })) }) .catch((connError: any) => { - console.error(`Connectivity check failed for ${newServer.name}:`, connError) + logger.error(`Connectivity check failed for ${newServer.name}:`, connError) window.message.error({ content: t(`${newServer.name} settings.mcp.addServer.importFrom.connectionFailed`), key: 'mcp-quick-add-failed' @@ -368,7 +371,7 @@ const parseAndExtractServer = ( serverToAdd = { ...potentialServer } serverToAdd!.name = potentialServer.name ?? firstServerKey } else { - console.error('Invalid server data under mcpServers key:', potentialServer) + logger.error('Invalid server data under mcpServers key:', potentialServer) return { serverToAdd: null, error: t('settings.mcp.addServer.importFrom.invalid') } } } else if (Array.isArray(parsedJson) && parsedJson.length > 0) { @@ -377,7 +380,7 @@ const parseAndExtractServer = ( serverToAdd = { ...parsedJson[0] } serverToAdd!.name = parsedJson[0].name ?? t('settings.mcp.newServer') } else { - console.error('Invalid server data in array:', parsedJson[0]) + logger.error('Invalid server data in array:', parsedJson[0]) return { serverToAdd: null, error: t('settings.mcp.addServer.importFrom.invalid') } } } else if ( @@ -401,7 +404,7 @@ const parseAndExtractServer = ( // 確保 serverToAdd 存在且 name 存在 if (!serverToAdd || !serverToAdd.name) { - console.error('Invalid JSON structure for server config or missing name:', parsedJson) + logger.error('Invalid JSON structure for server config or missing name:', parsedJson) return { serverToAdd: null, error: t('settings.mcp.addServer.importFrom.invalid') } } diff --git a/src/renderer/src/pages/settings/MCPSettings/EditMcpJsonPopup.tsx b/src/renderer/src/pages/settings/MCPSettings/EditMcpJsonPopup.tsx index 0e505b3840..3328aeb514 100644 --- a/src/renderer/src/pages/settings/MCPSettings/EditMcpJsonPopup.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/EditMcpJsonPopup.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import CodeEditor from '@renderer/components/CodeEditor' import { TopView } from '@renderer/components/TopView' import { useAppDispatch, useAppSelector } from '@renderer/store' @@ -11,6 +12,8 @@ interface Props { resolve: (data: any) => void } +const logger = loggerService.withContext('EditMcpJsonPopup') + const PopupContainer: React.FC = ({ resolve }) => { const [open, setOpen] = useState(true) const [jsonConfig, setJsonConfig] = useState('') @@ -40,7 +43,7 @@ const PopupContainer: React.FC = ({ resolve }) => { setJsonConfig(formattedJson) setJsonError('') } catch (error) { - console.error('Failed to format JSON:', error) + logger.error('Failed to format JSON:', error) setJsonError(t('settings.mcp.jsonFormatError')) } finally { setIsLoading(false) @@ -87,7 +90,7 @@ const PopupContainer: React.FC = ({ resolve }) => { setJsonError('') setOpen(false) } catch (error: any) { - console.error('Failed to save JSON config:', error) + logger.error('Failed to save JSON config:', error) setJsonError(error.message || t('settings.mcp.jsonSaveError')) window.message.error(t('settings.mcp.jsonSaveError')) } finally { diff --git a/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx b/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx index 75546857fd..c4ece15f04 100644 --- a/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx @@ -1,4 +1,5 @@ import { DeleteOutlined, SaveOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { useTheme } from '@renderer/context/ThemeProvider' import { useMCPServer, useMCPServers } from '@renderer/hooks/useMCPServers' import MCPDescription from '@renderer/pages/settings/MCPSettings/McpDescription' @@ -17,6 +18,8 @@ import MCPPromptsSection from './McpPrompt' import MCPResourcesSection from './McpResource' import MCPToolsSection from './McpTool' +const logger = loggerService.withContext('McpSettings') + interface MCPFormValues { name: string description?: string @@ -315,7 +318,7 @@ const McpSettings: React.FC = () => { setLoading(false) } catch (error: any) { setLoading(false) - console.error('Failed to save MCP server settings:', error) + logger.error('Failed to save MCP server settings:', error) } } diff --git a/src/renderer/src/pages/settings/MCPSettings/modelscopeSyncUtils.ts b/src/renderer/src/pages/settings/MCPSettings/modelscopeSyncUtils.ts index 13a7de1f28..dc9a34768e 100644 --- a/src/renderer/src/pages/settings/MCPSettings/modelscopeSyncUtils.ts +++ b/src/renderer/src/pages/settings/MCPSettings/modelscopeSyncUtils.ts @@ -1,7 +1,10 @@ +import { loggerService } from '@logger' import { nanoid } from '@reduxjs/toolkit' import { MCPServer } from '@renderer/types' import i18next from 'i18next' +const logger = loggerService.withContext('ModelScopeSyncUtils') + // Token storage constants and utilities const TOKEN_STORAGE_KEY = 'modelscope_token' @@ -114,7 +117,7 @@ export const syncModelScopeServers = async ( addedServers.push(mcpServer) } catch (err) { - console.error('Error processing ModelScope server:', err) + logger.error('Error processing ModelScope server:', err) } } @@ -124,7 +127,7 @@ export const syncModelScopeServers = async ( addedServers } } catch (error) { - console.error('ModelScope sync error:', error) + logger.error('ModelScope sync error:', error) return { success: false, message: t('settings.mcp.sync.error'), diff --git a/src/renderer/src/pages/settings/MCPSettings/providers/302ai.ts b/src/renderer/src/pages/settings/MCPSettings/providers/302ai.ts index 8a8cb10cd9..856b2d6d87 100644 --- a/src/renderer/src/pages/settings/MCPSettings/providers/302ai.ts +++ b/src/renderer/src/pages/settings/MCPSettings/providers/302ai.ts @@ -1,7 +1,10 @@ +import { loggerService } from '@logger' import { nanoid } from '@reduxjs/toolkit' import type { MCPServer } from '@renderer/types' import i18next from 'i18next' +const logger = loggerService.withContext('302ai') + // Token storage constants and utilities const TOKEN_STORAGE_KEY = 'ai302_token' export const AI302_HOST = 'https://api.302.ai/mcp' @@ -65,7 +68,7 @@ export const syncAi302Servers = async (token: string, existingServers: MCPServer // Process successful response const data = await response.json() const servers: MCPServer[] = data.mcps || [] - console.log('servers', servers) + logger.debug('servers', servers) if (servers.length === 0) { return { @@ -97,7 +100,7 @@ export const syncAi302Servers = async (token: string, existingServers: MCPServer addedServers.push(mcpServer) } catch (err) { - console.error('Error processing 302ai server:', err) + logger.error('Error processing 302ai server:', err) } } @@ -107,7 +110,7 @@ export const syncAi302Servers = async (token: string, existingServers: MCPServer addedServers } } catch (error) { - console.error('302ai sync error:', error) + logger.error('302ai sync error:', error) return { success: false, message: t('settings.mcp.sync.error'), diff --git a/src/renderer/src/pages/settings/MCPSettings/providers/lanyun.ts b/src/renderer/src/pages/settings/MCPSettings/providers/lanyun.ts index d8aac2fa9c..67acf6667f 100644 --- a/src/renderer/src/pages/settings/MCPSettings/providers/lanyun.ts +++ b/src/renderer/src/pages/settings/MCPSettings/providers/lanyun.ts @@ -1,6 +1,9 @@ +import { loggerService } from '@logger' import type { MCPServer } from '@renderer/types' import i18next from 'i18next' +const logger = loggerService.withContext('TokenLanYunSyncUtils') + // Token storage constants and utilities const TOKEN_STORAGE_KEY = 'tokenLanyunToken' export const TOKENLANYUN_HOST = 'https://mcp.lanyun.net' @@ -157,7 +160,7 @@ export const syncTokenLanYunServers = async ( addedServers.push(mcpServer) } catch (err) { - console.error('Error processing LanYun server:', err) + logger.error('Error processing LanYun server:', err) } } @@ -167,7 +170,7 @@ export const syncTokenLanYunServers = async ( addedServers } } catch (error) { - console.error('TokenLanyun sync error:', error) + logger.error('TokenLanyun sync error:', error) return { success: false, message: t('settings.mcp.sync.error'), diff --git a/src/renderer/src/pages/settings/MCPSettings/providers/tokenflux.ts b/src/renderer/src/pages/settings/MCPSettings/providers/tokenflux.ts index 7b039cda79..3bf9636e63 100644 --- a/src/renderer/src/pages/settings/MCPSettings/providers/tokenflux.ts +++ b/src/renderer/src/pages/settings/MCPSettings/providers/tokenflux.ts @@ -1,7 +1,10 @@ +import { loggerService } from '@logger' import { nanoid } from '@reduxjs/toolkit' import type { MCPServer } from '@renderer/types' import i18next from 'i18next' +const logger = loggerService.withContext('TokenFluxSyncUtils') + // Token storage constants and utilities const TOKEN_STORAGE_KEY = 'tokenflux_token' export const TOKENFLUX_HOST = 'https://tokenflux.ai' @@ -125,7 +128,7 @@ export const syncTokenFluxServers = async ( addedServers.push(mcpServer) } catch (err) { - console.error('Error processing TokenFlux server:', err) + logger.error('Error processing TokenFlux server:', err) } } @@ -135,7 +138,7 @@ export const syncTokenFluxServers = async ( addedServers } } catch (error) { - console.error('TokenFlux sync error:', error) + logger.error('TokenFlux sync error:', error) return { success: false, message: t('settings.mcp.sync.error'), diff --git a/src/renderer/src/pages/settings/MemorySettings/MemoriesSettingsModal.tsx b/src/renderer/src/pages/settings/MemorySettings/MemoriesSettingsModal.tsx index eabac08e51..236bfd1cb4 100644 --- a/src/renderer/src/pages/settings/MemorySettings/MemoriesSettingsModal.tsx +++ b/src/renderer/src/pages/settings/MemorySettings/MemoriesSettingsModal.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import AiProvider from '@renderer/aiCore' import { isEmbeddingModel, isRerankModel } from '@renderer/config/models' import { useModel } from '@renderer/hooks/useModel' @@ -11,6 +12,8 @@ import { sortBy } from 'lodash' import { FC, useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' +const logger = loggerService.withContext('MemoriesSettingsModal') + interface MemoriesSettingsModalProps { visible: boolean onSubmit: (values: any) => void @@ -80,7 +83,7 @@ const MemoriesSettingsModal: FC = ({ visible, onSubm const aiProvider = new AiProvider(provider) finalDimensions = await aiProvider.getEmbeddingDimensions(embedderModel) } catch (error) { - console.error('Error getting embedding dimensions:', error) + logger.error('Error getting embedding dimensions:', error) window.message.error(t('message.error.get_embedding_dimensions') + '\n' + getErrorMessage(error)) setLoading(false) return @@ -117,7 +120,7 @@ const MemoriesSettingsModal: FC = ({ visible, onSubm setLoading(false) } } catch (error) { - console.error('Error submitting form:', error) + logger.error('Error submitting form:', error) setLoading(false) } } diff --git a/src/renderer/src/pages/settings/MemorySettings/MemorySettings.tsx b/src/renderer/src/pages/settings/MemorySettings/MemorySettings.tsx index 7fa9ebcf86..4e0fdcf7ed 100644 --- a/src/renderer/src/pages/settings/MemorySettings/MemorySettings.tsx +++ b/src/renderer/src/pages/settings/MemorySettings/MemorySettings.tsx @@ -10,6 +10,7 @@ import { UserDeleteOutlined, UserOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { HStack } from '@renderer/components/Layout' import { useTheme } from '@renderer/context/ThemeProvider' import { useModel } from '@renderer/hooks/useModel' @@ -56,6 +57,8 @@ import { } from '../index' import MemoriesSettingsModal from './MemoriesSettingsModal' +const logger = loggerService.withContext('MemorySettings') + dayjs.extend(relativeTime) const DEFAULT_USER_ID = 'default-user' @@ -331,7 +334,7 @@ const MemorySettings = () => { const users = usersList.map((user) => user.userId) setUniqueUsers(users) } catch (error) { - console.error('Failed to load users list:', error) + logger.error('Failed to load users list:', error) } }, [memoryService]) @@ -339,7 +342,7 @@ const MemorySettings = () => { const loadMemories = useCallback( async (userId?: string) => { const targetUser = userId || currentUser - console.log('Loading all memories for user:', targetUser) + logger.debug('Loading all memories for user:', targetUser) setLoading(true) try { // First, ensure the memory service is using the correct user @@ -350,10 +353,10 @@ const MemorySettings = () => { // Get all memories for current user context (load up to 10000) const result = await memoryService.list({ limit: 10000, offset: 0 }) - console.log('Loaded memories for user:', targetUser, 'count:', result.results?.length || 0) + logger.verbose('Loaded memories for user:', targetUser, 'count:', result.results?.length || 0) setAllMemories(result.results || []) } catch (error) { - console.error('Failed to load memories:', error) + logger.error('Failed to load memories:', error) window.message.error(t('memory.load_failed')) } finally { setLoading(false) @@ -364,7 +367,7 @@ const MemorySettings = () => { // Sync memoryService with Redux store on mount and when currentUser changes useEffect(() => { - console.log('useEffect triggered for currentUser:', currentUser) + logger.verbose('useEffect triggered for currentUser:', currentUser) // Reset to first page when user changes setCurrentPage(1) loadMemories(currentUser) @@ -419,7 +422,7 @@ const MemorySettings = () => { setCurrentPage(1) await loadMemories(currentUser) } catch (error) { - console.error('Failed to add memory:', error) + logger.error('Failed to add memory:', error) window.message.error(t('memory.add_failed')) } } @@ -431,7 +434,7 @@ const MemorySettings = () => { // Reload all memories await loadMemories(currentUser) } catch (error) { - console.error('Failed to delete memory:', error) + logger.error('Failed to delete memory:', error) window.message.error(t('memory.delete_failed')) } } @@ -448,13 +451,13 @@ const MemorySettings = () => { // Reload all memories await loadMemories(currentUser) } catch (error) { - console.error('Failed to update memory:', error) + logger.error('Failed to update memory:', error) window.message.error(t('memory.update_failed')) } } const handleUserSwitch = async (userId: string) => { - console.log('Switching to user:', userId) + logger.verbose('Switching to user:', userId) // First update Redux state dispatch(setCurrentUserId(userId)) @@ -473,7 +476,7 @@ const MemorySettings = () => { t('memory.user_switched', { user: userId === DEFAULT_USER_ID ? t('memory.default_user') : userId }) ) } catch (error) { - console.error('Failed to switch user:', error) + logger.error('Failed to switch user:', error) window.message.error(t('memory.user_switch_failed')) } } @@ -493,7 +496,7 @@ const MemorySettings = () => { window.message.success(t('memory.user_created', { user: userId })) setAddUserModalVisible(false) } catch (error) { - console.error('Failed to add user:', error) + logger.error('Failed to add user:', error) window.message.error(t('memory.add_user_failed')) } } @@ -530,7 +533,7 @@ const MemorySettings = () => { // Reload memories to show the empty state await loadMemories(currentUser) } catch (error) { - console.error('Failed to reset memories:', error) + logger.error('Failed to reset memories:', error) window.message.error(t('memory.reset_memories_failed')) } } @@ -564,7 +567,7 @@ const MemorySettings = () => { await loadMemories(currentUser) } } catch (error) { - console.error('Failed to delete user:', error) + logger.error('Failed to delete user:', error) window.message.error(t('memory.delete_user_failed')) } } diff --git a/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx index a77af8f331..16961c749e 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { Center, VStack } from '@renderer/components/Layout' import { TopView } from '@renderer/components/TopView' import ImageStorage from '@renderer/services/ImageStorage' @@ -8,6 +9,8 @@ import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' +const logger = loggerService.withContext('AddProviderPopup') + interface Props { provider?: Provider resolve: (result: { name: string; type: ProviderType; logo?: string; logoFile?: File }) => void @@ -30,7 +33,7 @@ const PopupContainer: React.FC = ({ provider, resolve }) => { setLogo(logoData) } } catch (error) { - console.error('Failed to load logo', error) + logger.error('Failed to load logo', error) } } loadLogo() diff --git a/src/renderer/src/pages/settings/ProviderSettings/EditModelsPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/EditModelsPopup.tsx index ed3de73186..9d0bce9707 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/EditModelsPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/EditModelsPopup.tsx @@ -1,4 +1,5 @@ import { MinusOutlined, PlusOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import CustomCollapse from '@renderer/components/CustomCollapse' import CustomTag from '@renderer/components/CustomTag' import ExpandableText from '@renderer/components/ExpandableText' @@ -33,6 +34,7 @@ import styled from 'styled-components' import { TopView } from '../../../components/TopView' +const logger = loggerService.withContext('EditModelsPopup') interface ShowParams { provider: Provider } @@ -180,7 +182,7 @@ const PopupContainer: React.FC = ({ provider: _provider, resolve }) => { .filter((model) => !isEmpty(model.name)) ) } catch (error) { - console.error('Failed to fetch models', error) + logger.error('Failed to fetch models', error) } finally { setTimeout(() => setLoading(false), 300) } diff --git a/src/renderer/src/pages/settings/ProviderSettings/GithubCopilotSettings.tsx b/src/renderer/src/pages/settings/ProviderSettings/GithubCopilotSettings.tsx index 3a79a328a1..b106f7a739 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/GithubCopilotSettings.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/GithubCopilotSettings.tsx @@ -1,4 +1,5 @@ import { CheckCircleOutlined, CopyOutlined, ExclamationCircleOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { useCopilot } from '@renderer/hooks/useCopilot' import { useProvider } from '@renderer/hooks/useProvider' import { Alert, Button, Input, Slider, Steps, Tooltip, Typography } from 'antd' @@ -8,6 +9,8 @@ import styled from 'styled-components' import { SettingRow, SettingSubtitle } from '..' +const logger = loggerService.withContext('GithubCopilotSettings') + interface GithubCopilotSettingsProps { providerId: string } @@ -53,9 +56,9 @@ const GithubCopilotSettings: FC = ({ providerId }) = setLoading(true) setCurrentStep(1) const { device_code, user_code, verification_uri } = await window.api.copilot.getAuthMessage(defaultHeaders) - console.log('device_code', device_code) - console.log('user_code', user_code) - console.log('verification_uri', verification_uri) + logger.debug('device_code', device_code) + logger.debug('user_code', user_code) + logger.debug('verification_uri', verification_uri) setDeviceCode(device_code) setUserCode(user_code) setVerificationUri(verification_uri) @@ -66,10 +69,10 @@ const GithubCopilotSettings: FC = ({ providerId }) = await navigator.clipboard.writeText(user_code) window.message.success(t('settings.provider.copilot.code_copied')) } catch (error) { - console.error('Failed to copy to clipboard:', error) + logger.error('Failed to copy to clipboard:', error) } } catch (error) { - console.error('Failed to get device code:', error) + logger.error('Failed to get device code:', error) window.message.error(t('settings.provider.copilot.code_failed')) setCurrentStep(0) } finally { @@ -95,7 +98,7 @@ const GithubCopilotSettings: FC = ({ providerId }) = window.message.success(t('settings.provider.copilot.auth_success')) } } catch (error) { - console.error('Failed to get token:', error) + logger.error('Failed to get token:', error) window.message.error(t('settings.provider.copilot.auth_failed')) setCurrentStep(2) } finally { @@ -124,7 +127,7 @@ const GithubCopilotSettings: FC = ({ providerId }) = window.message.success(t('settings.provider.copilot.logout_success')) } catch (error) { - console.error('Failed to logout:', error) + logger.error('Failed to logout:', error) window.message.error(t('settings.provider.copilot.logout_failed')) // 如果登出失败,重置登出状态 updateProvider({ ...provider, apiKey: '', isAuthed: false }) @@ -139,7 +142,7 @@ const GithubCopilotSettings: FC = ({ providerId }) = await navigator.clipboard.writeText(userCode) window.message.success(t('common.copied')) } catch (error) { - console.error('Failed to copy to clipboard:', error) + logger.error('Failed to copy to clipboard:', error) window.message.error(t('common.copy_failed')) } }, [userCode, t]) diff --git a/src/renderer/src/pages/settings/ProviderSettings/index.tsx b/src/renderer/src/pages/settings/ProviderSettings/index.tsx index d7a3469a86..f74796bf1e 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/index.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/index.tsx @@ -1,5 +1,6 @@ import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons' import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd' +import { loggerService } from '@logger' import Scrollbar from '@renderer/components/Scrollbar' import { getProviderLogo } from '@renderer/config/providers' import { useAllProviders, useProviders } from '@renderer/hooks/useProvider' @@ -18,6 +19,8 @@ import AddProviderPopup from './AddProviderPopup' import ModelNotesPopup from './ModelNotesPopup' import ProviderSetting from './ProviderSetting' +const logger = loggerService.withContext('ProvidersList') + const ProvidersList: FC = () => { const [searchParams] = useSearchParams() const providers = useAllProviders() @@ -39,7 +42,7 @@ const ProvidersList: FC = () => { logos[provider.id] = logoData } } catch (error) { - console.error(`Failed to load logo for provider ${provider.id}`, error) + logger.error(`Failed to load logo for provider ${provider.id}`, error) } } } @@ -291,7 +294,7 @@ const ProvidersList: FC = () => { } setProviderLogos(updatedLogos) } catch (error) { - console.error('Failed to save logo', error) + logger.error('Failed to save logo', error) window.message.error('保存Provider Logo失败') } } @@ -326,7 +329,7 @@ const ProvidersList: FC = () => { [provider.id]: logo })) } catch (error) { - console.error('Failed to save logo', error) + logger.error('Failed to save logo', error) window.message.error('更新Provider Logo失败') } } else if (logo === undefined && logoFile === undefined) { @@ -338,7 +341,7 @@ const ProvidersList: FC = () => { return newLogos }) } catch (error) { - console.error('Failed to reset logo', error) + logger.error('Failed to reset logo', error) } } } @@ -369,7 +372,7 @@ const ProvidersList: FC = () => { return newLogos }) } catch (error) { - console.error('Failed to delete logo', error) + logger.error('Failed to delete logo', error) } } diff --git a/src/renderer/src/pages/settings/SelectionAssistantSettings/components/SelectionActionSearchModal.tsx b/src/renderer/src/pages/settings/SelectionAssistantSettings/components/SelectionActionSearchModal.tsx index 8dfd77d2b8..f6609bf3dc 100644 --- a/src/renderer/src/pages/settings/SelectionAssistantSettings/components/SelectionActionSearchModal.tsx +++ b/src/renderer/src/pages/settings/SelectionAssistantSettings/components/SelectionActionSearchModal.tsx @@ -1,9 +1,12 @@ +import { loggerService } from '@logger' import type { ActionItem } from '@renderer/types/selectionTypes' import { Button, Form, Input, Modal, Select } from 'antd' import { Globe } from 'lucide-react' import { FC, useEffect } from 'react' import { useTranslation } from 'react-i18next' +const logger = loggerService.withContext('SelectionActionSearchModal') + interface SearchEngineOption { label: string value: string @@ -124,7 +127,7 @@ const SelectionActionSearchModal: FC = ({ onOk(searchEngine) } catch (error) { - console.error('Validation failed:', error) + logger.debug('Validation failed:', error) } } diff --git a/src/renderer/src/pages/settings/SelectionAssistantSettings/hooks/useSettingsActionsList.ts b/src/renderer/src/pages/settings/SelectionAssistantSettings/hooks/useSettingsActionsList.ts index ee416ad404..3ffe453599 100644 --- a/src/renderer/src/pages/settings/SelectionAssistantSettings/hooks/useSettingsActionsList.ts +++ b/src/renderer/src/pages/settings/SelectionAssistantSettings/hooks/useSettingsActionsList.ts @@ -1,4 +1,5 @@ import { DropResult } from '@hello-pangea/dnd' +import { loggerService } from '@logger' import { defaultActionItems } from '@renderer/store/selectionStore' import type { ActionItem } from '@renderer/types/selectionTypes' import { useMemo, useState } from 'react' @@ -6,6 +7,8 @@ import { useTranslation } from 'react-i18next' import { DEFAULT_SEARCH_ENGINES } from '../components/SelectionActionSearchModal' +const logger = loggerService.withContext('useSettingsActionsList') + const MAX_CUSTOM_ITEMS = 8 const MAX_ENABLED_ITEMS = 6 @@ -49,7 +52,7 @@ export const useActionItems = ( const currentItems = initialItems || [] setActionItems([...currentItems, actionItem]) } catch (error) { - console.error('Error adding item:', error) + logger.debug('Error adding item:', error) } } setIsUserModalOpen(false) diff --git a/src/renderer/src/pages/settings/ToolSettings/WebSearchSettings/BlacklistSettings.tsx b/src/renderer/src/pages/settings/ToolSettings/WebSearchSettings/BlacklistSettings.tsx index 00ffe3e34a..91c8e09851 100644 --- a/src/renderer/src/pages/settings/ToolSettings/WebSearchSettings/BlacklistSettings.tsx +++ b/src/renderer/src/pages/settings/ToolSettings/WebSearchSettings/BlacklistSettings.tsx @@ -1,5 +1,5 @@ import { CheckOutlined, InfoCircleOutlined, LoadingOutlined } from '@ant-design/icons' -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import { useTheme } from '@renderer/context/ThemeProvider' import { useBlacklist } from '@renderer/hooks/useWebSearchProviders' import { useAppDispatch, useAppSelector } from '@renderer/store' @@ -20,6 +20,8 @@ interface DataType { name: string } +const logger = loggerService.withContext('BlacklistSettings') + const columns: TableProps['columns'] = [ { title: t('common.name'), dataIndex: 'name', key: 'name' }, { @@ -56,7 +58,7 @@ const BlacklistSettings: FC = () => { name: source.name })) ) - Logger.log('subscribeSources', websearch.subscribeSources) + logger.info('subscribeSources', websearch.subscribeSources) }, [websearch.subscribeSources]) useEffect(() => { @@ -90,7 +92,7 @@ const BlacklistSettings: FC = () => { }) } const onSelectChange = (newSelectedRowKeys: React.Key[]) => { - Logger.log('selectedRowKeys changed: ', newSelectedRowKeys) + logger.info('selectedRowKeys changed: ', newSelectedRowKeys) setSelectedRowKeys(newSelectedRowKeys) } @@ -128,7 +130,7 @@ const BlacklistSettings: FC = () => { }) } } catch (error) { - console.error(`Error updating subscribe source ${source.url}:`, error) + logger.error(`Error updating subscribe source ${source.url}:`, error) // 显示具体源更新失败的消息 window.message.warning({ content: t('settings.tool.websearch.subscribe_source_update_failed', { url: source.url }), @@ -152,7 +154,7 @@ const BlacklistSettings: FC = () => { throw new Error('No valid sources updated') } } catch (error) { - console.error('Error updating subscribes:', error) + logger.error('Error updating subscribes:', error) window.message.error({ content: t('settings.tool.websearch.subscribe_update_failed'), duration: 2 @@ -211,7 +213,7 @@ const BlacklistSettings: FC = () => { // 清空选中状态 setSelectedRowKeys([]) } catch (error) { - console.error('Error deleting subscribes:', error) + logger.error('Error deleting subscribes:', error) } } diff --git a/src/renderer/src/pages/settings/ToolSettings/WebSearchSettings/CompressionSettings/RagSettings.tsx b/src/renderer/src/pages/settings/ToolSettings/WebSearchSettings/CompressionSettings/RagSettings.tsx index f5169889b8..ebd4c636e2 100644 --- a/src/renderer/src/pages/settings/ToolSettings/WebSearchSettings/CompressionSettings/RagSettings.tsx +++ b/src/renderer/src/pages/settings/ToolSettings/WebSearchSettings/CompressionSettings/RagSettings.tsx @@ -1,6 +1,6 @@ +import { loggerService } from '@logger' import AiProvider from '@renderer/aiCore' import { DEFAULT_WEBSEARCH_RAG_DOCUMENT_COUNT } from '@renderer/config/constant' -import Logger from '@renderer/config/logger' import { isEmbeddingModel, isRerankModel } from '@renderer/config/models' import { NOT_SUPPORTED_REANK_PROVIDERS } from '@renderer/config/providers' import { useProviders } from '@renderer/hooks/useProvider' @@ -14,6 +14,8 @@ import { Info, RefreshCw } from 'lucide-react' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' +const logger = loggerService.withContext('RagSettings') + const INPUT_BOX_WIDTH = '200px' const RagSettings = () => { @@ -91,14 +93,14 @@ const RagSettings = () => { const handleAutoGetDimensions = async () => { if (!compressionConfig?.embeddingModel) { - Logger.log('[RagSettings] handleAutoGetDimensions: no embedding model') + logger.info('handleAutoGetDimensions: no embedding model') window.message.error(t('settings.tool.websearch.compression.error.embedding_model_required')) return } const provider = providers.find((p) => p.id === compressionConfig.embeddingModel?.provider) if (!provider) { - Logger.log('[RagSettings] handleAutoGetDimensions: provider not found') + logger.info('handleAutoGetDimensions: provider not found') window.message.error(t('settings.tool.websearch.compression.error.provider_not_found')) return } @@ -112,7 +114,7 @@ const RagSettings = () => { window.message.success(t('settings.tool.websearch.compression.info.dimensions_auto_success', { dimensions })) } catch (error) { - Logger.error('[RagSettings] handleAutoGetDimensions: failed to get embedding dimensions', error) + logger.error('handleAutoGetDimensions: failed to get embedding dimensions', error) window.message.error(t('settings.tool.websearch.compression.error.dimensions_auto_failed')) } finally { setLoadingDimensions(false) diff --git a/src/renderer/src/pages/settings/ToolSettings/WebSearchSettings/WebSearchProviderSetting.tsx b/src/renderer/src/pages/settings/ToolSettings/WebSearchSettings/WebSearchProviderSetting.tsx index 242de8641d..d12a4f22f9 100644 --- a/src/renderer/src/pages/settings/ToolSettings/WebSearchSettings/WebSearchProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ToolSettings/WebSearchSettings/WebSearchProviderSetting.tsx @@ -1,4 +1,5 @@ import { CheckOutlined, ExportOutlined, LoadingOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import ApiKeyListPopup from '@renderer/components/Popups/ApiKeyListPopup/popup' import { getWebSearchProviderLogo, WEB_SEARCH_PROVIDER_CONFIG } from '@renderer/config/webSearchProviders' import { useWebSearchProvider } from '@renderer/hooks/useWebSearchProviders' @@ -20,6 +21,7 @@ import { SettingTitle } from '../..' +const logger = loggerService.withContext('WebSearchProviderSetting') interface Props { providerId: string } @@ -116,7 +118,7 @@ const WebSearchProviderSetting: FC = ({ providerId }) => { setApiValid(valid) } catch (err) { - console.error('Check search error:', err) + logger.error('Check search error:', err) setApiValid(false) window.message.error({ key: 'check-search-error', diff --git a/src/renderer/src/pages/translate/TranslatePage.tsx b/src/renderer/src/pages/translate/TranslatePage.tsx index 7d1d78eff3..c50d869d16 100644 --- a/src/renderer/src/pages/translate/TranslatePage.tsx +++ b/src/renderer/src/pages/translate/TranslatePage.tsx @@ -1,4 +1,5 @@ import { CheckOutlined, DeleteOutlined, HistoryOutlined, RedoOutlined, SendOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import CopyIcon from '@renderer/components/Icons/CopyIcon' import { HStack } from '@renderer/components/Layout' @@ -34,6 +35,8 @@ import { FC, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' +const logger = loggerService.withContext('TranslatePage') + let _text = '' let _result = '' let _targetLanguage = LanguagesEnum.enUS @@ -428,7 +431,7 @@ const TranslatePage: FC = () => { await saveTranslateHistory(text, translatedText, actualSourceLanguage.langCode, actualTargetLanguage.langCode) setLoading(false) } catch (error) { - console.error('Translation error:', error) + logger.error('Translation error:', error) window.message.error({ content: String(error), key: 'translate-message' @@ -545,7 +548,7 @@ const TranslatePage: FC = () => { ) } } catch (error) { - console.error('Error getting language display:', error) + logger.error('Error getting language display:', error) setBidirectionalPair([LanguagesEnum.enUS, LanguagesEnum.zhCN]) } diff --git a/src/renderer/src/providers/WebSearchProvider/BochaProvider.ts b/src/renderer/src/providers/WebSearchProvider/BochaProvider.ts index 1a3d53d87c..71df56a7ff 100644 --- a/src/renderer/src/providers/WebSearchProvider/BochaProvider.ts +++ b/src/renderer/src/providers/WebSearchProvider/BochaProvider.ts @@ -1,9 +1,12 @@ +import { loggerService } from '@logger' import { WebSearchState } from '@renderer/store/websearch' import { WebSearchProvider, WebSearchProviderResponse } from '@renderer/types' import { BochaSearchParams, BochaSearchResponse } from '@renderer/utils/bocha' import BaseWebSearchProvider from './BaseWebSearchProvider' +const logger = loggerService.withContext('BochaProvider') + export default class BochaProvider extends BaseWebSearchProvider { constructor(provider: WebSearchProvider) { super(provider) @@ -62,7 +65,7 @@ export default class BochaProvider extends BaseWebSearchProvider { })) } } catch (error) { - console.error('Bocha search failed:', error) + logger.error('Bocha search failed:', error) throw new Error(`Search failed: ${error instanceof Error ? error.message : 'Unknown error'}`) } } diff --git a/src/renderer/src/providers/WebSearchProvider/ExaProvider.ts b/src/renderer/src/providers/WebSearchProvider/ExaProvider.ts index 7aee19609f..87907bd212 100644 --- a/src/renderer/src/providers/WebSearchProvider/ExaProvider.ts +++ b/src/renderer/src/providers/WebSearchProvider/ExaProvider.ts @@ -1,9 +1,11 @@ import { ExaClient } from '@agentic/exa' +import { loggerService } from '@logger' import { WebSearchState } from '@renderer/store/websearch' import { WebSearchProvider, WebSearchProviderResponse } from '@renderer/types' import BaseWebSearchProvider from './BaseWebSearchProvider' +const logger = loggerService.withContext('ExaProvider') export default class ExaProvider extends BaseWebSearchProvider { private exa: ExaClient @@ -43,7 +45,7 @@ export default class ExaProvider extends BaseWebSearchProvider { }) } } catch (error) { - console.error('Exa search failed:', error) + logger.error('Exa search failed:', error) throw new Error(`Search failed: ${error instanceof Error ? error.message : 'Unknown error'}`) } } diff --git a/src/renderer/src/providers/WebSearchProvider/LocalBaiduProvider.ts b/src/renderer/src/providers/WebSearchProvider/LocalBaiduProvider.ts index 5c63747bc2..86845134d9 100644 --- a/src/renderer/src/providers/WebSearchProvider/LocalBaiduProvider.ts +++ b/src/renderer/src/providers/WebSearchProvider/LocalBaiduProvider.ts @@ -1,7 +1,9 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import LocalSearchProvider, { SearchItem } from './LocalSearchProvider' +const logger = loggerService.withContext('LocalBaiduProvider') + export default class LocalBaiduProvider extends LocalSearchProvider { protected parseValidUrls(htmlContent: string): SearchItem[] { const results: SearchItem[] = [] @@ -22,9 +24,9 @@ export default class LocalBaiduProvider extends LocalSearchProvider { } }) } catch (error) { - console.error('Failed to parse Baidu search HTML:', error) + logger.error('Failed to parse Baidu search HTML:', error) } - Logger.log('Parsed Baidu search results:', results) + logger.info('Parsed Baidu search results:', results) return results } } diff --git a/src/renderer/src/providers/WebSearchProvider/LocalBingProvider.ts b/src/renderer/src/providers/WebSearchProvider/LocalBingProvider.ts index fe233c469e..62b626ee01 100644 --- a/src/renderer/src/providers/WebSearchProvider/LocalBingProvider.ts +++ b/src/renderer/src/providers/WebSearchProvider/LocalBingProvider.ts @@ -1,5 +1,9 @@ +import { loggerService } from '@logger' + import LocalSearchProvider, { SearchItem } from './LocalSearchProvider' +const logger = loggerService.withContext('LocalBingProvider') + export default class LocalBingProvider extends LocalSearchProvider { protected parseValidUrls(htmlContent: string): SearchItem[] { const results: SearchItem[] = [] @@ -20,7 +24,7 @@ export default class LocalBingProvider extends LocalSearchProvider { } }) } catch (error) { - console.error('Failed to parse Bing search HTML:', error) + logger.error('Failed to parse Bing search HTML:', error) } return results } diff --git a/src/renderer/src/providers/WebSearchProvider/LocalGoogleProvider.ts b/src/renderer/src/providers/WebSearchProvider/LocalGoogleProvider.ts index 60b3f9aa9c..cbfb42f03c 100644 --- a/src/renderer/src/providers/WebSearchProvider/LocalGoogleProvider.ts +++ b/src/renderer/src/providers/WebSearchProvider/LocalGoogleProvider.ts @@ -1,5 +1,9 @@ +import { loggerService } from '@logger' + import LocalSearchProvider, { SearchItem } from './LocalSearchProvider' +const logger = loggerService.withContext('LocalGoogleProvider') + export default class LocalGoogleProvider extends LocalSearchProvider { protected parseValidUrls(htmlContent: string): SearchItem[] { const results: SearchItem[] = [] @@ -21,7 +25,7 @@ export default class LocalGoogleProvider extends LocalSearchProvider { } }) } catch (error) { - console.error('Failed to parse Google search HTML:', error) + logger.error('Failed to parse Google search HTML:', error) } return results } diff --git a/src/renderer/src/providers/WebSearchProvider/LocalSearchProvider.ts b/src/renderer/src/providers/WebSearchProvider/LocalSearchProvider.ts index 8a09b76016..c87964459a 100644 --- a/src/renderer/src/providers/WebSearchProvider/LocalSearchProvider.ts +++ b/src/renderer/src/providers/WebSearchProvider/LocalSearchProvider.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { nanoid } from '@reduxjs/toolkit' import { WebSearchState } from '@renderer/store/websearch' import { WebSearchProvider, WebSearchProviderResponse, WebSearchProviderResult } from '@renderer/types' @@ -7,6 +8,8 @@ import { fetchWebContent, noContent } from '@renderer/utils/fetch' import BaseWebSearchProvider from './BaseWebSearchProvider' +const logger = loggerService.withContext('LocalSearchProvider') + export interface SearchItem { title: string url: string @@ -69,7 +72,7 @@ export default class LocalSearchProvider extends BaseWebSearchProvider { if (isAbortError(error)) { throw error } - console.error('Local search failed:', error) + logger.error('Local search failed:', error) throw new Error(`Search failed: ${error instanceof Error ? error.message : 'Unknown error'}`) } finally { await window.api.searchService.closeSearchWindow(uid) diff --git a/src/renderer/src/providers/WebSearchProvider/SearxngProvider.ts b/src/renderer/src/providers/WebSearchProvider/SearxngProvider.ts index 82b95142f6..d2205b0562 100644 --- a/src/renderer/src/providers/WebSearchProvider/SearxngProvider.ts +++ b/src/renderer/src/providers/WebSearchProvider/SearxngProvider.ts @@ -1,5 +1,5 @@ import { SearxngClient } from '@agentic/searxng' -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import { WebSearchState } from '@renderer/store/websearch' import { WebSearchProvider, WebSearchProviderResponse } from '@renderer/types' import { fetchWebContent, noContent } from '@renderer/utils/fetch' @@ -8,6 +8,8 @@ import ky from 'ky' import BaseWebSearchProvider from './BaseWebSearchProvider' +const logger = loggerService.withContext('SearxngProvider') + export default class SearxngProvider extends BaseWebSearchProvider { private searxng: SearxngClient private engines: string[] = [] @@ -41,11 +43,11 @@ export default class SearxngProvider extends BaseWebSearchProvider { `Failed to initialize SearxNG client: ${error instanceof Error ? error.message : 'Unknown error'}` ) } - this.initEngines().catch((err) => console.error('Failed to initialize SearxNG engines:', err)) + this.initEngines().catch((err) => logger.error('Failed to initialize SearxNG engines:', err)) } private async initEngines(): Promise { try { - Logger.log(`Initializing SearxNG with API host: ${this.apiHost}`) + logger.info(`Initializing SearxNG with API host: ${this.apiHost}`) const auth = this.basicAuthUsername ? { username: this.basicAuthUsername, @@ -67,7 +69,7 @@ export default class SearxngProvider extends BaseWebSearchProvider { } const allEngines = response.data.engines - Logger.log(`Found ${allEngines.length} total engines in SearxNG`) + logger.info(`Found ${allEngines.length} total engines in SearxNG`) this.engines = allEngines .filter( @@ -84,11 +86,11 @@ export default class SearxngProvider extends BaseWebSearchProvider { } this.isInitialized = true - Logger.log(`SearxNG initialized successfully with ${this.engines.length} engines: ${this.engines.join(', ')}`) + logger.info(`SearxNG initialized successfully with ${this.engines.length} engines: ${this.engines.join(', ')}`) } catch (err) { this.isInitialized = false - Logger.error('Failed to fetch SearxNG engine configuration:', err) + logger.error('Failed to fetch SearxNG engine configuration:', err) throw new Error(`Failed to initialize SearxNG: ${err}`) } } @@ -133,7 +135,7 @@ export default class SearxngProvider extends BaseWebSearchProvider { results: results.filter((result) => result.content != noContent) } } catch (error) { - Logger.error('Searxng search failed:', error) + logger.error('Searxng search failed:', error) throw new Error(`Search failed: ${error instanceof Error ? error.message : 'Unknown error'}`) } } diff --git a/src/renderer/src/providers/WebSearchProvider/TavilyProvider.ts b/src/renderer/src/providers/WebSearchProvider/TavilyProvider.ts index 225bce308f..a4cbe69870 100644 --- a/src/renderer/src/providers/WebSearchProvider/TavilyProvider.ts +++ b/src/renderer/src/providers/WebSearchProvider/TavilyProvider.ts @@ -1,9 +1,11 @@ import { TavilyClient } from '@agentic/tavily' +import { loggerService } from '@logger' import { WebSearchState } from '@renderer/store/websearch' import { WebSearchProvider, WebSearchProviderResponse } from '@renderer/types' import BaseWebSearchProvider from './BaseWebSearchProvider' +const logger = loggerService.withContext('TavilyProvider') export default class TavilyProvider extends BaseWebSearchProvider { private tvly: TavilyClient @@ -39,7 +41,7 @@ export default class TavilyProvider extends BaseWebSearchProvider { }) } } catch (error) { - console.error('Tavily search failed:', error) + logger.error('Tavily search failed:', error) throw new Error(`Search failed: ${error instanceof Error ? error.message : 'Unknown error'}`) } } diff --git a/src/renderer/src/queue/KnowledgeQueue.ts b/src/renderer/src/queue/KnowledgeQueue.ts index 0f60760f64..912483ad0e 100644 --- a/src/renderer/src/queue/KnowledgeQueue.ts +++ b/src/renderer/src/queue/KnowledgeQueue.ts @@ -1,4 +1,4 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import db from '@renderer/databases' import { getStoreSetting } from '@renderer/hooks/useSettings' import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService' @@ -15,6 +15,8 @@ import { uuid } from '@renderer/utils' import type { LoaderReturn } from '@shared/config/types' import { t } from 'i18next' +const logger = loggerService.withContext('KnowledgeQueue') + class KnowledgeQueue { private processing: Map = new Map() private readonly MAX_RETRIES = 1 @@ -43,7 +45,7 @@ class KnowledgeQueue { async processQueue(baseId: string): Promise { if (this.processing.get(baseId)) { - Logger.log(`[KnowledgeQueue] Queue for base ${baseId} is already being processed`) + logger.info(`Queue for base ${baseId} is already being processed`) return } @@ -77,7 +79,7 @@ class KnowledgeQueue { processableItem = findProcessableItem() } } finally { - Logger.log(`[KnowledgeQueue] Finished processing queue for base ${baseId}`) + logger.info(`Finished processing queue for base ${baseId}`) this.processing.set(baseId, false) } } @@ -97,11 +99,11 @@ class KnowledgeQueue { const userId = getStoreSetting('userId') try { if (item.retryCount && item.retryCount >= this.MAX_RETRIES) { - Logger.log(`[KnowledgeQueue] Item ${item.id} has reached max retries, skipping`) + logger.info(`Item ${item.id} has reached max retries, skipping`) return } - Logger.log(`[KnowledgeQueue] Starting to process item ${item.id} (${item.type})`) + logger.info(`Starting to process item ${item.id} (${item.type})`) store.dispatch( updateItemProcessingStatus({ @@ -128,7 +130,7 @@ class KnowledgeQueue { let result: LoaderReturn | null = null let note, content - Logger.log(`[KnowledgeQueue] Processing item: ${sourceItem.content}`) + logger.info(`Processing item: ${sourceItem.content}`) switch (item.type) { case 'note': @@ -148,7 +150,7 @@ class KnowledgeQueue { } if (result.status === 'failed') { - Logger.error(`[KnowledgeQueue] Backend processing error for item ${item.id}: ${result.message}`) + logger.error(`Backend processing error for item ${item.id}: ${result.message}`) const errorPrefix = result.messageSource === 'embedding' @@ -160,7 +162,7 @@ class KnowledgeQueue { ) } - Logger.log(`[KnowledgeQueue] Successfully completed processing item ${item.id}`) + logger.info(`Successfully completed processing item ${item.id}`) notificationService.send({ id: uuid(), @@ -197,11 +199,11 @@ class KnowledgeQueue { }) ) } - Logger.log(`[KnowledgeQueue] Updated uniqueId for item ${item.id} in base ${baseId} `) + logger.info(`Updated uniqueId for item ${item.id} in base ${baseId} `) store.dispatch(clearCompletedProcessing({ baseId })) } catch (error) { - Logger.error(`[KnowledgeQueue] Error processing item ${item.id}: `, error) + logger.error(`Error processing item ${item.id}: `, error) notificationService.send({ id: uuid(), type: 'error', diff --git a/src/renderer/src/services/ApiService.ts b/src/renderer/src/services/ApiService.ts index c0f3f71a69..0f34a95b6b 100644 --- a/src/renderer/src/services/ApiService.ts +++ b/src/renderer/src/services/ApiService.ts @@ -1,5 +1,5 @@ +import { loggerService } from '@logger' import { CompletionsParams } from '@renderer/aiCore/middleware/schemas' -import Logger from '@renderer/config/logger' import { isEmbeddingModel, isGenerateImageModel, @@ -60,6 +60,8 @@ import { } from './MessagesService' import WebSearchService from './WebSearchService' +const logger = loggerService.withContext('ApiService') + // TODO:考虑拆开 async function fetchExternalTool( lastUserMessage: Message, @@ -123,7 +125,7 @@ async function fetchExternalTool( knowledge: needKnowledgeExtract ? extracted?.knowledge : undefined } } catch (e: any) { - console.error('extract error', e) + logger.error('extract error', e) if (isAbortError(e)) throw e return getFallbackResult() } @@ -148,7 +150,7 @@ async function fetchExternalTool( // Add check for extractResults existence early if (!extractResults?.websearch) { - console.warn('searchTheWeb called without valid extractResults.websearch') + logger.warn('searchTheWeb called without valid extractResults.websearch') return } @@ -156,7 +158,7 @@ async function fetchExternalTool( // Add check for assistant.model before using it if (!assistant.model) { - console.warn('searchTheWeb called without assistant.model') + logger.warn('searchTheWeb called without assistant.model') return undefined } @@ -174,7 +176,7 @@ async function fetchExternalTool( } } catch (error) { if (isAbortError(error)) throw error - console.error('Web search failed:', error) + logger.error('Web search failed:', error) return } } @@ -185,7 +187,7 @@ async function fetchExternalTool( const memoryConfig = selectMemoryConfig(store.getState()) const content = getMainTextContent(lastUserMessage) if (!content) { - console.warn('searchMemory called without valid content in lastUserMessage') + logger.warn('searchMemory called without valid content in lastUserMessage') return [] } @@ -193,7 +195,7 @@ async function fetchExternalTool( const currentUserId = selectCurrentUserId(store.getState()) // Search for relevant memories const processorConfig = MemoryProcessor.getProcessorConfig(memoryConfig, assistant.id, currentUserId) - console.log('Searching for relevant memories with content:', content) + logger.info('Searching for relevant memories with content:', content) const memoryProcessor = new MemoryProcessor() const relevantMemories = await memoryProcessor.searchRelevantMemories( content, @@ -202,17 +204,17 @@ async function fetchExternalTool( ) if (relevantMemories?.length > 0) { - console.log('Found relevant memories:', relevantMemories) + logger.info('Found relevant memories:', relevantMemories) return relevantMemories } return [] } else { - console.warn('Memory is enabled but embedding or LLM model is not configured') + logger.warn('Memory is enabled but embedding or LLM model is not configured') return [] } } catch (error) { - console.error('Error processing memory search:', error) + logger.error('Error processing memory search:', error) // Continue with conversation even if memory processing fails return [] } @@ -232,7 +234,7 @@ async function fetchExternalTool( } else { // auto mode if (!extractResults?.knowledge) { - console.warn('searchKnowledgeBase: No valid search criteria in auto mode') + logger.warn('searchKnowledgeBase: No valid search criteria in auto mode') return } searchCriteria = extractResults.knowledge @@ -253,7 +255,7 @@ async function fetchExternalTool( // .find((block) => block?.type === MessageBlockType.MAIN_TEXT) as MainTextMessageBlock | undefined return await processKnowledgeSearch(tempExtractResults, knowledgeBaseIds) } catch (error) { - console.error('Knowledge base search failed:', error) + logger.error('Knowledge base search failed:', error) return } } @@ -265,7 +267,7 @@ async function fetchExternalTool( // 根据配置决定是否需要提取 if (shouldWebSearch || hasKnowledgeBase) { extractResults = await extract() - Logger.log('[fetchExternalTool] Extraction results:', extractResults) + logger.info('[fetchExternalTool] Extraction results:', extractResults) } let webSearchResponseFromSearch: WebSearchResponse | undefined @@ -322,7 +324,7 @@ async function fetchExternalTool( const tools = await window.api.mcp.listTools(mcpServer) return tools.filter((tool: any) => !mcpServer.disabledTools?.includes(tool.name)) } catch (error) { - console.error(`Error fetching tools from MCP server ${mcpServer.name}:`, error) + logger.error(`Error fetching tools from MCP server ${mcpServer.name}:`, error) return [] } }) @@ -332,14 +334,14 @@ async function fetchExternalTool( .map((result) => result.value) .flat() } catch (toolError) { - console.error('Error fetching MCP tools:', toolError) + logger.error('Error fetching MCP tools:', toolError) } } return { mcpTools } } catch (error) { if (isAbortError(error)) throw error - console.error('Tool execution failed:', error) + logger.error('Tool execution failed:', error) // 发送错误状态 const wasAnyToolEnabled = shouldWebSearch || shouldKnowledgeSearch || shouldSearchMemory @@ -379,7 +381,7 @@ export async function fetchChatCompletion({ const lastUserMessage = findLast(messages, (m) => m.role === 'user') const lastAnswer = findLast(messages, (m) => m.role === 'assistant') if (!lastUserMessage) { - console.error('fetchChatCompletion returning early: Missing lastUserMessage or lastAnswer') + logger.error('fetchChatCompletion returning early: Missing lastUserMessage or lastAnswer') return } // try { @@ -457,14 +459,14 @@ async function processConversationMemory(messages: Message[], assistant: Assista getFirstEmbeddingModel() if (!embedderModel) { - console.warn( + logger.warn( 'Memory processing skipped: no embedding model available. Please configure an embedding model in memory settings.' ) return } if (!llmModel) { - console.warn('Memory processing skipped: LLM model not available') + logger.warn('Memory processing skipped: LLM model not available') return } @@ -525,10 +527,10 @@ async function processConversationMemory(messages: Message[], assistant: Assista } }) .catch((error) => { - console.error('Background memory processing failed:', error) + logger.error('Background memory processing failed:', error) }) } catch (error) { - console.error('Error in post-conversation memory processing:', error) + logger.error('Error in post-conversation memory processing:', error) } } diff --git a/src/renderer/src/services/BackupService.ts b/src/renderer/src/services/BackupService.ts index a165e9f27a..7f66ea378c 100644 --- a/src/renderer/src/services/BackupService.ts +++ b/src/renderer/src/services/BackupService.ts @@ -1,4 +1,4 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import db from '@renderer/databases' import { upgradeToV7, upgradeToV8 } from '@renderer/databases/upgrades' import i18n from '@renderer/i18n' @@ -10,6 +10,8 @@ import dayjs from 'dayjs' import { NotificationService } from './NotificationService' +const logger = loggerService.withContext('BackupService') + // 重试删除S3文件的辅助函数 async function deleteS3FileWithRetry(fileName: string, s3Config: S3Config, maxRetries = 3) { let lastError: Error | null = null @@ -17,11 +19,11 @@ async function deleteS3FileWithRetry(fileName: string, s3Config: S3Config, maxRe for (let attempt = 1; attempt <= maxRetries; attempt++) { try { await window.api.backup.deleteS3File(fileName, s3Config) - Logger.log(`[Backup] Successfully deleted old backup file: ${fileName} (attempt ${attempt})`) + logger.verbose(`Successfully deleted old backup file: ${fileName} (attempt ${attempt})`) return true } catch (error: any) { lastError = error - Logger.warn(`[Backup] Delete attempt ${attempt}/${maxRetries} failed for ${fileName}:`, error.message) + logger.warn(`Delete attempt ${attempt}/${maxRetries} failed for ${fileName}:`, error.message) // 如果不是最后一次尝试,等待一段时间再重试 if (attempt < maxRetries) { @@ -31,7 +33,7 @@ async function deleteS3FileWithRetry(fileName: string, s3Config: S3Config, maxRe } } - Logger.error(`[Backup] Failed to delete old backup file after ${maxRetries} attempts: ${fileName}`, lastError) + logger.error(`Failed to delete old backup file after ${maxRetries} attempts: ${fileName}`, lastError) return false } @@ -42,11 +44,11 @@ async function deleteWebdavFileWithRetry(fileName: string, webdavConfig: WebDavC for (let attempt = 1; attempt <= maxRetries; attempt++) { try { await window.api.backup.deleteWebdavFile(fileName, webdavConfig) - Logger.log(`[Backup] Successfully deleted old backup file: ${fileName} (attempt ${attempt})`) + logger.verbose(`Successfully deleted old backup file: ${fileName} (attempt ${attempt})`) return true } catch (error: any) { lastError = error - Logger.warn(`[Backup] Delete attempt ${attempt}/${maxRetries} failed for ${fileName}:`, error.message) + logger.warn(`Delete attempt ${attempt}/${maxRetries} failed for ${fileName}:`, error.message) // 如果不是最后一次尝试,等待一段时间再重试 if (attempt < maxRetries) { @@ -56,7 +58,7 @@ async function deleteWebdavFileWithRetry(fileName: string, webdavConfig: WebDavC } } - Logger.error(`[Backup] Failed to delete old backup file after ${maxRetries} attempts: ${fileName}`, lastError) + logger.error(`Failed to delete old backup file after ${maxRetries} attempts: ${fileName}`, lastError) return false } @@ -98,7 +100,7 @@ export async function restore() { channel: 'system' }) } catch (error) { - Logger.error('[Backup] restore: Error restoring backup file:', error) + logger.error('restore: Error restoring backup file:', error) window.message.error({ content: i18n.t('error.backup.file_format'), key: 'restore' }) } } @@ -142,7 +144,7 @@ export async function backupToWebdav({ }: { showMessage?: boolean; customFileName?: string; autoBackupProcess?: boolean } = {}) { const notificationService = NotificationService.getInstance() if (isManualBackupRunning) { - Logger.log('[Backup] Manual backup already in progress') + logger.verbose('Manual backup already in progress') return } // force set showMessage to false when auto backup process @@ -169,7 +171,7 @@ export async function backupToWebdav({ deviceType = (await window.api.system.getDeviceType()) || 'unknown' hostname = (await window.api.system.getHostname()) || 'unknown' } catch (error) { - Logger.error('[Backup] Failed to get device type or hostname:', error) + logger.error('Failed to get device type or hostname:', error) } const timestamp = dayjs().format('YYYYMMDDHHmmss') const backupFileName = customFileName || `cherry-studio.${timestamp}.${hostname}.${deviceType}.zip` @@ -227,7 +229,7 @@ export async function backupToWebdav({ // 文件已按修改时间降序排序,所以最旧的文件在末尾 const filesToDelete = currentDeviceFiles.slice(webdavMaxBackups) - Logger.log(`[Backup] Cleaning up ${filesToDelete.length} old backup files`) + logger.verbose(`Cleaning up ${filesToDelete.length} old backup files`) // 串行删除文件,避免并发请求导致的问题 for (let i = 0; i < filesToDelete.length; i++) { @@ -246,7 +248,7 @@ export async function backupToWebdav({ } } } catch (error) { - Logger.error('[Backup] Failed to clean up old backup files:', error) + logger.error('Failed to clean up old backup files:', error) } } } else { @@ -275,7 +277,7 @@ export async function backupToWebdav({ }) store.dispatch(setWebDAVSyncState({ lastSyncError: error.message })) showMessage && window.message.error({ content: i18n.t('message.backup.failed'), key: 'backup' }) - console.error('[Backup] backupToWebdav: Error uploading file to WebDAV:', error) + logger.error('[Backup] backupToWebdav: Error uploading file to WebDAV:', error) throw error } finally { if (!autoBackupProcess) { @@ -298,7 +300,7 @@ export async function restoreFromWebdav(fileName?: string) { try { data = await window.api.backup.restoreFromWebdav({ webdavHost, webdavUser, webdavPass, webdavPath, fileName }) } catch (error: any) { - console.error('[Backup] restoreFromWebdav: Error downloading file from WebDAV:', error) + logger.error('[Backup] restoreFromWebdav: Error downloading file from WebDAV:', error) window.modal.error({ title: i18n.t('message.restore.failed'), content: error.message @@ -308,7 +310,7 @@ export async function restoreFromWebdav(fileName?: string) { try { await handleData(JSON.parse(data)) } catch (error) { - console.error('[Backup] Error downloading file from WebDAV:', error) + logger.error('[Backup] Error downloading file from WebDAV:', error) window.message.error({ content: i18n.t('error.backup.file_format'), key: 'restore' }) } } @@ -320,7 +322,7 @@ export async function backupToS3({ }: { showMessage?: boolean; customFileName?: string; autoBackupProcess?: boolean } = {}) { const notificationService = NotificationService.getInstance() if (isManualBackupRunning) { - Logger.log('[Backup] Manual backup already in progress') + logger.verbose('Manual backup already in progress') return } @@ -339,7 +341,7 @@ export async function backupToS3({ deviceType = (await window.api.system.getDeviceType()) || 'unknown' hostname = (await window.api.system.getHostname()) || 'unknown' } catch (error) { - Logger.error('[Backup] Failed to get device type or hostname:', error) + logger.error('Failed to get device type or hostname:', error) } const timestamp = dayjs().format('YYYYMMDDHHmmss') const backupFileName = customFileName || `cherry-studio.${timestamp}.${hostname}.${deviceType}.zip` @@ -387,7 +389,7 @@ export async function backupToS3({ if (currentDeviceFiles.length > s3Config.maxBackups) { const filesToDelete = currentDeviceFiles.slice(s3Config.maxBackups) - Logger.log(`[Backup] Cleaning up ${filesToDelete.length} old backup files`) + logger.verbose(`Cleaning up ${filesToDelete.length} old backup files`) for (let i = 0; i < filesToDelete.length; i++) { const file = filesToDelete[i] @@ -399,7 +401,7 @@ export async function backupToS3({ } } } catch (error) { - Logger.error('[Backup] Failed to clean up old backup files:', error) + logger.error('Failed to clean up old backup files:', error) } } } else { @@ -425,7 +427,7 @@ export async function backupToS3({ channel: 'system' }) store.dispatch(setS3SyncState({ lastSyncError: error.message })) - console.error('[Backup] backupToS3: Error uploading file to S3:', error) + logger.error('backupToS3: Error uploading file to S3:', error) showMessage && window.message.error({ content: i18n.t('message.backup.failed'), key: 'backup' }) throw error } finally { @@ -508,7 +510,7 @@ export function startAutoSync(immediate = false, type?: BackupType) { const { webdavAutoSync, webdavHost } = settings if (!webdavAutoSync || !webdavHost) { - Logger.log('[WebdavAutoSync] Invalid sync settings, auto sync disabled') + logger.info('[WebdavAutoSync] Invalid sync settings, auto sync disabled') return } @@ -524,7 +526,7 @@ export function startAutoSync(immediate = false, type?: BackupType) { const s3Settings = settings.s3 if (!s3Settings?.autoSync || !s3Settings?.endpoint) { - Logger.log('[S3AutoSync] Invalid sync settings, auto sync disabled') + logger.verbose('Invalid sync settings, auto sync disabled') return } @@ -540,7 +542,7 @@ export function startAutoSync(immediate = false, type?: BackupType) { const { localBackupAutoSync, localBackupDir } = settings if (!localBackupAutoSync || !localBackupDir) { - Logger.log('[LocalAutoSync] Invalid sync settings, auto sync disabled') + logger.verbose('Invalid sync settings, auto sync disabled') return } @@ -587,7 +589,7 @@ export function startAutoSync(immediate = false, type?: BackupType) { } if (!syncInterval || syncInterval <= 0) { - Logger.log(`${logPrefix} Invalid sync interval, auto sync disabled`) + logger.verbose(`${logPrefix} Invalid sync interval, auto sync disabled`) stopAutoSync(backupType) return } @@ -615,7 +617,7 @@ export function startAutoSync(immediate = false, type?: BackupType) { localSyncTimeout = timeout } - Logger.log( + logger.verbose( `${logPrefix} Next sync scheduled in ${Math.floor(timeUntilNextSync / 1000 / 60)} minutes ${Math.floor( (timeUntilNextSync / 1000) % 60 )} seconds` @@ -640,7 +642,7 @@ export function startAutoSync(immediate = false, type?: BackupType) { } if (isRunning || isManualBackupRunning) { - Logger.log(`${logPrefix} Backup already in progress, rescheduling`) + logger.verbose(`${logPrefix} Backup already in progress, rescheduling`) scheduleNextBackup('fromNow', backupType) return } @@ -659,7 +661,7 @@ export function startAutoSync(immediate = false, type?: BackupType) { while (retryCount < maxRetries) { try { - Logger.log(`${logPrefix} Starting auto backup... (attempt ${retryCount + 1}/${maxRetries})`) + logger.verbose(`${logPrefix} Starting auto backup... (attempt ${retryCount + 1}/${maxRetries})`) if (backupType === 'webdav') { await backupToWebdav({ autoBackupProcess: true }) @@ -704,7 +706,7 @@ export function startAutoSync(immediate = false, type?: BackupType) { } catch (error: any) { retryCount++ if (retryCount === maxRetries) { - Logger.error(`${logPrefix} Auto backup failed after all retries:`, error) + logger.error(`${logPrefix} Auto backup failed after all retries:`, error) if (backupType === 'webdav') { store.dispatch( @@ -749,7 +751,7 @@ export function startAutoSync(immediate = false, type?: BackupType) { } } else { const backoffDelay = Math.pow(2, retryCount - 1) * 10000 - 3000 - Logger.log(`${logPrefix} Failed, retry ${retryCount}/${maxRetries} after ${backoffDelay / 1000}s`) + logger.warn(`${logPrefix} Failed, retry ${retryCount}/${maxRetries} after ${backoffDelay / 1000}s`) await new Promise((resolve) => setTimeout(resolve, backoffDelay)) @@ -764,7 +766,7 @@ export function startAutoSync(immediate = false, type?: BackupType) { } if (!currentRunning) { - Logger.log(`${logPrefix} retry cancelled by user, exit`) + logger.info(`${logPrefix} retry cancelled by user, exit`) break } } @@ -784,7 +786,7 @@ export function stopAutoSync(type?: BackupType) { if (type === 'webdav') { if (webdavSyncTimeout) { - Logger.log('[WebdavAutoSync] Stopping auto sync') + logger.info('[WebdavAutoSync] Stopping auto sync') clearTimeout(webdavSyncTimeout) webdavSyncTimeout = null } @@ -792,7 +794,7 @@ export function stopAutoSync(type?: BackupType) { webdavAutoSyncStarted = false } else if (type === 's3') { if (s3SyncTimeout) { - Logger.log('[S3AutoSync] Stopping auto sync') + logger.info('[S3AutoSync] Stopping auto sync') clearTimeout(s3SyncTimeout) s3SyncTimeout = null } @@ -800,7 +802,7 @@ export function stopAutoSync(type?: BackupType) { s3AutoSyncStarted = false } else if (type === 'local') { if (localSyncTimeout) { - Logger.log('[LocalAutoSync] Stopping auto sync') + logger.info('[LocalAutoSync] Stopping auto sync') clearTimeout(localSyncTimeout) localSyncTimeout = null } @@ -903,7 +905,7 @@ export async function backupToLocal({ }: { showMessage?: boolean; customFileName?: string; autoBackupProcess?: boolean } = {}) { const notificationService = NotificationService.getInstance() if (isManualBackupRunning) { - Logger.log('[Backup] Manual backup already in progress') + logger.verbose('Manual backup already in progress') return } // force set showMessage to false when auto backup process @@ -922,7 +924,7 @@ export async function backupToLocal({ deviceType = (await window.api.system.getDeviceType()) || 'unknown' hostname = (await window.api.system.getHostname()) || 'unknown' } catch (error) { - Logger.error('[Backup] Failed to get device type or hostname:', error) + logger.error('Failed to get device type or hostname:', error) } const timestamp = dayjs().format('YYYYMMDDHHmmss') const backupFileName = customFileName || `cherry-studio.${timestamp}.${hostname}.${deviceType}.zip` @@ -974,12 +976,12 @@ export async function backupToLocal({ // Delete older backups for (const file of filesToDelete) { - Logger.log(`[LocalBackup] Deleting old backup: ${file.fileName}`) + logger.verbose(`[LocalBackup] Deleting old backup: ${file.fileName}`) await window.api.backup.deleteLocalBackupFile(file.fileName, localBackupDir) } } } catch (error) { - Logger.error('[LocalBackup] Failed to clean up old backups:', error) + logger.error('[LocalBackup] Failed to clean up old backups:', error) } } } else { @@ -1007,7 +1009,7 @@ export async function backupToLocal({ throw error } - Logger.error('[LocalBackup] Backup failed:', error) + logger.error('[LocalBackup] Backup failed:', error) store.dispatch( setLocalBackupSyncState({ @@ -1046,7 +1048,7 @@ export async function restoreFromLocal(fileName: string) { return true } catch (error) { - Logger.error('[LocalBackup] Restore failed:', error) + logger.error('[LocalBackup] Restore failed:', error) window.message.error({ content: i18n.t('error.backup.file_format'), key: 'restore' }) throw error } diff --git a/src/renderer/src/services/FileAction.ts b/src/renderer/src/services/FileAction.ts index 679c2741d1..d9f14a563f 100644 --- a/src/renderer/src/services/FileAction.ts +++ b/src/renderer/src/services/FileAction.ts @@ -1,5 +1,5 @@ +import { loggerService } from '@logger' import TextEditPopup from '@renderer/components/Popups/TextEditPopup' -import Logger from '@renderer/config/logger' import db from '@renderer/databases' import FileManager from '@renderer/services/FileManager' import store from '@renderer/store' @@ -11,6 +11,8 @@ import dayjs from 'dayjs' export type SortField = 'created_at' | 'size' | 'name' export type SortOrder = 'asc' | 'desc' +const logger = loggerService.withContext('FileAction') + export function tempFilesSort(files: FileType[]): FileType[] { return files.sort((a, b) => { const aIsTemp = a.origin_name.startsWith('temp_file') @@ -80,9 +82,9 @@ export async function handleDelete(fileId: string, t: (key: string) => string) { await Promise.all(Object.entries(topicsToUpdate).map(([id, data]) => db.topics.update(id, data))) await db.message_blocks.bulkDelete(blockIdsToDelete) }) - Logger.log(`Deleted ${blockIdsToDelete.length} blocks for file ${fileId}`) + logger.info(`Deleted ${blockIdsToDelete.length} blocks for file ${fileId}`) } catch (err) { - Logger.error(`Error removing file blocks for ${fileId}:`, err) + logger.error(`Error removing file blocks for ${fileId}:`, err) window.modal.error({ content: t('files.delete.db_error'), centered: true }) } } diff --git a/src/renderer/src/services/FileManager.ts b/src/renderer/src/services/FileManager.ts index b61f9e4e10..75b59f3bc5 100644 --- a/src/renderer/src/services/FileManager.ts +++ b/src/renderer/src/services/FileManager.ts @@ -1,4 +1,4 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import db from '@renderer/databases' import i18n from '@renderer/i18n' import store from '@renderer/store' @@ -6,6 +6,8 @@ import { FileMetadata } from '@renderer/types' import { getFileDirectory } from '@renderer/utils' import dayjs from 'dayjs' +const logger = loggerService.withContext('FileManager') + class FileManager { static async selectFiles(options?: Electron.OpenDialogOptions): Promise { const files = await window.api.file.select(options) @@ -40,7 +42,7 @@ class FileManager { } static async addBase64File(file: FileMetadata): Promise { - Logger.log(`[FileManager] Adding base64 file: ${JSON.stringify(file)}`) + logger.info(`Adding base64 file: ${JSON.stringify(file)}`) const base64File = await window.api.file.base64File(file.id + file.ext) const fileRecord = await db.files.get(base64File.id) @@ -56,10 +58,10 @@ class FileManager { } static async uploadFile(file: FileMetadata): Promise { - Logger.log(`[FileManager] Uploading file: ${JSON.stringify(file)}`) + logger.info(`Uploading file: ${JSON.stringify(file)}`) const uploadFile = await window.api.file.upload(file) - console.log('[FileManager] Uploaded file:', uploadFile) + logger.info('Uploaded file:', uploadFile) const fileRecord = await db.files.get(uploadFile.id) if (fileRecord) { @@ -95,7 +97,7 @@ class FileManager { static async deleteFile(id: string, force: boolean = false): Promise { const file = await this.getFile(id) - Logger.log('[FileManager] Deleting file:', file) + logger.info('Deleting file:', file) if (!file) { return @@ -113,7 +115,7 @@ class FileManager { try { await window.api.file.delete(id + file.ext) } catch (error) { - Logger.error('[FileManager] Failed to delete file:', error) + logger.error('Failed to delete file:', error) } } diff --git a/src/renderer/src/services/HealthCheckService.ts b/src/renderer/src/services/HealthCheckService.ts index 598074b87e..c350e6de73 100644 --- a/src/renderer/src/services/HealthCheckService.ts +++ b/src/renderer/src/services/HealthCheckService.ts @@ -1,8 +1,11 @@ +import { loggerService } from '@logger' import i18n from '@renderer/i18n' import { Model, Provider } from '@renderer/types' import { checkModel } from './ModelService' +const logger = loggerService.withContext('HealthCheckService') + /** * Model check status states */ @@ -224,7 +227,7 @@ export async function checkModelsHealth( } } } catch (error) { - console.error('Model health check failed:', error) + logger.error('Model health check failed:', error) } return results diff --git a/src/renderer/src/services/ImageStorage.ts b/src/renderer/src/services/ImageStorage.ts index b976c6b431..457970d70f 100644 --- a/src/renderer/src/services/ImageStorage.ts +++ b/src/renderer/src/services/ImageStorage.ts @@ -1,6 +1,9 @@ +import { loggerService } from '@logger' import db from '@renderer/databases' import { convertToBase64 } from '@renderer/utils' +const logger = loggerService.withContext('ImageStorage') + const IMAGE_PREFIX = 'image://' export default class ImageStorage { @@ -26,7 +29,7 @@ export default class ImageStorage { } } } catch (error) { - console.error('Error storing the image', error) + logger.error('Error storing the image', error) } } @@ -43,7 +46,7 @@ export default class ImageStorage { await db.settings.delete(id) } } catch (error) { - console.error('Error removing the image', error) + logger.error('Error removing the image', error) throw error } } diff --git a/src/renderer/src/services/KnowledgeService.ts b/src/renderer/src/services/KnowledgeService.ts index d87a45e646..930422e116 100644 --- a/src/renderer/src/services/KnowledgeService.ts +++ b/src/renderer/src/services/KnowledgeService.ts @@ -1,8 +1,8 @@ import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' +import { loggerService } from '@logger' import AiProvider from '@renderer/aiCore' import { DEFAULT_KNOWLEDGE_DOCUMENT_COUNT, DEFAULT_KNOWLEDGE_THRESHOLD } from '@renderer/config/constant' import { getEmbeddingMaxContext } from '@renderer/config/embedings' -import Logger from '@renderer/config/logger' import store from '@renderer/store' import { FileMetadata, KnowledgeBase, KnowledgeBaseParams, KnowledgeReference } from '@renderer/types' import { ExtractResults } from '@renderer/utils/extract' @@ -11,6 +11,8 @@ import { isEmpty } from 'lodash' import { getProviderByModel } from './AssistantService' import FileManager from './FileManager' +const logger = loggerService.withContext('KnowledgeService') + export const getKnowledgeBaseParams = (base: KnowledgeBase): KnowledgeBaseParams => { const provider = getProviderByModel(base.model) const rerankProvider = getProviderByModel(base.rerankModel) @@ -138,7 +140,7 @@ export const searchKnowledgeBase = async ( }) ) } catch (error) { - Logger.error(`Error searching knowledge base ${base.name}:`, error) + logger.error(`Error searching knowledge base ${base.name}:`, error) throw error } } @@ -152,7 +154,7 @@ export const processKnowledgeSearch = async ( extractResults.knowledge.question.length === 0 || isEmpty(knowledgeBaseIds) ) { - Logger.log('No valid question found in extractResults.knowledge') + logger.info('No valid question found in extractResults.knowledge') return [] } @@ -161,7 +163,7 @@ export const processKnowledgeSearch = async ( const bases = store.getState().knowledge.bases.filter((kb) => knowledgeBaseIds?.includes(kb.id)) if (!bases || bases.length === 0) { - Logger.log('Skipping knowledge search: No matching knowledge bases found.') + logger.info('Skipping knowledge search: No matching knowledge bases found.') return [] } diff --git a/src/renderer/src/services/LoggerService.ts b/src/renderer/src/services/LoggerService.ts new file mode 100644 index 0000000000..de5c2b4121 --- /dev/null +++ b/src/renderer/src/services/LoggerService.ts @@ -0,0 +1,173 @@ +import { isDev } from '@renderer/utils/env' +import type { LogLevel, LogSourceWithContext } from '@shared/config/types' + +const IS_DEV = await getIsDev() +async function getIsDev() { + return await isDev() +} + +// the level number is different from real definition, it only for convenience +const LEVEL_MAP: Record = { + error: 5, + warn: 4, + info: 3, + verbose: 2, + debug: 1, + silly: 0 +} + +const DEFAULT_LEVEL = IS_DEV ? 'silly' : 'info' +const MAIN_LOG_LEVEL = 'warn' + +/** + * IMPORTANT: How to use LoggerService + * please refer to + * English: `docs/technical/how-to-use-logger-en.md` + * Chinese: `docs/technical/how-to-use-logger-zh.md` + */ +export class LoggerService { + private static instance: LoggerService + + private level: LogLevel = DEFAULT_LEVEL + private logToMainLevel: LogLevel = MAIN_LOG_LEVEL + + private window: string = '' + private module: string = '' + private context: Record = {} + + private constructor() { + // + } + + public static getInstance(): LoggerService { + if (!LoggerService.instance) { + LoggerService.instance = new LoggerService() + } + return LoggerService.instance + } + + // init window source for renderer process + // can only be called once + public initWindowSource(window: string): boolean { + if (this.window) { + return false + } + this.window = window + return true + } + + // create a new logger with a new context + public withContext(module: string, context?: Record): LoggerService { + const newLogger = Object.create(this) + + // Copy all properties from the base logger + newLogger.module = module + newLogger.context = { ...this.context, ...context } + + return newLogger + } + + private processLog(level: LogLevel, message: string, data: any[]): void { + if (!this.window) { + console.error('LoggerService: window source not initialized, please initialize window source first') + return + } + + // skip log if level is lower than default level + const levelNumber = LEVEL_MAP[level] + if (levelNumber < LEVEL_MAP[this.level]) { + return + } + + const logMessage = this.module ? `[${this.module}] ${message}` : message + + switch (level) { + case 'error': + console.error(logMessage, ...data) + break + case 'warn': + console.warn(logMessage, ...data) + break + case 'info': + console.info(logMessage, ...data) + break + case 'verbose': + console.log(logMessage, ...data) + break + case 'debug': + console.debug(logMessage, ...data) + break + case 'silly': + console.log(logMessage, ...data) + break + } + + // if the last data is an object with logToMain: true, force log to main + const forceLogToMain = data.length > 0 && data[data.length - 1]?.logToMain === true + + if (levelNumber >= LEVEL_MAP[this.logToMainLevel] || forceLogToMain) { + const source: LogSourceWithContext = { + process: 'renderer', + window: this.window, + module: this.module + } + + if (Object.keys(this.context).length > 0) { + source.context = this.context + } + + // remove the last item if it is an object with logToMain: true + if (forceLogToMain) { + data = data.slice(0, -1) + } + + window.api.logToMain(source, level, message, data) + } + } + + public error(message: string, ...data: any[]): void { + this.processLog('error', message, data) + } + public warn(message: string, ...data: any[]): void { + this.processLog('warn', message, data) + } + public info(message: string, ...data: any[]): void { + this.processLog('info', message, data) + } + public verbose(message: string, ...data: any[]): void { + this.processLog('verbose', message, data) + } + public debug(message: string, ...data: any[]): void { + this.processLog('debug', message, data) + } + public silly(message: string, ...data: any[]): void { + this.processLog('silly', message, data) + } + + public setLevel(level: LogLevel): void { + this.level = level + } + + public getLevel(): string { + return this.level + } + + // Method to reset log level to environment default + public resetLevel(): void { + this.setLevel(DEFAULT_LEVEL) + } + + public setLogToMainLevel(level: LogLevel): void { + this.logToMainLevel = level + } + + public getLogToMainLevel(): LogLevel { + return this.logToMainLevel + } + + public resetLogToMainLevel(): void { + this.setLogToMainLevel(MAIN_LOG_LEVEL) + } +} + +export const loggerService = LoggerService.getInstance() diff --git a/src/renderer/src/services/MemoryProcessor.ts b/src/renderer/src/services/MemoryProcessor.ts index 11a5a9c528..bbceae0b81 100644 --- a/src/renderer/src/services/MemoryProcessor.ts +++ b/src/renderer/src/services/MemoryProcessor.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { getModel } from '@renderer/hooks/useModel' import { AssistantMessage } from '@renderer/types' import { @@ -13,6 +14,8 @@ import jaison from 'jaison/lib/index.js' import { fetchGenerate } from './ApiService' import MemoryService from './MemoryService' +const logger = loggerService.withContext('MemoryProcessor') + export interface MemoryProcessorConfig { memoryConfig: MemoryConfig assistantId?: string @@ -58,7 +61,7 @@ export class MemoryProcessor { // Parse response using Zod schema try { - console.log('Response content for extraction:', responseContent) + logger.debug('Response content for extraction:', responseContent) const jsonParsed = jaison(responseContent) // Handle both expected format and potential variations let dataToValidate = jsonParsed @@ -72,11 +75,11 @@ export class MemoryProcessor { const parsed = FactRetrievalSchema.parse(dataToValidate) return parsed.facts } catch (error) { - console.error('Failed to parse fact extraction response:', error, 'responseContent: ', responseContent) + logger.error('Failed to parse fact extraction response:', error, 'responseContent: ', responseContent) return [] } } catch (error) { - console.error('Error extracting facts:', error) + logger.error('Error extracting facts:', error) return [] } } @@ -128,13 +131,13 @@ export class MemoryProcessor { } try { - console.log('Response content for memory update:', responseContent) + logger.debug('Response content for memory update:', responseContent) const jsonParsed = jaison(responseContent) // Handle both direct array and wrapped object format const dataToValidate = Array.isArray(jsonParsed) ? jsonParsed : jsonParsed.memory parsed = MemoryUpdateSchema.parse(dataToValidate) } catch (error) { - console.error('Failed to parse memory update response:', error, 'responseContent: ', responseContent) + logger.error('Failed to parse memory update response:', error, 'responseContent: ', responseContent) return [] } } @@ -149,7 +152,7 @@ export class MemoryProcessor { }) operations.push({ action: 'ADD', memory: memoryOp.text, result }) } catch (error) { - console.error('Failed to add memory:', error) + logger.error('Failed to add memory:', error) } break @@ -171,7 +174,7 @@ export class MemoryProcessor { }) } } catch (error) { - console.error('Failed to update memory:', error) + logger.error('Failed to update memory:', error) } break @@ -180,7 +183,7 @@ export class MemoryProcessor { await this.memoryService.delete(memoryOp.id) operations.push({ action: 'DELETE', id: memoryOp.id, memory: memoryOp.text }) } catch (error) { - console.error('Failed to delete memory:', error) + logger.error('Failed to delete memory:', error) } break @@ -213,7 +216,7 @@ export class MemoryProcessor { return { facts, operations } } catch (error) { - console.error('Error processing conversation:', error) + logger.error('Error processing conversation:', error) return { facts: [], operations: [] } } } @@ -247,7 +250,7 @@ export class MemoryProcessor { ) return result.results } catch (error) { - console.error('Error searching memories:', error) + logger.error('Error searching memories:', error) return [] } } diff --git a/src/renderer/src/services/MemoryService.ts b/src/renderer/src/services/MemoryService.ts index 54b1f0e4a1..563992144f 100644 --- a/src/renderer/src/services/MemoryService.ts +++ b/src/renderer/src/services/MemoryService.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import store from '@renderer/store' import { selectMemoryConfig } from '@renderer/store/memory' import { @@ -9,6 +10,8 @@ import { MemorySearchResult } from '@types' +const logger = loggerService.withContext('MemoryService') + // Main process SearchResult type (matches what the IPC actually returns) interface SearchResult { memories: any[] @@ -39,7 +42,7 @@ class MemoryService { if (!MemoryService.instance) { MemoryService.instance = new MemoryService() MemoryService.instance.updateConfig().catch((error) => { - console.error('Failed to initialize MemoryService:', error) + logger.error('Failed to initialize MemoryService:', error) }) } return MemoryService.instance @@ -81,7 +84,7 @@ class MemoryService { // Handle error responses from main process if (result.error) { - console.error('Memory service error:', result.error) + logger.error('Memory service error:', result.error) throw new Error(result.error) } @@ -91,7 +94,7 @@ class MemoryService { relations: [] } } catch (error) { - console.error('Failed to list memories:', error) + logger.error('Failed to list memories:', error) // Return empty result on error to prevent UI crashes return { results: [], @@ -195,7 +198,7 @@ class MemoryService { public async updateConfig(): Promise { try { if (!store || !store.getState) { - console.warn('Store not available, skipping memory config update') + logger.warn('Store not available, skipping memory config update') return } @@ -211,7 +214,7 @@ class MemoryService { return window.api.memory.setConfig(configWithProviders) } catch (error) { - console.warn('Failed to update memory config:', error) + logger.warn('Failed to update memory config:', error) return } } diff --git a/src/renderer/src/services/MessagesService.ts b/src/renderer/src/services/MessagesService.ts index 25b822cd21..b60dde49c8 100644 --- a/src/renderer/src/services/MessagesService.ts +++ b/src/renderer/src/services/MessagesService.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import SearchPopup from '@renderer/components/Popups/SearchPopup' import { DEFAULT_CONTEXTCOUNT, MAX_CONTEXT_COUNT, UNLIMITED_CONTEXT_COUNT } from '@renderer/config/constant' import { getTopicById } from '@renderer/hooks/useTopic' @@ -30,6 +31,8 @@ import { getAssistantById, getAssistantProvider, getDefaultModel } from './Assis import { EVENT_NAMES, EventEmitter } from './EventService' import FileManager from './FileManager' +const logger = loggerService.withContext('MessagesService') + export { filterContextMessages, filterEmptyMessages, @@ -232,7 +235,7 @@ export async function getMessageTitle(message: Message, length = 30): Promise { const result = await window.api.nutstore.decryptToken(nutstoreToken) if (!result) { - Logger.log('[createNutstoreConfig] Invalid nutstore token') + logger.warn('Invalid nutstore token') return null } @@ -74,7 +76,7 @@ export async function backupToNutstore({ } if (isManualBackupRunning) { - Logger.log('[backupToNutstore] Backup already in progress') + logger.verbose('[backupToNutstore] Backup already in progress') return } @@ -87,7 +89,7 @@ export async function backupToNutstore({ try { deviceType = (await window.api.system.getDeviceType()) || 'unknown' } catch (error) { - Logger.error('[backupToNutstore] Failed to get device type:', error) + logger.error('[backupToNutstore] Failed to get device type:', error) } const timestamp = dayjs().format('YYYYMMDDHHmmss') const backupFileName = customFileName || `cherry-studio.${timestamp}.${deviceType}.zip` @@ -119,7 +121,7 @@ export async function backupToNutstore({ } } catch (error) { store.dispatch(setNutstoreSyncState({ lastSyncError: 'Backup failed' })) - console.error('[Nutstore] Backup failed:', error) + logger.error('[Nutstore] Backup failed:', error) window.message.error({ content: i18n.t('message.backup.failed'), key: 'backup' }) } finally { store.dispatch(setNutstoreSyncState({ lastSyncTime: Date.now(), syncing: false })) @@ -143,7 +145,7 @@ export async function restoreFromNutstore(fileName?: string) { try { data = await window.api.backup.restoreFromWebdav({ ...config, fileName }) } catch (error: any) { - console.error('[backup] restoreFromWebdav: Error downloading file from WebDAV:', error) + logger.error('[backup] restoreFromWebdav: Error downloading file from WebDAV:', error) window.modal.error({ title: i18n.t('message.restore.failed'), content: error.message @@ -153,7 +155,7 @@ export async function restoreFromNutstore(fileName?: string) { try { await handleData(JSON.parse(data)) } catch (error) { - console.error('[backup] Error downloading file from WebDAV:', error) + logger.error('[backup] Error downloading file from WebDAV:', error) window.message.error({ content: i18n.t('error.backup.file_format'), key: 'restore' }) } } @@ -166,7 +168,7 @@ export async function startNutstoreAutoSync() { const nutstoreToken = getNutstoreToken() if (!nutstoreToken) { - Logger.log('[startNutstoreAutoSync] Invalid nutstore token, nutstore auto sync disabled') + logger.warn('[startNutstoreAutoSync] Invalid nutstore token, nutstore auto sync disabled') return } @@ -185,7 +187,7 @@ export async function startNutstoreAutoSync() { const { nutstoreSyncInterval, nutstoreSyncState } = store.getState().nutstore if (nutstoreSyncInterval <= 0) { - Logger.log('[Nutstore AutoSync] Invalid sync interval, nutstore auto sync disabled') + logger.warn('[Nutstore AutoSync] Invalid sync interval, nutstore auto sync disabled') stopNutstoreAutoSync() return } @@ -200,7 +202,7 @@ export async function startNutstoreAutoSync() { syncTimeout = setTimeout(performAutoBackup, timeUntilNextSync) - Logger.log( + logger.verbose( `[Nutstore AutoSync] Next sync scheduled in ${Math.floor(timeUntilNextSync / 1000 / 60)} minutes ${Math.floor( (timeUntilNextSync / 1000) % 60 )} seconds` @@ -209,17 +211,17 @@ export async function startNutstoreAutoSync() { async function performAutoBackup() { if (isAutoBackupRunning || isManualBackupRunning) { - Logger.log('[Nutstore AutoSync] Backup already in progress, rescheduling') + logger.verbose('[Nutstore AutoSync] Backup already in progress, rescheduling') scheduleNextBackup() return } isAutoBackupRunning = true try { - Logger.log('[Nutstore AutoSync] Starting auto backup...') + logger.verbose('[Nutstore AutoSync] Starting auto backup...') await backupToNutstore({ showMessage: false }) } catch (error) { - Logger.error('[Nutstore AutoSync] Auto backup failed:', error) + logger.error('[Nutstore AutoSync] Auto backup failed:', error) } finally { isAutoBackupRunning = false scheduleNextBackup() @@ -229,7 +231,7 @@ export async function startNutstoreAutoSync() { export function stopNutstoreAutoSync() { if (syncTimeout) { - Logger.log('[Nutstore AutoSync] Stopping nutstore auto sync') + logger.verbose('[Nutstore AutoSync] Stopping nutstore auto sync') clearTimeout(syncTimeout) syncTimeout = null } diff --git a/src/renderer/src/services/PasteService.ts b/src/renderer/src/services/PasteService.ts index 4cef946229..5de96097f1 100644 --- a/src/renderer/src/services/PasteService.ts +++ b/src/renderer/src/services/PasteService.ts @@ -1,7 +1,9 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import { FileMetadata } from '@renderer/types' import { getFileExtension } from '@renderer/utils' +const logger = loggerService.withContext('PasteService') + // Track last focused component type ComponentType = 'inputbar' | 'messageEditor' | null let lastFocusedComponent: ComponentType = 'inputbar' // Default to inputbar @@ -103,7 +105,7 @@ export const handlePaste = async ( } } } catch (error) { - Logger.error('[PasteService] onPaste:', error) + logger.error('onPaste:', error) if (t) { window.message.error(t('chat.input.file_error')) } @@ -113,7 +115,7 @@ export const handlePaste = async ( // 其他情况默认粘贴 return false } catch (error) { - Logger.error('[PasteService] handlePaste error:', error) + logger.error('handlePaste error:', error) return false } } @@ -145,7 +147,7 @@ export const init = () => { }) isInitialized = true - Logger.info('[PasteService] Global paste handler initialized') + logger.verbose('Global paste handler initialized') } /** diff --git a/src/renderer/src/services/PyodideService.ts b/src/renderer/src/services/PyodideService.ts index 374561ec02..8c6849ae8b 100644 --- a/src/renderer/src/services/PyodideService.ts +++ b/src/renderer/src/services/PyodideService.ts @@ -1,5 +1,8 @@ +import { loggerService } from '@logger' import { uuid } from '@renderer/utils' +const logger = loggerService.withContext('PyodideService') + // 定义结果类型接口 export interface PyodideOutput { result: any @@ -127,7 +130,7 @@ class PyodideService { try { await this.initialize() } catch (error: unknown) { - console.error('Pyodide initialization failed, cannot execute Python code', error) + logger.error('Pyodide initialization failed, cannot execute Python code', error) return `Initialization failed: ${error instanceof Error ? error.message : String(error)}` } diff --git a/src/renderer/src/services/QuickPhraseService.ts b/src/renderer/src/services/QuickPhraseService.ts index 3ec0439053..1c41bd2de4 100644 --- a/src/renderer/src/services/QuickPhraseService.ts +++ b/src/renderer/src/services/QuickPhraseService.ts @@ -1,7 +1,10 @@ +import { loggerService } from '@logger' import db from '@renderer/databases' import { QuickPhrase } from '@renderer/types' import { v4 as uuidv4 } from 'uuid' +const logger = loggerService.withContext('QuickPhraseService') + export class QuickPhraseService { private static _isInitialized: boolean = false @@ -14,7 +17,7 @@ export class QuickPhraseService { await db.open() QuickPhraseService._isInitialized = true } catch (error) { - console.error('Failed to open Dexie database:', error) + logger.error('Failed to open Dexie database:', error) } } diff --git a/src/renderer/src/services/ShikiStreamService.ts b/src/renderer/src/services/ShikiStreamService.ts index 8d5c6f6223..01ab34ae4e 100644 --- a/src/renderer/src/services/ShikiStreamService.ts +++ b/src/renderer/src/services/ShikiStreamService.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { DEFAULT_LANGUAGES, DEFAULT_THEMES, @@ -10,6 +11,8 @@ import type { HighlighterGeneric, ThemedToken } from 'shiki/core' import { ShikiStreamTokenizer, ShikiStreamTokenizerOptions } from './ShikiStreamTokenizer' +const logger = loggerService.withContext('ShikiStreamService') + export type ShikiPreProperties = { class: string style: string @@ -101,7 +104,7 @@ class ShikiStreamService { if (this.worker) return if (this.workerInitRetryCount >= ShikiStreamService.MAX_WORKER_INIT_RETRY) { - console.debug('ShikiStream worker initialization failed too many times, stop trying') + logger.debug('ShikiStream worker initialization failed too many times, stop trying') return } @@ -329,7 +332,7 @@ class ShikiStreamService { return result } catch (error) { // 处理失败时不更新缓存,保持之前的状态 - console.error('Failed to highlight streaming code:', error) + logger.error('Failed to highlight streaming code:', error) throw error } } @@ -361,7 +364,7 @@ class ShikiStreamService { try { await this.initWorker() } catch (error) { - console.warn('Failed to initialize worker, falling back to main thread:', error) + logger.warn('Failed to initialize worker, falling back to main thread:', error) } } @@ -380,7 +383,7 @@ class ShikiStreamService { // Worker 处理失败,记录callerId并永久降级到主线程 // FIXME: 这种情况如果出现,流式高亮语法状态就会丢失,目前用降级策略来处理 this.workerDegradationCache.set(callerId, true) - console.error( + logger.error( `Worker highlight failed for callerId ${callerId}, permanently falling back to main thread:`, error ) @@ -416,7 +419,7 @@ class ShikiStreamService { recall: result.recall } } catch (error) { - console.error('Failed to highlight code chunk:', error) + logger.error('Failed to highlight code chunk:', error) // 提供简单的 fallback const fallbackToken: ThemedToken = { content: chunk || '', color: '#000000', offset: 0 } @@ -474,7 +477,7 @@ class ShikiStreamService { type: 'cleanup', callerId }).catch((error) => { - console.error('Failed to cleanup worker tokenizer:', error) + logger.error('Failed to cleanup worker tokenizer:', error) }) } @@ -499,7 +502,7 @@ class ShikiStreamService { dispose() { if (this.worker) { this.sendWorkerMessage({ type: 'dispose' }).catch((error) => { - console.warn('Failed to dispose worker:', error) + logger.warn('Failed to dispose worker:', error) }) this.worker.terminate() this.worker = null diff --git a/src/renderer/src/services/StoreSyncService.ts b/src/renderer/src/services/StoreSyncService.ts index 37d87b2ea9..cd207aed12 100644 --- a/src/renderer/src/services/StoreSyncService.ts +++ b/src/renderer/src/services/StoreSyncService.ts @@ -1,7 +1,10 @@ +import { loggerService } from '@logger' import { Middleware } from '@reduxjs/toolkit' import { IpcChannel } from '@shared/IpcChannel' import type { StoreSyncAction } from '@types' +const logger = loggerService.withContext('StoreSyncService') + type SyncOptions = { syncList: string[] } @@ -102,7 +105,7 @@ export class StoreSyncService { window.store.dispatch(action) } } catch (error) { - console.error('Error dispatching synced action:', error) + logger.error('Error dispatching synced action:', error) } } ) diff --git a/src/renderer/src/services/StreamProcessingService.ts b/src/renderer/src/services/StreamProcessingService.ts index daae53ec4a..adbea1dfb4 100644 --- a/src/renderer/src/services/StreamProcessingService.ts +++ b/src/renderer/src/services/StreamProcessingService.ts @@ -1,9 +1,12 @@ +import { loggerService } from '@logger' import type { ExternalToolResult, GenerateImageResponse, MCPToolResponse, WebSearchResponse } from '@renderer/types' import type { Chunk } from '@renderer/types/chunk' import { ChunkType } from '@renderer/types/chunk' import type { Response } from '@renderer/types/newMessage' import { AssistantMessageStatus } from '@renderer/types/newMessage' +const logger = loggerService.withContext('StreamProcessingService') + // Define the structure for the callbacks that the StreamProcessor will invoke export interface StreamProcessorCallbacks { // LLM response created @@ -135,11 +138,11 @@ export function createStreamProcessor(callbacks: StreamProcessorCallbacks = {}) } default: { // Handle unknown chunk types or log an error - console.warn(`Unknown chunk type: ${data.type}`) + logger.warn(`Unknown chunk type: ${data.type}`) } } } catch (error) { - console.error('Error processing stream chunk:', error) + logger.error('Error processing stream chunk:', error) callbacks.onError?.(error) } } diff --git a/src/renderer/src/services/WebSearchService.ts b/src/renderer/src/services/WebSearchService.ts index 9f6e367ddc..8e851e491a 100644 --- a/src/renderer/src/services/WebSearchService.ts +++ b/src/renderer/src/services/WebSearchService.ts @@ -1,5 +1,5 @@ +import { loggerService } from '@logger' import { DEFAULT_WEBSEARCH_RAG_DOCUMENT_COUNT } from '@renderer/config/constant' -import Logger from '@renderer/config/logger' import i18n from '@renderer/i18n' import WebSearchEngineProvider from '@renderer/providers/WebSearchProvider' import store from '@renderer/store' @@ -27,6 +27,8 @@ import { sliceByTokens } from 'tokenx' import { getKnowledgeBaseParams } from './KnowledgeService' import { getKnowledgeSourceUrl, searchKnowledgeBase } from './KnowledgeService' +const logger = loggerService.withContext('WebSearchService') + interface RequestState { signal: AbortSignal | null searchBase?: KnowledgeBase @@ -53,7 +55,7 @@ class WebSearchService { if (!requestState.searchBase) return window.api.knowledgeBase .delete(requestState.searchBase.id) - .catch((error) => Logger.warn(`[WebSearchService] Failed to cleanup search base for ${requestId}:`, error)) + .catch((error) => logger.warn(`Failed to cleanup search base for ${requestId}:`, error)) } }) @@ -190,7 +192,7 @@ class WebSearchService { public async checkSearch(provider: WebSearchProvider): Promise<{ valid: boolean; error?: any }> { try { const response = await this.search(provider, 'test query') - Logger.log('[checkSearch] Search response:', response) + logger.debug('Search response:', response) // 优化的判断条件:检查结果是否有效且没有错误 return { valid: response.results !== undefined, error: undefined } } catch (error) { @@ -357,7 +359,7 @@ class WebSearchService { // 4. 使用 Round Robin 策略选择引用 const selectedReferences = selectReferences(rawResults, references, totalDocumentCount) - Logger.log('[WebSearchService] With RAG, the number of search results:', { + logger.verbose('With RAG, the number of search results:', { raw: rawResults.length, retrieved: references.length, selected: selectedReferences.length @@ -379,7 +381,7 @@ class WebSearchService { config: CompressionConfig ): Promise { if (!config.cutoffLimit) { - Logger.warn('[WebSearchService] Cutoff limit is not set, skipping compression') + logger.warn('Cutoff limit is not set, skipping compression') return rawResults } @@ -431,7 +433,7 @@ class WebSearchService { // 检查 websearch 和 question 是否有效 if (!extractResults.websearch?.question || extractResults.websearch.question.length === 0) { - Logger.log('[processWebsearch] No valid question found in extractResults.websearch') + logger.info('No valid question found in extractResults.websearch') return { results: [] } } @@ -504,7 +506,7 @@ class WebSearchService { 1000 ) } catch (error) { - Logger.warn('[WebSearchService] RAG compression failed, will return empty results:', error) + logger.warn('RAG compression failed, will return empty results:', error) window.message.error({ key: 'websearch-rag-failed', duration: 10, diff --git a/src/renderer/src/services/messageStreaming/BlockManager.ts b/src/renderer/src/services/messageStreaming/BlockManager.ts index 7ccb6cfeec..bb6ba50dac 100644 --- a/src/renderer/src/services/messageStreaming/BlockManager.ts +++ b/src/renderer/src/services/messageStreaming/BlockManager.ts @@ -1,8 +1,11 @@ +import { loggerService } from '@logger' import type { AppDispatch, RootState } from '@renderer/store' import { updateOneBlock, upsertOneBlock } from '@renderer/store/messageBlock' import { newMessagesActions } from '@renderer/store/newMessage' import { MessageBlock, MessageBlockType } from '@renderer/types/newMessage' +const logger = loggerService.withContext('BlockManager') + interface ActiveBlockInfo { id: string type: MessageBlockType @@ -128,7 +131,7 @@ export class BlockManager { newBlock ]) } else { - console.error( + logger.error( `[handleBlockTransition] Failed to get updated message ${this.deps.assistantMsgId} from state for DB save.` ) } diff --git a/src/renderer/src/services/messageStreaming/callbacks/citationCallbacks.ts b/src/renderer/src/services/messageStreaming/callbacks/citationCallbacks.ts index 140cd41686..cbaef2cb5a 100644 --- a/src/renderer/src/services/messageStreaming/callbacks/citationCallbacks.ts +++ b/src/renderer/src/services/messageStreaming/callbacks/citationCallbacks.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import type { ExternalToolResult } from '@renderer/types' import { CitationMessageBlock, MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage' import { createCitationBlock } from '@renderer/utils/messageUtils/create' @@ -5,6 +6,8 @@ import { findMainTextBlocks } from '@renderer/utils/messageUtils/find' import { BlockManager } from '../BlockManager' +const logger = loggerService.withContext('CitationCallbacks') + interface CitationCallbacksDependencies { blockManager: BlockManager assistantMsgId: string @@ -33,7 +36,7 @@ export const createCitationCallbacks = (deps: CitationCallbacksDependencies) => } blockManager.smartBlockUpdate(citationBlockId, changes, MessageBlockType.CITATION, true) } else { - console.error('[onExternalToolComplete] citationBlockId is null. Cannot update.') + logger.error('[onExternalToolComplete] citationBlockId is null. Cannot update.') } }, diff --git a/src/renderer/src/services/messageStreaming/callbacks/imageCallbacks.ts b/src/renderer/src/services/messageStreaming/callbacks/imageCallbacks.ts index e68e229a9e..9728f564a5 100644 --- a/src/renderer/src/services/messageStreaming/callbacks/imageCallbacks.ts +++ b/src/renderer/src/services/messageStreaming/callbacks/imageCallbacks.ts @@ -1,8 +1,11 @@ +import { loggerService } from '@logger' import { ImageMessageBlock, MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage' import { createImageBlock } from '@renderer/utils/messageUtils/create' import { BlockManager } from '../BlockManager' +const logger = loggerService.withContext('ImageCallbacks') + interface ImageCallbacksDependencies { blockManager: BlockManager assistantMsgId: string @@ -62,7 +65,7 @@ export const createImageCallbacks = (deps: ImageCallbacksDependencies) => { } imageBlockId = null } else { - console.error('[onImageGenerated] Last block was not an Image block or ID is missing.') + logger.error('[onImageGenerated] Last block was not an Image block or ID is missing.') } } } diff --git a/src/renderer/src/services/messageStreaming/callbacks/textCallbacks.ts b/src/renderer/src/services/messageStreaming/callbacks/textCallbacks.ts index d9e9e7f17c..657cf6f0f3 100644 --- a/src/renderer/src/services/messageStreaming/callbacks/textCallbacks.ts +++ b/src/renderer/src/services/messageStreaming/callbacks/textCallbacks.ts @@ -1,9 +1,12 @@ +import { loggerService } from '@logger' import { WebSearchSource } from '@renderer/types' import { CitationMessageBlock, MessageBlock, MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage' import { createMainTextBlock } from '@renderer/utils/messageUtils/create' import { BlockManager } from '../BlockManager' +const logger = loggerService.withContext('TextCallbacks') + interface TextCallbacksDependencies { blockManager: BlockManager getState: any @@ -60,7 +63,7 @@ export const createTextCallbacks = (deps: TextCallbacksDependencies) => { blockManager.smartBlockUpdate(mainTextBlockId, changes, MessageBlockType.MAIN_TEXT, true) mainTextBlockId = null } else { - console.warn( + logger.warn( `[onTextComplete] Received text.complete but last block was not MAIN_TEXT (was ${blockManager.lastBlockType}) or lastBlockId is null.` ) } diff --git a/src/renderer/src/services/messageStreaming/callbacks/thinkingCallbacks.ts b/src/renderer/src/services/messageStreaming/callbacks/thinkingCallbacks.ts index 535ae70c75..80c63858c7 100644 --- a/src/renderer/src/services/messageStreaming/callbacks/thinkingCallbacks.ts +++ b/src/renderer/src/services/messageStreaming/callbacks/thinkingCallbacks.ts @@ -1,8 +1,10 @@ +import { loggerService } from '@logger' import { MessageBlock, MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage' import { createThinkingBlock } from '@renderer/utils/messageUtils/create' import { BlockManager } from '../BlockManager' +const logger = loggerService.withContext('ThinkingCallbacks') interface ThinkingCallbacksDependencies { blockManager: BlockManager assistantMsgId: string @@ -57,7 +59,7 @@ export const createThinkingCallbacks = (deps: ThinkingCallbacksDependencies) => blockManager.smartBlockUpdate(thinkingBlockId, changes, MessageBlockType.THINKING, true) thinkingBlockId = null } else { - console.warn( + logger.warn( `[onThinkingComplete] Received thinking.complete but last block was not THINKING (was ${blockManager.lastBlockType}) or lastBlockId is null.` ) } diff --git a/src/renderer/src/services/messageStreaming/callbacks/toolCallbacks.ts b/src/renderer/src/services/messageStreaming/callbacks/toolCallbacks.ts index 8d89bd05dd..3782b4f35f 100644 --- a/src/renderer/src/services/messageStreaming/callbacks/toolCallbacks.ts +++ b/src/renderer/src/services/messageStreaming/callbacks/toolCallbacks.ts @@ -1,9 +1,12 @@ +import { loggerService } from '@logger' import type { MCPToolResponse } from '@renderer/types' import { MessageBlockStatus, MessageBlockType, ToolMessageBlock } from '@renderer/types/newMessage' import { createToolBlock } from '@renderer/utils/messageUtils/create' import { BlockManager } from '../BlockManager' +const logger = loggerService.withContext('ToolCallbacks') + interface ToolCallbacksDependencies { blockManager: BlockManager assistantMsgId: string @@ -38,7 +41,7 @@ export const createToolCallbacks = (deps: ToolCallbacksDependencies) => { blockManager.handleBlockTransition(toolBlock, MessageBlockType.TOOL) toolCallIdToBlockIdMap.set(toolResponse.id, toolBlock.id) } else { - console.warn( + logger.warn( `[onToolCallPending] Received unhandled tool status: ${toolResponse.status} for ID: ${toolResponse.id}` ) } @@ -55,12 +58,12 @@ export const createToolCallbacks = (deps: ToolCallbacksDependencies) => { } blockManager.smartBlockUpdate(targetBlockId, changes, MessageBlockType.TOOL) } else if (!targetBlockId) { - console.warn( + logger.warn( `[onToolCallInProgress] No block ID found for tool ID: ${toolResponse.id}. Available mappings:`, Array.from(toolCallIdToBlockIdMap.entries()) ) } else { - console.warn( + logger.warn( `[onToolCallInProgress] Received unhandled tool status: ${toolResponse.status} for ID: ${toolResponse.id}` ) } @@ -72,7 +75,7 @@ export const createToolCallbacks = (deps: ToolCallbacksDependencies) => { if (toolResponse.status === 'done' || toolResponse.status === 'error' || toolResponse.status === 'cancelled') { if (!existingBlockId) { - console.error( + logger.error( `[onToolCallComplete] No existing block found for completed/error tool call ID: ${toolResponse.id}. Cannot update.` ) return @@ -95,7 +98,7 @@ export const createToolCallbacks = (deps: ToolCallbacksDependencies) => { blockManager.smartBlockUpdate(existingBlockId, changes, MessageBlockType.TOOL, true) } else { - console.warn( + logger.warn( `[onToolCallComplete] Received unhandled tool status: ${toolResponse.status} for ID: ${toolResponse.id}` ) } diff --git a/src/renderer/src/store/mcp.ts b/src/renderer/src/store/mcp.ts index f267c546e9..0b1d2bb7da 100644 --- a/src/renderer/src/store/mcp.ts +++ b/src/renderer/src/store/mcp.ts @@ -1,7 +1,9 @@ +import { loggerService } from '@logger' import { createSlice, nanoid, type PayloadAction } from '@reduxjs/toolkit' -import Logger from '@renderer/config/logger' import type { MCPConfig, MCPServer } from '@renderer/types' +const logger = loggerService.withContext('Store:MCP') + export const initialState: MCPConfig = { servers: [], isUvInstalled: true, @@ -161,7 +163,7 @@ export const initializeMCPServers = (existingServers: MCPServer[], dispatch: (ac // Filter out any built-in servers that are already present const newServers = builtinMCPServers.filter((server) => !serverIds.has(server.name)) - Logger.log('[initializeMCPServers] Adding new servers:', newServers) + logger.info('Adding new servers:', newServers) // Add the new built-in servers to the existing servers newServers.forEach((server) => { dispatch(addMCPServer(server)) diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 71990b05b5..a2b5d3d830 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { nanoid } from '@reduxjs/toolkit' import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE, isMac } from '@renderer/config/constant' import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' @@ -20,6 +21,8 @@ import { DEFAULT_SIDEBAR_ICONS, initialState as settingsInitialState } from './s import { initialState as shortcutsInitialState } from './shortcuts' import { defaultWebSearchProviders } from './websearch' +const logger = loggerService.withContext('Migrate') + // remove logo base64 data to reduce the size of the state function removeMiniAppIconsFromState(state: RootState) { if (state.minapps) { @@ -1153,7 +1156,7 @@ const migrateConfig = { state.settings.trayOnClose = true return state } catch (error) { - console.error(error) + logger.error('migrate 83 error', error) return state } }, @@ -1162,7 +1165,7 @@ const migrateConfig = { addProvider(state, 'voyageai') return state } catch (error) { - console.error(error) + logger.error('migrate 84 error', error) return state } }, @@ -1175,7 +1178,6 @@ const migrateConfig = { state.settings.gridPopoverTrigger = 'click' return state } catch (error) { - console.error(error) return state } }, @@ -1188,7 +1190,6 @@ const migrateConfig = { })) } } catch (error) { - console.error(error) return state } return state @@ -1385,6 +1386,7 @@ const migrateConfig = { }) return state } catch (error) { + logger.error('migrate 100 error', error) return state } }, @@ -1412,6 +1414,7 @@ const migrateConfig = { } return state } catch (error) { + logger.error('migrate 101 error', error) return state } }, @@ -1446,6 +1449,7 @@ const migrateConfig = { delete state.settings.codeCacheThreshold return state } catch (error) { + logger.error('migrate 102 error', error) return state } }, @@ -1474,6 +1478,7 @@ const migrateConfig = { } return state } catch (error) { + logger.error('migrate 103 error', error) return state } }, @@ -1483,6 +1488,7 @@ const migrateConfig = { state.llm.providers = moveProvider(state.llm.providers, 'burncloud', 10) return state } catch (error) { + logger.error('migrate 104 error', error) return state } }, @@ -1498,6 +1504,7 @@ const migrateConfig = { } return state } catch (error) { + logger.error('migrate 105 error', error) return state } }, @@ -1507,6 +1514,7 @@ const migrateConfig = { state.llm.providers = moveProvider(state.llm.providers, 'tokenflux', 15) return state } catch (error) { + logger.error('migrate 106 error', error) return state } }, @@ -1517,6 +1525,7 @@ const migrateConfig = { } return state } catch (error) { + logger.error('migrate 107 error', error) return state } }, @@ -1526,6 +1535,7 @@ const migrateConfig = { state.inputTools.isCollapsed = false return state } catch (error) { + logger.error('migrate 108 error', error) return state } }, @@ -1534,6 +1544,7 @@ const migrateConfig = { state.settings.userTheme = settingsInitialState.userTheme return state } catch (error) { + logger.error('migrate 109 error', error) return state } }, @@ -1546,6 +1557,7 @@ const migrateConfig = { state.settings.testPlan = false return state } catch (error) { + logger.error('migrate 110 error', error) return state } }, @@ -1564,6 +1576,7 @@ const migrateConfig = { return state } catch (error) { + logger.error('migrate 111 error', error) return state } }, @@ -1577,6 +1590,7 @@ const migrateConfig = { state.llm.providers = moveProvider(state.llm.providers, 'lanyun', 15) return state } catch (error) { + logger.error('migrate 112 error', error) return state } }, @@ -1594,6 +1608,7 @@ const migrateConfig = { }) return state } catch (error) { + logger.error('migrate 113 error', error) return state } }, @@ -1610,6 +1625,7 @@ const migrateConfig = { } return state } catch (error) { + logger.error('migrate 114 error', error) return state } }, @@ -1630,6 +1646,7 @@ const migrateConfig = { }) return state } catch (error) { + logger.error('migrate 115 error', error) return state } }, @@ -1658,6 +1675,7 @@ const migrateConfig = { return state } catch (error) { + logger.error('migrate 116 error', error) return state } }, @@ -1695,6 +1713,7 @@ const migrateConfig = { }) return state } catch (error) { + logger.error('migrate 117 error', error) return state } }, @@ -1715,6 +1734,7 @@ const migrateConfig = { return state } catch (error) { + logger.error('migrate 118 error', error) return state } }, @@ -1732,6 +1752,7 @@ const migrateConfig = { } return state } catch (error) { + logger.error('migrate 119 error', error) return state } }, @@ -1779,6 +1800,7 @@ const migrateConfig = { state.settings.localBackupSyncInterval = 0 return state } catch (error) { + logger.error('migrate 120 error', error) return state } }, @@ -1810,6 +1832,7 @@ const migrateConfig = { return state } catch (error) { + logger.error('migrate 121 error', error) return state } } diff --git a/src/renderer/src/store/newMessage.ts b/src/renderer/src/store/newMessage.ts index 66030125ef..38f44bec6e 100644 --- a/src/renderer/src/store/newMessage.ts +++ b/src/renderer/src/store/newMessage.ts @@ -1,8 +1,11 @@ +import { loggerService } from '@logger' import { createEntityAdapter, createSlice, EntityState, PayloadAction } from '@reduxjs/toolkit' // Separate type-only imports from value imports import type { Message } from '@renderer/types/newMessage' import { AssistantMessageStatus, MessageBlockStatus } from '@renderer/types/newMessage' +const logger = loggerService.withContext('newMessage') + // 1. Create the Adapter const messagesAdapter = createEntityAdapter() @@ -145,7 +148,7 @@ export const messagesSlice = createSlice({ } } } else { - console.warn(`[updateMessage] Message ${messageId} not found in entities.`) + logger.warn(`[updateMessage] Message ${messageId} not found in entities.`) } } else { messagesAdapter.updateOne(state, { id: messageId, changes: otherUpdates }) @@ -199,7 +202,7 @@ export const messagesSlice = createSlice({ const messageToUpdate = state.entities[messageId] if (!messageToUpdate) { - console.error(`[upsertBlockReference] Message ${messageId} not found.`) + logger.error(`[upsertBlockReference] Message ${messageId} not found.`) return } diff --git a/src/renderer/src/store/paintings.ts b/src/renderer/src/store/paintings.ts index 779b60ed45..187812459e 100644 --- a/src/renderer/src/store/paintings.ts +++ b/src/renderer/src/store/paintings.ts @@ -1,6 +1,9 @@ +import { loggerService } from '@logger' import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { PaintingAction, PaintingsState } from '@renderer/types' +const logger = loggerService.withContext('Store:paintings') + const initialState: PaintingsState = { paintings: [], generate: [], @@ -46,7 +49,7 @@ const paintingsSlice = createSlice({ if (existingIndex !== -1) { state[namespace] = state[namespace].map((c) => (c.id === painting.id ? painting : c)) } else { - console.error(`Painting with id ${painting.id} not found in ${namespace}`) + logger.error(`Painting with id ${painting.id} not found in ${namespace}`) } }, updatePaintings: ( diff --git a/src/renderer/src/store/thunk/messageThunk.ts b/src/renderer/src/store/thunk/messageThunk.ts index 8f380d523a..4a285f51e9 100644 --- a/src/renderer/src/store/thunk/messageThunk.ts +++ b/src/renderer/src/store/thunk/messageThunk.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import db from '@renderer/databases' import { fetchChatCompletion } from '@renderer/services/ApiService' import FileManager from '@renderer/services/FileManager' @@ -25,6 +26,8 @@ import type { AppDispatch, RootState } from '../index' import { removeManyBlocks, updateOneBlock, upsertManyBlocks, upsertOneBlock } from '../messageBlock' import { newMessagesActions, selectMessagesForTopic } from '../newMessage' +const logger = loggerService.withContext('MessageThunk') + const handleChangeLoadingOfTopic = async (topicId: string) => { await waitForTopicQueue(topicId) store.dispatch(newMessagesActions.setTopicLoading({ topicId, loading: false })) @@ -52,10 +55,10 @@ export const saveMessageAndBlocksToDB = async (message: Message, blocks: Message await db.topics.update(message.topicId, { messages: updatedMessages }) store.dispatch(updateTopicUpdatedAt({ topicId: message.topicId })) } else { - console.error(`[saveMessageAndBlocksToDB] Topic ${message.topicId} not found.`) + logger.error(`[saveMessageAndBlocksToDB] Topic ${message.topicId} not found.`) } } catch (error) { - console.error(`[saveMessageAndBlocksToDB] Failed to save message ${message.id}:`, error) + logger.error(`[saveMessageAndBlocksToDB] Failed to save message ${message.id}:`, error) } } @@ -95,7 +98,7 @@ const updateExistingMessageAndBlocksInDB = async ( } }) } catch (error) { - console.error(`[updateExistingMsg] Failed to update message ${updatedMessage.id}:`, error) + logger.error(`[updateExistingMsg] Failed to update message ${updatedMessage.id}:`, error) } } @@ -213,7 +216,7 @@ const saveUpdatesToDB = async ( } await updateExistingMessageAndBlocksInDB(messageDataToSave, blocksToUpdate) } catch (error) { - console.error(`[DB Save Updates] Failed for message ${messageId}:`, error) + logger.error(`[DB Save Updates] Failed for message ${messageId}:`, error) } } @@ -225,7 +228,7 @@ const saveUpdatedBlockToDB = async ( getState: () => RootState ) => { if (!blockId) { - console.warn('[DB Save Single Block] Received null/undefined blockId. Skipping save.') + logger.warn('[DB Save Single Block] Received null/undefined blockId. Skipping save.') return } const state = getState() @@ -233,7 +236,7 @@ const saveUpdatedBlockToDB = async ( if (blockToSave) { await saveUpdatesToDB(messageId, topicId, {}, [blockToSave]) // Pass messageId, topicId, empty message updates, and the block } else { - console.warn(`[DB Save Single Block] Block ${blockId} not found in state. Cannot save.`) + logger.warn(`[DB Save Single Block] Block ${blockId} not found in state. Cannot save.`) } } @@ -269,7 +272,7 @@ const dispatchMultiModelResponses = async ( const messagesToSaveInDB = currentTopicMessageIds.map((id) => currentEntities[id]).filter((m): m is Message => !!m) await db.topics.update(topicId, { messages: messagesToSaveInDB }) } else { - console.error(`[dispatchMultiModelResponses] Topic ${topicId} not found in DB during multi-model save.`) + logger.error(`[dispatchMultiModelResponses] Topic ${topicId} not found in DB during multi-model save.`) throw new Error(`Topic ${topicId} not found in DB.`) } @@ -399,7 +402,7 @@ const fetchAndProcessAssistantResponseImpl = async ( const userMessageIndex = allMessagesForTopic.findIndex((m) => m?.id === userMessageId) if (userMessageIndex === -1) { - console.error( + logger.error( `[fetchAndProcessAssistantResponseImpl] Triggering user message ${userMessageId} (askId of ${assistantMsgId}) not found. Falling back.` ) const assistantMessageIndexFallback = allMessagesForTopic.findIndex((m) => m?.id === assistantMsgId) @@ -889,7 +892,7 @@ const fetchAndProcessAssistantResponseImpl = async ( onChunkReceived: streamProcessorCallbacks }) } catch (error: any) { - console.error('Error fetching chat completion:', error) + logger.error('Error fetching chat completion:', error) if (assistantMessage) { callbacks.onError?.(error) throw error @@ -909,7 +912,7 @@ export const sendMessage = async (dispatch: AppDispatch, getState: () => RootState) => { try { if (userMessage.blocks.length === 0) { - console.warn('sendMessage: No blocks in the provided message.') + logger.warn('sendMessage: No blocks in the provided message.') return } await saveMessageAndBlocksToDB(userMessage, userMessageBlocks) @@ -937,7 +940,7 @@ export const sendMessage = }) } } catch (error) { - console.error('Error in sendMessage thunk:', error) + logger.error('Error in sendMessage thunk:', error) } finally { handleChangeLoadingOfTopic(topicId) } @@ -982,7 +985,7 @@ export const loadTopicMessagesThunk = dispatch(newMessagesActions.messagesReceived({ topicId, messages: [] })) } } catch (error: any) { - console.error(`[loadTopicMessagesThunk] Failed to load messages for topic ${topicId}:`, error) + logger.error(`[loadTopicMessagesThunk] Failed to load messages for topic ${topicId}:`, error) // dispatch(newMessagesActions.setTopicLoading({ topicId, loading: false })) } } @@ -995,7 +998,7 @@ export const deleteSingleMessageThunk = const currentState = getState() const messageToDelete = currentState.messages.entities[messageId] if (!messageToDelete || messageToDelete.topicId !== topicId) { - console.error(`[deleteSingleMessage] Message ${messageId} not found in topic ${topicId}.`) + logger.error(`[deleteSingleMessage] Message ${messageId} not found in topic ${topicId}.`) return } @@ -1012,7 +1015,7 @@ export const deleteSingleMessageThunk = dispatch(updateTopicUpdatedAt({ topicId })) } } catch (error) { - console.error(`[deleteSingleMessage] Failed to delete message ${messageId}:`, error) + logger.error(`[deleteSingleMessage] Failed to delete message ${messageId}:`, error) } } @@ -1039,7 +1042,7 @@ export const deleteMessageGroupThunk = // } if (messagesToDelete.length === 0) { - console.warn(`[deleteMessageGroup] No messages found with askId ${askId} in topic ${topicId}.`) + logger.warn(`[deleteMessageGroup] No messages found with askId ${askId} in topic ${topicId}.`) return } @@ -1056,7 +1059,7 @@ export const deleteMessageGroupThunk = dispatch(updateTopicUpdatedAt({ topicId })) } } catch (error) { - console.error(`[deleteMessageGroup] Failed to delete messages with askId ${askId}:`, error) + logger.error(`[deleteMessageGroup] Failed to delete messages with askId ${askId}:`, error) } } @@ -1086,7 +1089,7 @@ export const clearTopicMessagesThunk = await db.message_blocks.bulkDelete(blockIdsToDelete) } } catch (error) { - console.error(`[clearTopicMessagesThunk] Failed to clear messages for topic ${topicId}:`, error) + logger.error(`[clearTopicMessagesThunk] Failed to clear messages for topic ${topicId}:`, error) } } @@ -1114,7 +1117,7 @@ export const resendMessageThunk = window.keyv.remove(`web-search-${userMessageToResend.id}`) window.keyv.remove(`knowledge-search-${userMessageToResend.id}`) } catch (error) { - console.warn(`Failed to clear keyv cache for message ${userMessageToResend.id}:`, error) + logger.warn(`Failed to clear keyv cache for message ${userMessageToResend.id}:`, error) } const resetDataList: Message[] = [] @@ -1179,7 +1182,7 @@ export const resendMessageThunk = const finalMessagesToSave = selectMessagesForTopic(getState(), topicId) await db.topics.update(topicId, { messages: finalMessagesToSave }) } catch (dbError) { - console.error('[resendMessageThunk] Error updating database:', dbError) + logger.error('[resendMessageThunk] Error updating database:', dbError) } const queue = getTopicQueue(topicId) @@ -1193,7 +1196,7 @@ export const resendMessageThunk = }) } } catch (error) { - console.error(`[resendMessageThunk] Error resending user message ${userMessageToResend.id}:`, error) + logger.error(`[resendMessageThunk] Error resending user message ${userMessageToResend.id}:`, error) } finally { handleChangeLoadingOfTopic(topicId) } @@ -1225,14 +1228,14 @@ export const regenerateAssistantResponseThunk = const askId = assistantMessageToRegenerate.askId if (!askId) { - console.error( + logger.error( `[appendAssistantResponseThunk] Existing assistant message ${assistantMessageToRegenerate.id} does not have an askId.` ) return // Stop if askId is missing } if (!state.messages.entities[askId]) { - console.error( + logger.error( `[appendAssistantResponseThunk] Original user query (askId: ${askId}) not found in entities. Cannot create assistant response without corresponding user message.` ) @@ -1248,7 +1251,7 @@ export const regenerateAssistantResponseThunk = // 2. Find the original user query (Restored Logic) const originalUserQuery = allMessagesForTopic.find((m) => m.id === assistantMessageToRegenerate.askId) if (!originalUserQuery) { - console.error( + logger.error( `[regenerateAssistantResponseThunk] Original user query (askId: ${assistantMessageToRegenerate.askId}) not found for assistant message ${assistantMessageToRegenerate.id}. Cannot regenerate.` ) return @@ -1258,7 +1261,7 @@ export const regenerateAssistantResponseThunk = const messageToResetEntity = state.messages.entities[assistantMessageToRegenerate.id] if (!messageToResetEntity) { // No need to check topicId again as selector implicitly handles it - console.error( + logger.error( `[regenerateAssistantResponseThunk] Assistant message ${assistantMessageToRegenerate.id} not found in entities despite being in the topic list. State might be inconsistent.` ) return @@ -1323,7 +1326,7 @@ export const regenerateAssistantResponseThunk = ) }) } catch (error) { - console.error( + logger.error( `[regenerateAssistantResponseThunk] Error regenerating response for assistant message ${assistantMessageToRegenerate.id}:`, error ) @@ -1349,7 +1352,7 @@ export const initiateTranslationThunk = const originalMessage = state.messages.entities[messageId] if (!originalMessage) { - console.error(`[initiateTranslationThunk] Original message ${messageId} not found.`) + logger.error(`[initiateTranslationThunk] Original message ${messageId} not found.`) return undefined } @@ -1386,7 +1389,7 @@ export const initiateTranslationThunk = }) return newBlock.id // Return the ID } catch (error) { - console.error(`[initiateTranslationThunk] Failed for message ${messageId}:`, error) + logger.error(`[initiateTranslationThunk] Failed for message ${messageId}:`, error) return undefined // Optional: Dispatch an error action or show notification } @@ -1411,7 +1414,7 @@ export const updateTranslationBlockThunk = await db.message_blocks.update(blockId, changes) // Logger.log(`[updateTranslationBlockThunk] Successfully updated translation block ${blockId}.`) } catch (error) { - console.error(`[updateTranslationBlockThunk] Failed to update translation block ${blockId}:`, error) + logger.error(`[updateTranslationBlockThunk] Failed to update translation block ${blockId}:`, error) } } @@ -1433,20 +1436,20 @@ export const appendAssistantResponseThunk = // 1. Find the existing assistant message to get the original askId const existingAssistantMsg = state.messages.entities[existingAssistantMessageId] if (!existingAssistantMsg) { - console.error( + logger.error( `[appendAssistantResponseThunk] Existing assistant message ${existingAssistantMessageId} not found.` ) return // Stop if the reference message doesn't exist } if (existingAssistantMsg.role !== 'assistant') { - console.error( + logger.error( `[appendAssistantResponseThunk] Message ${existingAssistantMessageId} is not an assistant message.` ) return // Ensure it's an assistant message } const askId = existingAssistantMsg.askId if (!askId) { - console.error( + logger.error( `[appendAssistantResponseThunk] Existing assistant message ${existingAssistantMessageId} does not have an askId.` ) return // Stop if askId is missing @@ -1454,7 +1457,7 @@ export const appendAssistantResponseThunk = // (Optional but recommended) Verify the original user query exists if (!state.messages.entities[askId]) { - console.error( + logger.error( `[appendAssistantResponseThunk] Original user query (askId: ${askId}) not found in entities. Cannot create assistant response without corresponding user message.` ) @@ -1500,7 +1503,7 @@ export const appendAssistantResponseThunk = ) }) } catch (error) { - console.error(`[appendAssistantResponseThunk] Error appending assistant response:`, error) + logger.error(`[appendAssistantResponseThunk] Error appending assistant response:`, error) // Optionally dispatch an error action or notification // Resetting loading state should be handled by the underlying fetchAndProcessAssistantResponseImpl } finally { @@ -1525,7 +1528,7 @@ export const cloneMessagesToNewTopicThunk = ) => async (dispatch: AppDispatch, getState: () => RootState): Promise => { if (!newTopic || !newTopic.id) { - console.error(`[cloneMessagesToNewTopicThunk] Invalid newTopic provided.`) + logger.error(`[cloneMessagesToNewTopicThunk] Invalid newTopic provided.`) return false } try { @@ -1533,14 +1536,14 @@ export const cloneMessagesToNewTopicThunk = const sourceMessages = selectMessagesForTopic(state, sourceTopicId) if (!sourceMessages || sourceMessages.length === 0) { - console.error(`[cloneMessagesToNewTopicThunk] Source topic ${sourceTopicId} not found or is empty.`) + logger.error(`[cloneMessagesToNewTopicThunk] Source topic ${sourceTopicId} not found or is empty.`) return false } // 1. Slice messages to clone const messagesToClone = sourceMessages.slice(0, branchPointIndex) if (messagesToClone.length === 0) { - console.warn(`[cloneMessagesToNewTopicThunk] No messages to branch (index ${branchPointIndex}).`) + logger.warn(`[cloneMessagesToNewTopicThunk] No messages to branch (index ${branchPointIndex}).`) return true // Nothing to clone, operation considered successful but did nothing. } @@ -1565,7 +1568,7 @@ export const cloneMessagesToNewTopicThunk = // This happens if the user message corresponding to askId was *before* the branch point index // and thus wasn't included in messagesToClone or the map. // In this case, the link is broken in the new topic. - console.warn( + logger.warn( `[cloneMessages] Could not find new ID mapping for original askId ${oldMessage.askId} (likely outside branch). Setting askId to undefined for new assistant message ${newMsgId}.` ) // newAskId remains undefined @@ -1594,7 +1597,7 @@ export const cloneMessagesToNewTopicThunk = } } } else { - console.warn( + logger.warn( `[cloneMessagesToNewTopicThunk] Block ${oldBlockId} not found in state for message ${oldMessage.id}. Skipping block clone.` ) } @@ -1647,7 +1650,7 @@ export const cloneMessagesToNewTopicThunk = return true // Indicate success } catch (error) { - console.error(`[cloneMessagesToNewTopicThunk] Failed to clone messages:`, error) + logger.error(`[cloneMessagesToNewTopicThunk] Failed to clone messages:`, error) return false // Indicate failure } } @@ -1668,7 +1671,7 @@ export const updateMessageAndBlocksThunk = const messageId = messageUpdates?.id if (messageUpdates && !messageId) { - console.error('[updateMessageAndUpdateBlocksThunk] Message ID is required.') + logger.error('[updateMessageAndUpdateBlocksThunk] Message ID is required.') return } @@ -1699,13 +1702,13 @@ export const updateMessageAndBlocksThunk = Object.assign(topic.messages[messageIndex], messageUpdates) await db.topics.update(topicId, { messages: topic.messages }) } else { - console.error( + logger.error( `[updateMessageAndBlocksThunk] Message ${messageId} not found in DB topic ${topicId} for property update.` ) throw new Error(`Message ${messageId} not found in DB topic ${topicId} for property update.`) } } else { - console.error( + logger.error( `[updateMessageAndBlocksThunk] Topic ${topicId} not found or empty for message property update.` ) throw new Error(`Topic ${topicId} not found or empty for message property update.`) @@ -1719,7 +1722,7 @@ export const updateMessageAndBlocksThunk = dispatch(updateTopicUpdatedAt({ topicId })) } catch (error) { - console.error(`[updateMessageAndBlocksThunk] Failed to process updates for message ${messageId}:`, error) + logger.error(`[updateMessageAndBlocksThunk] Failed to process updates for message ${messageId}:`, error) } } @@ -1727,7 +1730,7 @@ export const removeBlocksThunk = (topicId: string, messageId: string, blockIdsToRemove: string[]) => async (dispatch: AppDispatch, getState: () => RootState): Promise => { if (!blockIdsToRemove.length) { - console.warn('[removeBlocksFromMessageThunk] No block IDs provided to remove.') + logger.warn('[removeBlocksFromMessageThunk] No block IDs provided to remove.') return } @@ -1736,7 +1739,7 @@ export const removeBlocksThunk = const message = state.messages.entities[messageId] if (!message) { - console.error(`[removeBlocksFromMessageThunk] Message ${messageId} not found in state.`) + logger.error(`[removeBlocksFromMessageThunk] Message ${messageId} not found in state.`) return } const blockIdsToRemoveSet = new Set(blockIdsToRemove) @@ -1764,7 +1767,7 @@ export const removeBlocksThunk = return } catch (error) { - console.error(`[removeBlocksFromMessageThunk] Failed to remove blocks from message ${messageId}:`, error) + logger.error(`[removeBlocksFromMessageThunk] Failed to remove blocks from message ${messageId}:`, error) throw error } } diff --git a/src/renderer/src/utils/abortController.ts b/src/renderer/src/utils/abortController.ts index 973e30a36a..21ac75c494 100644 --- a/src/renderer/src/utils/abortController.ts +++ b/src/renderer/src/utils/abortController.ts @@ -1,4 +1,6 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' + +const logger = loggerService.withContext('AbortController') export const abortMap = new Map void)[]>() @@ -36,7 +38,7 @@ export function createAbortPromise(signal: AbortSignal, finallyPromise: Promise< } const abortHandler = (e: Event) => { - Logger.log('[createAbortPromise] abortHandler', e) + logger.debug('abortHandler', e) reject(new DOMException('Operation aborted', 'AbortError')) } diff --git a/src/renderer/src/utils/blacklistMatchPattern.ts b/src/renderer/src/utils/blacklistMatchPattern.ts index 1e40257e45..ea2acc3843 100644 --- a/src/renderer/src/utils/blacklistMatchPattern.ts +++ b/src/renderer/src/utils/blacklistMatchPattern.ts @@ -1,7 +1,9 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import { WebSearchState } from '@renderer/store/websearch' import { WebSearchProviderResponse } from '@renderer/types' +const logger = loggerService.withContext('BlacklistMatchPattern') + /* * MIT License * @@ -180,7 +182,7 @@ export async function parseSubscribeContent(url: string): Promise { try { // 获取订阅源内容 const response = await fetch(url) - Logger.log('[parseSubscribeContent] response', response) + logger.debug('[parseSubscribeContent] response', response) if (!response.ok) { throw new Error('Failed to fetch subscribe content') } @@ -196,7 +198,7 @@ export async function parseSubscribeContent(url: string): Promise { .map((line) => line.trim()) .filter((pattern) => parseMatchPattern(pattern) !== null) } catch (error) { - console.error('Error parsing subscribe content:', error) + logger.error('Error parsing subscribe content:', error) throw error } } @@ -204,7 +206,7 @@ export async function filterResultWithBlacklist( response: WebSearchProviderResponse, websearch: WebSearchState ): Promise { - Logger.log('[filterResultWithBlacklist]', response) + logger.debug('[filterResultWithBlacklist]', response) // 没有结果或者没有黑名单规则时,直接返回原始结果 if ( @@ -238,14 +240,14 @@ export async function filterResultWithBlacklist( const regexPattern = pattern.slice(1, -1) regexPatterns.push(new RegExp(regexPattern, 'i')) } catch (error) { - console.error('Invalid regex pattern:', pattern, error) + logger.error('Invalid regex pattern:', pattern, error) } } else { // 处理匹配模式格式 try { patternMap.set(pattern, pattern) } catch (error) { - console.error('Invalid match pattern:', pattern, error) + logger.error('Invalid match pattern:', pattern, error) } } }) @@ -265,12 +267,12 @@ export async function filterResultWithBlacklist( const matchesPattern = patternMap.get(result.url).length > 0 return !matchesPattern } catch (error) { - console.error('Error processing URL:', result.url, error) + logger.error('Error processing URL: ' + result.url, error) return true // 如果URL解析失败,保留该结果 } }) - Logger.log('filterResultWithBlacklist filtered results:', filteredResults) + logger.debug('filterResultWithBlacklist filtered results:', filteredResults) return { ...response, diff --git a/src/renderer/src/utils/download.ts b/src/renderer/src/utils/download.ts index 454c1ac82a..930f5c67fe 100644 --- a/src/renderer/src/utils/download.ts +++ b/src/renderer/src/utils/download.ts @@ -1,5 +1,8 @@ +import { loggerService } from '@logger' import i18n from '@renderer/i18n' +const logger = loggerService.withContext('Utils:download') + export const download = (url: string, filename?: string) => { // 处理可直接通过 标签下载的 URL: // - 本地文件 ( file:// ) @@ -79,7 +82,7 @@ export const download = (url: string, filename?: string) => { link.remove() }) .catch((error) => { - console.error('Download failed:', error) + logger.error('Download failed:', error) // 显示用户友好的错误提示 if (error.message) { window.message?.error(`${i18n.t('message.download.failed')}:${error.message}`) diff --git a/src/renderer/src/utils/env.ts b/src/renderer/src/utils/env.ts new file mode 100644 index 0000000000..92bbc0b933 --- /dev/null +++ b/src/renderer/src/utils/env.ts @@ -0,0 +1,17 @@ +/** + * Check if the application is running in production mode + * @returns {Promise} true if in production, false otherwise + */ +export async function isProduction(): Promise { + const { isPackaged } = await window.api.getAppInfo() + return isPackaged +} + +/** + * Check if the application is running in development mode + * @returns {Promise} true if in development, false otherwise + */ +export async function isDev(): Promise { + const isProd = await isProduction() + return !isProd +} diff --git a/src/renderer/src/utils/error.ts b/src/renderer/src/utils/error.ts index 4150b200f2..d3ae982648 100644 --- a/src/renderer/src/utils/error.ts +++ b/src/renderer/src/utils/error.ts @@ -1,5 +1,8 @@ +import { loggerService } from '@logger' import { t } from 'i18next' +const logger = loggerService.withContext('Utils:error') + export function getErrorDetails(err: any, seen = new WeakSet()): any { // Handle circular references if (err === null || typeof err !== 'object' || seen.has(err)) { @@ -28,7 +31,7 @@ export function getErrorDetails(err: any, seen = new WeakSet()): any { } export function formatErrorMessage(error: any): string { - console.error('Original error:', error) + logger.error('Original error:', error) try { const detailedError = getErrorDetails(error) diff --git a/src/renderer/src/utils/export.ts b/src/renderer/src/utils/export.ts index 61b9696f33..204ea8cdfa 100644 --- a/src/renderer/src/utils/export.ts +++ b/src/renderer/src/utils/export.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { Client } from '@notionhq/client' import i18n from '@renderer/i18n' import { getMessageTitle } from '@renderer/services/MessagesService' @@ -12,6 +13,8 @@ import { markdownToBlocks } from '@tryfabric/martian' import dayjs from 'dayjs' import { appendBlocks } from 'notion-helper' // 引入 notion-helper 的 appendBlocks 函数 +const logger = loggerService.withContext('Utils:export') + /** * 获取话题的消息列表,使用TopicManager确保消息被正确加载 * 这样可以避免从未打开过的话题导出为空的问题 @@ -540,7 +543,7 @@ export const exportMarkdownToObsidian = async (attributes: any) => { window.open(obsidianUrl) window.message.success(i18n.t('chat.topics.export.obsidian_export_success')) } catch (error) { - console.error('导出到Obsidian失败:', error) + logger.error('导出到Obsidian失败:', error) window.message.error(i18n.t('chat.topics.export.obsidian_export_failed')) } } @@ -696,7 +699,7 @@ export const exportMarkdownToSiyuan = async (title: string, content: string) => key: 'siyuan-success' }) } catch (error) { - console.error('导出到思源笔记失败:', error) + logger.error('导出到思源笔记失败:', error) window.message.error({ content: i18n.t('message.error.siyuan.export') + (error instanceof Error ? `: ${error.message}` : ''), key: 'siyuan-error' diff --git a/src/renderer/src/utils/fetch.ts b/src/renderer/src/utils/fetch.ts index 2093d1be1c..66b96869a0 100644 --- a/src/renderer/src/utils/fetch.ts +++ b/src/renderer/src/utils/fetch.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { Readability } from '@mozilla/readability' import { nanoid } from '@reduxjs/toolkit' import { WebSearchProviderResult } from '@renderer/types' @@ -5,6 +6,8 @@ import { createAbortPromise } from '@renderer/utils/abortController' import { isAbortError } from '@renderer/utils/error' import TurndownService from 'turndown' +const logger = loggerService.withContext('Utils:fetch') + const turndownService = new TurndownService() export const noContent = 'No content found' @@ -118,7 +121,7 @@ export async function fetchWebContent( throw e } - console.error(`Failed to fetch ${url}`, e) + logger.error(`Failed to fetch ${url}`, e) return { title: url, url: url, @@ -139,7 +142,7 @@ export async function fetchRedirectUrl(url: string) { }) return response.url } catch (e) { - console.error(`Failed to fetch redirect url: ${e}`) + logger.error(`Failed to fetch redirect url: ${e}`) return url } } diff --git a/src/renderer/src/utils/image.ts b/src/renderer/src/utils/image.ts index 25ba33aabf..43d784a5f8 100644 --- a/src/renderer/src/utils/image.ts +++ b/src/renderer/src/utils/image.ts @@ -1,7 +1,10 @@ +import { loggerService } from '@logger' import i18n from '@renderer/i18n' import imageCompression from 'browser-image-compression' import * as htmlToImage from 'html-to-image' +const logger = loggerService.withContext('Utils:image') + /** * 将文件转换为 Base64 编码的字符串或 ArrayBuffer。 * @param {File} file 要转换的文件 @@ -41,7 +44,7 @@ export async function captureDiv(divRef: React.RefObject) { const imageData = canvas.toDataURL('image/png') return imageData } catch (error) { - console.error('Error capturing div:', error) + logger.error('Error capturing div:', error) return Promise.reject() } } @@ -135,7 +138,7 @@ export const captureScrollableDiv = async (divRef: React.RefObject void} fn 要执行的函数 @@ -58,16 +60,6 @@ export function isFreeModel(model: Model) { return (model.id + model.name).toLocaleLowerCase().includes('free') } -export async function isProduction() { - const { isPackaged } = await window.api.getAppInfo() - return isPackaged -} - -export async function isDev() { - const isProd = await isProduction() - return !isProd -} - /** * 从错误对象中提取错误信息。 * @param {any} error 错误对象或字符串 @@ -149,7 +141,7 @@ export function hasPath(url: string): boolean { const parsedUrl = new URL(url) return parsedUrl.pathname !== '/' && parsedUrl.pathname !== '' } catch (error) { - console.error('Invalid URL:', error) + logger.error('Invalid URL:', error) return false } } @@ -220,7 +212,7 @@ export function getMcpConfigSampleFromReadme(readme: string): Record): Promise => { if (e.dataTransfer.files.length > 0) { // 使用新的API获取文件路径 @@ -15,7 +17,7 @@ export const getFilesFromDropEvent = async (e: React.DragEvent): } return null } catch (error) { - Logger.error('[src/renderer/src/utils/input.ts] getFilesFromDropEvent - getPathForFile error:', error) + logger.error('getFilesFromDropEvent - getPathForFile error:', error) return null } }) @@ -26,7 +28,7 @@ export const getFilesFromDropEvent = async (e: React.DragEvent): if (result.status === 'fulfilled' && result.value !== null) { list.push(result.value) } else if (result.status === 'rejected') { - Logger.error('[src/renderer/src/utils/input.ts] getFilesFromDropEvent:', result.reason) + logger.error('getFilesFromDropEvent:', result.reason) } } return list diff --git a/src/renderer/src/utils/mcp-tools.ts b/src/renderer/src/utils/mcp-tools.ts index 8beffb4cd2..6e2364b145 100644 --- a/src/renderer/src/utils/mcp-tools.ts +++ b/src/renderer/src/utils/mcp-tools.ts @@ -1,6 +1,6 @@ import { ContentBlockParam, MessageParam, ToolUnion, ToolUseBlock } from '@anthropic-ai/sdk/resources' import { Content, FunctionCall, Part, Tool, Type as GeminiSchemaType } from '@google/genai' -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import { isFunctionCallingModel, isVisionModel } from '@renderer/config/models' import i18n from '@renderer/i18n' import store from '@renderer/store' @@ -29,6 +29,8 @@ import { import { CompletionsParams } from '../aiCore/middleware/schemas' import { confirmSameNameTools, requestToolConfirmation, setToolIdToNameMapping } from './userConfirmation' +const logger = loggerService.withContext('Utils:MCPTools') + const MCP_AUTO_INSTALL_SERVER_NAME = '@cherry/mcp-auto-install' const EXTRA_SCHEMA_KEYS = ['schema', 'headers'] @@ -259,7 +261,7 @@ export function openAIToolsToMcpTool( }) if (!tool) { - console.warn('No MCP Tool found for tool call:', toolCall) + logger.warn('No MCP Tool found for tool call:', toolCall) return undefined } @@ -267,7 +269,7 @@ export function openAIToolsToMcpTool( } export async function callMCPTool(toolResponse: MCPToolResponse): Promise { - Logger.log(`[MCP] Calling Tool: ${toolResponse.tool.serverName} ${toolResponse.tool.name}`, toolResponse.tool) + logger.info(`Calling Tool: ${toolResponse.tool.serverName} ${toolResponse.tool.name}`, toolResponse.tool) try { const server = getMcpServerByTool(toolResponse.tool) @@ -299,10 +301,10 @@ export async function callMCPTool(toolResponse: MCPToolResponse): Promise tool.id === toolName) if (!mcpTool) { - Logger.error(`Tool "${toolName}" not found in MCP tools`) + logger.error(`Tool "${toolName}" not found in MCP tools`) window.message.error(i18n.t('settings.mcp.errors.toolNotFound', { name: toolName })) continue } @@ -654,7 +656,7 @@ export async function parseAndCallTools( toolResults.push(convertedMessage) } } catch (error) { - Logger.error(`🔧 [MCP] Error executing tool ${toolResponse.id}:`, error) + logger.error(`Error executing tool ${toolResponse.id}:`, error) // 更新为错误状态 upsertMCPToolResponse( allToolResponses, @@ -696,7 +698,7 @@ export async function parseAndCallTools( } }) .catch((error) => { - Logger.error(`🔧 [MCP] Error waiting for tool confirmation ${toolResponse.id}:`, error) + logger.error(`Error waiting for tool confirmation ${toolResponse.id}:`, error) // 立即更新为cancelled状态 upsertMCPToolResponse( allToolResponses, diff --git a/src/renderer/src/utils/messageUtils/create.ts b/src/renderer/src/utils/messageUtils/create.ts index 5f6e1f16d2..f3af4c858f 100644 --- a/src/renderer/src/utils/messageUtils/create.ts +++ b/src/renderer/src/utils/messageUtils/create.ts @@ -1,4 +1,4 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' import type { Assistant, FileMetadata, Topic } from '@renderer/types' import { FileTypes } from '@renderer/types' import type { @@ -24,6 +24,8 @@ import { v4 as uuidv4 } from 'uuid' type PartialBy = Omit & Partial> +const logger = loggerService.withContext('Utils:MessageUtils') + /** * Creates a base message block with common properties. * @param messageId - The ID of the parent message. @@ -101,7 +103,7 @@ export function createImageBlock( overrides: Partial> = {} ): ImageMessageBlock { if (overrides.file && overrides.file.type !== FileTypes.IMAGE) { - console.warn('Attempted to create ImageBlock with non-image file type:', overrides.file.type) + logger.warn('Attempted to create ImageBlock with non-image file type:', overrides.file.type) } const { file, url, metadata, ...baseOverrides } = overrides const baseBlock = createBaseMessageBlock(messageId, MessageBlockType.IMAGE, baseOverrides) @@ -178,7 +180,7 @@ export function createFileBlock( overrides: Partial> = {} ): FileMessageBlock { if (file.type === FileTypes.IMAGE) { - console.warn('Use createImageBlock for image file types.') + logger.warn('Use createImageBlock for image file types.') } return { ...createBaseMessageBlock(messageId, MessageBlockType.FILE, overrides), @@ -232,9 +234,9 @@ export function createToolBlock( metadata: metadata, ...baseOnlyOverrides } - Logger.log('createToolBlock_baseOverrides', baseOverrides.metadata) + logger.info('createToolBlock_baseOverrides', baseOverrides.metadata) const baseBlock = createBaseMessageBlock(messageId, MessageBlockType.TOOL, baseOverrides) - Logger.log('createToolBlock_baseBlock', baseBlock.metadata) + logger.info('createToolBlock_baseBlock', baseBlock.metadata) return { ...baseBlock, toolId, @@ -296,7 +298,7 @@ export function createMessage( let blocks: string[] = initialBlocks || [] if (role !== 'system' && (!initialBlocks || initialBlocks.length === 0)) { - console.warn('createMessage: initialContent provided but no initialBlocks. Block must be created separately.') + logger.warn('createMessage: initialContent provided but no initialBlocks. Block must be created separately.') } blocks = blocks.map(String) @@ -393,7 +395,7 @@ export const resetAssistantMessage = ( ): Message => { // Ensure we are only resetting assistant messages if (originalMessage.role !== 'assistant') { - console.warn( + logger.warn( `[resetAssistantMessage] Attempted to reset a non-assistant message (ID: ${originalMessage.id}, Role: ${originalMessage.role}). Returning original.` ) return originalMessage diff --git a/src/renderer/src/utils/oauth.ts b/src/renderer/src/utils/oauth.ts index 519eeaf94c..1da6a29bba 100644 --- a/src/renderer/src/utils/oauth.ts +++ b/src/renderer/src/utils/oauth.ts @@ -1,6 +1,9 @@ +import { loggerService } from '@logger' import { PPIO_APP_SECRET, PPIO_CLIENT_ID, SILICON_CLIENT_ID, TOKENFLUX_HOST } from '@renderer/config/constant' import i18n, { getLanguageCode } from '@renderer/i18n' +const logger = loggerService.withContext('Utils:oauth') + export const oauthWithSiliconFlow = async (setKey) => { const authUrl = `https://account.siliconflow.cn/oauth?client_id=${SILICON_CLIENT_ID}` @@ -47,7 +50,7 @@ export const oauthWithAihubmix = async (setKey) => { window.removeEventListener('message', messageHandler) } } catch (error) { - console.error('[oauthWithAihubmix] error', error) + logger.error('[oauthWithAihubmix] error', error) popup?.close() window.message.error(i18n.t('oauth.error')) } @@ -69,11 +72,11 @@ export const oauthWithPPIO = async (setKey) => { ) if (!setKey) { - console.log('[PPIO OAuth] No setKey callback provided, returning early') + logger.debug('[PPIO OAuth] No setKey callback provided, returning early') return } - console.log('[PPIO OAuth] Setting up protocol listener') + logger.debug('[PPIO OAuth] Setting up protocol listener') return new Promise((resolve, reject) => { const removeListener = window.api.protocol.onReceiveData(async (data) => { @@ -110,7 +113,7 @@ export const oauthWithPPIO = async (setKey) => { if (!tokenResponse.ok) { const errorText = await tokenResponse.text() - console.error('[PPIO OAuth] Token exchange failed:', tokenResponse.status, errorText) + logger.error('[PPIO OAuth] Token exchange failed:', tokenResponse.status, errorText) throw new Error(`Failed to exchange code for token: ${tokenResponse.status} ${errorText}`) } @@ -124,7 +127,7 @@ export const oauthWithPPIO = async (setKey) => { reject(new Error('No access token received')) } } catch (error) { - console.error('[PPIO OAuth] Error processing callback:', error) + logger.error('[PPIO OAuth] Error processing callback:', error) reject(error) } finally { removeListener() diff --git a/src/renderer/src/utils/prompt.ts b/src/renderer/src/utils/prompt.ts index 288bcfec4a..cf3738f50f 100644 --- a/src/renderer/src/utils/prompt.ts +++ b/src/renderer/src/utils/prompt.ts @@ -1,6 +1,9 @@ +import { loggerService } from '@logger' import store from '@renderer/store' import { Assistant, MCPTool } from '@renderer/types' +const logger = loggerService.withContext('Utils:Prompt') + export const SYSTEM_PROMPT = `In this environment you have access to a set of tools you can use to answer the user's question. \ You can use one or more tools per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. @@ -175,7 +178,7 @@ export const buildSystemPrompt = async ( const systemType = await window.api.system.getDeviceType() userSystemPrompt = userSystemPrompt.replace(/{{system}}/g, systemType) } catch (error) { - console.error('Failed to get system type:', error) + logger.error('Failed to get system type:', error) userSystemPrompt = userSystemPrompt.replace(/{{system}}/g, 'Unknown System') } } @@ -185,7 +188,7 @@ export const buildSystemPrompt = async ( const language = store.getState().settings.language userSystemPrompt = userSystemPrompt.replace(/{{language}}/g, language) } catch (error) { - console.error('Failed to get language:', error) + logger.error('Failed to get language:', error) userSystemPrompt = userSystemPrompt.replace(/{{language}}/g, 'Unknown System Language') } } @@ -195,7 +198,7 @@ export const buildSystemPrompt = async ( const appInfo = await window.api.getAppInfo() userSystemPrompt = userSystemPrompt.replace(/{{arch}}/g, appInfo.arch) } catch (error) { - console.error('Failed to get architecture:', error) + logger.error('Failed to get architecture:', error) userSystemPrompt = userSystemPrompt.replace(/{{arch}}/g, 'Unknown Architecture') } } @@ -204,7 +207,7 @@ export const buildSystemPrompt = async ( try { userSystemPrompt = userSystemPrompt.replace(/{{model_name}}/g, assistant?.model?.name || 'Unknown Model') } catch (error) { - console.error('Failed to get model name:', error) + logger.error('Failed to get model name:', error) userSystemPrompt = userSystemPrompt.replace(/{{model_name}}/g, 'Unknown Model') } } @@ -214,7 +217,7 @@ export const buildSystemPrompt = async ( const username = store.getState().settings.userName || 'Unknown Username' userSystemPrompt = userSystemPrompt.replace(/{{username}}/g, username) } catch (error) { - console.error('Failed to get username:', error) + logger.error('Failed to get username:', error) userSystemPrompt = userSystemPrompt.replace(/{{username}}/g, 'Unknown Username') } } diff --git a/src/renderer/src/utils/translate.ts b/src/renderer/src/utils/translate.ts index 77a88928b9..b0edfddc23 100644 --- a/src/renderer/src/utils/translate.ts +++ b/src/renderer/src/utils/translate.ts @@ -1,8 +1,11 @@ +import { loggerService } from '@logger' import { LanguagesEnum } from '@renderer/config/translate' import { Language, LanguageCode } from '@renderer/types' import { franc } from 'franc-min' import React, { MutableRefObject } from 'react' +const logger = loggerService.withContext('Utils:translate') + /** * 使用Unicode字符范围检测语言 * 适用于较短文本的语言检测 @@ -71,7 +74,7 @@ export const detectLanguageByUnicode = (text: string): Language => { case 'en': return LanguagesEnum.enUS default: - console.error(`Unknown language: ${maxLang}`) + logger.error(`Unknown language: ${maxLang}`) return LanguagesEnum.enUS } } @@ -241,7 +244,7 @@ export const createOutputScrollHandler = ( export const getLanguageByLangcode = (langcode: LanguageCode): Language => { const result = Object.values(LanguagesEnum).find((item) => item.langCode === langcode) if (!result) { - console.error(`Language not found for langcode: ${langcode}`) + logger.error(`Language not found for langcode: ${langcode}`) return LanguagesEnum.enUS } return result diff --git a/src/renderer/src/utils/userConfirmation.ts b/src/renderer/src/utils/userConfirmation.ts index b9c88886c5..9efc44ce4a 100644 --- a/src/renderer/src/utils/userConfirmation.ts +++ b/src/renderer/src/utils/userConfirmation.ts @@ -1,10 +1,12 @@ -import Logger from '@renderer/config/logger' +import { loggerService } from '@logger' // 存储每个工具的确认Promise的resolve函数 const toolConfirmResolvers = new Map void>() // 存储每个工具的abort监听器清理函数 const abortListeners = new Map void>() +const logger = loggerService.withContext('Utils:UserConfirmation') + export function requestUserConfirmation(): Promise { return new Promise((resolve) => { const globalKey = '_global' @@ -55,7 +57,7 @@ export function confirmToolAction(toolId: string) { cleanup() } } else { - Logger.warn(`🔧 [userConfirmation] No resolver found for tool: ${toolId}`) + logger.warn(`No resolver found for tool: ${toolId}`) } } @@ -71,7 +73,7 @@ export function cancelToolAction(toolId: string) { cleanup() } } else { - Logger.warn(`🔧 [userConfirmation] No resolver found for tool: ${toolId}`) + logger.warn(`No resolver found for tool: ${toolId}`) } } diff --git a/src/renderer/src/windows/mini/entryPoint.tsx b/src/renderer/src/windows/mini/entryPoint.tsx index b8fe037e1d..cc4da3f940 100644 --- a/src/renderer/src/windows/mini/entryPoint.tsx +++ b/src/renderer/src/windows/mini/entryPoint.tsx @@ -2,11 +2,14 @@ import '@renderer/assets/styles/index.scss' import '@ant-design/v5-patch-for-react-19' import KeyvStorage from '@kangfenmao/keyv-storage' +import { loggerService } from '@logger' import storeSyncService from '@renderer/services/StoreSyncService' import { createRoot } from 'react-dom/client' import MiniWindowApp from './MiniWindowApp' +loggerService.initWindowSource('MiniWindow') + /** * This function is required for model API * eg. BaseProviders.ts diff --git a/src/renderer/src/windows/mini/home/HomeWindow.tsx b/src/renderer/src/windows/mini/home/HomeWindow.tsx index 325b74b215..bfcd02bafa 100644 --- a/src/renderer/src/windows/mini/home/HomeWindow.tsx +++ b/src/renderer/src/windows/mini/home/HomeWindow.tsx @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { isMac } from '@renderer/config/constant' import { useTheme } from '@renderer/context/ThemeProvider' import { useAssistant } from '@renderer/hooks/useAssistant' @@ -33,6 +34,8 @@ import FeatureMenus, { FeatureMenusRef } from './components/FeatureMenus' import Footer from './components/Footer' import InputBar from './components/InputBar' +const logger = loggerService.withContext('HomeWindow') + const HomeWindow: FC = () => { const { language, readClipboardAtStartup, windowStyle } = useSettings() const { theme } = useTheme() @@ -106,7 +109,7 @@ const HomeWindow: FC = () => { } } catch (error) { // Silently handle clipboard read errors (common in some environments) - console.warn('Failed to read clipboard:', error) + logger.warn('Failed to read clipboard:', error) } }, [readClipboardAtStartup]) @@ -394,7 +397,7 @@ const HomeWindow: FC = () => { } catch (err) { if (isAbortError(err)) return handleError(err instanceof Error ? err : new Error('An error occurred')) - console.error('Error fetching result:', err) + logger.error('Error fetching result:', err) } finally { setIsLoading(false) setIsOutputted(true) diff --git a/src/renderer/src/windows/mini/translate/TranslateWindow.tsx b/src/renderer/src/windows/mini/translate/TranslateWindow.tsx index 9cfde74bd3..56ef9312df 100644 --- a/src/renderer/src/windows/mini/translate/TranslateWindow.tsx +++ b/src/renderer/src/windows/mini/translate/TranslateWindow.tsx @@ -1,4 +1,5 @@ import { SwapOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' import Scrollbar from '@renderer/components/Scrollbar' import { LanguagesEnum, translateLanguageOptions } from '@renderer/config/translate' import db from '@renderer/databases' @@ -15,6 +16,8 @@ import { useHotkeys } from 'react-hotkeys-hook' import { useTranslation } from 'react-i18next' import styled from 'styled-components' +const logger = loggerService.withContext('TranslateWindow') + interface Props { text: string } @@ -55,7 +58,7 @@ const Translate: FC = ({ text }) => { translatingRef.current = false } catch (error) { - console.error(error) + logger.error('Error fetching result:', error) } finally { translatingRef.current = false } diff --git a/src/renderer/src/windows/selection/action/components/ActionUtils.ts b/src/renderer/src/windows/selection/action/components/ActionUtils.ts index 91daa4eac8..148229526d 100644 --- a/src/renderer/src/windows/selection/action/components/ActionUtils.ts +++ b/src/renderer/src/windows/selection/action/components/ActionUtils.ts @@ -1,3 +1,4 @@ +import { loggerService } from '@logger' import { fetchChatCompletion } from '@renderer/services/ApiService' import { getAssistantMessage, getUserMessage } from '@renderer/services/MessagesService' import store from '@renderer/store' @@ -10,6 +11,8 @@ import { AssistantMessageStatus, MessageBlockStatus } from '@renderer/types/newM import { isAbortError } from '@renderer/utils/error' import { createMainTextBlock, createThinkingBlock } from '@renderer/utils/messageUtils/create' +const logger = loggerService.withContext('ActionUtils') + export const processMessages = async ( assistant: Assistant, topic: Topic, @@ -190,6 +193,6 @@ export const processMessages = async ( } catch (err) { if (isAbortError(err)) return onError(err instanceof Error ? err : new Error('An error occurred')) - console.error('Error fetching result:', err) + logger.error('Error fetching result:', err) } } diff --git a/src/renderer/src/windows/selection/action/entryPoint.tsx b/src/renderer/src/windows/selection/action/entryPoint.tsx index 3d91006c46..bbbe2aff0f 100644 --- a/src/renderer/src/windows/selection/action/entryPoint.tsx +++ b/src/renderer/src/windows/selection/action/entryPoint.tsx @@ -2,6 +2,7 @@ import '@renderer/assets/styles/index.scss' import '@ant-design/v5-patch-for-react-19' import KeyvStorage from '@kangfenmao/keyv-storage' +import { loggerService } from '@logger' import AntdProvider from '@renderer/context/AntdProvider' import { CodeStyleProvider } from '@renderer/context/CodeStyleProvider' import { ThemeProvider } from '@renderer/context/ThemeProvider' @@ -15,6 +16,8 @@ import { PersistGate } from 'redux-persist/integration/react' import SelectionActionApp from './SelectionActionApp' +loggerService.initWindowSource('SelectionActionWindow') + /** * fetchChatCompletion depends on this, * which is not a good design, but we have to add it for now diff --git a/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx b/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx index 342e4122a7..6a40498cb4 100644 --- a/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx +++ b/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx @@ -1,5 +1,6 @@ import '@renderer/assets/styles/selection-toolbar.scss' +import { loggerService } from '@logger' import { AppLogo } from '@renderer/config/env' import { useSelectionAssistant } from '@renderer/hooks/useSelectionAssistant' import { useSettings } from '@renderer/hooks/useSettings' @@ -15,11 +16,13 @@ import { useTranslation } from 'react-i18next' import { TextSelectionData } from 'selection-hook' import styled from 'styled-components' +const logger = loggerService.withContext('SelectionToolbar') + //tell main the actual size of the content const updateWindowSize = () => { const rootElement = document.getElementById('root') if (!rootElement) { - console.error('SelectionToolbar: Root element not found') + logger.error('Root element not found') return } window.api?.selection.determineToolbarSize(rootElement.scrollWidth, rootElement.scrollHeight) diff --git a/src/renderer/src/windows/selection/toolbar/entryPoint.tsx b/src/renderer/src/windows/selection/toolbar/entryPoint.tsx index 398dc5c97f..02d68607ee 100644 --- a/src/renderer/src/windows/selection/toolbar/entryPoint.tsx +++ b/src/renderer/src/windows/selection/toolbar/entryPoint.tsx @@ -1,5 +1,6 @@ import '@ant-design/v5-patch-for-react-19' +import { loggerService } from '@logger' import { ThemeProvider } from '@renderer/context/ThemeProvider' import storeSyncService from '@renderer/services/StoreSyncService' import store, { persistor } from '@renderer/store' @@ -10,6 +11,8 @@ import { PersistGate } from 'redux-persist/integration/react' import SelectionToolbar from './SelectionToolbar' +loggerService.initWindowSource('SelectionToolbar') + //subscribe to store sync storeSyncService.subscribe() diff --git a/tests/__mocks__/MainLoggerService.ts b/tests/__mocks__/MainLoggerService.ts new file mode 100644 index 0000000000..43b1f33e4a --- /dev/null +++ b/tests/__mocks__/MainLoggerService.ts @@ -0,0 +1,62 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ + +// Simple mock LoggerService class for main process +export class MockMainLoggerService { + private static instance: MockMainLoggerService + + public static getInstance(): MockMainLoggerService { + if (!MockMainLoggerService.instance) { + MockMainLoggerService.instance = new MockMainLoggerService() + } + return MockMainLoggerService.instance + } + + public static resetInstance(): void { + MockMainLoggerService.instance = new MockMainLoggerService() + } + + public withContext(): MockMainLoggerService { + return this + } + public finish(): void {} + public setLevel(): void {} + public getLevel(): string { + return 'silly' + } + public resetLevel(): void {} + public getLogsDir(): string { + return '/mock/logs' + } + public getBaseLogger(): any { + return {} + } + public error(...args: any[]): void { + console.error(...args) + } + public warn(...args: any[]): void { + console.warn(...args) + } + public info(...args: any[]): void { + console.info(...args) + } + public verbose(...args: any[]): void { + console.log(...args) + } + public debug(...args: any[]): void { + console.debug(...args) + } + public silly(...args: any[]): void { + console.log(...args) + } +} + +// Create and export the mock instance +export const mockMainLoggerService = MockMainLoggerService.getInstance() + +// Mock the LoggerService module for main process +const MainLoggerServiceMock = { + LoggerService: MockMainLoggerService, + loggerService: mockMainLoggerService +} + +export default MainLoggerServiceMock diff --git a/tests/__mocks__/RendererLoggerService.ts b/tests/__mocks__/RendererLoggerService.ts new file mode 100644 index 0000000000..40f77635eb --- /dev/null +++ b/tests/__mocks__/RendererLoggerService.ts @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ + +// Simple mock LoggerService class for renderer process +export class MockRendererLoggerService { + private static instance: MockRendererLoggerService + + public static getInstance(): MockRendererLoggerService { + if (!MockRendererLoggerService.instance) { + MockRendererLoggerService.instance = new MockRendererLoggerService() + } + return MockRendererLoggerService.instance + } + + public static resetInstance(): void { + MockRendererLoggerService.instance = new MockRendererLoggerService() + } + + public initWindowSource(): void {} + public withContext(): MockRendererLoggerService { + return this + } + public setLevel(): void {} + public getLevel(): string { + return 'silly' + } + public resetLevel(): void {} + public error(...args: any[]): void { + console.error(...args) + } + public warn(...args: any[]): void { + console.warn(...args) + } + public info(...args: any[]): void { + console.info(...args) + } + public verbose(...args: any[]): void { + console.log(...args) + } + public debug(...args: any[]): void { + console.debug(...args) + } + public silly(...args: any[]): void { + console.log(...args) + } +} + +// Create and export the mock instance +export const mockRendererLoggerService = MockRendererLoggerService.getInstance() + +// Mock the LoggerService module +const RendererLoggerServiceMock = { + LoggerService: MockRendererLoggerService, + loggerService: mockRendererLoggerService +} + +export default RendererLoggerServiceMock diff --git a/tests/main.setup.ts b/tests/main.setup.ts new file mode 100644 index 0000000000..5cadb89d02 --- /dev/null +++ b/tests/main.setup.ts @@ -0,0 +1,139 @@ +import { vi } from 'vitest' + +// Mock LoggerService globally for main process tests +vi.mock('@logger', async () => { + const { MockMainLoggerService, mockMainLoggerService } = await import('./__mocks__/MainLoggerService') + return { + LoggerService: MockMainLoggerService, + loggerService: mockMainLoggerService + } +}) + +// Mock electron modules that are commonly used in main process +vi.mock('electron', () => ({ + app: { + getPath: vi.fn((key: string) => { + switch (key) { + case 'userData': + return '/mock/userData' + case 'temp': + return '/mock/temp' + case 'logs': + return '/mock/logs' + default: + return '/mock/unknown' + } + }), + getVersion: vi.fn(() => '1.0.0') + }, + ipcMain: { + handle: vi.fn(), + on: vi.fn(), + once: vi.fn(), + removeHandler: vi.fn(), + removeAllListeners: vi.fn() + }, + BrowserWindow: vi.fn(), + dialog: { + showErrorBox: vi.fn(), + showMessageBox: vi.fn(), + showOpenDialog: vi.fn(), + showSaveDialog: vi.fn() + }, + shell: { + openExternal: vi.fn(), + showItemInFolder: vi.fn() + }, + session: { + defaultSession: { + clearCache: vi.fn(), + clearStorageData: vi.fn() + } + }, + webContents: { + getAllWebContents: vi.fn(() => []) + }, + systemPreferences: { + getMediaAccessStatus: vi.fn(), + askForMediaAccess: vi.fn() + }, + screen: { + getPrimaryDisplay: vi.fn(), + getAllDisplays: vi.fn() + }, + Notification: vi.fn() +})) + +// Mock Winston for LoggerService dependencies +vi.mock('winston', () => ({ + createLogger: vi.fn(() => ({ + log: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + debug: vi.fn(), + level: 'info', + on: vi.fn(), + end: vi.fn() + })), + format: { + combine: vi.fn(), + splat: vi.fn(), + timestamp: vi.fn(), + errors: vi.fn(), + json: vi.fn() + }, + transports: { + Console: vi.fn(), + File: vi.fn() + } +})) + +// Mock winston-daily-rotate-file +vi.mock('winston-daily-rotate-file', () => { + return vi.fn().mockImplementation(() => ({ + on: vi.fn(), + log: vi.fn() + })) +}) + +// Mock Node.js modules +vi.mock('node:os', () => ({ + platform: vi.fn(() => 'darwin'), + arch: vi.fn(() => 'x64'), + version: vi.fn(() => '20.0.0'), + cpus: vi.fn(() => [{ model: 'Mock CPU' }]), + totalmem: vi.fn(() => 8 * 1024 * 1024 * 1024) // 8GB +})) + +vi.mock('node:path', async () => { + const actual = await vi.importActual('node:path') + return { + ...actual, + join: vi.fn((...args: string[]) => args.join('/')), + resolve: vi.fn((...args: string[]) => args.join('/')) + } +}) + +vi.mock('node:fs', () => ({ + promises: { + access: vi.fn(), + readFile: vi.fn(), + writeFile: vi.fn(), + mkdir: vi.fn(), + readdir: vi.fn(), + stat: vi.fn(), + unlink: vi.fn(), + rmdir: vi.fn() + }, + existsSync: vi.fn(), + readFileSync: vi.fn(), + writeFileSync: vi.fn(), + mkdirSync: vi.fn(), + readdirSync: vi.fn(), + statSync: vi.fn(), + unlinkSync: vi.fn(), + rmdirSync: vi.fn(), + createReadStream: vi.fn(), + createWriteStream: vi.fn() +})) diff --git a/tests/renderer.setup.ts b/tests/renderer.setup.ts index cafad80a31..ea4057eca8 100644 --- a/tests/renderer.setup.ts +++ b/tests/renderer.setup.ts @@ -5,22 +5,12 @@ import { expect, vi } from 'vitest' expect.addSnapshotSerializer(styleSheetSerializer) -vi.mock('electron-log/renderer', () => { +// Mock LoggerService globally for renderer tests +vi.mock('@logger', async () => { + const { MockRendererLoggerService, mockRendererLoggerService } = await import('./__mocks__/RendererLoggerService') return { - default: { - info: console.log, - error: console.error, - warn: console.warn, - debug: console.debug, - verbose: console.log, - silly: console.log, - log: console.log, - transports: { - console: { - level: 'info' - } - } - } + LoggerService: MockRendererLoggerService, + loggerService: mockRendererLoggerService } }) diff --git a/tsconfig.node.json b/tsconfig.node.json index ec30dcfec7..50fe4405a0 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -17,7 +17,8 @@ "paths": { "@main/*": ["src/main/*"], "@types": ["src/renderer/src/types/index.ts"], - "@shared/*": ["packages/shared/*"] + "@shared/*": ["packages/shared/*"], + "@logger": ["src/main/services/LoggerService"] } } } diff --git a/tsconfig.web.json b/tsconfig.web.json index ddcece6352..9d165a2d36 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -4,7 +4,8 @@ "src/renderer/src/**/*", "src/preload/*.d.ts", "local/src/renderer/**/*", - "packages/shared/**/*" + "packages/shared/**/*", + "tests/__mocks__/**/*" ], "compilerOptions": { "composite": true, @@ -14,7 +15,8 @@ "paths": { "@renderer/*": ["src/renderer/src/*"], "@shared/*": ["packages/shared/*"], - "@types": ["src/renderer/src/types/index.ts"] + "@types": ["src/renderer/src/types/index.ts"], + "@logger": ["src/renderer/src/services/LoggerService"] } } } diff --git a/vitest.config.ts b/vitest.config.ts index 43f577eb1c..9736d0ab57 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -18,6 +18,7 @@ export default defineConfig({ test: { name: 'main', environment: 'node', + setupFiles: ['tests/main.setup.ts'], include: ['src/main/**/*.{test,spec}.{ts,tsx}', 'src/main/**/__tests__/**/*.{test,spec}.{ts,tsx}'] } }, diff --git a/yarn.lock b/yarn.lock index 2e6903a99a..7f6882c850 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1773,6 +1773,13 @@ __metadata: languageName: node linkType: hard +"@colors/colors@npm:1.6.0, @colors/colors@npm:^1.6.0": + version: 1.6.0 + resolution: "@colors/colors@npm:1.6.0" + checksum: 10c0/9328a0778a5b0db243af54455b79a69e3fb21122d6c15ef9e9fcc94881d8d17352d8b2b2590f9bdd46fac5c2d6c1636dcfc14358a20c70e22daf89e1a759b629 + languageName: node + linkType: hard + "@csstools/color-helpers@npm:^5.0.2": version: 5.0.2 resolution: "@csstools/color-helpers@npm:5.0.2" @@ -1819,6 +1826,17 @@ __metadata: languageName: node linkType: hard +"@dabh/diagnostics@npm:^2.0.2": + version: 2.0.3 + resolution: "@dabh/diagnostics@npm:2.0.3" + dependencies: + colorspace: "npm:1.1.x" + enabled: "npm:2.0.x" + kuler: "npm:^2.0.0" + checksum: 10c0/a5133df8492802465ed01f2f0a5784585241a1030c362d54a602ed1839816d6c93d71dde05cf2ddb4fd0796238c19774406bd62fa2564b637907b495f52425fe + languageName: node + linkType: hard + "@develar/schema-utils@npm:~2.6.5": version: 2.6.5 resolution: "@develar/schema-utils@npm:2.6.5" @@ -5870,6 +5888,13 @@ __metadata: languageName: node linkType: hard +"@types/triple-beam@npm:^1.3.2": + version: 1.3.5 + resolution: "@types/triple-beam@npm:1.3.5" + checksum: 10c0/d5d7f25da612f6d79266f4f1bb9c1ef8f1684e9f60abab251e1261170631062b656ba26ff22631f2760caeafd372abc41e64867cde27fba54fafb73a35b9056a + languageName: node + linkType: hard + "@types/trusted-types@npm:^2.0.7": version: 2.0.7 resolution: "@types/trusted-types@npm:2.0.7" @@ -6908,7 +6933,6 @@ __metadata: electron: "npm:35.6.0" electron-builder: "npm:26.0.15" electron-devtools-installer: "npm:^3.2.0" - electron-log: "npm:^5.1.5" electron-store: "npm:^8.2.0" electron-updater: "npm:6.6.4" electron-vite: "npm:^3.1.0" @@ -6992,6 +7016,8 @@ __metadata: vite: "npm:6.2.6" vitest: "npm:^3.1.4" webdav: "npm:^5.8.0" + winston: "npm:^3.17.0" + winston-daily-rotate-file: "npm:^5.0.0" word-extractor: "npm:^1.0.4" zipread: "npm:^1.3.3" dependenciesMeta: @@ -8390,6 +8416,15 @@ __metadata: languageName: node linkType: hard +"color-convert@npm:^1.9.3": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: "npm:1.1.3" + checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c + languageName: node + linkType: hard + "color-convert@npm:^2.0.1": version: 2.0.1 resolution: "color-convert@npm:2.0.1" @@ -8408,6 +8443,20 @@ __metadata: languageName: node linkType: hard +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 + languageName: node + linkType: hard + +"color-name@npm:^1.0.0, color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + "color-name@npm:^2.0.0": version: 2.0.0 resolution: "color-name@npm:2.0.0" @@ -8415,10 +8464,13 @@ __metadata: languageName: node linkType: hard -"color-name@npm:~1.1.4": - version: 1.1.4 - resolution: "color-name@npm:1.1.4" - checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 +"color-string@npm:^1.6.0": + version: 1.9.1 + resolution: "color-string@npm:1.9.1" + dependencies: + color-name: "npm:^1.0.0" + simple-swizzle: "npm:^0.2.2" + checksum: 10c0/b0bfd74c03b1f837f543898b512f5ea353f71630ccdd0d66f83028d1f0924a7d4272deb278b9aef376cacf1289b522ac3fb175e99895283645a2dc3a33af2404 languageName: node linkType: hard @@ -8431,6 +8483,16 @@ __metadata: languageName: node linkType: hard +"color@npm:^3.1.3": + version: 3.2.1 + resolution: "color@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.3" + color-string: "npm:^1.6.0" + checksum: 10c0/39345d55825884c32a88b95127d417a2c24681d8b57069413596d9fcbb721459ef9d9ec24ce3e65527b5373ce171b73e38dbcd9c830a52a6487e7f37bf00e83c + languageName: node + linkType: hard + "color@npm:^5.0.0": version: 5.0.0 resolution: "color@npm:5.0.0" @@ -8448,6 +8510,16 @@ __metadata: languageName: node linkType: hard +"colorspace@npm:1.1.x": + version: 1.1.4 + resolution: "colorspace@npm:1.1.4" + dependencies: + color: "npm:^3.1.3" + text-hex: "npm:1.0.x" + checksum: 10c0/af5f91ff7f8e146b96e439ac20ed79b197210193bde721b47380a75b21751d90fa56390c773bb67c0aedd34ff85091883a437ab56861c779bd507d639ba7e123 + languageName: node + linkType: hard + "combined-stream@npm:^1.0.8": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" @@ -9829,13 +9901,6 @@ __metadata: languageName: node linkType: hard -"electron-log@npm:^5.1.5": - version: 5.3.3 - resolution: "electron-log@npm:5.3.3" - checksum: 10c0/8379736e6c9123ccc55d7ba58ed3d8c6916f1d5c97e9a714a4855513c098bf7843642d896a89cb068b3ebae3835692500322dd1ce316ca6fe7154e67fb3e3b90 - languageName: node - linkType: hard - "electron-publish@npm:26.0.13": version: 26.0.13 resolution: "electron-publish@npm:26.0.13" @@ -9965,6 +10030,13 @@ __metadata: languageName: node linkType: hard +"enabled@npm:2.0.x": + version: 2.0.0 + resolution: "enabled@npm:2.0.0" + checksum: 10c0/3b2c2af9bc7f8b9e291610f2dde4a75cf6ee52a68f4dd585482fbdf9a55d65388940e024e56d40bb03e05ef6671f5f53021fa8b72a20e954d7066ec28166713f + languageName: node + linkType: hard + "encodeurl@npm:^2.0.0": version: 2.0.0 resolution: "encodeurl@npm:2.0.0" @@ -10939,6 +11011,13 @@ __metadata: languageName: node linkType: hard +"fecha@npm:^4.2.0": + version: 4.2.3 + resolution: "fecha@npm:4.2.3" + checksum: 10c0/0e895965959cf6a22bb7b00f0bf546f2783836310f510ddf63f463e1518d4c96dec61ab33fdfd8e79a71b4856a7c865478ce2ee8498d560fe125947703c9b1cf + languageName: node + linkType: hard + "fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": version: 3.2.0 resolution: "fetch-blob@npm:3.2.0" @@ -10982,6 +11061,15 @@ __metadata: languageName: node linkType: hard +"file-stream-rotator@npm:^0.6.1": + version: 0.6.1 + resolution: "file-stream-rotator@npm:0.6.1" + dependencies: + moment: "npm:^2.29.1" + checksum: 10c0/ebb53cc22a33b0b57457c49df96ac96d8f7bace5e495f19577b37c4d87712b5fbe3539724de384852f2f6221aa0f2045e81e1f09a991fcf190f8954ef83caca1 + languageName: node + linkType: hard + "file-type@npm:18.5.0": version: 18.5.0 resolution: "file-type@npm:18.5.0" @@ -11120,6 +11208,13 @@ __metadata: languageName: node linkType: hard +"fn.name@npm:1.x.x": + version: 1.1.0 + resolution: "fn.name@npm:1.1.0" + checksum: 10c0/8ad62aa2d4f0b2a76d09dba36cfec61c540c13a0fd72e5d94164e430f987a7ce6a743112bbeb14877c810ef500d1f73d7f56e76d029d2e3413f20d79e3460a9a + languageName: node + linkType: hard + "follow-redirects@npm:^1.15.6": version: 1.15.9 resolution: "follow-redirects@npm:1.15.9" @@ -12335,6 +12430,13 @@ __metadata: languageName: node linkType: hard +"is-arrayish@npm:^0.3.1": + version: 0.3.2 + resolution: "is-arrayish@npm:0.3.2" + checksum: 10c0/f59b43dc1d129edb6f0e282595e56477f98c40278a2acdc8b0a5c57097c9eff8fe55470493df5775478cf32a4dc8eaf6d3a749f07ceee5bc263a78b2434f6a54 + languageName: node + linkType: hard + "is-buffer@npm:^2.0.0": version: 2.0.5 resolution: "is-buffer@npm:2.0.5" @@ -12963,6 +13065,13 @@ __metadata: languageName: node linkType: hard +"kuler@npm:^2.0.0": + version: 2.0.0 + resolution: "kuler@npm:2.0.0" + checksum: 10c0/0a4e99d92ca373f8f74d1dc37931909c4d0d82aebc94cf2ba265771160fc12c8df34eaaac80805efbda367e2795cb1f1dd4c3d404b6b1cf38aec94035b503d2d + languageName: node + linkType: hard + "ky@npm:^1.8.0": version: 1.8.1 resolution: "ky@npm:1.8.1" @@ -13354,6 +13463,20 @@ __metadata: languageName: node linkType: hard +"logform@npm:^2.7.0": + version: 2.7.0 + resolution: "logform@npm:2.7.0" + dependencies: + "@colors/colors": "npm:1.6.0" + "@types/triple-beam": "npm:^1.3.2" + fecha: "npm:^4.2.0" + ms: "npm:^2.1.1" + safe-stable-stringify: "npm:^2.3.1" + triple-beam: "npm:^1.3.0" + checksum: 10c0/4789b4b37413c731d1835734cb799240d31b865afde6b7b3e06051d6a4127bfda9e88c99cfbf296d084a315ccbed2647796e6a56b66e725bcb268c586f57558f + languageName: node + linkType: hard + "longest-streak@npm:^2.0.0": version: 2.0.4 resolution: "longest-streak@npm:2.0.4" @@ -14918,6 +15041,13 @@ __metadata: languageName: node linkType: hard +"moment@npm:^2.29.1": + version: 2.30.1 + resolution: "moment@npm:2.30.1" + checksum: 10c0/865e4279418c6de666fca7786607705fd0189d8a7b7624e2e56be99290ac846f90878a6f602e34b4e0455c549b85385b1baf9966845962b313699e7cb847543a + languageName: node + linkType: hard + "motion-dom@npm:^12.10.5": version: 12.10.5 resolution: "motion-dom@npm:12.10.5" @@ -15310,6 +15440,13 @@ __metadata: languageName: node linkType: hard +"object-hash@npm:^3.0.0": + version: 3.0.0 + resolution: "object-hash@npm:3.0.0" + checksum: 10c0/a06844537107b960c1c8b96cd2ac8592a265186bfa0f6ccafe0d34eabdb526f6fa81da1f37c43df7ed13b12a4ae3457a16071603bcd39d8beddb5f08c37b0f47 + languageName: node + linkType: hard + "object-inspect@npm:^1.13.3": version: 1.13.4 resolution: "object-inspect@npm:1.13.4" @@ -15383,6 +15520,15 @@ __metadata: languageName: node linkType: hard +"one-time@npm:^1.0.0": + version: 1.0.0 + resolution: "one-time@npm:1.0.0" + dependencies: + fn.name: "npm:1.x.x" + checksum: 10c0/6e4887b331edbb954f4e915831cbec0a7b9956c36f4feb5f6de98c448ac02ff881fd8d9b55a6b1b55030af184c6b648f340a76eb211812f4ad8c9b4b8692fdaa + languageName: node + linkType: hard + "onetime@npm:^5.1.0, onetime@npm:^5.1.2": version: 5.1.2 resolution: "onetime@npm:5.1.2" @@ -17128,7 +17274,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:3, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0": +"readable-stream@npm:3, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.2": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -17760,6 +17906,13 @@ __metadata: languageName: node linkType: hard +"safe-stable-stringify@npm:^2.3.1": + version: 2.5.0 + resolution: "safe-stable-stringify@npm:2.5.0" + checksum: 10c0/baea14971858cadd65df23894a40588ed791769db21bafb7fd7608397dbdce9c5aac60748abae9995e0fc37e15f2061980501e012cd48859740796bea2987f49 + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -18087,6 +18240,15 @@ __metadata: languageName: node linkType: hard +"simple-swizzle@npm:^0.2.2": + version: 0.2.2 + resolution: "simple-swizzle@npm:0.2.2" + dependencies: + is-arrayish: "npm:^0.3.1" + checksum: 10c0/df5e4662a8c750bdba69af4e8263c5d96fe4cd0f9fe4bdfa3cbdeb45d2e869dff640beaaeb1ef0e99db4d8d2ec92f85508c269f50c972174851bc1ae5bd64308 + languageName: node + linkType: hard + "simple-update-notifier@npm:2.0.0": version: 2.0.0 resolution: "simple-update-notifier@npm:2.0.0" @@ -18279,6 +18441,13 @@ __metadata: languageName: node linkType: hard +"stack-trace@npm:0.0.x": + version: 0.0.10 + resolution: "stack-trace@npm:0.0.10" + checksum: 10c0/9ff3dabfad4049b635a85456f927a075c9d0c210e3ea336412d18220b2a86cbb9b13ec46d6c37b70a302a4ea4d49e30e5d4944dd60ae784073f1cde778ac8f4b + languageName: node + linkType: hard + "stackback@npm:0.0.2": version: 0.0.2 resolution: "stackback@npm:0.0.2" @@ -18744,6 +18913,13 @@ __metadata: languageName: node linkType: hard +"text-hex@npm:1.0.x": + version: 1.0.0 + resolution: "text-hex@npm:1.0.0" + checksum: 10c0/57d8d320d92c79d7c03ffb8339b825bb9637c2cbccf14304309f51d8950015c44464b6fd1b6820a3d4821241c68825634f09f5a2d9d501e84f7c6fd14376860d + languageName: node + linkType: hard + "throttle-debounce@npm:^2.1.0": version: 2.3.0 resolution: "throttle-debounce@npm:2.3.0" @@ -18996,6 +19172,13 @@ __metadata: languageName: node linkType: hard +"triple-beam@npm:^1.3.0, triple-beam@npm:^1.4.1": + version: 1.4.1 + resolution: "triple-beam@npm:1.4.1" + checksum: 10c0/4bf1db71e14fe3ff1c3adbe3c302f1fdb553b74d7591a37323a7badb32dc8e9c290738996cbb64f8b10dc5a3833645b5d8c26221aaaaa12e50d1251c9aba2fea + languageName: node + linkType: hard + "trough@npm:^1.0.0": version: 1.0.5 resolution: "trough@npm:1.0.5" @@ -20029,6 +20212,50 @@ __metadata: languageName: node linkType: hard +"winston-daily-rotate-file@npm:^5.0.0": + version: 5.0.0 + resolution: "winston-daily-rotate-file@npm:5.0.0" + dependencies: + file-stream-rotator: "npm:^0.6.1" + object-hash: "npm:^3.0.0" + triple-beam: "npm:^1.4.1" + winston-transport: "npm:^4.7.0" + peerDependencies: + winston: ^3 + checksum: 10c0/c6dfd8ceff8d3801ca702efabaf696bb82919a62c4fbeded8cbf7d2cb188960872eb7a412547af86f12f7257061088a25bac9e318af8932719a2ccba215d7d3f + languageName: node + linkType: hard + +"winston-transport@npm:^4.7.0, winston-transport@npm:^4.9.0": + version: 4.9.0 + resolution: "winston-transport@npm:4.9.0" + dependencies: + logform: "npm:^2.7.0" + readable-stream: "npm:^3.6.2" + triple-beam: "npm:^1.3.0" + checksum: 10c0/e2990a172e754dbf27e7823772214a22dc8312f7ec9cfba831e5ef30a5d5528792e5ea8f083c7387ccfc5b2af20e3691f64738546c8869086110a26f98671095 + languageName: node + linkType: hard + +"winston@npm:^3.17.0": + version: 3.17.0 + resolution: "winston@npm:3.17.0" + dependencies: + "@colors/colors": "npm:^1.6.0" + "@dabh/diagnostics": "npm:^2.0.2" + async: "npm:^3.2.3" + is-stream: "npm:^2.0.0" + logform: "npm:^2.7.0" + one-time: "npm:^1.0.0" + readable-stream: "npm:^3.4.0" + safe-stable-stringify: "npm:^2.3.1" + stack-trace: "npm:0.0.x" + triple-beam: "npm:^1.3.0" + winston-transport: "npm:^4.9.0" + checksum: 10c0/ec8eaeac9a72b2598aedbff50b7dac82ce374a400ed92e7e705d7274426b48edcb25507d78cff318187c4fb27d642a0e2a39c57b6badc9af8e09d4a40636a5f7 + languageName: node + linkType: hard + "word-extractor@npm:^1.0.4": version: 1.0.4 resolution: "word-extractor@npm:1.0.4"