📣 极限科技诚招搜索运维工程师(Elasticsearch/Easysearch)- 全职/北京 👉 : 立即申请加入

适用版本: 6.8-8.9

1. 错误说明 #

unknown month 是 Elasticsearch 在解析日期、时间表达式或 Watcher 调度配置时抛出的 ElasticsearchParseException。当输入的月份值无法被识别为合法的月份名称或数字时,Elasticsearch 会抛出此异常并中断当前操作。

该错误最常见于以下场景:

  • Watcher 定时任务配置:在 trigger.schedule.monthlycron 表达式中使用了非法月份值。
  • 日期字段解析:索引中文档的日期字段格式与 mapping 中定义的 format 不匹配,导致 _bulk 写入或更新时解析失败。
  • 脚本与聚合:在 Painless 脚本或 date_histogram 聚合中使用了动态构造的日期字符串,其中包含非法月份。
  • 快照恢复调度:在配置快照生命周期管理(SLM)或定时快照时,调度表达式中的月份部分有误。

典型报错 #

ElasticsearchParseException: unknown month [13]
ElasticsearchParseException: unknown month [Foo]

在更复杂的场景中,还可能伴随以下异常栈:

org.elasticsearch.ElasticsearchParseException: unknown month [13]
    at org.elasticsearch.common.joda.JodaDateParser.parseMonth(JodaDateParser.java:xxx)
    at org.elasticsearch.xpack.watcher.trigger.schedule.CronScheduleFactory.parse(CronScheduleFactory.java:xxx)
    at org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTrigger.parseSchedule(ScheduleTrigger.java:xxx)

2. 原因分析 #

从 Elasticsearch 源码中可以看到,月份解析逻辑位于日期解析器中,当输入值无法匹配任何已知月份时抛出 ElasticsearchParseException。常见根因如下:

2.1 月份数字超出合法范围 #

月份的有效范围是 1-12(对应 January 到 December)。以下情况会触发异常:

  • 使用了 0 或负数,如 on: [0]
  • 使用了大于 12 的数字,如 on: [13]on: [99]
  • 使用了浮点数或科学记数法,如 on: [1.5]on: [1e1]

2.2 月份名称拼写错误 #

Elasticsearch 仅识别英文月份名称(不区分大小写),且支持以下形式:

月份合法值
1月jan, january
2月feb, february
3月mar, march
4月apr, april
5月may
6月jun, june
7月jul, july
8月aug, august
9月sep, september
10月oct, october
11月nov, november
12月dec, december, last

以下拼写会被拒绝:Janurary(多了一个 u)、Decemeber(少了一个 c)、Sept(少了 em)。

2.3 本地化月份名称不被支持 #

Elasticsearch 的日期解析器默认基于英文月份名称,不支持中文、日文、西班牙文等本地化月份名称。例如:

  • 一月二月 等中文月份名称会触发 unknown month
  • enerofebrero 等西班牙文月份名称同样不被识别。

2.4 日期格式配置错误 #

当索引 mapping 中日期字段的 format 与写入数据不匹配时,也会间接触发此异常。例如:

  • mapping 定义 format: "yyyy-MM-dd",但写入了 2024-13-01(月份为 13)。
  • 使用 strict_ 前缀格式(如 strict_year_month)时,任何非法值都会直接抛异常而非被忽略。

2.5 Watcher cron 表达式错误 #

在 Watcher 的 cron 表达式中,月份字段的有效值为 1-12JAN-DEC(大写)。常见错误包括:

  • 使用了 0 作为月份值(cron 月份从 1 开始)。
  • 使用了三位缩写但拼写错误,如 JUNN
  • 混淆了 cron 月份(1-12)与 Calendar 月份(0-11)的索引差异。

3. 解决方案 #

方案一:修正 Watcher 调度配置中的月份值 #

错误示例 — 月份值超出范围:

{
  "trigger": {
    "schedule": {
      "monthly": {
        "on": [13]
      }
    }
  }
}

