Portal Community

How Loki Differs from Elasticsearch

The most important thing to understand about Loki is what it does not do: it does not full-text index log content. This is a deliberate design decision that has significant implications:

CapabilityLokiElasticsearch
Index log contentNo — log lines are not indexedYes — every word is indexed
Index labels/fieldsYes — labels onlyYes — all fields
Full-text searchRegex/substring filter (grep-style, slower)Full inverted index (fast)
Storage costLow (compression only, no index)High (index is 1–3x raw data size)
Ingest performanceExcellent (no indexing overhead)High CPU cost at ingest
Query patternBest for labeled stream queriesBest for arbitrary field search

For BizFirstGO's structured log format — where all important context is in labels (service, tenant, environment) and log content is filtered by executionId or error message — Loki's model is a natural fit.

Loki's Data Model

Loki organizes logs into streams. A stream is a sequence of log entries that all share the same set of label key-value pairs. Every log line belongs to exactly one stream.

# A Loki stream is identified by its label set:
{
  "job": "processengine",
  "service": "flow-studio",
  "tenant_id": "tenant-abc",
  "environment": "production",
  "level": "error"
}

# All log lines with these labels form one stream.
# Adding a new label value creates a new stream.

BizFirstGO Label Strategy for Loki

Labels in Loki must be low-cardinality. The following label strategy is used across all BizFirstGO services:

LabelValuesCardinalityRationale
jobprocessengine, edgestream, octopus, apiVery low (<10)Service category for broad filtering
serviceSpecific service nameLow (<50)Narrows to exact service
tenant_idTenant identifiersMedium (hundreds)Multi-tenant isolation
environmentproduction, staging, developmentVery low (<5)Environment separation
leveltrace, debug, info, warn, error, fatalVery low (<6)Log severity filtering
Never Use High-Cardinality Values as Labels

Do not use execution_id, user_id, trace_id, or request_id as Loki labels. Each unique value creates a new stream. Thousands of unique streams degrade Loki performance significantly. Put these values in the log line body instead, and use |= "execution_id=abc" filter expressions to find them.

LogQL — Quick Reference

# Stream selector — required first step
{job="processengine", environment="production"}

# Add a filter — substring match
{job="processengine"} |= "error"

# Case-insensitive filter
{job="processengine"} |~ "(?i)failed"

# Exclude a pattern
{job="processengine"} != "health check"

# JSON parsing — extract fields from JSON log lines
{job="processengine"} | json | level="error"

# Find logs for a specific execution
{job="processengine"} |= "execution_id=exec-d1e2f3a4"

# Count errors per minute (metric query)
rate({job="processengine"} |= "error"[1m])
Deep Dive Available

This page covers Loki as part of the default stack. For the complete Loki reference — deployment options, advanced LogQL, alert rules, and retention configuration — see Guide3: Loki.