LabHub

Blog

Complete Guide to Grafana Loki Log Management: LogQL Queries, Collection Pipelines, and Alerting

한국어English日本語

Grafana Loki Log Management

Introduction

As microservice architectures and Kubernetes-based infrastructure have become the norm, efficiently collecting and analyzing logs pouring out of hundreds of containers has become a core operational challenge. The Elasticsearch-based ELK stack has long been the standard for log management, but high infrastructure costs and operational complexity at scale have been persistent pain points.

Grafana Loki was born from the philosophy of being "like Prometheus, but for logs." Instead of indexing the full content of every log line, it indexes only label metadata, dramatically reducing storage costs while providing powerful real-time log analysis and metric extraction through its query language, LogQL.

This guide covers Loki architecture, LogQL query syntax, collection pipeline configuration, alerting setup, and production operational patterns in a comprehensive manner.


1. Loki Architecture Overview

Loki is designed as a microservices architecture where each component can be horizontally scaled independently. The core components are as follows.

Distributor

The first component that receives log push requests from collection agents (Promtail, Alloy, etc.). It validates incoming log streams and routes them to the appropriate Ingesters using a consistent hash ring. Based on the replication factor, it sends data to multiple Ingesters simultaneously to prevent data loss.

Ingester

Buffers logs received from the Distributor in memory, then writes them as compressed chunks to long-term storage (S3, GCS, Azure Blob, etc.). When query requests arrive, it also returns in-memory data that has not yet been flushed to storage.

Querier

The core component of the read path that processes LogQL queries. It merges in-memory data from Ingesters with chunk data from long-term storage to produce query results. The Query Frontend splits and caches queries to optimize performance for large range queries.

Compactor

A background component that compresses and optimizes index files stored in long-term storage. It also handles retention policy enforcement and deletion processing.

# Loki microservices mode basic configuration example
auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096

common:
  path_prefix: /loki
  storage:
    s3:
      endpoint: s3.amazonaws.com
      bucketnames: loki-chunks
      region: ap-northeast-2
      access_key_id: ACCESS_KEY
      secret_access_key: SECRET_KEY
  replication_factor: 3
  ring:
    kvstore:
      store: memberlist

schema_config:
  configs:
    - from: '2024-01-01'
      store: tsdb
      object_store: s3
      schema: v13
      index:
        prefix: index_
        period: 24h

storage_config:
  tsdb_shipper:
    active_index_directory: /loki/tsdb-index
    cache_location: /loki/tsdb-cache

limits_config:
  max_query_parallelism: 32
  max_query_series: 500
  retention_period: 30d

compactor:
  working_directory: /loki/compactor
  compaction_interval: 10m
  retention_enabled: true
  retention_delete_delay: 2h

2. Storage Architecture and Indexing Strategy

Loki's most distinguishing feature is its label-based indexing strategy. While Elasticsearch builds an inverted index for every token in log content, Loki indexes only label metadata and stores log bodies as compressed chunks in object storage.

Separation of Index and Chunks

This architecture can save approximately 70-80% of storage costs compared to Elasticsearch when processing 100GB of logs per day.

Label Design Principles

High label cardinality causes explosive index growth, so the following principles must be observed:


3. LogQL Query Syntax Deep Dive

LogQL is Loki's query language inspired by PromQL, composed of log stream selectors and pipeline stages.

3.1 Log Stream Selectors

Specify label matchers inside curly braces to select target log streams.

# Exact match
{namespace="production", app="api-gateway"}

# Negative match
{namespace="production", app!="debug-tool"}

# Regex matching
{namespace="production", app=~"api-.+"}

# Regex exclusion
{namespace=~"prod|staging", app!~"test-.+"}

3.2 Pipeline Stages

Chain multiple processing stages after the stream selector using the pipe (|) symbol.

Line Filters

# String contains filter
{app="api-gateway"} |= "error"

# String not contains filter
{app="api-gateway"} != "healthcheck"

# Regex filter
{app="api-gateway"} |~ "status=[45]\\d{2}"

# Regex exclusion filter
{app="api-gateway"} !~ "GET /health"

Parsers

# JSON log parsing - extract all JSON fields as labels
{app="api-gateway"} | json

# Extract specific JSON fields only
{app="api-gateway"} | json level, method, duration

# logfmt format parsing
{app="api-gateway"} | logfmt

# Regex parsing - extract fields via pattern matching
{app="nginx"} | regexp `(?P<ip>\\S+) - - \\[(?P<ts>.+?)\\] "(?P<method>\\S+) (?P<path>\\S+)"`

# Pattern parser - concise pattern matching
{app="nginx"} | pattern `<ip> - - [<_>] "<method> <path> <_>" <status> <size>`

Label Filters

# Filter by parsed labels
{app="api-gateway"} | json | level="error"
{app="api-gateway"} | json | duration > 500ms
{app="api-gateway"} | json | status >= 400 and method="POST"

