适用版本: 6.8-8.9
1. 错误异常的基本描述 #
could not parse [yearly] schedule. expected either an object or an array of objects representing year times; but found [token] instead 是 Elasticsearch Watcher 在解析 yearly 类型调度(schedule)时抛出的解析异常。该错误表明 yearly 字段的值类型不符合预期——解析器期望接收一个 JSON 对象或对象数组,但实际传入的是字符串、数字、布尔值或其他非法类型。
常见现象 #
- 在创建或更新 Watcher(通过
_watcherAPI 或 Kibana 界面)时,Elasticsearch 返回400 Bad Request,响应体中包含上述错误信息。 - 使用 Dev Tools 或 curl 执行 PUT 请求时,控制台直接输出
ElasticsearchParseException。 - 如果是通过脚本或自动化工具批量部署 Watcher,该错误可能导致整批部署失败,且错误信息中
but found [...]部分会提示实际收到的 token 类型(如VALUE_STRING、VALUE_NUMBER、START_ARRAY等)。
典型报错与异常栈 #
ElasticsearchParseException[could not parse [yearly] schedule. expected either an object or an array of objects representing year times; but found [VALUE_STRING] instead]
at org.elasticsearch.xpack.watcher.trigger.schedule.YearlyScheduleParser.parse(YearlyScheduleParser.java)
at org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleRegistry.parseSchedule(ScheduleRegistry.java)
2. 为什么会发生这个错误 #
yearly 是 Watcher 调度(schedule)的一种类型,用于按年度周期触发。其语法要求外层必须是一个对象(定义一组触发时间)或一个对象数组(定义多组触发时间)。常见原因包括:
- 直接写成了字符串:误将
yearly的值写成类似"Jan 1 12:00"的字符串,而非结构化对象。 - 写成了数字或布尔值:例如
yearly: 1或yearly: true,解析器无法将其识别为时间配置。 - 模板渲染错误:使用脚本或模板引擎生成 Watcher JSON 时,某些变量未正确展开,导致最终输出变成了字符串字面量而非对象。
- 错误嵌套结构:把
yearly写成了数组但数组元素是字符串,如["Jan 1", "Jul 1"],而非对象数组。 - 从其他调度类型误抄:例如将
cron表达式的字符串写法直接套用到yearly上,两者语法不兼容。
3. 如何排查和解决这个异常 #
建议按以下顺序定位问题:
- 从报错信息中提取
but found [token]中的 token 类型,确认实际传入的数据类型。 - 检查完整的 Watcher JSON,重点查看
trigger.schedule.yearly的路径结构。 - 如果是通过 API 创建的,用
GET _watcher/watch/<watch_id>查看已存储的 Watcher 定义,确认是否存在格式问题。 - 如果使用了模板或脚本生成配置,检查模板渲染前后的差异,确认变量是否正确替换。
- 对照 Elasticsearch 官方文档中
yearly调度的语法规范,逐项核对字段名和值类型。
排查时需要注意的问题 #
yearly、monthly、weekly、daily、hourly等调度类型的语法各不相同,不要混用语法。- 如果 Watcher 是通过 CI/CD 流水线自动部署的,优先检查最近一次变更涉及的模板文件。
- JSON 中的尾随逗号、注释(标准 JSON 不支持注释)等语法问题也可能导致解析异常,需一并检查。
4. 如何解决这个错误 #
常用修复方式 #
- 将
yearly的值改为对象形式,包含in(月份)、on(日期)、at(时间)三个字段。 - 如果需要定义多个年度触发时间点,使用对象数组包裹多个配置对象。
- 确保整个 Watcher JSON 是合法的 JSON 格式,无多余逗号或非法字符。
正确示例 #
单个时间点:
{
"trigger": {
"schedule": {
"yearly": {
"in": "january",
"on": 1,
"at": "12:00"
}
}
}
}
多个时间点(每年 1 月 1 日和 7 月 1 日各触发一次):
{
"trigger": {
"schedule": {
"yearly": [
{
"in": "january",
"on": 1,
"at": "12:00"
},
{
"in": "july",
"on": 1,
"at": "12:00"
}
]
}
}
}
使用 INFINI 产品简化 Watcher 管理 #
- INFINI Console 提供可视化的 Watcher 管理界面,可以在创建和编辑时实时校验 JSON 格式,避免低级语法错误。
- INFINI Gateway 可部署在 Elasticsearch 前端,对 Watcher 相关 API 请求进行审计和日志记录,帮助快速定位异常请求的来源和具体内容。
5. 小结 #
could not parse [yearly] schedule 错误的核心在于 类型不匹配,而非字段值本身有误。排查时应优先确认 yearly 字段的外层结构是否为对象或对象数组,再进一步检查内层字段的合法性。借助 INFINI Console 和 INFINI Gateway 的观测能力,可以在开发阶段就发现此类配置问题,减少生产环境的异常风险。
相关错误 #
附:日志上下文 #
throw new ElasticsearchParseException("could not parse [{}] schedule. invalid year times", pe, TYPE);
}
}
return times.isEmpty() ? new YearlySchedule() : new YearlySchedule(times.toArray(new YearTimes[times.size()]));
}
throw new ElasticsearchParseException("could not parse [{}] schedule. expected either an object or an array " +
"of objects representing year times; but found [{}] instead", TYPE, parser.currentToken());
}
}
public static class Builder {





