Skip to content

诊断系统

Viewer 用统一的诊断事件告诉宿主:渲染、字体、绑定、打印或导出过程中发生了什么。

诊断不等于异常。很多可恢复问题会继续渲染,同时把事件交给你记录、展示或上报。

订阅读诊断

最常见的接入口是 open()

ts
await viewer.open({
  schema,
  data,
  onDiagnostic(event) {
    console.warn(`[${event.severity}] ${event.code}: ${event.message}`)
  },
})

这个回调会保存在当前 Viewer 实例上。后续 render()print()exportDocument() 里通过运行时发出的诊断,也会进入这个回调。

打印和导出也可以传单次任务回调:

ts
await viewer.print({
  onDiagnostic(event) {
    console.warn(event.code, event.message)
  },
})

任务回调适合更新当前按钮或进度面板。open() 里的回调适合接日志、埋点和全局诊断面板。

事件结构

诊断事件的类型是 ViewerDiagnosticEvent

ts
interface ViewerDiagnosticEvent {
  category: 'schema' | 'datasource' | 'viewer' | 'material' | 'print' | 'exporter'
  severity: 'error' | 'warning' | 'info'
  code: string
  message: string
  nodeId?: string
  detail?: unknown
  scope?: 'schema' | 'datasource' | 'font' | 'material' | 'print' | 'exporter' | 'hook'
  cause?: unknown
}

先理解两个字段就够了:

  • category:这条诊断归到哪类结果。
  • scope:问题发生在哪个阶段。

例如字体加载失败的 categoryviewer,但 scopefont。这样你既能按大类统计,也能按具体阶段过滤。

Schema 校验

open() 一开始会校验 Schema。

ts
try {
  await viewer.open({ schema, data, onDiagnostic })
}
catch (error) {
  console.error(error)
}

校验失败时,Viewer 会先发出一条错误诊断:

ts
{
  category: 'schema',
  severity: 'error',
  code: 'INVALID_SCHEMA',
  scope: 'schema',
}

然后 open() 会抛出 Invalid schema: ...。这类错误不会继续进入渲染流程。

数据绑定

绑定解析阶段会把节点上的绑定应用到 props

ts
await viewer.open({
  schema,
  data,
  onDiagnostic(event) {
    if (event.scope === 'datasource') {
      console.warn(event.nodeId, event.code)
    }
  },
})

绑定格式化器可以主动上报诊断。绑定解析本身抛错时,Viewer 会发出 BINDING_RESOLVE_ERROR,并回退到节点原始 props 继续渲染。

字体加载

字体加载失败通常是可恢复问题。

ts
await viewer.open({
  schema,
  data,
  onDiagnostic(event) {
    if (event.scope === 'font') {
      console.warn(event.code, event.message)
    }
  },
})

当前常见诊断码有两个:

  • FONT_LOAD_FAILED:单个字体加载失败。
  • FONT_LOAD_ERROR:字体加载流程整体抛错。

它们都是 warning 级别。Viewer 会继续渲染,浏览器会按可用字体回退。

物料渲染

物料测量和物料渲染有不同处理。

ts
viewer.registerMaterial('my-widget', {
  render() {
    throw new Error('render failed')
  },
})

render() 抛错时,Viewer 会发出 MATERIAL_RENDER_ERROR,并用错误占位内容替代这个节点。整页不会因为一个节点失败而直接中断。

measure() 抛错时,Viewer 会发出 MATERIAL_MEASURE_ERROR,并跳过这个节点的运行时测量结果。

如果某个物料类型没有注册,当前注册表会渲染一个 [Unknown: type] 占位内容。这个路径不会自动发诊断事件。

打印和导出也使用同一套诊断事件。

ts
await viewer.exportDocument({
  format: 'pdf',
  onDiagnostic(event) {
    console.warn(event.category, event.code)
  },
})

常见诊断码包括:

  • NO_PRINT_DRIVER:指定了不存在的打印驱动。
  • PRINT_RENDER_METRICS_MISSING:连续纸固定尺寸打印缺少已渲染页面尺寸。
  • PRINT_ERROR:打印调用或打印驱动失败。
  • NO_EXPORTER:没有找到匹配导出器。
  • EXPORTER_ERROR:导出器执行失败。

这些错误默认会通过诊断暴露。传 throwOnError: true 时,对应调用会继续抛出错误。

Hook 错误

Viewer 内部有少量 hook,用来支撑运行时扩展。

ts
viewer.hooks.diagnosticsEmitted.tap(async () => {
  throw new Error('hook failed')
})

如果 diagnosticsEmitted hook 自己失败,Viewer 会向 open() 注册的诊断回调发出 DIAGNOSTIC_HOOK_ERROR

beforeSchemaNormalizebeforePagePlan 失败时,当前渲染流程会抛出原始错误,并发出对应 hook 错误诊断。

接入策略

我们建议先按严重程度分层处理。

ts
function handleDiagnostic(event: ViewerDiagnosticEvent) {
  if (event.severity === 'error') {
    reportError(event)
    return
  }

  if (event.severity === 'warning') {
    addToDiagnosticsPanel(event)
  }
}

一个可用的默认策略是:

  • error:进入日志、告警或用户可见反馈。
  • warning:进入诊断面板、开发环境控制台或调试日志。
  • info:只做调试辅助。

这样你能看见问题,也不会把字体回退、单节点渲染失败这类可恢复情况都变成整页不可用。

关于诊断系统,目前知道这些就够用了。打印链路的接入细节继续看 打印与导出