正确示例

{
  "trigger": {
    "schedule": {
      "monthly": {
        "on": [1, 6, 12]
      }
    }
  }
}

方案二:使用合法的月份名称 #

在支持月份名称的配置项中,使用英文全称或标准三字母缩写:

{
  "trigger": {
    "schedule": {
      "cron": "0 0 1 JAN,APR,JUL,OCT * ?"
    }
  }
}

如果需要使用"最后一个月"的语义,可以使用 last 关键字(仅特定 API 支持):

{
  "trigger": {
    "schedule": {
      "monthly": {
        "on": ["last"]
      }
    }
  }
}

方案三:修正日期字段 mapping 与写入数据 #

检查索引 mapping 中日期字段的格式定义:

GET /your_index/_mapping

确保写入的日期字符串符合 mapping 中声明的格式:

# 假设 mapping 中 format 为 "yyyy-MM-dd"
# 正确写入
POST /your_index/_doc
{
  "event_date": "2024-06-15"
}

# 错误写入(月份 13 不存在)
POST /your_index/_doc
{
  "event_date": "2024-13-15"
}

如需同时支持多种日期格式,可在 mapping 中使用逗号分隔多个格式:

{
  "mappings": {
    "properties": {
      "event_date": {
        "type": "date",
        "format": "yyyy-MM-dd||yyyy/MM/dd||epoch_millis"
      }
    }
  }
}

方案四:验证并修复 cron 表达式 #

使用以下命令验证 cron 表达式是否合法:

# 在 Elasticsearch 中测试 Watcher cron 表达式
POST /_watcher/_parse
{
  "schedule": {
    "cron": "0 0 1 * * ?"
  }
}

4. 预防措施 #

  • 配置校验:在提交 Watcher 或索引模板前,使用 Kibana 的 Dev Tools 或 INFINI Console 事先校验 JSON 合法性,重点检查日期和调度字段。
  • 统一日期格式:在团队内部约定统一的日期字符串格式(推荐 ISO-8601:yyyy-MM-dd'T'HH:mm:ssZ),并在 mapping 中明确声明。
  • 使用 strict 格式:在 mapping 中使用 strict_ 前缀格式(如 strict_date_optional_time),可以在写入阶段快速暴露非法日期值,避免脏数据进入索引。
  • CI/CD 校验:将 Watcher 配置和索引模板纳入版本管理,在 CI 流水线中加入静态检查步骤,拦截非法月份值和日期格式。
  • 监控与告警:通过 INFINI Console 监控集群中 ElasticsearchParseException 的出现频率,及时发现由日期解析失败引起的写入异常。

5. 借助 INFINI 产品提升排障效率 #

  • INFINI Console 可用于查看集群日志、检索 ElasticsearchParseException 异常分布,并快速定位触发异常的索引和请求来源。
  • INFINI Gateway 部署在 Elasticsearch 前端,可以对写入请求做日期字段预校验和异常请求拦截,在请求到达 Elasticsearch 之前发现并阻断非法日期数据。

6. 小结 #

unknown month 的本质是 Elasticsearch 日期解析器无法将输入值映射为合法月份。排查时应优先检查 Watcher 调度配置、cron 表达式以及日期字段的 mapping 与写入数据是否一致。大多数情况下,将月份值修正为 1-12 范围内的整数或使用标准英文月份名称即可解决。

相关错误 #

附:日志上下文 #

下面是 Elasticsearch 源码中月份解析的相关片段,便于结合异常调用栈定位问题:

case "8"; "aug"; "august" -> AUGUST;
case "9"; "sep"; "september" -> SEPTEMBER;
case "10"; "oct"; "october" -> OCTOBER;
case "11"; "nov"; "november" -> NOVEMBER;
case "12"; "dec"; "last"; "december" -> DECEMBER;
default -> throw new ElasticsearchParseException("unknown month [{}]", value);