3.3 Metric Queries

LogQL can transform log streams into metrics to generate time series data. This is essential for Grafana dashboards and alerting rules.

# Log rate per second (log range aggregation)
rate({app="api-gateway"} |= "error" [5m])

# Rate of specific status codes per second
sum(rate({app="api-gateway"} | json | status >= 500 [5m])) by (method)

# Response time distribution (quantile extraction)
quantile_over_time(0.99, {app="api-gateway"} | json | unwrap duration [5m]) by (method)

# Total bytes transferred
sum(bytes_over_time({app="nginx"} [1h])) by (namespace)

# Error rate calculation (error log count / total log count)
sum(rate({app="api-gateway"} | json | level="error" [5m]))
/
sum(rate({app="api-gateway"} [5m]))

4. Promtail and Grafana Alloy Collection Pipelines

Promtail (Legacy Agent)

Promtail is a Loki-dedicated log collection agent that runs as a DaemonSet on each node to watch log files and ship them to Loki. It transitioned to official LTS (Long-Term Support) mode in February 2025, with EOL scheduled for March 2026.

# Promtail configuration example
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki-gateway:3100/loki/api/v1/push
    tenant_id: default

scrape_configs:
  - job_name: kubernetes-pods
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      # Add namespace label
      - source_labels: [__meta_kubernetes_namespace]
        target_label: namespace
      # Add pod name label
      - source_labels: [__meta_kubernetes_pod_name]
        target_label: pod
      # Add container name label
      - source_labels: [__meta_kubernetes_pod_container_name]
        target_label: container
    pipeline_stages:
      # Parse Docker log format
      - docker: {}
      # Parse JSON logs
      - json:
          expressions:
            level: level
            msg: message
      # Set labels
      - labels:
          level:
      # Extract timestamp
      - timestamp:
          source: time
          format: RFC3339Nano

Grafana Alloy (Next-Generation Agent)

Grafana Alloy is the successor to Promtail, a unified telemetry collector based on OpenTelemetry that collects not only logs but also metrics, traces, and profiling data through a single agent.

// Grafana Alloy configuration example (River syntax)
discovery.kubernetes "pods" {
  role = "pod"
}

discovery.relabel "pod_logs" {
  targets = discovery.kubernetes.pods.targets

  rule {
    source_labels = ["__meta_kubernetes_namespace"]
    target_label  = "namespace"
  }

  rule {
    source_labels = ["__meta_kubernetes_pod_name"]
    target_label  = "pod"
  }

  rule {
    source_labels = ["__meta_kubernetes_pod_container_name"]
    target_label  = "container"
  }
}

loki.source.kubernetes "pod_logs" {
  targets    = discovery.relabel.pod_logs.output
  forward_to = [loki.process.pipeline.receiver]
}

loki.process "pipeline" {
  stage.json {
    expressions = {
      level   = "level",
      message = "msg",
    }
  }

  stage.labels {
    values = {
      level = "",
    }
  }

  forward_to = [loki.write.default.receiver]
}

loki.write "default" {
  endpoint {
    url = "http://loki-gateway:3100/loki/api/v1/push"
  }
}

5. Kubernetes Log Collection

When deploying Loki in a Kubernetes environment, using Helm charts is the standard approach.

# Install Loki Helm chart (Simple Scalable mode)
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

helm install loki grafana/loki \
  --namespace observability \
  --create-namespace \
  --values loki-values.yaml

# Install Grafana Alloy DaemonSet
helm install alloy grafana/alloy \
  --namespace observability \
  --values alloy-values.yaml

Key collection configuration points to consider in Kubernetes environments:


6. Alerting Rule Configuration (Loki Ruler)

The Loki Ruler periodically evaluates LogQL metric queries and sends alerts to Alertmanager when thresholds are exceeded. It uses the same YAML format as Prometheus alerting rules.

# loki-alert-rules.yaml
groups:
  - name: application-errors
    rules:
      # Detect HTTP 5xx error spike
      - alert: HighHTTP5xxRate
        expr: |
          sum(rate({namespace="production"} | json | status >= 500 [5m])) by (app)
          > 10
        for: 5m
        labels:
          severity: critical
          team: backend
        annotations:
          summary: 'HTTP 5xx error rate spike detected'
          description: 'App {{ .Labels.app }} is generating more than 10 5xx errors per second for 5 minutes.'

      # Monitor error log ratio
      - alert: HighErrorLogRatio
        expr: |
          sum(rate({namespace="production"} | json | level="error" [10m])) by (app)
          /
          sum(rate({namespace="production"} [10m])) by (app)
          > 0.05
        for: 10m
        labels:
          severity: warning
          team: platform
        annotations:
          summary: 'Error log ratio exceeds 5%'
          description: 'App {{ .Labels.app }} has an error log ratio exceeding 5% for 10 minutes.'

      # Detect log ingestion stoppage
      - alert: LogIngestionStopped
        expr: |
          sum(rate({namespace="production"} [15m])) by (app) == 0
        for: 15m
        labels:
          severity: critical
          team: platform
        annotations:
          summary: 'Log ingestion stopped'
          description: 'No logs have been collected from app {{ .Labels.app }} for 15 minutes.'

  - name: security-alerts
    rules:
      # Detect multiple authentication failures
      - alert: BruteForceAttempt
        expr: |
          sum(rate({app="auth-service"} |= "authentication failed" [5m])) by (source_ip)
          > 5
        for: 2m
        labels:
          severity: critical
          team: security
        annotations:
          summary: 'Suspected brute force attack'
          description: 'IP {{ .Labels.source_ip }} has generated more than 5 authentication failures per second for 5 minutes.'

