菜单
开源 RSS

理解标签

标签是 Loki 的关键组成部分。它们允许 Loki 将日志消息组织和分组到日志流中。每个日志流必须至少有一个标签才能在 Loki 中存储和查询。

在本主题中,我们将学习标签以及为什么在将日志发送到 Loki 时,您选择的标签很重要。

注意

标签旨在存储描述日志来源的低基数值。如果您经常在日志中搜索高基数数据,则应使用结构化元数据

理解标签

在 Loki 中,每行日志的内容不会被索引。相反,日志条目被分组到日志流中,并使用标签进行索引。

标签是一个键值对,例如以下都是标签

  • deployment_environment = development
  • cloud_region = us-west-1
  • namespace = grafana-server

共享上述所有标签的一组日志消息称为日志流。当 Loki 执行搜索时,它首先查找所选日志流中的所有消息,然后迭代该日志流中的日志以执行查询。

标签策略会影响您的查询,进而影响您的仪表盘。在开始将日志摄取到 Loki 之前,值得花时间思考您的标签策略。

所有用户的默认标签

Loki 在摄取时不会解析或处理您的日志消息。但是,根据您用于收集日志的客户端,您的日志可能会自动应用一些标签。

service_name

Loki 在摄取日志时会自动尝试填充默认的 service_name 标签。service_name 标签用于在以下 Grafana 和 Grafana Cloud 功能中查找和探索日志

  • 日志 Drilldown
  • Grafana Cloud 应用可观测性

注意

如果您已经应用了 service_name,Loki 将使用该值。

Loki 将按以下顺序查找以下标签,尝试创建 service_name 标签

  • service_name
  • service
  • app
  • application
  • name
  • app_kubernetes_io_name
  • container
  • container_name
  • component
  • workload
  • job

如果找不到与列表匹配的标签,则应用值 unknown_service。

您可以通过在 limits_config 块中为 discover_service_name 提供一个标签列表来更改此列表。如果您正在使用 Grafana Cloud,请联系支持人员配置此设置。

OpenTelemetry 的默认标签

如果您使用 Grafana Alloy 或 OpenTelemetry Collector 作为 Loki 客户端,Loki 会自动将一些 OTel 资源属性分配为标签。资源属性与 Loki 中的索引标签非常匹配,因为两者通常都用于标识日志源。

默认情况下,以下资源属性将存储为标签,其中句点(.)替换为下划线(_),其余属性则作为结构化元数据与每个日志条目一起存储

  • cloud.availability_zone
  • cloud.region
  • container.name
  • deployment.environment.name
  • k8s.cluster.name
  • k8s.container.name
  • k8s.cronjob.name
  • k8s.daemonset.name
  • k8s.deployment.name
  • k8s.job.name
  • k8s.namespace.name
  • k8s.pod.name
  • k8s.replicaset.name
  • k8s.statefulset.name
  • service.instance.id
  • service.name
  • service.namespace

注意

由于 Loki 的默认索引标签限制为 15 个,我们建议仅将选定的资源属性存储为标签。尽管默认配置选择了超过 15 个资源属性,但其中一些是互斥的。

提示

对于 Grafana Cloud Logs,请参阅当前的 OpenTelemetry 指南

存储为标签的默认资源属性列表可以在 distributor 的 otlp_config 下使用 default_resource_attributes_as_index_labels 进行配置。您可以使用 limits_config.otlp_config 设置全局限制。如果您正在使用 Grafana Cloud,请联系支持人员配置此设置。

标签是迭代的

您需要从少量标签开始。虽然接受 Grafana Alloy、OpenTelemetry Collector 或 Kubernetes Monitoring Helm Chart 分配的默认标签可能足以满足您的需求,但随着时间的推移,您可能会发现需要修改您的标签策略。

一旦您了解了第一组标签的工作原理以及如何应用和使用这些标签进行查询,您可能会发现它们无法满足您的查询模式。您可能需要修改或更改标签并再次测试您的查询。

