1. Apa Itu Grafana Loki?
Grafana Loki adalah sistem log aggregation open-source yang dikembangkan oleh Grafana Labs. Berbeda dengan solusi logging tradisional seperti ELK Stack (Elasticsearch, Logstash, Kibana), Loki dirancang dengan filosofi "like Prometheus, but for logs" — yaitu hanya meng-index label (metadata) dari log, bukan isi keseluruhan teks log.
Pendekatan ini membuat Loki sangat hemat sumber daya dan storage. Loki tidak memerlukan full-text index yang besar seperti Elasticsearch, sehingga biaya operasional jauh lebih rendah. Loki sangat cocok untuk lingkungan cloud-native, Kubernetes, dan IoT di mana volume log bisa sangat besar namun pencarian hanya perlu dilakukan berdasarkan label tertentu.
Loki menggunakan label yang sama dengan Prometheus, sehingga sangat mudah diintegrasikan. Kamu bisa mengkorelasikan metrics dari Prometheus dengan logs dari Loki dalam satu Grafana dashboard.
Loki vs ELK Stack
| Aspek | Grafana Loki | ELK Stack |
|---|---|---|
| Indexing | Hanya label (metadata) | Full-text index |
| Penggunaan Memori | Rendah | Tinggi (Elasticsearch berat) |
| Storage Cost | Sangat rendah | Tinggi karena index size |
| Query Language | LogQL | KQL / Lucene |
| Full-text Search | Tidak (grep-like saat query) | Ya (di-index) |
| Ekosistem | Grafana, Promtail, Alloy | Kibana, Logstash, Beats |
| Kompleksitas | Rendah | Tinggi |
Kapan Menggunakan Loki?
- Cloud-native & Kubernetes: Loki dirancang khusus untuk lingkungan kontainer dengan label-based indexing
- IoT Monitoring: Volume log besar dari ribuan perangkat — Loki sangat efisien untuk skala ini
- Cost-sensitive environment: Biaya storage 5-10x lebih murah dari Elasticsearch
- Unified Observability: Sudah menggunakan Grafana untuk metrics dan traces
- Microservices logging: Setiap service mengirim log dengan label namespace, pod, container
2. Arsitektur & Komponen Loki
Loki memiliki dua mode deployment: monolithic (semua komponen dalam satu proses) dan microservices (setiap komponen terpisah). Untuk development dan skala kecil, monolithic sudah cukup. Untuk produksi skala besar, mode microservices memberikan fleksibilitas dan skalabilitas lebih baik.
Komponen Utama Loki
| Komponen | Fungsi |
|---|---|
| Distributor | Menerima log dari client, melakukan validasi, dan mendistribusikan ke ingester |
| Ingester | Menerima log streams, mengompresinya ke chunks, dan menyimpan ke storage |
| Querier | Menangani query LogQL dari Grafana, membaca dari ingester dan storage |
| Query Frontend | Splitting dan caching query untuk optimasi performa |
| Compactor | Mengompresi dan merge chunks, menghapus data expired |
| Ruler | Menjalankan alerting rules dan recording rules |
Data Flow
Promtail/Alloy → mengumpulkan log dari file/container → Distributor → memvalidasi dan meneruskan → Ingester → mengompres ke chunks → Object Storage (S3, GCS, local filesystem). Saat query: Grafana → Query Frontend → Querier → baca dari ingester + storage.
Log Streams dan Label
Setiap entry log di Loki termasuk dalam sebuah log stream. Stream diidentifikasi oleh kombinasi label unik. Misalnya:
# Label set untuk stream
{job="iot-gateway", instance="sensor-01", level="error"}
# Label set berbeda = stream berbeda
{job="iot-gateway", instance="sensor-01", level="info"}
Label yang umum digunakan di konteks IoT:
# Contoh label di Promtail config
scrape_configs:
- job_name: iot-devices
static_configs:
- targets: [localhost]
labels:
job: iot-gateway
environment: production
region: jakarta
__path__: /var/log/iot/*.log
3. Instalasi & Konfigurasi Loki
Ada beberapa cara menginstal Loki: Docker, Helm chart untuk Kubernetes, atau binary langsung. Untuk development, Docker Compose adalah cara tercepat.
Instalasi dengan Docker Compose
# docker-compose.yml
version: "3.8"
services:
loki:
image: grafana/loki:3.0.0
ports:
- "3100:3100"
volumes:
- loki-data:/loki
- ./loki-config.yml:/etc/loki/local-config.yaml
command: -config.file=/etc/loki/local-config.yaml
restart: unless-stopped
promtail:
image: grafana/promtail:3.0.0
volumes:
- /var/log:/var/log
- ./promtail-config.yml:/etc/promtail/config.yml
command: -config.file=/etc/promtail/config.yml
depends_on:
- loki
grafana:
image: grafana/grafana:11.0.0
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin123
depends_on:
- loki
volumes:
loki-data:
grafana-data:
Konfigurasi Loki
# loki-config.yml
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
log_level: info
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory
schema_config:
configs:
- from: "2024-01-01"
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
retention_period: 744h # 31 hari
max_query_length: 721h
max_query_parallelism: 4
ingestion_rate_mb: 10
ingestion_burst_size_mb: 20
compactor:
working_directory: /loki/compactor
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
retention_delete_worker_count: 150
Konfigurasi Promtail
# promtail-config.yml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: iot-logs
static_configs:
- targets: [localhost]
labels:
job: iot-gateway
environment: production
__path__: /var/log/iot/*.log
pipeline_stages:
- regex:
expression: '^(?P\S+)\s+(?P\w+)\s+(?P\w+)\s+(?P.*)$'
- labels:
level:
device:
- timestamp:
source: timestamp
format: RFC3339Nano
Verifikasi Instalasi
# Cek Loki health
curl http://localhost:3100/ready
# Push log secara manual
curl -X POST http://localhost:3100/loki/api/v1/push \
-H "Content-Type: application/json" \
-d '{
"streams": [{
"stream": {
"job": "iot-gateway",
"device": "sensor-01",
"level": "info"
},
"values": [
["1719600000000000000", "Temperature reading: 25.6°C"]
]
}]
}'
# Query log via API
curl http://localhost:3100/loki/api/v1/query_range \
--data-urlencode 'query={job="iot-gateway"}' \
--data-urlencode 'start=1719600000000000000' \
--data-urlencode 'end=1719603600000000000'
4. LogQL: Query Language Loki
LogQL adalah bahasa query Grafana Loki yang terinspirasi dari PromQL (Prometheus Query Language). LogQL memiliki dua jenis query utama: log query untuk memfilter log streams dan metric query untuk menghitung nilai dari log.
Log Query (Filter)
# Filter berdasarkan label — semua log dari job iot-gateway
{job="iot-gateway"}
# Filter multiple label
{job="iot-gateway", level="error"}
# Filter menggunakan regex
{job="iot-gateway", device=~"sensor-.*"}
# Filter negasi
{job="iot-gateway", level!="debug"}
# Line filter — contains
{job="iot-gateway"} |= "temperature"
# Line filter — NOT contains
{job="iot-gateway"} != "heartbeat"
# Line filter — regex match
{job="iot-gateway"} |~ "error|warning|critical"
# Line filter — regex negation
{job="iot-gateway"} !~ "DEBUG|TRACE"
# Kombinasi filter
{job="iot-gateway", level="error"} |= "timeout" !~ "retry"
Pipeline & Parser
# JSON parser — ekstrak field dari log JSON
{job="iot-gateway"} | json
# Filter setelah parse
{job="iot-gateway"} | json | temperature > 30
# Logfmt parser — untuk log format key=value
{job="iot-gateway"} | logfmt | level="error"
# Pattern parser — custom pattern
{job="iot-gateway"} | pattern ` `
# Regexp parser
{job="iot-gateway"} | regexp `(?P\w+): temp=(?P[\d.]+)`
# Label formatting — rename extracted field
{job="iot-gateway"} | json | label_format device_name="device"
# Template formatting
{job="iot-gateway"} | json | line_format "{{.device}}: {{.temperature}}°C"
# Unwrap — untuk metric query dari nilai numerik
{job="iot-gateway"} | json | unwrap temperature
Metric Query
# Count log per stream
count_over_time({job="iot-gateway"}[5m])
# Rate log per detik
rate({job="iot-gateway"} |= "error" [1m])
# Jumlah error per device
sum by (device) (count_over_time({job="iot-gateway"} | json | level="error" [1h]))
# Quantile dari nilai numerik
quantile_over_time(0.95, {job="iot-gateway"} | json | unwrap temperature [1h])
# Top 5 device dengan error terbanyak
topk(5, sum by (device) (count_over_time({job="iot-gateway"} | json | level="error" [24h])))
# Bytes rate — volume log
bytes_rate({job="iot-gateway"}[1m])
# Variance dari temperature
variance_over_time({job="iot-gateway"} | json | unwrap temperature [1h])
Selalu filter berdasarkan label terlebih dahulu sebelum line filter. Label di-index sehingga pencarian sangat cepat. Line filter dilakukan dengan mem-browse chunk secara linier, sehingga semakin spesifik label filter, semakin cepat query dieksekusi.
5. Label Indexing & Struktur Data
Pemahaman tentang bagaimana Loki meng-index data sangat penting untuk optimasi performa dan penggunaan storage. Loki menggunakan index store untuk memetakan label set ke chunk IDs, dan chunk store untuk menyimpan actual log data.
Struktur Index
# Index entries di Loki:
# 1. Label name → label value (inverse index)
# "job" → ["iot-gateway", "iot-api", "iot-worker"]
# 2. Label set → chunk references
# {job="iot-gateway", level="error"} → [chunk1, chunk2, chunk3]
# 3. Chunk → (fingerprint, labels, time range, data)
# chunk1 → (fp=12345, {job="iot-gateway",...}, [t0-t1], compressed_data)
Best Practices untuk Label
| Praktik | Benar ✅ | Salah ❌ |
|---|---|---|
| Cardinality | {job="iot", level="error"} |
{user_id="12345", request_id="abc"} (terlalu banyak kombinasi) |
| Jumlah Label | 3-5 label per stream | 20+ label per stream (high cardinality) |
| Dynamic vs Static | Static: job, env, region | Dynamic: timestamp, random_id |
| Ukuran Value | Singkat: "prod", "error" | Panjang: full error message |
High Cardinality Problem
High cardinality terjadi ketika terlalu banyak kombinasi label unik, menyebabkan banyak stream kecil. Ini memperburuk performa dan meningkatkan penggunaan memori.
# ❌ BURUK: High cardinality — setiap sensor jadi stream terpisah
{device_id="sensor-00001", device_id="sensor-00002", ..., device_id="sensor-99999"}
# ✅ BAIK: Gunakan satu label untuk kelompok, extract device_id dari log
{job="iot-sensors", location="warehouse-a"} | json | device_id="sensor-00001"
Tenant Isolation
# Di production, gunakan multi-tenancy
# Header X-Scope-OrgID menentukan tenant
# Setiap tenant memiliki namespace label terpisah
# Distributor config
distributor:
shard_by_all_labels: true
# Tenant limit override
overrides:
"tenant-iot":
ingestion_rate_mb: 50
max_streams_per_user: 10000
retention_period: 90d
"tenant-test":
ingestion_rate_mb: 5
max_streams_per_user: 1000
retention_period: 7d
6. Chunks Storage & Retensi
Loki menyimpan log data dalam bentuk chunks yang terkompresi. Setiap chunk berisi baris-baris log dari satu stream dalam rentang waktu tertentu. Pemahaman tentang chunks sangat penting untuk mengelola storage dan performa query.
Struktur Chunk
# Setiap chunk memiliki:
# - Fingerprint: hash dari label set
# - Labels: label set yang mengidentifikasi stream
# - Time range: rentang waktu (dari timestamp pertama ke terakhir)
# - Encoding: compression codec (snappy, lz4, gzip, zstd)
# - Data: blok terkompresi berisi baris log
# Chunk size target:
# - Default: 1.5MB compressed (target)
# - Max chunk age: 2h (sebelum dipaksa flushed)
# - Chunk idle period: 30m (flush jika tidak ada data baru)
Storage Backend
| Backend | Keterangan | Cocok Untuk |
|---|---|---|
| Filesystem | Menyimpan di disk lokal | Development, single node |
| S3 / MinIO | Object storage AWS atau self-hosted | Production, scalable |
| GCS | Google Cloud Storage | GCP environment |
| Azure Blob | Azure Blob Storage | Azure environment |
| Swift | OpenStack Object Storage | OpenStack environment |
Retention Policies
# Global retention — hapus data lebih dari 30 hari
limits_config:
retention_period: 744h # 31 hari
# Per-stream retention override
# (gunakan compactor untuk apply)
compactor:
retention_enabled: true
retention_delete_delay: 2h
retention_delete_worker_count: 150
# Per-tenant retention (overrides API)
# POST /loki/api/v1/limits/retention
# Body: {"retention_period": "2160h"} # 90 hari
Retensi hanya bekerja jika compactor aktif. Pastikan compactor berjalan dan retention_enabled: true. Tanpa compactor, data lama tidak akan dihapus meskipun retention_period sudah diatur.
Mengukur Penggunaan Storage
# Cek chunk metrics via API
curl http://localhost:3100/metrics | grep loki_chunk
# Metrik penting:
# loki_ingester_chunk_entries — jumlah entries per chunk
# loki_ingester_chunk_size_bytes — ukuran chunk saat ini
# loki_bloomstore_chunks_downloaded_total — chunks downloaded saat query
# Monitoring storage di Grafana — tambahkan dashboard ID 13639
# (Loki Stack Monitoring dashboard)
7. Compactor & Optimasi Storage
Compactor adalah komponen yang melakukan compaction (penggabungan) dan retention (penghapusan data lama). Compactor membaca chunks dari storage, menggabungkan chunks kecil menjadi lebih besar, dan menghapus chunks yang sudah melewati batas retensi.
Mengapa Compaction Penting?
- Mengurangi jumlah chunks: Banyak chunks kecil = lebih banyak object storage request saat query
- Meningkatkan query performa: Lebih sedikit chunks = lebih sedikit I/O
- Menghemat storage: Kompresi yang lebih baik setelah merge
- Menjalankan retention: Menghapus data expired secara otomatis
Konfigurasi Compactor
# loki-config.yml — compactor section
compactor:
working_directory: /loki/compactor
shared_store: filesystem # harus sama dengan chunk store
compaction_interval: 10m # seberapa sering compaction berjalan
compaction_window: 4h # rentang waktu data yang di-compact
retention_enabled: true
retention_mode: filter-and-delete # atau "disabled"
retention_delete_delay: 2h # delay sebelum benar-benar dihapus
retention_delete_worker_count: 150 # parallelism delete
delete_request_store: filesystem
# Untuk skala besar, gunakan mode microservices:
# Jalankan compactor sebagai service terpisah
# Target: 1 compactor instance per 100GB data per hari
Monitoring Compactor
# Metrik penting compactor:
# loki_compactor_runs_total — total runs
# loki_compaction_work_duration_seconds — durasi compaction
# loki_compactor_deleted_lines_total — baris yang dihapus retention
# loki_compactor_load_pending_requests_total — pending delete requests
# Alert compactor stalled:
# loki_compactor_runs_total tidak bertambah selama 30 menit
Tips Optimasi Storage
- Gunakan zstd compression — rasio kompresi terbaik untuk log
- Atur chunk_target_size ke 1.5MB untuk keseimbangan antara ukuran dan query speed
- Gunakan per-tenant retention untuk data dengan prioritas berbeda
- Monitor ingested bytes per label untuk mendeteksi high-cardinality
- Pertimbangkan structured metadata (Loki 3.0+) untuk mengurangi label cardinality
8. Alerting Rules dengan Grafana
Loki mendukung alerting rules melalui komponen Ruler atau langsung dari Grafana. Alerting rules memungkinkan kamu mendeteksi pola dalam log dan mengirim notifikasi ketika kondisi tertentu terpenuhi.
Loki Ruler Configuration
# Tambahkan ruler ke loki-config.yml
ruler:
enable_api: true
storage:
type: local
local:
directory: /loki/rules
rule_path: /loki/rules-temp
alertmanager_url: http://alertmanager:9093
ring:
kvstore:
store: inmemory
enable_alertmanager_v2: true
Contoh Alerting Rules
# /loki/rules/fake/iot-alerts.yaml
groups:
- name: iot-device-alerts
interval: 1m
rules:
# Alert: Device offline lebih dari 5 menit
- alert: DeviceOffline
expr: |
absent_over_time(
{job="iot-gateway"} |= "heartbeat" [5m]
)
for: 2m
labels:
severity: critical
annotations:
summary: "IoT device heartbeat hilang"
description: "Tidak ada heartbeat dari device selama lebih dari 5 menit"
# Alert: Temperature terlalu tinggi
- alert: HighTemperature
expr: |
sum by (device) (
count_over_time(
{job="iot-gateway"} | json | temperature > 40 [5m]
)
) > 0
for: 3m
labels:
severity: warning
annotations:
summary: "Suhu device {{ $labels.device }} di atas 40°C"
description: "Device {{ $labels.device }} melaporkan suhu tinggi"
# Alert: Error rate spike
- alert: ErrorRateSpike
expr: |
sum(rate({job="iot-gateway"} |= "error" [5m]))
/
sum(rate({job="iot-gateway"}[5m]))
> 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "Error rate IoT gateway di atas 10%"
description: "Error rate saat ini: {{ $value | humanizePercentage }}"
# Alert: Disk usage tinggi di gateway
- alert: GatewayDiskHigh
expr: |
count_over_time(
{job="iot-gateway"} |~ "disk_usage.*critical" [10m]
) > 0
for: 1m
labels:
severity: critical
annotations:
summary: "Disk usage IoT gateway kritis"
Alertmanager Integration
# alertmanager.yml
global:
smtp_smarthost: 'smtp.gmail.com:587'
smtp_from: 'alerts@company.com'
route:
group_by: ['alertname', 'severity']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'iot-team'
receivers:
- name: 'iot-team'
email_configs:
- to: 'iot-team@company.com'
send_resolved: true
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx/yyy/zzz'
channel: '#iot-alerts'
title: '{{ .GroupLabels.alertname }}'
text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
- name: 'pagerduty-critical'
pagerduty_configs:
- service_key: 'xxx-xxx-xxx'
Grafana Alert Rules (UI-based)
Selain Ruler, kamu juga bisa membuat alert langsung dari Grafana UI:
- Buka Grafana → Alerting → Alert rules
- Klik New alert rule
- Pilih data source: Loki
- Masukkan LogQL query, contoh:
count_over_time({job="iot-gateway"} |= "error" [5m]) - Atur threshold: IS ABOVE 10
- Konfigurasi labels dan annotations
- Pilih notification channel (email, Slack, webhook)
- Simpan dan aktifkan
9. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Grafana Loki: