ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ELK日志采集规范与Elasticsearch索引模板Mapping实战指南

ELK日志采集规范与Elasticsearch索引模板Mapping实战指南 手上有过几十套ELK日志系统落地经验的人大概率都会踩过同一个坑日志采集第一天数据量少看不出来等跑了三个月突然某天发现某个字段全是string类型没法做聚合或者mapping映射到了text导致内存爆掉再或者新业务上线发现索引里混进了几十个字段类型不一致。这些问题的根源基本都是采集侧没有一套规范Elasticsearch侧也没有把template和mapping提前设计好。这篇文章我打算一次性把logstash采集规范和elasticsearch的template、mapping讲透。内容会涵盖字段命名、类型统一、时区处理、索引模板设计原理、动态模板写法、mapping更新的限制和规避方式以及我实际排障中遇到的高频问题。适合刚接手ELK的运维和开发也适合已经在用、但想把手上的索引规范化的同学。1. 内容整体设计与思路拆解1.1 一套日志系统最容易被忽视的设计点绝大多数团队搭日志平台都是先装个logstashinput塞个beats或者kafkaoutput指向elasticsearch然后就没然后了。数据能进去Kibana能看到日志大家就觉得通了。真正的问题在数据量上来之后才暴露。我见过最典型的一个案例有个业务团队接入了十几套应用的日志每套应用的logstash配置都是自己写的字段命名五花八门——同一个请求耗时有的叫request_time有的叫cost有的叫duration。等到做性能分析的时候Kibana上没法统一聚合只能重新改造采集端回头清洗历史数据非常痛苦。所以整个设计的核心思路是在采集入口就把规范定死在写入端把约束兜住两边配合而不是等数据进去之后再想办法治理。Logstash负责数据清洗和标准化Elasticsearch的template和mapping负责兜底和约束两者是一条链路的上下游缺一不可。1.2 logstash、template、mapping三者各自的职责边界很多初学者会混淆这几个概念。简单说Logstash处理的是数据进来之前的事负责采集、解析、清洗、标准化。**Template索引模板**处理的是新索引创建时的事它告诉Elasticsearch当我新建一个符合匹配规则的索引时应该用什么样的settings和mapping。Mapping处理的是字段写入之后的事它定义了每个字段的类型、分词器、格式决定了这个字段能不能被搜索、被聚合、被排序。用生活化的类比Logstash是工厂里的质检员和包装工把散装货物整理成统一规格Template是仓库的货架规划图规定新来的货放在哪一排、隔板怎么装Mapping是货架上每个格子的标签标明里面装的是什么类型的货物。这个链路里最核心的是无论Logstash那边怎么清洗ES的mapping一定是最终约束。就算Logstash漏了什么字段没处理mapping的dynamic模板也能兜住避免字段类型失控。1.3 方案选型为什么用template而不是建索引时手动指定mapping实际项目中有人会问既然mapping这么重要那我每次建索引的时候手动指定mapping不就行了理论上可以但有两个现实问题。第一日志系统每天都会新建索引按天分索引是日志场景最常见的做法你不可能每天手动跑一次建索引的命令哪怕写脚本也很容易遗漏。第二业务发展快新字段不断出现手动维护一个固定的mapping列表根本不现实。所以正确做法是用索引模板来管理mapping和settings。模板通过通配符匹配索引名只要索引名符合规则创建那一刻就会自动套用模板里预设的mapping和settings。新建索引这事ES帮你自动完成不需要额外干预。2. Logstash采集规范设计2.1 采集链路整体架构与规范边界先说我个人比较推荐的一套采集架构业务日志文件 - Filebeat - Kafka - Logstash - Elasticsearch当然具体场景可以变比如没有Kafka的话Filebeat直接怼Logstash也行如果数据源是数据库就是用Logstash的JDBC input直接拉。但无论链路怎么变Logstash在中间做清洗和标准化的职责不变。规范要从哪里定起我总结了四个维度索引命名规范一般推荐项目名-环境-日志类型-yyyy.MM.dd比如order-service-prod-error-2025.01.15。索引名里带上日期是为了后续按周期滚动和清理。字段命名规范统一使用小写字母加下划线的snake_case风格比如request_time、user_id不要混用驼峰更不要用中文命名。字段类型规范日志里常见的字段无非是时间、IP、URL、状态码、耗时、消息体、业务自定义字段每种类型在ES里对应什么类型要提前定好。公共字段规范比如timestamp、host.name、log.level、service.name这类字段所有应用统一带上方便跨业务排查。2.2 字段命名与类型统一解决数据沼泽的关键我建议每接入一条新日志先做一个字段映射评估表。这个表长这样原始日志字段标准化字段名ES字段类型是否聚合/排序备注timestamptimestampdate是统一转为UTC存储levellog.levelkeyword是规范为INFO/WARN/ERRORclient_ipclient.ipip是便于后续IP地理库关联request_urirequest.urikeyword否不做全文搜索request_time_msrequest.time_mslong是统一毫秒单位messagemessagetext否仅用于全文检索这个表的核心目的是让每个参与接入的人都知道这个字段改叫什么、存成什么类型。特别是时间类字段单位必须统一有的日志打的是毫秒有的是秒还有的是ISO8601字符串Logstash里必须全部规整成统一格式再写入。2.3 时区、时间格式与索引分片设计日志场景下时区问题特别容易翻车。这里说一下我的处理习惯Logstash默认会给每条事件打一个timestamp字段它记录的是Logstash处理这条数据的时间。如果你的日志文件里有自己的时间戳老老实实用date过滤器把日志里的时间解析出来覆盖掉timestamp否则后面按时间排序、按时间检索全乱套。时区这一块统一用UTC存储Kibana展示时再转本地时区。为什么因为ES内部对日期类型的处理本质上是存了一个UTC的毫秒时间戳如果你在Logstash里就自作主张转成东八区反而会把数据搞乱。正确做法是解析日志时间时指定timezone Asia/Shanghai把日志里的本地时间正确解析成UTC瞬间然后存储就按UTC来。举个例子日志里写的是2025-01-15 10:30:00这其实是北京时间。在Logstash里这样解析date { match [log_timestamp, yyyy-MM-dd HH:mm:ss] timezone Asia/Shanghai target timestamp }这样解析出来的timestamp就是一个正确的UTC时间点。之后在Kibana里界面会把UTC时间自动转成你浏览器的本地时区看起来就是北京时间。分片设计。日志类索引我一般建议单分片起步最多两到三个分片。很多人有个误区觉得分片多性能好其实分片越多集群的元数据管理开销越大每个分片还要占用一定的内存。一天的日志数据量如果只有几个GB单分片完全够用查询时反而少一次分片合并的开销。2.4 filter pipeline的核心处理逻辑Logstash的filter阶段是整个采集规范落地的核心环节。我常用的pipeline逻辑一般是这个顺序第一grok解析。用grok把非结构化的日志行解析成结构化字段。比如Tomcat的访问日志{ input: 192.168.1.1 - - [15/Jan/2025:10:30:00 0800] GET /api/user HTTP/1.1 200 512, filters: [ { grok: { match: { message: %{IPORHOST:client.ip} - - \\[%{HTTPDATE:log_timestamp}\\] \%{WORD:request.method} %{URIPATHPARAM:request.uri} HTTP/%{NUMBER:http.version}\ %{INT:http.status_code} %{INT:response.bytes} } } } ] }第二mutate做标准化。这里包含几个动作把不需要的字段remove_field掉用convert把类型转对用rename把原始字段名改成统一规范名。比如原始字段叫req_time规范名是request.time_ms就rename一下。第三date解析时间覆盖timestamp这一步在上面已经说过了。第四通用字段补全。比如用add_field补上service.name从beat的metadata里拿host.name。这一步是为了让后续跨主机、跨业务的检索成为可能。完整的filter结构可以这样写filter { grok { match { message %{TIMESTAMP_ISO8601:log_timestamp} %{LOGLEVEL:log.level} %{GREEDYDATA:message_body} } overwrite [ message_body ] } date { match [log_timestamp, yyyy-MM-dd HH:mm:ss,SSS] timezone Asia/Shanghai target timestamp } mutate { rename { log_timestamp log.original_timestamp } convert { response.bytes integer } remove_field [message, path, offset] } mutate { add_field { service.name %{[metadata][service]} } } }注意remove_field不要一上来就删原始message。在接入初期调试问题时原始日志行非常有用建议至少保留几天等确认解析稳定了再考虑删。3. Elasticsearch Template详解3.1 索引模板的两种形态composable template与legacy templateElasticsearch里索引模板经历过一次大的演进。7.x之前的旧版模板叫legacy template7.8之后的推荐写法是composable template两者的API不同一个是_template一个是_index_template。现在新项目我建议直接用composable template。原因很直接legacy模板在8.x里虽然还能用但官方已经不推荐了而且composable模板支持模板优先级合并、支持组件模板component template复用、支持data stream对接这些能力都是旧模板没有的。而且这里有个坑如果你同时定义了legacy模板和composable模板且索引名同时匹配两者composable模板的优先级更高会覆盖legacy模板。所以老项目如果遇到我明明改了旧模板但索引没生效的情况多半是后台上还有一个composable模板在起作用。3.2 模板参数设计settings与mapping如何配合一个模板通常由三部分组成index patterns匹配规则、template包含settings和mappings、priority优先级。settings里我建议重点配置这几项{ index: { number_of_shards: 1, number_of_replicas: 1, refresh_interval: 10s, translog.durability: async, translog.sync_interval: 5s } }日志场景下这几项是经过验证的合理值。refresh_interval默认是1秒日志检索对实时性要求没那么高改成10秒能显著降低Segment合并的压力translog.durability和sync_interval配合可以让写入吞吐量提升不少代价是极端宕机情况下可能丢失最近几秒的数据日志场景一般可以接受。mapping部分要预设字段的静态类型同时用dynamic templates兜底新增字段。这两者的配合逻辑是已知的、核心的字段全部静态声明确保类型精确可控未知的新增字段用dynamic templates归类处理防止类型失守。priority这个参数很关键。当多个模板都匹配同一个索引名时ES会采用priority值高数字大的那个模板。项目里我会建一个基础日志模板priority设一个较低的值比如100再为每个重点业务建专属模板priority设200。这样基础规则兜底业务规则覆盖。3.3 实战设计一套适合日志场景的composable模板下面给一套我实际项目里在用的模板稍微脱敏处理过PUT _index_template/logs-default { index_patterns: [*-logs-*, *-error-*], priority: 100, template: { settings: { number_of_shards: 1, number_of_replicas: 1, refresh_interval: 10s, translog.durability: async, translog.sync_interval: 5s, index.lifecycle.name: logs-lifecycle, index.lifecycle.rollover_alias: logs-write }, mappings: { dynamic_templates: [ { strings_as_keyword: { match_mapping_type: string, mapping: { type: keyword } } }, { longs_as_long: { match_mapping_type: long, mapping: { type: long } } } ], properties: { timestamp: { type: date }, log.level: { type: keyword }, service.name: { type: keyword }, host.name: { type: keyword }, message: { type: text, fields: { keyword: { type: keyword, ignore_above: 256 } } } } } } }这里有几个细节值得说明。strings_as_keyword这条动态模板意味着所有未预定义的字符串字段默认映射为keyword而非text。这是日志场景里非常重要的一条策略。原因很简单日志里大部分字段是用于聚合和精确匹配的比如状态码、方法名、IP如果它们默认被映射为text一是会额外分词占用内存二是无法做聚合排序。真正需要全文搜索的通常只有message字段单独为它声明text就够了。message字段的mapping里我给text加了一个keyword子字段。这样既能支持match全文搜索又能支持精确匹配和聚合。ignore_above: 256的意思是超过256个字符的字符串不会索引到keyword字段里防止超长字符串把keyword字段撑爆。3.4 模板的版本管理与变更注意事项模板不是改完就完事的它会影响的是将来新建的索引对已存在的索引完全不生效。这是新手最容易误解的一点我改了模板为什么历史索引的mapping没变因为历史索引已经用旧模板创建了除非做reindex否则mapping不会自动更新。所以在项目里我通常把模板文件纳入版本管理用代码库保存一份JSON。每次变更模板时记下变更原因并且在变更后手动触发一次删除并重建或rollover来验证新模板是否按预期生效。用ILM滚动索引的场景新模板生效后的验证方式是手动rollover一个新的索引然后查看新索引的mapping。注意_index_template接口本身不会校验你的mapping是否合法有些非法写法比如字段类型写错可能会静默失败或者在整个索引创建时才报错。所以改了模板之后一定要实际触发一次索引创建用GET 索引名/_mapping验证。4. Mapping深入解析4.1 核心字段类型选型Elasticsearch的字段类型非常丰富但日志场景下高频用到的其实就那么几个。我把它们整理成一张速查表字段类型适用场景不适用场景项目实践经验keyword状态码、方法名、IP、URL、用户名、主机名大段文本正文聚合、精确查询、排序都靠它text日志消息体、错误堆栈、用户评价需要精确匹配的字段配一个keyword子字段最稳妥long / integer耗时、字节数、数量超过long范围的数字统一用long避免溢出double / float金额、比率精确累加计算能用long就不用浮点date时间戳需要做时间范围查询以外的场景统一UTC存储format明确指定ipIP地址普通字符串支持CIDR范围查询比keyword强大object嵌套的JSON日志结构需要独立查询关系的数组日志JSON自动解析为objectnested对象数组需要独立关联查询单层object查询语法复杂非必要不用flattened结构未知的大量嵌套字段需要对深层字段做精确分析适合容器annotations这类场景4.2 text与keyword的取舍为什么说默认keyword是日志场景的明智之选这是我在面试和带新人时必聊的一个问题。默认情况下ES对字符串有一个很尴尬的处理方式开启dynamic mapping时它会自动帮你把字符串映射为textkeyword子字段看起来很贴心但在日志场景下这往往是灾难。为什么因为text类型会做分词。假设一天日志几个GB每个字符串字段都做一次分词索引磁盘占用直接翻倍内存里的fielddata也会飙升。更麻烦的是text字段默认不能聚合、不能排序如果你没意识到某个字段变成了text写Kibana可视化报表的时候会碰一鼻子灰。所以我的策略是动态新增的字符串全部映射为keyword只有显式声明的message字段用text。这样既保证了聚合查询的能力又避免了无谓的分词开销。如果后期确实发现某个keyword字段需要全文检索再单独把它改成text重新索引比一开始就全text要可控得多。4.3 动态映射与显式映射的配合策略有人可能会说既然动态模板这么好用那所有字段都靠动态映射不行吗我的答案是核心字段必须显式声明非核心字段交给动态模板。显式声明的好处有三个第一类型可控。比如request.time_ms如果你让它动态映射成float而实际值是毫秒级的整数float的精度误差可能让你在聚合求和时莫名多出零点几毫秒。显式声明成long这个问题就没了。第二可以定制细节。比如date字段你希望解析多种格式显式声明里可以用format指定多个候选格式text字段你希望用自定义分词器动态映射做不到。第三防止字段类型被污染。如果一个字段第一次写入是字符串mapping定型后后面再写入数值就会被拒绝。显式声明能提前把这个坑填了。而动态模板的价值在于应对未知的新增字段。比如容器日志里不同镜像的kubernetes labels五花八门你不可能在mapping里预知所有label名这时候dynamic template兜底就好。4.4 mapping的更新限制与规避方式mapping一旦创建大部分字段类型是无法更改的。ES的官方说法是字段类型本身不能修改但你可以新增字段。这里列一下能改和不能改的可以新增一个字段、给现有字段新增fields子字段、修改ignore_above、修改format某些类型下可以不可以把keyword改成text、把long改成integer、把一个object改成keyword那业务变了字段类型真的需要改怎么办答案是_reindex。操作流程大致是新建一个索引配置正确的mapping然后用reindex把旧索引的数据迁移过去最后用alias切换。这个过程中如果数据量很大建议配合op_type和滚动批大小来减少对集群的压力。还有一个值得提的技巧保留原始字段的keyword副本。比如日志里有一个user_agent字符串你既想全文搜索又想做浏览器类型聚合那就声明user_agent: { type: text, fields: { keyword: { type: keyword, ignore_above: 256 } } }这样user_agent用于全文搜索user_agent.keyword用于聚合统计。核心思想就是给不确定未来用法的字段预留一个keyword副本后期免去reindex的痛苦。4.5 日期字段的format陷阱日期字段有一个很隐蔽的坑ES默认的date format是strict_date_optional_time||epoch_millis如果你的Logstash里已经把时间转成了毫秒时间戳写进去没问题但如果某个字段的值是类似2025-01-15 10:30:00这种格式ES直接拒绝写入。解决办法是在mapping里显式声明formatlog_time: { type: date, format: yyyy-MM-dd HH:mm:ss||yyyy-MM-dd HH:mm:ss.SSS||epoch_millis }这样多个格式都能被接受。但要注意ES内部的日期解析对格式是严格匹配的yyyy-MM-dd HH:mm:ss和yyyy-MM-ddTHH:mm:ss.SSSZ就是两种不同格式声明多少个候选格式都写在同一行用||分隔。我见过最离谱的情况是同一个date字段的候选格式写了十几个最后仍然漏了某一种排查了半天才发现在某台机器上的日志时间格式不一样。这种问题最好的解法是在Logstash阶段就把时间格式统一掉不要在mapping层面做太多宽容。5. 实操过程与完整配置示例5.1 从零配置一套日志采集链路做一个端到端的演示。假设我有一个Spring Boot应用日志格式是2025-01-15 10:30:00,123 INFO [http-nio-8080-exec-1] com.example.OrderService - order created, userId9527, amount199.9, costMs45第一步Logstash的input配置。如果是filebeat采集logstash接beats inputinput { beats { port 5044 } }如果直接读文件用file inputinput { file { path [/var/log/app/order-service/*.log] start_position beginning sincedb_path /usr/share/logstash/data/sincedb_order-service } }第二步filter配置。核心任务是解析、时间处理、标准化filter { grok { match { message ^%{TIMESTAMP_ISO8601:log_time} %{LOGLEVEL:log.level} \[%{DATA:thread_name}\] %{JAVACLASS:logger_class} - %{GREEDYDATA:log_message} } } date { match [log_time, yyyy-MM-dd HH:mm:ss,SSS] timezone Asia/Shanghai target timestamp } grok { match { log_message userId%{NUMBER:user_id}, amount%{NUMBER:amount}, costMs%{NUMBER:request.time_ms} } tag_on_failure [_grok_parse_business_failed] break_on_match false } mutate { convert { user_id integer } convert { amount double } convert { request.time_ms long } add_field { service.name order-service } rename { log_time log.origin_time } } if _grok_parse_business_failed in [tags] { mutate { add_field { parse_warning true } } } }这里有几个细节tag_on_failure不是为了好看是为了后续能快速筛出解析失败的日志判断采集规范的覆盖率break_on_match false是因为这条grok只是补充解析即使失败也不影响主解析。第三步output配置。把数据写入ES索引名按天滚动output { elasticsearch { hosts [http://es-node1:9200, http://es-node2:9200] index order-service-prod-logs-%{yyyy.MM.dd} user elastic password change_me ilm_enabled false } }如果集群里配了ILM就用ilm_enabled true配合rollover alias这里为了演示先关掉ILM直接按天建索引。5.2 创建template并验证mapping生效上面的output往ES写数据时索引名是order-service-prod-logs-2025.01.15。现在我建一个composable模板来匹配这类索引PUT _index_template/order-service-logs { index_patterns: [order-service-*-logs-*], priority: 200, template: { settings: { number_of_shards: 1, number_of_replicas: 1, refresh_interval: 5s }, mappings: { dynamic_templates: [ { strings_as_keyword: { match_mapping_type: string, mapping: { type: keyword } } } ], properties: { timestamp: { type: date }, log.level: { type: keyword }, log.origin_time: { type: date, format: yyyy-MM-dd HH:mm:ss,SSS }, logger_class: { type: keyword }, thread_name: { type: keyword }, user_id: { type: long }, amount: { type: double }, request.time_ms: { type: long }, service.name: { type: keyword }, log_message: { type: text, fields: { keyword: { type: keyword, ignore_above: 256 } } } } } } }创建之后等logstash写入第一条数据新索引会自动创建。这时验证一下mapping是否按模板生效GET order-service-prod-logs-2025.01.15/_mapping看返回结果里的字段类型是否和模板一致。同时再看看settingsGET order-service-prod-logs-2025.01.15/_settings确认分片数、refresh_interval都正确。5.3 日志数据验证检索、聚合与字段完整性数据写入后我建议做三项验证第一项字段覆盖率。用Kibana的Discover页面查一下_grok_parse_business_failed这个tag有没有出现。如果大量日志都带这个tag说明grok表达式覆盖不够需要回去优化。第二项类型正确性。在Kibana的Discover里看字段列表里的类型标识。user_id应该是numberlog.level应该是keyword。如果看到某个应该是数字的字段类型变成了string说明Logstash的convert没生效或者动态模板把字段映射成了keyword需要排查。第三项聚合验证。写一个简单的聚合查询按log.level分组统计数量GET order-service-prod-logs-2025.01.15/_search { size: 0, aggs: { levels: { terms: { field: log.level, size: 10 } } } }如果这个查询能正常返回说明keyword字段聚合没问题。再试一下request.time_ms的求平均值GET order-service-prod-logs-2025.01.15/_search { size: 0, aggs: { avg_cost: { avg: { field: request.time_ms } } } }返回不为空说明long类型聚合正常。6. 常见问题与排查技巧实录6.1 高频问题速查表这里把我这几年在ELK相关群里、以及自己项目里见过的高频问题整理成一个速查表问题现象根本原因解决方案索引创建了但没有套用mapping索引名不匹配template的pattern用GET _index_template检查模板匹配规则字段类型变成了text无法聚合动态映射把字符串默认映射为text在业务字段或模板中显式声明keyword写入时报mapper_parsing_exception同一字段不同类型冲突用_reindex迁移到新索引删除问题索引timestamp与日志实际时间不一致没用date filter解析日志时间增加date filter并指定timezone索引分片过多导致集群元数据膨胀每天索引固定多分片日志索引单分片起步按容量评估字段名首字母大写、驼峰混用采集端未统一命名规范Logstash里用mutate/rename统一snake_case新模板改了但是历史索引不变模板只作用于新建索引用rollover或reindex让新索引生效日志量极大时写入变慢translog频繁刷盘调整translog.durability为async动态模板只在某些索引生效存在多个匹配模板priority冲突检查所有模板的priority和patterndate字段报Invalid format日志时间格式与mapping声明的format不一致统一在Logstash侧标准化时间格式6.2 排查思路从数据入口到出口的链路追踪法遇到日志采集问题我的排查思路一般按这个顺序走先确认数据有没有进ES再看字段类型对不对最后看内容解析对不对。第一步确认数据入口。用Kibana的Discover查一下目标索引有没有最新数据。如果完全没有数据往回查Logstash的output日志有没有报错Kafka的消费组有没有积压filebeat的registry文件有没有异常。第二步看字段类型。如果数据在但聚合查不了最可能就是mapping问题。用GET 索引名/_mapping直接看字段类型对照规范表检查。第三步看内容解析。如果字段类型对但数据值不对去Logstash的调试输出排查。我习惯在filter阶段加一个临时的stdout { codec rubydebug }观察解析后的完整事件结构发现问题再移除。6.3 几个值得单独说的避坑经验第一grok的正则性能问题。grok解析看着方便但写不好会吃掉大量CPU。我自己踩过的坑是在一个高吞吐的日志流里用了一个特别复杂的grok正则结果Logstash的CPU直接打满延迟从秒级变成分钟级。后来把正则拆成几个小的grok并且在字段提取时尽量用%{DATA:field}替代%{GREEDYDATA:field}。核心原则能用DATA就不用GREEDYDATA因为贪婪匹配会回溯代价非常大。第二mutate的convert不是万能的。convert { amount double }只能处理看起来像数字的字符串如果字段值是1,234.56这种带千分位分隔符的convert会失败字段会被置为空或者保持原样。遇到这种情况先用grok或者ruby filter把格式清洗干净再转换。第三template中动态模板和静态字段不要冲突。如果dynamic_templates里有一条规则匹配了所有string而properties里也声明了同名字段的类型那么properties的显式声明优先级更高。这本身是好事但新手容易困惑——为什么我改了动态模板但某个字段的类型没变先检查properties里有没有显式声明这个字段这是排查此类问题的第一反应。第四日志场景不要迷信多分片。有次遇到集群性能问题排查下来发现有一个索引分了32个分片但每天数据量才几百MB分片多导致查询时需要协调大量分片结果慢得很。日志场景的数据模式是海量小索引而非少量大索引按天切分本来就是天然的分区单分片或双分片基本够用。第五联调时一定要用_validate/query验证查询语法。有时候Kibana上报错不一定是采集的问题可能是查询语句里用了错误的字段路径。用POST 索引名/_validate/query?explain可以快速验证查询是否合法避免在错误的方向上浪费时间。第六在template里定义好index.mapping.total_fields.limit。这个值默认是1000如果业务日志是打印的JSON字段特别多很容易触顶。一旦触顶新字段写入会报错。我在生产环境遇到过最后把limit调到2000才解决。当然调这个值的根本策略还是用完即弃的字段尽量在Logstash阶段remove掉只保留有价值的字段。7. 一点个人的体会做了这么多年日志平台我最大的感受是日志采集这件事规范比技术重要设计比调优重要。logstash采集规范决定的是数据长什么样template和mapping决定的是数据能怎么用。这两件事如果在一开始就设计好后面做Kibana看板、做告警、做数据分析都会顺很多。如果你现在要新建一套日志系统我的建议是先花半天时间把字段规范文档、模板JSON、Logstash配置模板都定下来再开始接数据。等数据进来再改成本至少是提前设计的三倍。另外别把模板和mapping当成建完就不管的东西业务在变字段在变定期检查动态模板兜底的字段类型是不是符合预期这是一个持续性的工作。最后分享一个小技巧在Logstash的filter里给每条事件加一个collection.version字段比如collection.version 1.3.0。这样每次调整采集规范都能通过这个字段判断当前索引里数据的采集版本。排查历史数据问题时这个字段能帮你快速定位这批数据是用哪套规范采的省掉大量对线时间。
返回列表