确定适合您业务需求的正确标签可能需要多轮测试。随着您不断调整 Loki 环境以满足业务需求,这是应该预料到的。

创建低基数标签

基数指的是唯一标签和值的组合,它影响您创建的日志流数量。高基数会导致 Loki 构建庞大的索引,并将成千上万的小块刷新到对象存储。当您的标签具有高基数时,Loki 的性能会非常差。如果不加以考虑,高基数将显著降低 Loki 的性能和成本效益。

高基数可能是由于使用了具有无界或大量可能值的标签(例如 timestamp 或 ip_address),或者即使标签具有少量有限的值,也应用了过多的标签。

高基数会导致显著的性能下降。优先使用较少的、具有有限值的标签。

创建自定义标签

提示

许多日志收集器(例如 Grafana Alloy 或 Kubernetes Monitoring Helm Chart)会自动为您分配适当的标签,因此您无需创建自己的标签策略。对于大多数用例,您只需接受默认标签即可。

通常,标签描述了日志的来源,例如

  • 应用程序的命名空间或其他逻辑分组
  • 生成日志的集群和/或区域
  • 磁盘上源日志文件的文件名
  • 如果环境中的机器或虚拟机具有独立的命名,则为主机名。如果您的环境中有临时机器或虚拟机,主机名应存储在结构化元数据中。

如果您的日志具有上述示例标签,那么您可以在 LogQL 中这样查询它们

{namespace="mynamespace", cluster="cluster123" filename="/var/log/myapp.log"}

与基于索引的日志聚合器不同,Loki 不需要您为日志内容中可能希望搜索的每个字段创建标签。标签仅用于组织和标识您的日志流。Loki 通过高度并行的方式迭代日志流来查找给定的字符串,从而执行搜索。

有关 Loki 如何执行搜索的更多信息,请参阅查询部分

这意味着您不需要为日志消息中的内容添加标签,例如

  • 日志级别
  • 日志消息
  • 异常名称

尽管如此,在某些情况下,您可能希望添加一些额外的标签,这有助于进一步缩小日志流范围。添加自定义标签时,请遵循以下原则

  • 请使用较少的标签,最多保持 10 - 15 个标签。标签越少意味着索引越小,从而获得更好的性能。
  • 请尽量具体地使用您的标签,Loki 需要执行的搜索越少,返回结果的速度就越快。
  • 请创建具有长期稳定值的标签,而不是无界值。要成为一个好的标签,我们希望它的值集合随着时间推移保持稳定——即使值数量很多。如果只有一个标签值发生变化,就会创建一个新的流。
  • 请根据用户实际会查询的术语创建标签。
  • 请勿为非常具体的搜索(例如用户 ID 或客户 ID)或很少使用的搜索(可能一年执行一次的搜索)创建标签。

标签格式

Loki 对标签命名施加了与 Prometheus 相同的限制

  • 它可能包含 ASCII 字母和数字,以及下划线和冒号。它必须匹配正则表达式 [a-zA-Z_:][a-zA-Z0-9_:]*
  • 标签中的不支持字符应转换为下划线。例如,标签 app.kubernetes.io/name 应写为 app_kubernetes_io_name
  • 但是,请勿在标签名称的开头和结尾使用双下划线,因为此命名约定用于内部标签(例如 _stream_shard_),这些内部标签在标签浏览器、查询构建器和自动完成功能中默认隐藏,以避免给用户带来困扰。

在 Loki 中,您不需要根据日志消息的内容添加标签。

标签和摄取顺序

Loki 支持摄取乱序的日志条目。乱序写入默认在全局启用,但可以在集群或每个租户基础上禁用/启用。如果您计划摄取乱序日志条目,您的标签选择非常重要。我们建议尝试使用标签将流分开,以便可以单独摄取。

给定日志流中的条目(由给定的标签名称和值集合标识)必须在默认的两小时时间窗口内按顺序摄取。如果您尝试发送对于给定日志流来说太旧的条目,Loki 将返回错误 too far behind。

