Apache SkyWalking 日志分析语言(LAL)完全指南:从 DSL 语法到日志、链路与指标联动
发布时间:2026/9/20 22:22:40 锦皓数字建站
完全指南:从 DSL 语法到日志、链路与指标联动`)
Apache SkyWalking 日志分析语言LAL完全指南从 DSL 语法到日志、链路与指标联动【免费下载链接】skywalkingAPM, Application Performance Monitoring System项目地址: https://gitcode.com/gh_mirrors/sky/skywalking导读Log Analysis LanguageLAL日志分析语言是 Apache SkyWalking OAP 服务端内置的一门领域特定语言DSL用于对上报到后端的原始日志进行解析、字段抽取、按需持久化并在此基础上实现日志与链路追踪Trace、指标Metrics系统的联动。阅读本文后你将掌握 LAL 规则文件的结构与激活方式、Filter / Parser / Extractor / Sink 四大组件的完整语法与配置项并能独立编写出解析日志 → 抽取 traceId → 生成指标 → 采样落库的完整分析规则。LAL 是什么SkyWalking 的日志分析 DSL在 SkyWalking 的架构中日志链路为各类 Agent / 接收器将日志上报到 OAP 的 log-analyzer 模块而 LAL 就是该模块用于加工日志的脚本语言。通过 LAL 可以完成四类工作解析Parse把非结构化或半结构化的原始日志解析为结构化字段抽取Extract从解析结果中抽出服务名、实例名、端点名、traceId、segmentId、spanId、时间戳等元数据保存Save决定哪些日志被持久化到存储中以及以何种采样策略保存联动Correlate通过 traceId / segmentId / spanId 将日志与现有链路关联通过 metrics extractor 将日志转化为指标送入 Meter 系统。LAL 规则文件是 YAML 格式存放在 OAP 的lal目录下。在默认发行包中该目录位于 oap-server/server-starter/src/main/resources/lal内含default.yaml、envoy-als.yaml、k8s-service.yaml、mesh-dp.yaml、mysql-slowsql.yaml、nginx.yaml、pgsql-slowsql.yaml、redis-slowsql.yaml等内置规则。激活 LAL 规则文件可以通过两种方式指定要加载的 LAL 配置文件在application.yml中设置log-analyzer/default/lalFiles设置环境变量SW_LOG_LAL_FILES。对应关系在 LogAnalyzerModuleConfig.java 中定义默认的lalPath为lal默认的lalFiles为default.yaml多个文件用逗号分隔。同样地由 metrics extractor 生成的指标交由 MALMeter Analysis Language参见 mal.md做进一步分析对应的malFiles配置项默认目录为log-mal-rules。# application.yml # ... log-analyzer: selector: ${SW_LOG_ANALYZER:default} default: lalFiles: ${SW_LOG_LAL_FILES:my-lal-config} # 文件位于 lal 目录下 malFiles: ${SW_LOG_MAL_FILES:my-lal-mal-config, folder1/another-lal-mal-config, folder2/*} # 文件位于 log-mal-rules 目录下LAL 规则文件结构一个 LAL 规则文件由rules列表组成每条规则包含name、layer与dsl。以默认规则 default.yaml 为例rules: - name: default layer: GENERAL dsl: | filter { sink { } }这条规则的行为是保存所有日志其行为与 8.5.0 之前的版本一致——空sink即代表将日志全部持久化。可见 LAL 语法本身是嵌入在 YAML 的dsl多行字符串中的类 Groovy 脚本。Layer日志的分析范围声明每条 LAL 规则都通过layer声明其日志分析范围。Layer 定义在 Layer.java 中例如GENERAL、MYSQL、MESH等。layer 不仅用于路由日志到对应的规则还会被抽取为 LogData 的一部分并与服务service关联。Filter处理流水线的编排单元Filter 是 LAL 的核心编排单元一个 filter 是 parser、extractor、sink 三者的组合。一条规则可以声明一个或多个 filter 来组织处理逻辑每一条日志都会被发送到规则内所有 filter。在 filter 内部日志以属性log的形式暴露因此可以通过log.service访问日志所属服务名。log的完整字段集合以 Logging.proto 中的 LogData 协议定义为准。filter 内所有组件严格按照声明顺序依次执行。全局函数以下函数在 parser、extractor、sink 等所有组件中都可以使用。abort快速失败机制默认情况下无论设置了dropped、saved等标志所有已声明的组件都会被执行。但有些场景希望在满足特定条件时提前终止整个 filter 链。abort函数会从声明处中止剩余的 filter 链其后的所有组件都不会再执行是 LAL 中的快速失败fast-fail机制filter { if (log.service TestingService) { // 不为 TestingService 浪费资源 abort {} // 剩余组件全部不再执行 } // ... parsers, extractors, sinks }注意当把regexp用在if语句中时必须用()将表达式括起来写成regexp(表达式)而不是regexp 表达式。tag读取日志标签值tag函数提供了一种便捷的方式来获取日志标签tag中指定 key 的值。假设上报的日志带有如下标签[ { tags:{ data:[ { key:TEST_KEY, value:TEST_VALUE } ] }, body:{ ... } ... } ]那么在 LAL 中即可这样读取TEST_KEY的值filter { if (tag(TEST_KEY) TEST_VALUE) { ... } }tag与parsed不同parsed访问的是解析器从 body 中解析出的字段而tag访问的是日志协议中tags部分的键值对例如用于标识慢 SQL 的LOG_KIND标签。Parser把原始日志解析为结构化数据Parser 负责把原始日志解析为 SkyWalking 可进一步处理的结构化数据。目前共有 3 类解析器json、yaml、text。日志被解析后LAL 会注入一个名为parsed的属性。parsed通常是一个 Map当解析器是json/yaml时parsed包含原始日志中的所有键值当解析器是text使用regexp/grok时parsed包含所有捕获组及其值。所有解析器共享以下选项选项类型描述默认值abortOnFailureboolean解析/匹配失败时是否中止整个 filter 链truejson解析器filter { json { abortOnFailure true // 可省略因为这是默认行为 } }yaml解析器filter { yaml { abortOnFailure true // 可省略因为这是默认行为 } }text解析器对于非结构化日志提供了若干text解析器。regexpregexp解析器使用正则表达式解析日志利用正则的捕获组captured groups所有捕获组都可以在后续的 extractor 或 sink 中使用。regexp返回一个boolean表示日志是否匹配该模式filter { text { abortOnFailure true // 可省略因为这是默认行为 // 这只是演示用的模式 regexp (?timestamp\\d{8}) (?thread\\w) (?level\\w) (?traceId\\w) (?msg.) } extractor { tag level: parsed.level // 添加一个名为 level 的标签值为 regexp 捕获的 parsed.level traceId parsed.traceId // 从解析结果中抽取 trace id用于将日志与链路关联 } // ... }grokTODO官方已知悉 grok Java 库存在一定性能问题目前正在进行调研与基准测试benchmark并欢迎社区贡献。解析器的安全性设计从实现上看LAL 脚本由 Groovy 编译执行。在 DSL.java 中可以看到通过SecureASTCustomizer禁止了while、do-while、for等循环语句避免恶意或失控脚本造成无限循环通过白名单限制了允许的接收者类型Object、Map、List、Array、String、ProcessRegistry等使用CompileStatic进行静态编译并预导入了ProcessRegistry类供sampledTrace生成虚拟进程 ID 使用。这意味着 LAL 是一门受限的、安全的脚本子集你无法在规则中写任意 Java 代码或循环只能使用 DSL 提供的组件与函数。Extractor从日志中抽取元数据Extractor 的目的是从日志中抽取元数据包括服务名、服务实例名、端点名甚至 trace ID这些元数据可用于与现有链路和指标关联。下面逐一介绍。service从parsed结果中抽取服务名写入LogData。该值会被持久化如果未被 drop并用于关联链路 / 指标。instance从parsed结果中抽取服务实例名写入LogData。会被持久化如果未被 drop并用于关联链路 / 指标。endpoint从parsed结果中抽取端点名写入LogData。会被持久化如果未被 drop并用于关联链路 / 指标。traceId从parsed结果中抽取 trace ID写入LogData。会被持久化如果未被 drop并用于关联链路 / 指标。segmentId从parsed结果中抽取 segment ID写入LogData。会被持久化如果未被 drop并用于关联链路 / 指标。spanId从parsed结果中抽取 span ID写入LogData。会被持久化如果未被 drop并用于关联链路 / 指标。timestamp从parsed结果中抽取时间戳写入LogData。会被持久化如果未被 drop并用于关联链路 / 指标。timestamp的参数可以是毫秒值filter { // ... parser extractor { timestamp parsed.time as String } }也可以是带指定格式的日期时间字符串filter { // ... parser extractor { timestamp parsed.time as String, yyyy-MM-dd HH:mm:ss } }layer从parsed结果中抽取 layer写入LogData。会被持久化如果未被 drop并用于关联服务。tag从parsed结果中抽取标签并写入LogData。形式为tag key1: value, key2: value2key 和 value 都可以使用parsed的属性import javax.swing.text.LayeredHighlighter filter { // ... parser extractor { tag level: parsed.level, (parsed.statusCode): parsed.statusMsg tag anotherKey: anotherConstantValue layer GENERAL } }metrics日志转指标metrics从日志中抽取 / 生成指标并发送到 Meter 系统。可以配置 MAL 对这些指标做进一步分析。专用的 MAL 配置文件位于log-mal-rules目录通过log-analyzer/default/malFiles启用见前文 application.yml 示例。一个生成两类指标的 extractor 示例如下filter { // ... extractor { service parsed.serviceName metrics { name log_count timestamp parsed.timestamp labels level: parsed.level, service: parsed.service, instance: parsed.instance value 1 } metrics { name http_response_time timestamp parsed.timestamp labels status_code: parsed.statusCode, service: parsed.service, instance: parsed.instance value parsed.duration } } // ... }上面的 extractor 生成了一个名为log_count的指标带标签 keylevel、值为1。随后可以在 MAL 规则中按日志级别聚合统计日志数量# ... MAL 的其他配置 metrics: - name: log_count_debug exp: log_count.tagEqual(level, DEBUG).sum([service, instance]).increase(PT1M) - name: log_count_error exp: log_count.tagEqual(level, ERROR).sum([service, instance]).increase(PT1M)生成的另一个指标是http_response_time可以配置 MAL 规则生成百分位数等更有价值的指标# ... MAL 的其他配置 metrics: - name: response_time_percentile exp: http_response_time.sum([le, service, instance]).increase(PT5M).histogram().histogram_percentile([50,70,90,99])slowSql日志转慢 SQL 语句slowSql用于将 LogData 转换为 DatabaseSlowStatement从parsed结果中抽取数据并保存为慢 SQL 语句记录。slowSql不会中止或修改日志你可以使用其他 LAL 规则做进一步处理。它会复用 extractor 中设置的service、layer和timestamp因此必须在这三者设置之后使用slowSql。OAP 依赖日志标签LOG_KIND SLOW_SQL来区分慢 SQL 日志与其他日志上报。注意慢 SQL 采样只会把该 SQL 标记进候选列表。OAP 会按服务维度做统计默认每 10 分钟由topNReportPeriod: ${SW_CORE_TOPN_REPORT_PERIOD:10}控制只持久化前 50 条。上报到 OAP 的 JSON 示例[ { tags:{ data:[ { key:LOG_KIND, value:SLOW_SQL } ] }, layer:MYSQL, body:{ json:{ json:{\time\:\1663063011\,\id\:\cb92c1a5b-2691e-fb2f-457a-9c72a392d9ed\,\service\:\root[root][localhost]\,\statement\:\select sleep(2);\,\layer\:\MYSQL\,\query_time\:2000} } }, service:root[root][localhost] } ]statement从parsed结果中抽取 SQL 语句写入DatabaseSlowStatement。会被持久化如果未被 drop用于关联 TopNDatabaseStatement。latency从parsed结果中抽取延迟写入DatabaseSlowStatement。会被持久化如果未被 drop用于关联 TopNDatabaseStatement。id从parsed结果中抽取 id写入DatabaseSlowStatement。会被持久化如果未被 drop用于关联 TopNDatabaseStatement。识别慢日志的完整 LAL 示例filter { json{ } extractor{ layer parsed.layer as String service parsed.service as String timestamp parsed.time as String if (tag(LOG_KIND) SLOW_SQL) { slowSql { id parsed.id as String statement parsed.statement as String latency parsed.query_time as Long } } } }这条规则与发行包内置的 mysql-slowsql.yaml 完全一致是生产环境验证过的真实模板。类似的模板还有pgsql-slowsql.yaml、redis-slowsql.yaml可分别用于 PostgreSQL 与 Redis 的慢语句分析。sampledTrace日志转采样链路记录sampledTrace用于将 LogData 转换为 SampledTraceRecord从parsed结果中抽取数据并保存为采样链路记录。sampledTrace不会中止或修改日志可以使用其他 LAL 规则继续处理。OAP 依赖日志标签LOG_KIND NET_PROFILING_SAMPLED_TRACE来区分采样慢链路日志与其他日志上报。上报到 OAP 的 JSON 示例[ { tags:{ data:[ { key:LOG_KIND, value:NET_PROFILING_SAMPLED_TRACE } ] }, layer:MESH, body:{ json:{ json:{\uri\:\/provider\,\reason\:\slow\,\latency\:2048,\client_process\:{\process_id\:\c1519f4555ec11eda8df0242ac1d0002\,\local\:false,\address\:\\},\server_process\:{\process_id\:\\,\local\:false,\address\:\172.31.0.3:443\},\detect_point\:\client\,\component\:\http\,\ssl\:true} } }, service:test-service, serviceInstance:test-service-instance, timestamp: 1666916962406, } ]处理该日志的 LAL 示例filter { json { } if (tag(LOG_KIND) NET_PROFILING_SAMPLED_TRACE) { sampledTrace { latency parsed.latency as Long uri parsed.uri as String reason parsed.reason as String if (parsed.client_process.process_id as String ! ) { processId parsed.client_process.process_id as String } else if (parsed.client_process.local as Boolean) { processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String } else { processId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.client_process.address as String) as String } if (parsed.server_process.process_id as String ! ) { destProcessId parsed.server_process.process_id as String } else if (parsed.server_process.local as Boolean) { destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String } else { destProcessId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.server_process.address as String) as String } detectPoint parsed.detect_point as String if (parsed.component as String http parsed.ssl as Boolean) { componentId 129 } else if (parsed.component as String http) { componentId 49 } else if (parsed.ssl as Boolean) { componentId 130 } else { componentId 110 } } } }注意这里的ProcessRegistry就是 DSL.java 中通过 ImportCustomizer 预导入的类用于为缺少进程 ID 的本地 / 远程端点生成虚拟进程 ID从而把采样到的网络链路关联到虚拟进程上。Sink决定日志的持久化方式Sink 是 LAL 的持久化层。默认情况下每个 filter 处理的日志都会被持久化到存储中。但有些机制允许你选择性地保存部分日志甚至在抽取完有用信息如指标后丢弃全部日志。Sampler按采样策略保存Sampler 允许以采样方式保存日志。目前支持以下采样策略rateLimit以每分钟最多n条的速度采样。rateLimit(SamplerID)需要为 sampler 指定一个 ID拥有相同 ID 的 sampler 声明共享同一个 sampler 实例从而共享相同的rpm与重置逻辑possibility每条日志有percentage的伪概率被采样该概率由 Java 随机数生成器生成并与给定的percentage比较得出。如果指定了多个 sampler最后一个 sampler 决定最终的采样结果。官方欢迎社区贡献更多采样策略。示例 1rateLimitfilter { // ... parser sink { sampler { if (parsed.service ImportantApp) { rateLimit(ImportantAppSampler) { rpm 1800 // 服务 ImportantApp 每分钟采样 1800 条日志 } } else { rateLimit(OtherSampler) { rpm 180 // 其他服务每分钟采样 180 条日志 } } } } }示例 2possibilityfilter { // ... parser sink { sampler { if (parsed.service ImportantApp) { possibility(80) { // 服务 ImportantApp 采样 80% 的日志 } } else { possibility(30) { // 其他服务采样 30% 的日志 } } } } }Dropper丢弃全部日志Dropper 是一种特殊的 sink表示无条件丢弃所有日志。这在你想丢弃调试日志时非常有用filter { // ... parser sink { if (parsed.level DEBUG) { dropper {} } else { sampler { // ... 采样配置 } } } }或者当你有多个 filter、其中一些只用于抽取指标时可以只让其中一个 filter 持久化日志filter { // filter A负责持久化 // ... parser sink { sampler { // .. sampler 配置 } } } filter { // filter B负责生成大量指标 // ... extractors to generate many metrics extractors { metrics { // ... 指标 } } sink { dropper {} // 丢弃所有日志因为它们已在 filter A 中被保存 } }Enforcer强制采样Enforcer 是另一种特殊的 sink用于强制采样日志。典型场景是已经配置了 sampler但仍希望某些日志被强制保存例如即使配置了采样机制也一定要保存错误日志filter { // ... parser sink { sampler { // ... sampler 配置 } if (parsed.level ERROR || parsed.userId TestingUserId) { // 即使配置了采样策略也强制采样错误日志或测试用户userId TestingUserId的日志 enforcer { } } } }一个完整的实战组合示例将以上组件串联起来一个典型的解析结构化日志 → 抽取链路上下文 → 生成指标 → 分级采样的规则如下rules: - name: my-app-log layer: GENERAL dsl: | filter { json { abortOnFailure true } extractor { service parsed.service instance parsed.instance endpoint parsed.endpoint traceId parsed.traceId timestamp parsed.timestamp as String, yyyy-MM-dd HH:mm:ss.SSS tag level: parsed.level metrics { name log_count timestamp parsed.timestamp labels level: parsed.level, service: parsed.service, instance: parsed.instance value 1 } } sink { sampler { if (parsed.level DEBUG) { possibility(10) } else { rateLimit(normal) { rpm 600 } } } if (parsed.level ERROR) { enforcer {} } } }这段规则实现了JSON 解析失败即中止链路抽取服务、实例、端点与 traceId 实现日志与链路关联按级别统计日志量送入指标系统DEBUG 日志只采样 10%、普通日志每分钟限速 600 条、ERROR 日志强制全量保存。总结与参考LAL 把解析、抽取、保存、联动四件事统一到了一门安全受限的 DSL 中配合 YAML 规则文件即可完成精细化的日志治理无需为每种日志格式编写独立的 Java 处理代码。实践中建议将不同业务、不同 layer 的日志拆分为独立规则文件通过lalFiles逗号分隔加载对仅用于指标抽取的 filter 使用dropper避免日志重复存储对高吞吐日志务必配置 sampler并结合enforcer保留错误日志等关键数据需要将日志关联到现有链路时优先抽取traceId、segmentId、spanId三个字段。更深入的内容可继续阅读MAL 指标分析语言docs/en/concepts-and-designs/mal.mdlog-analyzer 模块配置说明docs/en/setup/backend/log-analyzer.md配置词汇表docs/en/setup/backend/configuration-vocabulary.mdLAL 语法实现oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/DSL.java内置 LAL 规则oap-server/server-starter/src/main/resources/lal【免费下载链接】skywalkingAPM, Application Performance Monitoring System项目地址: https://gitcode.com/gh_mirrors/sky/skywalking创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。