正在载入

等待时间过长时请刷新页面

编写于

最近更新

审计服务

通过审计服务 LinAudit,可以记录运行日志、操作审计和带问题代码的异常报告:

LinAudit audit = lin.linAudit();

LinAudit 有三个成员,承担不同职责:

  1. logger() 日志服务,记录普通日志信息。
  2. record() 审计服务,记录谁在何时对什么对象执行了什么操作。
  3. problem() 记录稳定的问题代码,您可以通过代码查询 Linlang 遇到的异常问题。

运行日志

通过 logger() 取得 LinLogger

LinLogger logger = lin.linAudit().logger();

logger.info("Loaded {} entries", count);
logger.warn(
        "Failed to load {file}",
        exception,
        "file", file
);

普通日志支持 debuginfowarnerror 四个等级。warnerror 可以直接接收 Throwable,运行时会保留异常类型、原因链和堆栈。

{} 会按顺序使用参数,{name} 会使用后续的名称和值进行替换。没有出现在模板中的键值对会作为结构化字段保留:

logger.info(
        "Player {player} saved item",
        "player", player.getName(),
        "slot", slot
);

initstartupop 是特殊日志通道。startup 可以在服务器启动阶段暂存消息,op 会发送给在线管理员,没有合适接收者时则进入待发送队列。

审计记录

简单事件可以直接传入事件名称和字段:

lin.linAudit().record(
        "config.reload",
        "actor", sender.getName(),
        "file", "config.yml"
);

需要指定操作者、操作对象和结果等信息时,可以创建 AuditEvent

AuditEvent event = AuditEvent.builder("item.rename")
        .actor(player.getUniqueId().toString())
        .action("rename")
        .resource(itemId)
        .outcome(AuditOutcome.SUCCESS)
        .correlationId(requestId)
        .field("name", nextName)
        .build();

lin.linAudit().record(event);

事件名称应使用稳定、可检索的机器标识,不应放入会随语言变化的展示文本。actoractionresourceoutcomecorrelationId 分别记录操作者、动作、操作对象、结果和关联 ID;其他信息放入 field

代码异常报告

当错误需要被记录并继续运行时,可直接报告代码、原始异常和定位上下文:

lin.linAudit().problem().report(
        "PLUGIN-FILE-LOAD-FAIL",
        exception,
        "file", file,
        "operation", "reload"
);

问题记录只包含稳定代码、上下文和 Throwable。它不会读取 LangText 或语言对象,也不会在建立记录时查询文件,因此即使语言服务没有完成加载,原始错误仍然可以被保留下来。

需要向调用方传播失败时,应继续抛出标准 Java 异常,并把问题代码放在异常消息中:

throw new IllegalStateException("PLUGIN-FILE-LOAD-FAIL", exception);

同一个错误通常只需记录一次。底层方法抛出异常,由上层启动器、命令执行器或任务入口决定是否恢复,并调用 problem().report(...) 记录,避免每层都打印同一异常。

如果需要逐步收集上下文,可以使用 LinProblem 构建器:

LinProblem problem = LinProblem.builder("PLUGIN-FILE-LOAD-FAIL")
        .context("file", file)
        .context("operation", "reload")
        .cause(exception)
        .build();

lin.linAudit().problem().report(problem);

构建器不是异常类型,也不代替 Java 异常。它只用于组织需要写入问题日志的诊断信息。

查询代码

如果 Linlang 仍然能够正常启动,或 LinlangRuntimeBukkit 可用,则 Linlang 内建问题代码可以通过同一个入口查询:

Optional<ProblemDefinition> result =
        lin.linAudit().problem().lookup("LIN-AUDIT-FILE-WRITE-FAIL");

result.ifPresent(definition -> {
    logger.info("Component: {}", definition.component());
    logger.info("Description: {}", definition.description());
    logger.info("Resolution: {}", definition.resolution());
});

问题代码、组件名称和默认说明直接编译在 Runtime 中,不依赖配置文件、语言文件或数据库。语言服务就绪后,问题含义和处理建议会从运行时的问题目录语言包读取,并跟随语言切换与软重载更新;语言包不可用时自动回退到内建说明。只要审计 Provider 已经安装,即使其他服务初始化失败,仍可以查询代码。若 Linlang 在 Provider 安装前就无法启动,异常和控制台输出中的代码仍可用于查阅本文档。

使用 list() 可以取得按代码名称排序的全部内建定义:

List<ProblemDefinition> definitions =
        lin.linAudit().problem().list();

在安装了 Bukkit Runtime 的服务器中,也可以直接使用内建命令:

/linlang problems
/linlang problem LIN-AUDIT-FILE-WRITE-FAIL

第一条命令列出全部内建代码及简要含义,第二条命令显示指定代码的所属组件、完整说明、处理建议和文档位置。两条命令都需要 linlangruntimebukkit.admin 权限。

lookup()list() 当前只查询 Linlang 内建代码。插件可以定义自己的代码,例如 RAINBOW-FILE-LOAD-FAIL,但应在插件文档中维护对应说明,不应假定它会自动进入 Linlang 的内建目录。

问题代码由 LIN、组件名和故障描述组成,例如 LIN-FILE-CONFIG-SAVE-FAIL。要查询错误代码列表,请见 异常代码 ⇱

文件输出

每个插件拥有独立的审计配置和输出目录。配置中的相对路径以当前插件的数据目录为根目录,绝对路径和包含 .. 的越界路径会被拒绝。

level: INFO
json: true

console:
  enabled: true
  json: false

file:
  path: linlang/audit/log.log
  enabled: false
  json: true
  size-mb: 10
  retained: 5

audit:
  path: linlang/audit/audit.log
  enabled: false
  json: true
  size-mb: 10
  retained: 5

problem:
  path: linlang/audit/problem.log
  enabled: false
  json: true
  size-mb: 10
  retained: 5

queue-capacity: 4096

fileauditproblem 分别控制普通日志、行为审计和问题记录,互不共用开关。文件写入通过有界队列异步完成;队列满时会使用同步写入保护当前记录。达到 size-mb 后执行轮转,并最多保留 retained 个历史文件。

上下文字段中的密码、令牌和密钥等敏感名称会在输出前遮蔽。仍不应把完整凭据或无法安全转换的大型业务对象交给日志服务。

插件关闭或必须确保记录已经落盘时调用:

lin.linAudit().flush();

正常的 Runtime 关闭流程会自动刷新文件队列。

实现流程

flowchart LR
    Facade["linAudit"] --> Logger["LinLogger"]
    Facade --> Event["AuditEvent"]
    Facade --> Problem["LinProblem"]
    Logger --> Provider["Audit Provider"]
    Event --> Provider
    Problem --> Provider
    Provider --> Tenant["按插件选择配置与输出"]
    Tenant --> Console["控制台或特殊通道"]
    Tenant --> Queue["异步文件队列"]
    Problem --> Catalog["内建问题代码目录"]

讨论

请登录账号