对于具有不同摄取延迟和传输的系统,使用标签创建单独的流。而不是

{environment="production"}

您可以将日志流分开为

{environment="production", app="slow_app"} {environment="production", app="fast_app"}

现在,“fast_app” 和 “slow_app” 将日志发送到不同的流,允许每个流保持其摄取顺序。

Loki 标签示例

如何将标签添加到日志中是在您用于向 Loki 发送日志的客户端中配置的。具体配置对于不同的客户端会有所不同。

Alloy 示例

Grafana Labs 建议使用 Grafana Alloy 将日志发送到 Loki。这是一个示例配置

alloy

local.file_match "tmplogs" {
    path_targets = [{"__path__" = "/tmp/alloy-logs/*.log"}]
}

loki.source.file "local_files" {
    targets    = local.file_match.tmplogs.targets
    forward_to = [loki.process.add_new_label.receiver]
}

loki.process "add_new_label" {
    // Extract the value of "level" from the log line and add it to the extracted map as "extracted_level"
    // You could also use "level" = "", which would extract the value of "level" and add it to the extracted map as "level"
    // but to make it explicit for this example, we will use a different name.
    //
    // The extracted map will be covered in more detail in the next section.
    stage.logfmt {
        mapping = {
            "extracted_level" = "level",
        }
    }

    // Add the value of "extracted_level" from the extracted map as a "level" label
    stage.labels {
        values = {
            "level" = "extracted_level",
        }
    }

    forward_to = [loki.relabel.add_static_label.receiver]
}

loki.relabel "add_static_label" {
    forward_to = [loki.write.local_loki.receiver]

    rule {
        target_label = "os"
        replacement  = constants.os
    }
}

loki.write "local_loki" {
    endpoint {
        url = "https://:3100/loki/api/v1/push"
    }
}

Promtail 示例

这是一个 Promtail 配置示例,用于将日志发送到 Loki

yaml
scrape_configs:
- job_name: system
  pipeline_stages:
  static_configs:
  - targets:
     - localhost
    labels:
     job: syslog
     __path__: /var/log/syslog

此配置将跟踪一个文件并分配一个标签:job=syslog。这将在 Loki 中创建一个流。

您可以像这样查询它

bash
{job="syslog"}

现在让我们稍微扩展一下示例

yaml
scrape_configs:
- job_name: system
  pipeline_stages:
  static_configs:
  - targets:
     - localhost
    labels:
     job: syslog
     __path__: /var/log/syslog
- job_name: apache
  pipeline_stages:
  static_configs:
  - targets:
     - localhost
    labels:
     job: apache
     __path__: /var/log/apache.log

现在我们跟踪两个文件。每个文件只获得一个标签和一个值,所以 Loki 现在将存储两个流。

我们可以通过几种方式查询这些流

nohighlight
{job="apache"} <- show me logs where the job label is apache
{job="syslog"} <- show me logs where the job label is syslog
{job=~"apache|syslog"} <- show me logs where the job is apache **OR** syslog

在最后一个示例中,我们使用了正则表达式标签匹配器来查看使用 job 标签(具有两个可能值之一)的日志流。现在考虑如何使用额外的标签

yaml
scrape_configs:
- job_name: system
  pipeline_stages:
  static_configs:
  - targets:
     - localhost
    labels:
     job: syslog
     env: dev
     __path__: /var/log/syslog
- job_name: apache
  pipeline_stages:
  static_configs:
  - targets:
     - localhost
    labels:
     job: apache
     env: dev
     __path__: /var/log/apache.log

现在,我们可以这样做,而不是使用正则表达式

nohighlight
{env="dev"} <- will return all logs with env=dev, in this case this includes both log streams

希望您现在开始看到标签的力量。通过使用单个标签,您可以查询许多流。通过组合几个不同的标签,您可以创建非常灵活的日志查询。

标签是 Loki 日志数据的索引。它们用于查找压缩的日志内容,这些内容以块的形式单独存储。标签和值的每个唯一组合定义一个流,而流的日志会被批量处理、压缩并存储为块。

