一、loki简介 Loki是 Grafana Labs 团队最新的开源项目,是一个水平可扩展,高可用性,多租户的日志聚合系统。它的设计非常经济高效且易于操作,因为它不会为日志内容编制索引,而是为每个日志流编制一组标签。项目受 Prometheus 启发,官方的介绍就是: Like Prometheus, but for logs ,类似于 Prometheus 的日志系统。
二、Loki 快速上手 Loki 作为日志系统的后起之秀,设计上可以说非常优秀,设计的理念就是为了让日志聚合更简单,它被设计为非常经济高效且易于操作。它不索引日志的内容,而是为每个日志流设置一组标签。它主要由三部分组成。
与其他日志聚合系统相比, Loki 具有下面的一些特性:
1 2 3 4 5 不对日志进行全文索引。通过存储压缩非结构化日志和仅索引元数据, Loki 操作起来会更简 单,更省成本。 通过使用与 Prometheus 相同的标签记录流对日志进行索引和分组,这使得日志的扩展和操 作效率更高。 特别适合储存 Kubernetes Pod 日志 ; 诸如 Pod 标签之类的元数据会被自动删除和编入索引
各日志收集组件简单对比
名称
安装的组件
优点
ELK/EFK
elasticsearch、logstash、kibana 、filebeat、kafka、redis
支持自定义 grok 正则解析复杂日志内容;dashboard 支持主富的可视化展示
Loki
grafana 、 loki 、 promtail
占用资源小; grafana 原生支持;查询速度快
官方地址:https://grafana.com/oss/loki/ 文档地址: https://grafana.com/docs/grafana/latest/features/datasources/loki/ git 地址: https://github.com/grafana/loki/blob/master/docs/README.md 下载地址: Releases · grafana/loki (github.com)
第一步安装 Loki 简单介绍下 Loki : Grafana Loki 是一个日志聚合工具,它是功能齐全的日志堆栈的核心。 Loki 是一个为有效保存日志数据而优化的数据存储。日志数据的高效索引将 Loki 与其他日志系统区分开来, 与其他日志系统不同, Loki 索引是根据标签构建的,原始日志消息未编入索引。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 yum install -y https://github.com/grafana/loki/releases/download/v2.9.8/loki-2.9.8.x86_64.rpm [getui@39-210 loki ]$ cat config.yaml auth_enabled: false server: http_listen_port: 3200 querier: max_concurrent: 500 query_scheduler: max_outstanding_requests_per_tenant: 4096 ingester: lifecycler: address: 172.16 .39 .210 ring: kvstore: store: inmemory replication_factor: 1 final_sleep: 0s chunk_target_size: 104857600 chunk_idle_period: 5m chunk_retain_period: 30s schema_config: configs: - from: 2023-01-01 store: boltdb object_store: filesystem schema: v11 index: prefix: index_ period: 168h storage_config: boltdb: directory: /app/new/loki/data/index filesystem: directory: /app/new/loki/data/chunks compactor: working_directory: /app/new/loki/data/compactor shared_store: filesystem compaction_interval: 5m retention_enabled: true retention_delete_delay: 5m retention_delete_worker_count: 150 limits_config: enforce_metric_name: false reject_old_samples: true reject_old_samples_max_age: 168h cardinality_limit: 200000 ingestion_burst_size_mb: 1000 ingestion_rate_mb: 10000 max_entries_limit_per_query: 100000000000 max_label_value_length: 20480 max_label_name_length: 10240 max_label_names_per_series: 300 split_queries_by_interval: 24h retention_period: 1344h max_query_series: 2000 chunk_store_config: max_look_back_period: 672h table_manager: retention_deletes_enabled: true retention_period: 672h [getui@39-210 loki ]$ cat run.sh nohup ./loki-linux-amd64 -config.file=./config.yaml > /dev/null 2 >&1 &
第二步安装 Promtail(采集插件) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 yum install -y https://github.com/grafana/loki/releases/download/v2.9.8/promtail-2.9.8.x86_64.rpm [getui@bjmjq-service-13-93 current ]$ cat promtail.yaml server: http_listen_port: 9081 grpc_listen_port: 19081 positions: filename: ./positions.yaml clients: - url: http://172.16.39.210:3100/loki/api/v1/push scrape_configs: - job_name: ras_exception static_configs: - targets: - 10.10 .13 .93 - labels: job: ras_exception host: 10.10 .13 .93 __path__: /app/new/ras/logs/rp-exception*.log pipeline_stages: - match: selector: '{job="ras_exception"}' stages: - regex: expression: "^(?P<timestamp>\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})\\|(?P<brief>\\w+)\\|(?P<class>\\w+(\\.\\w+|\\$\\w+)*)\\|(?P<reason>.*)\\|(?P<jsonData>.*)\\|(?P<content>.*)\\|(?P<base64>.*)$" - labels: level: - timestamp: format: RFC3339 location: 'Asia/Shanghai' source: timestamp - template: source: content template: '{{.timestamp}} |{{.brief}} |{{.class}} |{{.reason}} |{{.content}} ' - output: source: content - job_name: ras_auth_exception static_configs: - targets: - 10.10 .13 .93 - labels: job: ras_auth_exception host: 10.10 .13 .93 __path__: /app/new/ras/logs/rp-message-*.log pipeline_stages: - match: selector: '{job="ras_auth_exception"}' stages: - multiline: firstline: "^(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})" negate: true match: after - drop: expression: "(no_user|ok|no_valid_push|invalid_param|other_error|ResetMasterSecret|query_cid|push_single|get_tag|no_appid|sign_error|set_tag|get-pushmsg-result|appid_notmatch|bind_alias)" [getui@13-93 current ]$ cat run.sh nohup ./promtail-linux-amd64 -config.file=./promtail.yaml &
Promtail Pipeline 日志处理配置
采集nginx日志 1、尝试搜集nginx日志 所以首先对nginx默认的日志进行改造,让他以json的方式进行输出到目录,然后用Promtail对其进行读取。 读取使用LogQL的json方式去读取,这个LogQL内容填写在grafana中。
2、nginx的部分配置改造 1 2 3 4 5 6 7 8 server { server_name loki.test.com; # 域名设置 listen 8888; access_log /var/log/nginx/loki_access.log promtail_json; location / { return 200 "It's ok!"; } }
promtail_json日志格式配置(nginx.conf)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 log_format promtail_json '{"@timestamp":"$time_iso8601",' '"@version":"Promtail json",' '"server_addr":"$server_addr",' '"remote_addr":"$remote_addr",' '"host":"$host",' '"uri":"$uri",' '"body_bytes_sent":$body_bytes_sent,' '"bytes_sent":$body_bytes_sent,' '"request":"$request",' '"request_length":$request_length,' '"request_time":$request_time,' '"status":"$status",' '"http_referer":"$http_referer",' '"http_user_agent":"$http_user_agent"' '}';
访问127.0.0.1:8888,观察日志已经正常输出为json格式,请保证该json格式正确。
1 2 root@test :/etc/nginx/conf.d$tail -f /var/log/nginx/loki_access.log {"@timestamp" :"2024-09-06T01:54:42-05:00" ,"@version" :"Promtail json" ,"server_addr" :"127.0.0.1" ,"remote_addr" :"192.168.65.130" ,"host" :"127.0.0.1" ,"uri" :"/" ,"body_bytes_sent" :8,"bytes_sent" :8,"request" :"GET / HTTP/1.1" ,"request_length" :78,"request_time" :0.000,"status" :"200" ,"http_referer" :"-" ,"http_user_agent" :"curl/7.29.0" }
3、Promtail配置文件修改 搜集日志是Promtail处理,所以自然而然是需要根据自己需求来配置Promtail的配置文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 server: http_listen_port: 9080 grpc_listen_port: 0 positions: filename: /tmp/loki-positions.yaml # 记录pos点 sync_period: 5s # 5s一次将当前读取到的pos点同步至filename配置的文件内 clients: - url: http://localhost:3100/loki/api/v1/push scrape_configs: - job_name: Loki static_configs: - labels: # 设定的部分标签 job: Loki-nginx host: localhost app: nginx __path__: /var/log/nginx/loki_access.log # 待读取的nginx日志
4、LogQL json部分文档理解 json的提取分为两种方式,带参数和不带参数
不带参数的方式: 使用|json来提取日志的json内容,前提是json内容为有效json格式。 嵌套的字段会用”_”将内外层的key进行拼接。 忽略数组。 官网中不带参数方式的样例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { "protocol" : "HTTP/2.0" , "servers" : [ "129.0.1.1" , "10.2.1.3" ] , "request" : { "time" : "6.032" , "method" : "GET" , "host" : "foo.grafana.net" , "size" : "55" , "headers" : { "Accept" : "*/*" , "User-Agent" : "curl/7.68.0" } } , "response" : { "status" : 401 , "size" : "228" , "latency_seconds" : "6.031" } }
被json解后,得到如下:
1 2 3 4 5 6 7 8 "protocol" => "HTTP/2.0" "request_time" => "6.032" "request_method" => "GET" "request_host" => "foo.grafana.net" "request_size" => "55" "response_status" => "401" "response_size" => "228" "response_size" => "228"
从输出能看到,原本request字段内容为嵌套,所以request里面的内容的key验证了如上第二点,使用”_”进行了拼接。同时servers由于是个数组,所以在解析后直接丢弃了servers这个key,验证了第三点。
带参数的方式: 带参数的方式,json只会根据参数来解开需要的部分(当单条json数据比较大的时候应该能省很多资源)。 使用| json label=”expression”, another=”expression”的方式来编写该方法。可以存在多个参数
看一下官网中带参数方式的样例 使用| json first_server=”servers[0]”, ua=”request.headers[“User-Agent”]进行提取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { "protocol" : "HTTP/2.0" , "servers" : [ "129.0.1.1" , "10.2.1.3" ] , "request" : { "time" : "6.032" , "method" : "GET" , "host" : "foo.grafana.net" , "size" : "55" , "headers" : { "Accept" : "*/*" , "User-Agent" : "curl/7.68.0" } } , "response" : { "status" : 401 , "size" : "228" , "latency_seconds" : "6.031" } }
输出结果为:
1 2 "first_server" => "129.0.1.1" "ua" => "curl/7.68.0"
first_server和ua都为原先参数中指定的key
如果要提取整个对象,可以使用| json server_list=”servers”, headers=”request.headers 这样就能得到如下输出:
1 2 "server_list" => `[ "129.0.1.1" , "10.2.1.3" ] `"headers" => `{ "Accept" : "*/*" , "User-Agent" : "curl/7.68.0" } `
5、尝试写一条LogQL表达式 一条完整的LogQL表达式由两部分构成:
a log stream selector,可以理解为,通过设定的label去匹配要抓取哪些日志。 a log pipeline,可以理解为表达式。比如json的提取。
1 {container="query-frontend" ,namespace="tempo-dev" } |= "metrics.go" | logfmt | duration > 10s and throughput_mb < 500
编写一个简单的nginx日志需求 Loki-nginx日志中状态码为200的条数。 根据当前选定时间范围,自动调整。
思考:
如何指定Loki-nginx,可以使用log stream selector的表达式来选定。 nginx日志已经转变为了json,所以可以用|json来提取。 如何获取status字段的信息? |json后面直接跟随|status即可,即|json|status。 如何根据当前选定的时间范围?使用内置变量[$__interval]。 条数该得用什么方法获得?LogQL有内置函数count_over_time配合sum,这边需要注意的是count_over_time是根据指定时间范围返回日志条目的具体内容,所以还需要配合sum获得时间段内的总数。 编写:
首先选定Loki-nginx的日志,{job=”Loki-nginx”}。 使用count_over_time函数配合[$__interval]来获取总共的条数。count_over_time({job=”Loki-nginx”}[$__interval]) 过滤status字段,让其等于200,表达式count_over_time({job=”Loki-nginx”} | json | status = 200 [$__interval]),此时会报错,因为status为字符串,可以添加__error__=””让其忽略转换出现的异常。得到count_over_time({job=”Loki-nginx”} | json | status = 200 __error__=”” [$__interval]) 此时在grafana上显示为多条数据,配合sum得到单独一个数值。 最终的表达式为:sum(count_over_time({job=”Loki-nginx”} | json | status = 200 error =”” [$__interval]))