Linlang 审计与日志服务通过两种方式处理运行中的错误。
其中 Java 异常负责改变当前调用流程,Problem 负责保存稳定代码、故障现场和处理建议。
| 错误类别 | 处理办法 | ||
|---|---|---|---|
| 参数不合法,调用不能继续 | 抛出合适的 Java 异常 | ||
| 故障需要留档但服务可以降级 | 报告 Problem | ||
| 故障需要留档且调用不能继续 | 报告一次 Problem,并且抛出 Java 异常 | ||
| 玩家输入错误等正常结果 | 返回业务提示 |
Problem 不是 Exception。报告之后代码仍会继续执行;需要终止操作时,调用方必须明确返回失败或抛出异常。
lin.linAudit().problem().report(
"PLUGIN-CACHE-REBUILD-FAIL",
exception,
"cache", "reward"
);
底层方法通常只抛出带原因链的异常,由能够决定恢复、降级或终止的边界报告 Problem。例如启动器、命令调度器、异步任务入口和资源关闭流程都适合作为报告边界。
try {
cache.reload();
} catch (IOException exception) {
lin.linAudit().problem().report(
"PLUGIN-CACHE-RELOAD-FAIL",
exception,
"file", cacheFile
);
throw new IllegalStateException("PLUGIN-CACHE-RELOAD-FAIL", exception);
}
不要在每一层捕获并报告同一个异常,否则控制台和问题文件会出现重复记录。
需要逐步组织上下文或缩短控制台输出时,可以使用 LinProblem 构建器:
LinProblem problem = LinProblem.builder("PLUGIN-CACHE-RELOAD-FAIL")
.context("file", cacheFile)
.cause(exception)
.consoleSummary("缓存重载失败,详情见问题日志")
.build();
lin.linAudit().problem().report(problem);
设置 consoleSummary 后,控制台只显示这条单行摘要,不附加问题堆栈;完整上下文和原因链仍写入 Problem 文件。
Core 内部使用少量专用异常区分失败来源。这些异常只是控制流分类,不是另一套错误系统。
ReportedProblemException 是公开的标记接口,表示当前异常已经在更靠近故障现场的位置报告过 Problem。运行时边界遇到这类异常时仍会停止或汇总失败,但不会再附加一个含义更宽泛的问题代码。ConfigLoadException、FileSaveException、ReloadException 以及命令组配置异常都使用这一标记。
该标记不表示异常可以忽略。业务代码仍应按照方法契约处理失败,也不应为了绕过报告而让普通业务异常实现它。
CommandArgumentException 表示输入没有通过已声明的命令参数规则,例如参数缺失、整数越界、UUID 无效或目标玩家不存在。
这是正常的命令结果。命令服务会显示参数错误和命令用法,不生成 Problem。为了兼容旧解析器,普通 IllegalArgumentException 当前也按参数拒绝处理;其他无法归类的异常才报告 LIN-COMMAND-ARGUMENT-PARSE-FAIL。
CommandSpecException 表示开发者注册的命令结构无效,例如参数名重复、可选参数后出现必填参数、根命令不一致或命令签名重复。
注册过程会报告 LIN-COMMAND-REGISTRATION-FAIL,然后把异常继续抛给调用方。该类位于 Core,不属于插件应依赖或捕获的公开 API;从 API 角度,它仍属于 IllegalArgumentException。
DataMappingException 表示实体声明不能形成有效表结构,例如缺少 @Id、存在重复列、字段类型不受支持或自动主键类型错误。
数据库入口会报告 LIN-DATA-MAPPING-INVALID 并附带实体类,再终止仓库创建或迁移。该异常是包内实现类型,插件不应依赖其类名;调用方应通过 Problem 代码和原始原因定位声明错误。
Problem 记录只保存稳定代码、上下文和 Throwable,建立记录时不会读取语言文件。这样语言服务自身加载失败时,错误仍然能够被保存。
查询 Problem 时,ProblemDefinition 的说明和处理建议可以使用当前运行时语言:
Optional<ProblemDefinition> result =
lin.linAudit().problem().lookup("LIN-DATA-MAPPING-INVALID");
问题语言资源不可用时自动回退到 Runtime 内建说明。代码本身不会翻译,也不应包含变化的文件名或异常文本;这些信息应放入上下文字段。
插件可以定义自己的稳定代码,例如:
RAINBOW-CONFIG-LOAD-FAIL
RAINBOW-REWARD-CALCULATE-FAIL
只需要您来维护解释文档或可解读即可。Linlang 不提供自定义的问题代码的相关服务。
讨论