为了让 Loki 高效且具有成本效益,我们必须负责任地使用标签。下一节将更详细地探讨这一点。

基数示例

前面两个示例使用了静态定义且具有单个值的标签;但是,也有动态定义标签的方法。让我们看看使用 Apache 日志和一个庞大的正则表达式来解析这样的日志行

nohighlight
11.11.11.11 - frank [25/Jan/2000:14:00:01 -0500] "GET /1986.js HTTP/1.1" 200 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"
yaml
- job_name: system
  pipeline_stages:
     - regex:
       expression: "^(?P<ip>\\S+) (?P<identd>\\S+) (?P<user>\\S+) \\[(?P<timestamp>[\\w:/]+\\s[+\\-]\\d{4})\\] \"(?P<action>\\S+)\\s?(?P<path>\\S+)?\\s?(?P<protocol>\\S+)?\" (?P<status_code>\\d{3}|-) (?P<size>\\d+|-)\\s?\"?(?P<referer>[^\"]*)\"?\\s?\"?(?P<useragent>[^\"]*)?\"?$"
   - labels:
       action:
       status_code:
  static_configs:
  - targets:
     - localhost
    labels:
     job: apache
     env: dev
     __path__: /var/log/apache.log

此正则表达式匹配日志行的每个组件,并将每个组件的值提取到捕获组中。在 pipeline 代码内部,此数据会存储在一个临时数据结构中,以便在处理日志行期间用于多种目的(此时该临时数据将被丢弃)。有关此内容的更多详细信息,参见 Promtail pipelines 文档。

从该正则表达式中,我们将使用两个捕获组,根据日志行本身的内容动态设置两个标签

action(例如,action="GET"action="POST"

status_code(例如,status_code="200"status_code="400"

现在让我们来看几个示例行

nohighlight
11.11.11.11 - frank [25/Jan/2000:14:00:01 -0500] "GET /1986.js HTTP/1.1" 200 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"
11.11.11.12 - frank [25/Jan/2000:14:00:02 -0500] "POST /1986.js HTTP/1.1" 200 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"
11.11.11.13 - frank [25/Jan/2000:14:00:03 -0500] "GET /1986.js HTTP/1.1" 400 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"
11.11.11.14 - frank [25/Jan/2000:14:00:04 -0500] "POST /1986.js HTTP/1.1" 400 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"

在 Loki 中将创建以下流

nohighlight
{job="apache",env="dev",action="GET",status_code="200"} 11.11.11.11 - frank [25/Jan/2000:14:00:01 -0500] "GET /1986.js HTTP/1.1" 200 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"
{job="apache",env="dev",action="POST",status_code="200"} 11.11.11.12 - frank [25/Jan/2000:14:00:02 -0500] "POST /1986.js HTTP/1.1" 200 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"
{job="apache",env="dev",action="GET",status_code="400"} 11.11.11.13 - frank [25/Jan/2000:14:00:03 -0500] "GET /1986.js HTTP/1.1" 400 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"
{job="apache",env="dev",action="POST",status_code="400"} 11.11.11.14 - frank [25/Jan/2000:14:00:04 -0500] "POST /1986.js HTTP/1.1" 400 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"

这四行日志将成为四个独立的流,并开始填充四个独立的块。

任何与这些标签/值组合匹配的附加日志行都将被添加到现有流中。如果出现另一个唯一的标签组合(例如 status_code="500"),则会创建一个新的流。

现在想象一下,如果您为 ip 设置一个标签。不仅来自用户的每个请求都会成为一个唯一的流。来自同一用户但 action 或 status_code 不同的每个请求都将获得自己的流。

做个快速计算,如果可能只有四种常见的 action(GET、PUT、POST、DELETE)和四种常见的 status code(尽管可能不止四种!),这将产生 16 个流和 16 个独立的块。现在,如果使用 ip 作为标签,再乘以每个用户。您可以快速拥有数千甚至上万个流。