diff --git a/src/logging.ts b/src/logging.ts index 7c405b7..1a21cdf 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,10 +1,22 @@ +// 导入 FileSystemConfig 类型和 isFileSystemConfig 函数,用于处理文件系统配置 import { FileSystemConfig, isFileSystemConfig } from 'common/fileSystemConfig'; +// 导入 vscode 模块,用于访问 VS Code 的 API import * as vscode from 'vscode'; + // Since the Extension Development Host runs with debugger, we can use this to detect if we're debugging. // The only things it currently does is copying Logging messages to the console, while also enabling // the webview (Settings UI) from trying a local dev server first instead of the pre-built version. +// 定义一个名为 DEBUG 的布尔类型的变量,初始值为 false export let DEBUG: boolean = false; + +/** + * 设置调试模式 + * @param debug - 一个布尔值,指示是否启用调试模式 + * @remarks + * 当设置为 true 时,会启用调试模式,并尝试安装一些调试工具。 + * 如果设置为 false,则不会进行任何操作。 + */ export function setDebug(debug: boolean) { console.warn(`[vscode-sshfs] Debug mode set to ${debug}`); DEBUG = debug; @@ -17,6 +29,7 @@ export function setDebug(debug: boolean) { } } +// 创建一个名为 'SSH FS' 的输出通道,用于在 VS Code 中显示日志信息 export const OUTPUT_CHANNEL = vscode.window.createOutputChannel('SSH FS'); export interface LoggingOptions { @@ -26,6 +39,12 @@ export interface LoggingOptions { * - `1`: Only report the name (or first line of stacktrace if missing) * - `2`: Report name and stacktrace (if available) (default for `WARNING`/`ERROR`) */ + /** + * 报告日志记录器名称/堆栈跟踪的级别: + * - `0`:不报告任何内容(默认值为 `WARNING`/`ERROR`) + * - `1`:仅报告名称(如果缺少堆栈跟踪,则报告第一行) + * - `2`:报告名称和堆栈跟踪(如果可用)(默认值为 `WARNING`/`ERROR`) + */ reportedFromLevel: number; /** * Whether to output a stacktrace of the .info() call etc @@ -35,12 +54,25 @@ export interface LoggingOptions { * * Defaults to `3` for `WARNING`, `5` for `ERROR` and `0` for everything else. */ + /** + * 是否输出调用堆栈跟踪: + * - `0`:不输出堆栈跟踪 + * - `-1`:输出整个堆栈跟踪 + * - `N`:仅输出前 N 帧 + * + * 默认值为 `3`(`WARNING`),`5`(`ERROR`),其他级别为 `0`。 + */ callStacktrace: number; /** * Used with `.callStacktrace` to skip the given amount of stacktraces in the beginning. * Useful when `.info()` etc is called from a helper function which itself isn't worth logging the stacktrace of. * Defaults to `0` meaning no offset. */ + /** + * 与 `.callStacktrace` 一起使用,用于在开始时跳过给定数量的堆栈跟踪。 + * 当 `.info()` 等方法从一个辅助函数调用时,这个选项很有用,因为辅助函数本身不值得记录堆栈跟踪。 + * 默认值为 `0`,表示没有偏移。 + */ callStacktraceOffset: number; /** * Used when the "message" to be logged is an Error object with a stack: @@ -48,49 +80,112 @@ export interface LoggingOptions { * - `-1`: Output the whole stack * - `N`: Only output the first N lines */ + /** + * 当要记录的“消息”是一个带有堆栈的 Error 对象时使用: + * - `0`:不输出堆栈(这是 `DEBUG` 和 `INFO` 的默认值) + * - `-1`:输出整个堆栈 + * - `N`:仅输出前 N 行 + */ maxErrorStack: number; } +// 定义了一个日志记录选项的常量,其中 callStacktrace 设置为 0,表示不输出堆栈跟踪信息。 export const LOGGING_NO_STACKTRACE: Partial = { callStacktrace: 0 }; + +// 定义了一个日志记录选项的常量,其中 callStacktrace 设置为 1,表示只输出一行堆栈跟踪信息。 export const LOGGING_SINGLE_LINE_STACKTRACE: Partial = { callStacktrace: 1 }; +/** + * 检查错误对象是否包含 promiseCause 属性,该属性是一个错误对象,并且还有一个名为 promiseCauseName 的字符串属性 + * @param error - 要检查的错误对象 + * @returns 如果 error 对象包含 promiseCause 属性,则返回 true,否则返回 false + */ function hasPromiseCause(error: Error): error is Error & { promiseCause: Error; promiseCauseName: string } { return 'promiseCause' in error && (error as any).promiseCause instanceof Error; } +/** + * 定义了日志记录器的默认级别。 + * 这些级别包括: + * - 'DEBUG':用于调试信息。 + * - 'INFO':用于一般信息。 + * - 'WARNING':用于警告信息。 + * - 'ERROR':用于错误信息。 + */ export type LoggerDefaultLevels = 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR'; +/** + * 定义了一个特定类型的日志记录器的行为。 + * 这个日志记录器具有以下属性和方法: + * - `logger`: 日志记录器的实例。 + * - `type`: 日志记录的类型,是 `LoggerDefaultLevels` 联合类型的一个值。 + * - `options`: 日志记录的选项,是 `LoggingOptions` 接口的一个部分。 + * - `(error: Error, options?: Partial): void`: 记录一个错误信息,可以指定额外的选项。 + * - `(message: string, options?: Partial): void`: 记录一个字符串信息,可以指定额外的选项。 + * - `(template: TemplateStringsArray,...args: any[]): void`: 记录一个模板字符串信息,可以指定额外的选项。 + * - `withOptions(options: Partial): LoggerForType`: 返回一个新的 `LoggerForType` 实例,具有指定的选项。 + */ export interface LoggerForType { + // 日志记录器的实例 logger: Logger; + // 日志记录的类型 type: LoggerDefaultLevels; + // 日志记录的选项 options: Partial; + // 记录一个错误信息,可以指定额外的选项 (error: Error, options?: Partial): void; + // 记录一个字符串信息,可以指定额外的选项 (message: string, options?: Partial): void; + // 记录一个模板字符串信息,可以指定额外的选项 (template: TemplateStringsArray, ...args: any[]): void; + // 返回一个新的 LoggerForType 实例,具有指定的选项 withOptions(options: Partial): LoggerForType; } -class Logger { +/** + * Logger 类用于记录日志信息 + */ + class Logger { + // 父 Logger 对象,用于在当前 Logger 无法处理日志时将其传递给父 Logger protected parent?: Logger; + // 堆栈跟踪信息,用于记录日志时提供更多的上下文信息 protected stack?: string; + // 默认的日志记录选项 protected defaultLoggingOptions: LoggingOptions = { + // 报告日志记录器名称/堆栈跟踪的级别 reportedFromLevel: 0, + // 是否输出调用堆栈跟踪 callStacktrace: 0, + // 在开始时跳过给定数量的堆栈跟踪 callStacktraceOffset: 0, + // 当要记录的“消息”是一个带有堆栈的 Error 对象时使用 maxErrorStack: 0, }; - protected constructor(protected name?: string, generateStack: number | boolean = false) { + + /** + * 构造函数,用于初始化 Logger 对象 + * @param name - 日志记录器的名称 + * @param generateStack - 是否生成堆栈跟踪信息 + */ + protected constructor(protected name?: string, generateStack: number | boolean = false) { if (generateStack) { const len = typeof generateStack === 'number' ? generateStack : 1; const stack = new Error().stack?.split('\n').slice(3, 3 + len).join('\n'); this.stack = stack || ''; } } - protected doPrint(type: string, message: string, options: LoggingOptions) { + + /** + * 打印日志信息 + * @param type - 日志类型 + * @param message - 日志信息 + * @param options - 日志记录选项 + */ + protected doPrint(type: string, message: string, options: LoggingOptions) { const { reportedFromLevel } = options; - // Calculate prefix - const prefix = this.name ? `[${this.name}] ` : ''; - // Calculate suffix + // 计算前缀 + const prefix = this.name? `[${this.name}] ` : ''; + // 计算后缀 let suffix = ''; if (this.name && this.stack && reportedFromLevel >= 2) { suffix = `\nReported by logger ${this.name}:\n${this.stack}`; @@ -101,24 +196,31 @@ class Logger { } else if (this.stack && reportedFromLevel === 1) { suffix = `\nReported by logger:\n${this.stack.split('\n', 2)[0]}`; } - // If there is a parent logger, pass the message with prefix/suffix on + // 如果有父 Logger,则将带有前缀/后缀的消息传递给父 Logger if (this.parent) return this.parent.doPrint(type, `${prefix}${message}${suffix}`, options); - // There is no parent, we're responsible for actually logging the message + // 没有父 Logger,我们负责实际记录消息 const space = ' '.repeat(Math.max(0, 8 - type.length)); const msg = `[${type}]${space}${prefix}${message}${suffix}` OUTPUT_CHANNEL.appendLine(msg); - // VS Code issue where console.debug logs twice in the Debug Console + // VS Code 问题,console.debug 在调试控制台中记录两次 if (type.toLowerCase() === 'debug') type = 'log'; if (DEBUG) (console[type.toLowerCase()] || console.log).call(console, msg); } - protected formatValue(value: any, options: LoggingOptions): string { + + /** + * 格式化值 + * @param value - 要格式化的值 + * @param options - 日志记录选项 + * @returns 格式化后的字符串 + */ + protected formatValue(value: any, options: LoggingOptions): string { if (typeof value === 'string') return value; if (value instanceof Error && value.stack) { - // Format errors with stacktraces to display the JSON and the stacktrace if needed + // 格式化带有堆栈跟踪的错误,以显示 JSON 和堆栈跟踪(如果需要) let result = `${value.name}: ${value.message}`; try { const json = JSON.stringify(value); - if (json !== '{}') result += `\nJSON: ${json}`; + if (json!== '{}') result += `\nJSON: ${json}`; } finally { } const { maxErrorStack } = options; if (value.stack && maxErrorStack) { @@ -128,7 +230,7 @@ class Logger { } result += '\n' + stack; } - if (maxErrorStack !== 0) for (let cause = value; hasPromiseCause(cause); cause = cause.promiseCause) { + if (maxErrorStack!== 0) for (let cause = value; hasPromiseCause(cause); cause = cause.promiseCause) { let promiseStack = cause.promiseCause.stack?.split(/\n/g); if (!promiseStack) continue; if (maxErrorStack > 0) promiseStack = promiseStack.slice(1, maxErrorStack + 1); @@ -150,78 +252,182 @@ class Logger { } } } + + /** + * 打印日志信息 + * @param type - 日志类型 + * @param message - 日志信息 + * @param partialOptions - 日志记录选项 + */ protected print(type: string, message: string | Error, partialOptions?: Partial) { - const options: LoggingOptions = { ...this.defaultLoggingOptions, ...partialOptions }; + const options: LoggingOptions = {...this.defaultLoggingOptions,...partialOptions }; message = this.formatValue(message, options); - // Do we need to also output a stacktrace? + // 是否需要输出堆栈跟踪? const { callStacktrace, callStacktraceOffset = 0 } = options; if (callStacktrace) { let stack = new Error().stack; let split = stack && stack.split('\n'); - split = split && split.slice(callStacktraceOffset + 3, callStacktrace > 0 ? callStacktraceOffset + 3 + callStacktrace : undefined); - stack = split ? split.join('\n') : ''; + split = split && split.slice(callStacktraceOffset + 3, callStacktrace > 0? callStacktraceOffset + 3 + callStacktrace : undefined); + stack = split? split.join('\n') : ''; message += `\nLogged at:\n${stack}`; } - // Start the (recursive parent-related) printing + // 开始(递归父相关)打印 this.doPrint(type.toUpperCase(), message, options as LoggingOptions) } - protected printTemplate(type: string, template: TemplateStringsArray, args: any[], partialOptions?: Partial) { + + /** + * 打印模板字符串日志信息 + * @param type - 日志类型 + * @param template - 模板字符串 + * @param args - 模板字符串参数 + * @param partialOptions - 日志记录选项 + */ + protected printTemplate(type: string, template: TemplateStringsArray, args: any[], partialOptions?: Partial) { const options: LoggingOptions = { ...this.defaultLoggingOptions, ...partialOptions }; options.callStacktraceOffset = (options.callStacktraceOffset || 0) + 1; this.print(type, template.reduce((acc, part, i) => acc + part + (i < args.length ? this.formatValue(args[i], options) : ''), ''), partialOptions); } - public scope(name?: string, generateStack: number | boolean = false) { + + /** + * 创建一个新的 Logger 对象,该对象是当前 Logger 的子 Logger + * @param name - 子 Logger 的名称 + * @param generateStack - 是否生成堆栈跟踪信息 + * @returns 新的 Logger 对象 + */ + public scope(name?: string, generateStack: number | boolean = false) { const logger = new Logger(name, generateStack); logger.parent = this; return logger; } + + /** + * 包装日志类型 + * @param type - 日志类型 + * @param options - 日志记录选项 + * @returns 包装后的日志记录器 + */ public wrapType(type: LoggerDefaultLevels, options: Partial = {}): LoggerForType { + /** + * 定义一个函数,它接受一个消息参数,可以是字符串、错误对象或模板字符串数组,并接受一个可选的部分日志记录选项参数 + * 如果消息是字符串或错误对象,则调用 logger 的 print 方法记录日志 + * 如果消息是模板字符串数组,则调用 logger 的 printTemplate 方法记录日志 + * 如果消息既不是字符串也不是错误对象也不是模板字符串数组,则记录一个错误信息 + * @param message - 要记录的消息,可以是字符串、错误对象或模板字符串数组 + * @param args - 模板字符串数组的参数 + * @param partialOptions - 部分日志记录选项 + * @returns 返回记录的日志信息 + */ const result: LoggerForType = (message: string | Error | TemplateStringsArray, ...args: any[]) => { + /** + * 合并当前日志记录器的选项和传入的选项,生成一个新的选项对象 + * 同时,将新的选项对象中的 callStacktraceOffset 属性值加 1 + */ const options = { ...result.options }; options.callStacktraceOffset = (options.callStacktraceOffset || 0) + 1; + /** + * 根据传入的 message 参数类型,选择不同的日志记录方法 + * 如果 message 是字符串或 Error 对象,则调用 print 方法记录日志 + * 如果 message 是模板字符串数组,则调用 printTemplate 方法记录日志 + * 如果 message 既不是字符串也不是 Error 对象也不是模板字符串数组,则记录一个错误信息 + */ if (typeof message === 'string' || message instanceof Error) { return result.logger.print(result.type, message, options) } else if (Array.isArray(message)) { return result.logger.printTemplate(result.type, message, args, options) } + // 记录错误信息 result.logger.error`Trying to log type ${type} with message=${message} and args=${args}`; }; + + // 设置 result 对象的 logger 属性为当前 Logger 实例 result.logger = this; + // 设置 result 对象的 type 属性为传入的 type 参数 result.type = type; + // 设置 result 对象的 options 属性为传入的 options 参数 result.options = options; + // 定义一个名为 withOptions 的方法,该方法接受一个新的选项对象 newOptions + // 并返回一个新的 LoggerForType 实例,该实例的类型和当前实例相同,但选项是合并后的结果 result.withOptions = newOptions => this.wrapType(result.type, { ...result.options, ...newOptions }); + // 返回 result 对象,该对象是一个新的 LoggerForType 实例 return result; } + + // 创建一个用于记录调试信息的日志记录器 public debug = this.wrapType('DEBUG'); + + // 创建一个用于记录一般信息的日志记录器 public info = this.wrapType('INFO'); + + /** + * 创建一个用于记录警告信息的日志记录器,并设置了一些额外的选项 + * @param callStacktrace - 调用堆栈跟踪的深度 + * @param reportedFromLevel - 报告的起始级别 + */ public warning = this.wrapType('WARNING', { callStacktrace: 3, reportedFromLevel: 2 }); + + /** + * 创建一个用于记录错误信息的日志记录器,并设置了一些额外的选项 + * @param callStacktrace - 调用堆栈跟踪的深度 + * @param reportedFromLevel - 报告的起始级别 + * @param maxErrorStack - 最大错误堆栈跟踪深度 + */ public error = this.wrapType('ERROR', { callStacktrace: 5, reportedFromLevel: 2, maxErrorStack: 10 }); } +// 导出 Logger 类型,以便在其他文件中使用 export type { Logger }; +/** + * 增加日志记录选项中的堆栈跟踪偏移量 + * @param amount - 要增加的堆栈跟踪偏移量,默认为 1 + * @param options - 日志记录选项 + * @returns 一个新的日志记录选项对象,其中堆栈跟踪偏移量增加了指定的 amount + */ export function withStacktraceOffset(amount: number = 1, options: Partial = {}): Partial { return { ...options, callStacktraceOffset: (options.callStacktraceOffset || 0) + amount }; } +/** + * 定义一个接口,继承自 FileSystemConfig 并省略了 sock 和 _calculated 属性 + * 同时重新定义了这两个属性,使其为可选的字符串类型 + * 此外,_calculated 属性的类型是 CensoredFileSystemConfig 类型,允许嵌套 + */ export interface CensoredFileSystemConfig extends Omit { + // 可选的 sock 属性,用于存储套接字信息 sock?: string; + // 可选的 _calculated 属性,用于存储计算后的值 _calculated?: CensoredFileSystemConfig; } +/** + * 审查配置对象,将敏感信息替换为 `` + * @param config - 要审查的文件系统配置对象 + * @returns 审查后的文件系统配置对象,其中敏感信息已被替换 + */ function censorConfig(config: FileSystemConfig): CensoredFileSystemConfig { return { + // 复制 config 对象的所有属性 ...config, + // 如果 password 是字符串类型,则替换为 password: typeof config.password === 'string' ? '' : config.password, + // 如果 passphrase 是字符串类型,则替换为 passphrase: typeof config.passphrase === 'string' ? '' : config.passphrase, + // 如果 privateKey 是 Buffer 类型,则替换为 Buffer(长度) privateKey: config.privateKey instanceof Buffer ? `Buffer(${config.privateKey.length})` : config.privateKey, + // 如果 sock 存在,则替换为 sock: config.sock ? '' : config.sock, + // 如果 _calculated 存在,则递归调用 censorConfig 函数进行审查 _calculated: config._calculated ? censorConfig(config._calculated) : config._calculated, }; } +/** + * 创建一个全局的日志记录器实例 + * 这个实例是 Logger 类的一个实例,用于记录应用程序的日志信息 + */ export const Logging = new (Logger as any) as Logger; +// 在全局日志记录器中记录一条信息 Logging.info` Created output channel for vscode-sshfs When posting your logs somewhere, keep the following in mind: