前面学习了Promtail的基础知识和配置示例,本节做一个实战练习,使用Promtail收集Java应用日志发送给Loki。
这里假设的场景是一个Spring Boot的Java程序,日志框架使用了Logback。这个程序使用Logback将日志以文件形式写到磁盘上。 Logback的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="false" scanPeriod="30 seconds">
<contextName>myapp-name</contextName>
<property name="logsPath" value="logs" />
<timestamp key="currentMonth" datePattern="yyyyMM" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${logsPath}/${currentMonth}/myapp.log</file>
<encoder>
<charset>UTF-8</charset>
<pattern>
<![CDATA[
%d{yyyy-MM-dd HH:mm:ss.SSS,+00:00} [%t] ${SPRING_PROFILES_ACTIVE} %p %logger ${CONTEXT_NAME} - %m%n
]]>
</pattern>
</encoder>
</appender>
<logger name="com.myapp" additivity="false" level="INFO">
<appender-ref ref="FILE" />
</logger>
<root level="WARN">
<appender-ref ref="FILE" />
</root>
</configuration>其中FileAppender的encoder.pattern决定了日志文件中每行日志的格式。处理java应用的日志是需要关注多行日志模式的,即一条日志可能由多行文本组成。例如:
2023-05-28 16:43:20
[pool-8-thread-1] dev ERROR com.myapp.ProjectQuery myapp-name - query error
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:77)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)
at com.sun.proxy.$Proxy108.selectOne(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:166)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:83)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
...针对这个例子,java应用使用logback将日志按照固定格式落盘,与应用在同一服务器节点上的Promtail将跟踪日志文件,解析日志并将日志发送到Loki。 promtail.yaml配置如下:
server:
disable: true
clients:
- url: http://loki-write:3100/loki/api/v1/push
tenant_id: org1
positions:
filename: /app/logs/positions.yaml
target_config:
sync_period: 10s
scrape_configs:
- job_name: java_logs
static_configs:
- targets:
- localhost
labels:
job: java_logs
__path__: /app/logs/*/*.log
pipeline_stages:
- multiline:
firstline: '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}'
max_lines: 256
max_wait_time: 5s
- regex:
expression: '^(?P<time>\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) (?P<message>\[(?P<thread>.*?)\] (?P<profileName>[^\s]+) (?P<level>[^\s]+) (?P<logger>[^\s]+) (?P<contextName>[^\s]+) - [\s\S]*)'
- labels:
contextName:
profileName:
level:
- timestamp:
source: time
format: '2006-01-02 15:04:05.999'
location: "UTC"
- drop:
older_than: 120h
drop_counter_reason: "line_too_old"
- labeldrop:
- filename
- output:
source: messageserver.disable=true将禁用Promtail的HTTP和gRPC服务监听
clients块配置了Promtail如何连接到Loki的实例,配置了loki write的地址,以及使用的租户id
positions.filename设置了Promtail读取日志文件时记录读取位置的文件
scrape_configs中配置了一个job java_logs,将跟踪匹配/app/logs/*/*.log的日志,并为其配置了multiline, regex, labels, timestamp, drop, labeldrop, output等7个 pipeline stage。
multiline阶段的功能是将多行合并成一个多行块,然后将其传递到管道中的下一个阶段。通过firstline首行正则表达式来识别新的块。不匹配该表达式的任何行都被视为前一个匹配块的一部分。regex阶段接受一个正则表达式,并提取捕获的命名分组,以便在后续阶段中使用。labels阶段从提取到的map中获取数据,并修改将发送到Loki的日志条目的标签集合。这个例子中的配置将从前面regex阶段获取到contextName,profileName,level作为日志的label标记timestamp阶段从提取的map中解析数据,并覆盖Loki存储的日志的最终时间值。如果没有这个阶段,Promtail将将日志条目的时间戳与读取该条目的时间关联起来。drop阶段是一个筛选阶段,可以根据多个选项丢弃日志。这里配置的是丢弃120小时之前的日志。labeldrop阶段从发送到Loki的日志条目的标签集合中删除标签,这里我们将filename这个标签删除了。output阶段从提取的map中获取数据并更改将发送到Loki的日志行。