To apply the Ruler configuration to Loki:

# Loki ruler configuration block
ruler:
  storage:
    type: local
    local:
      directory: /loki/rules
  rule_path: /loki/rules-temp
  alertmanager_url: http://alertmanager:9093
  ring:
    kvstore:
      store: memberlist
  enable_api: true
  evaluation_interval: 1m

7. Dashboard Composition Patterns

Effective dashboard composition patterns using Loki data sources in Grafana include the following.

Core Panel Configuration

  1. Log Volume Histogram: Stack log volume by level (info, warn, error) across time
  2. Error Rate Time Series Graph: Monitor error log ratios per service in real time
  3. Top-N Error Messages Table: Aggregate the most frequent error patterns to prioritize
  4. Log Explorer Panel: Dynamic filtering with variables for drill-down investigation

Grafana Variable Setup


8. Comparison Table: Loki vs Elasticsearch vs CloudWatch

CategoryGrafana LokiElasticsearchAWS CloudWatch Logs
Indexing ApproachLabels onlyFull-text inverted indexLog group based
Storage CostVery low (object storage)High (SSD required)Medium (pay-per-use)
Query LanguageLogQL (PromQL-like)Lucene / KQL / ES|QLCloudWatch Insights
Full-text SearchLimited (brute-force)Very powerfulMedium
K8s IntegrationNativeAdditional setup neededEKS integration
Operational ComplexityLow to mediumHigh (JVM tuning)Very low (managed)
Horizontal ScalingIndependent per componentShard/replica managementAutomatic
Alerting IntegrationRuler + AlertmanagerWatcher / ElastAlertCloudWatch Alarms
Multi-tenancyNative supportIndex separationAccount/region separation
Est. Cost at 100GB/day~50-100 USD/month~300-600 USD/month~150-300 USD/month

Selection Criteria Summary


9. Failure Scenarios and Recovery Procedures

Scenario 1: Ingester OOM (Out of Memory)

Symptoms: Ingester pods repeatedly OOMKilled, log collection halted

Root Cause: Excessive in-memory stream creation due to label cardinality explosion

Recovery Steps:

  1. Identify high-cardinality labels: check unique stream count with LogQL queries
  2. Remove or relabel problematic labels in Promtail/Alloy configuration
  3. Increase Ingester memory limits (temporary measure)
  4. Set limits_config.max_streams_per_user to an appropriate limit

Scenario 2: Query Timeouts

Symptoms: Dashboard queries in Grafana take over 30 seconds or time out

Root Cause: Excessive time range queries or inefficient LogQL

Recovery Steps:

  1. Reduce query range and make stream selectors more specific
  2. Split queries with split_queries_by_interval in Query Frontend configuration
  3. Pre-compute frequently used queries with Recording Rules
  4. Verify and apply cache settings (memcached, Redis)

Scenario 3: Chunk Storage Failures

Symptoms: Repeated S3/GCS upload errors in Ingester logs

Recovery Steps:

  1. Verify object storage IAM permissions
  2. Check network connectivity
  3. Confirm Ingester WAL (Write-Ahead Log) integrity
  4. Ensure flush_on_shutdown: true for safe termination

10. Operational Checklist

Pre-Deployment Checklist

Ongoing Operations Checklist

Performance Optimization Checklist


Conclusion

Grafana Loki enables cost-effective log management in large-scale Kubernetes environments through the paradigm shift of "not all logs need to be indexed." LogQL's pipeline-based queries, alerting through the Ruler, and tight integration with the Grafana ecosystem have established Loki as a core tool in cloud-native observability.

With the ongoing transition from Promtail to Grafana Alloy, we are entering an era where logs, metrics, traces, and profiling data can all be collected through a single unified agent. For teams burdened by the high operational costs of the ELK stack, now is the time to seriously evaluate Loki adoption.

The most critical aspect of operations is label design and cardinality management. Without a proper label strategy, even Loki can face storage and performance issues. Use the architecture understanding, LogQL techniques, alerting configuration, and failure response patterns covered in this guide to build a robust log management system.

Comments

No comments yet.

Sign in to leave a comment