feat: 统一日志系统——结构化滚动文件 + api.log 插件通道 + 73 个 IPC 通道追踪 + pi 事件/渲染层错误归档
This commit is contained in:
@@ -36,6 +36,10 @@ import type {
|
||||
} from '@aso/plugin-sdk'
|
||||
import type { PluginInfo, PluginStatusEntry, PluginToolbarEntry, PluginsUiState } from '@shared/types'
|
||||
import { BUILTIN_PLUGINS, type BuiltinPlugin } from './builtin'
|
||||
import { getLogger } from '../logger'
|
||||
|
||||
const log = getLogger('plugin-host')
|
||||
const pluginLog = getLogger('plugin')
|
||||
|
||||
/** harness 插件契约版本(manifest.minContract 与之比对;破坏性变更时提升) */
|
||||
export const HARNESS_CONTRACT_VERSION = '0.2.0'
|
||||
@@ -122,6 +126,10 @@ class PluginHost {
|
||||
} catch {
|
||||
// 已存在或创建失败都继续(扫描时再报)
|
||||
}
|
||||
log.info('plugin host init', {
|
||||
externalRoot: this.externalRoot,
|
||||
externalLoadingEnabled: EXTERNAL_PLUGINS_ENABLED
|
||||
})
|
||||
try {
|
||||
const raw = JSON.parse(readFileSync(this.settingsFile, 'utf8')) as { disabled?: unknown; settingsNav?: unknown }
|
||||
if (Array.isArray(raw.disabled)) this.disabled = new Set(raw.disabled.filter((d): d is string => typeof d === 'string'))
|
||||
@@ -234,6 +242,13 @@ class PluginHost {
|
||||
}
|
||||
this.send('plugins:settingsSchemas', this.settingsSchemasSnapshot())
|
||||
await this.loadExternalMains()
|
||||
const externalCount = [...byId.values()].filter((p) => p.source === 'external').length
|
||||
log.info('scan complete', {
|
||||
builtin: byId.size - externalCount,
|
||||
external: externalCount,
|
||||
disabled: this.disabled.size,
|
||||
projects: this.projectExtensionsDir ?? '-'
|
||||
})
|
||||
this.send('plugins:listChanged', null)
|
||||
}
|
||||
|
||||
@@ -279,27 +294,33 @@ class PluginHost {
|
||||
const mod = (await import(url)) as { default?: unknown }
|
||||
if (typeof mod.default !== 'function') {
|
||||
plugin.error = 'main.mjs 默认导出必须是插件工厂函数'
|
||||
log.error('external main invalid export', { pluginId: plugin.manifest.id, file })
|
||||
continue
|
||||
}
|
||||
plugin.externalFactory = mod.default as HarnessPluginFactory
|
||||
plugin.error = undefined
|
||||
log.info('external main loaded', { pluginId: plugin.manifest.id, version: plugin.manifest.version })
|
||||
} catch (err) {
|
||||
plugin.externalFactory = undefined
|
||||
plugin.error = `main 加载失败: ${err instanceof Error ? err.message : String(err)}`
|
||||
log.error('external main load failed', { pluginId: plugin.manifest.id, file, err })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 重新扫描 + 重新导入 + 清 UI 状态 + 重建 runtime(= pi /reload 等效) */
|
||||
async reload(): Promise<void> {
|
||||
log.info('reload begin')
|
||||
this.statusItems.clear()
|
||||
this.toolbarButtons.clear()
|
||||
await this.scan()
|
||||
this.send('plugins:uiState', this.uiState())
|
||||
if (this.rebuildRuntime) await this.rebuildRuntime()
|
||||
log.info('reload done')
|
||||
}
|
||||
|
||||
async setEnabled(id: string, enabled: boolean): Promise<void> {
|
||||
log.info('set plugin enabled', { pluginId: id, enabled })
|
||||
if (enabled) this.disabled.delete(id)
|
||||
else this.disabled.add(id)
|
||||
this.persistPluginPrefs()
|
||||
@@ -310,6 +331,7 @@ class PluginHost {
|
||||
async setSettingsNav(id: string, on: boolean): Promise<void> {
|
||||
const plugin = this.resolved.get(id)
|
||||
if (!plugin || !this.settingsSchemas.has(id)) return
|
||||
log.info('set settings nav', { pluginId: id, on })
|
||||
if (on) this.settingsNavSet.add(id)
|
||||
else this.settingsNavSet.delete(id)
|
||||
this.persistPluginPrefs()
|
||||
@@ -324,7 +346,7 @@ class PluginHost {
|
||||
'utf8'
|
||||
)
|
||||
} catch (err) {
|
||||
console.error('[plugins] save settings failed:', err)
|
||||
log.error('save plugin prefs failed', { err })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,17 +399,18 @@ class PluginHost {
|
||||
try {
|
||||
fn({ ...(this.settingsValues.get(id) ?? {}) })
|
||||
} catch (err) {
|
||||
console.error('[plugins] settings listener error:', err)
|
||||
log.error('settings listener error', { pluginId: id, err })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
settingsAction(id: string, key: string): void {
|
||||
log.debug('settings action', { pluginId: id, key })
|
||||
for (const fn of this.settingsActions.get(id) ?? []) {
|
||||
try {
|
||||
fn(key)
|
||||
} catch (err) {
|
||||
console.error('[plugins] settings action error:', err)
|
||||
log.error('settings action error', { pluginId: id, key, err })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -401,7 +424,7 @@ class PluginHost {
|
||||
try {
|
||||
writeFileSync(this.valuesFile, JSON.stringify(doc, null, 2), 'utf8')
|
||||
} catch (err) {
|
||||
console.error('[plugins] save values failed:', err)
|
||||
log.error('save values failed', { err })
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
@@ -554,7 +577,7 @@ class PluginHost {
|
||||
try {
|
||||
writeFileSync(this.storageFile, JSON.stringify(doc, null, 2), 'utf8')
|
||||
} catch (err) {
|
||||
console.error('[plugins] save storage failed:', err)
|
||||
log.error('save storage failed', { err })
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
@@ -574,6 +597,7 @@ class PluginHost {
|
||||
this.resetSettingsCallbacks(plugin.manifest.id)
|
||||
try {
|
||||
const result = factory(this.buildApi(plugin, pi))
|
||||
log.info('plugin activated', { pluginId: plugin.manifest.id, source: plugin.source })
|
||||
if (typeof result === 'function') {
|
||||
disposers.push(result)
|
||||
} else if (result && typeof (result as Promise<unknown>).then === 'function') {
|
||||
@@ -583,6 +607,7 @@ class PluginHost {
|
||||
}
|
||||
} catch (err) {
|
||||
plugin.error = `激活失败: ${err instanceof Error ? err.message : String(err)}`
|
||||
log.error('plugin activation failed', { pluginId: plugin.manifest.id, err })
|
||||
this.clearPluginUi(plugin.manifest.id)
|
||||
this.send('plugins:listChanged', null)
|
||||
}
|
||||
@@ -592,7 +617,7 @@ class PluginHost {
|
||||
try {
|
||||
dispose()
|
||||
} catch (err) {
|
||||
console.error('[plugins] dispose error:', err)
|
||||
log.error('plugin dispose error', { err })
|
||||
}
|
||||
}
|
||||
disposers.length = 0
|
||||
@@ -623,6 +648,12 @@ class PluginHost {
|
||||
pi,
|
||||
manifest: plugin.manifest,
|
||||
storage: this.storageFor(id),
|
||||
log: {
|
||||
debug: (message: string, kv?: Record<string, unknown>) => pluginLog.debug(message, { pluginId: id, ...kv }),
|
||||
info: (message: string, kv?: Record<string, unknown>) => pluginLog.info(message, { pluginId: id, ...kv }),
|
||||
warn: (message: string, kv?: Record<string, unknown>) => pluginLog.warn(message, { pluginId: id, ...kv }),
|
||||
error: (message: string, kv?: Record<string, unknown>) => pluginLog.error(message, { pluginId: id, ...kv })
|
||||
},
|
||||
ui: {
|
||||
openView: (view: string, params?: unknown) => this.openView(view, params),
|
||||
setStatusItem: (itemId: string, item: StatusItem) => this.setStatusItem(id, itemId, item),
|
||||
@@ -721,7 +752,7 @@ class PluginHost {
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('[plugins] serve failed:', file, err)
|
||||
log.error('plugin:// serve failed', { file, err })
|
||||
return new Response('serve error', { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user