Logging

Logging #

Server yang tidak bisa diinvestigasi saat terjadi masalah adalah server yang tidak bisa dikelola. Log adalah sumber informasi paling mendasar untuk memahami apa yang terjadi di sistem — namun log yang tersebar di puluhan server tanpa agregasi terpusat hampir sama tidak bergunanya dengan tidak adanya log sama sekali. Mengotomasi setup infrastruktur logging dengan Ansible memastikan setiap server mengirimkan log ke tempat yang tepat dengan format yang konsisten, sejak pertama kali server dibuat — bukan setelah insiden pertama terjadi dan kita menyadari tidak ada jejak apa pun untuk dianalisis.

Artikel ini akan membawa kita dari konsep fundamental tentang tiga pilar log — log aplikasi, log sistem, dan log akses — sampai ke setup konkret log shipper, backend penyimpanan, log rotation, dan integrasi dengan stack /observability/monitoring/ yang sudah kita bangun. Setelah selesai membaca, kita akan memiliki playbook dan role Ansible yang bisa langsung digunakan untuk men-deploy pipeline logging di seluruh fleet server kita.

Anatomi Pipeline Logging #

Sebelum menulis role Ansible, kita perlu memahami alur lengkap sebuah log dari baris teks yang ditulis aplikasi sampai bisa kita query di dashboard. Setiap komponen memiliki peran spesifik, dan kegagalan di satu titik akan memutuskan seluruh rantai pengiriman log:

flowchart LR
    A["Aplikasi<br/>stdout / file"] --> B["Log Shipper<br/>Filebeat / Promtail"]
    C["OS & Services<br/>syslog / journald"] --> B
    B --> D["Buffer / Queue<br/>in-memory"]
    D --> E["Backend Storage<br/>Elasticsearch / Loki"]
    E --> F["Query Layer<br/>Kibana / Grafana"]
    F --> G["SRE / Developer<br/>Investigasi insiden"]

    style A stroke:#b45309,stroke-width:2px
    style C stroke:#b45309,stroke-width:2px
    style B stroke:#1d4ed8,stroke-width:2px
    style E stroke:#15803d,stroke-width:2px
    style F stroke:#be185d,stroke-width:2px
    style G stroke:#7e22ce,stroke-width:2px

Log shipper berjalan di setiap host. Tugasnya sangat spesifik: membaca file log, mem-parsing setiap baris, dan mengirimkannya ke backend. Log shipper bukan tempat menyimpan log jangka panjang — itu tugas backend. Memahami pemisahan tanggung jawab ini penting supaya kita tidak salah memilih tool: jika kita membutuhkan query full-text dengan index cepat, Elasticsearch adalah pilihannya. Namun, jika kita membutuhkan solusi ringan yang hemat disk dan integrasi mulus dengan Prometheus, Loki adalah pilihan terbaik.

Log yang sampai di backend hanya seberguna formatnya. Plain text tanpa struktur memaksa kita menulis regex yang berbeda untuk setiap microservice. JSON terstruktur dengan field konsisten (level, timestamp, service, trace_id) membuat query di backend menjadi satu baris PromQL atau KQL. Investasi dua jam di awal untuk setup structured logging menghemat ratusan jam debugging dalam setahun.

Tiga Format Log yang Wajib Kita Kenal #

Pemilihan format log menentukan bagaimana backend bisa melakukan query, agregasi, dan alerting. Ketiganya memiliki karakteristik tersendiri di infrastruktur:

Format Contoh Baris Kelebihan Kekurangan Backend Ideal
Plain text 2026-06-07 08:01:23 INFO User 42 logged in Kompatibel di mana-mana, mudah dibaca manusia Tidak ada field konsisten, regex brittle Tidak disarankan untuk produksi
logfmt level=info ts=2026-06-07T08:01:23Z user=42 msg="logged in" Ringkas, mudah di-parse, format standar Heroku/Cloudflare Kurang ekspresif untuk nested data Loki, ELK
JSON {"level":"info","ts":"2026-06-07T08:01:23Z","user":42,"msg":"logged in"} Mendukung nested, library client di semua bahasa, schema validation Verbose, butuh pretty-print untuk debugging lokal ELK, Loki, Datadog, semua vendor

Saat ini, JSON adalah default yang seharusnya kita pilih untuk aplikasi baru. Hampir semua bahasa memiliki library structured logger (seperti Python structlog, Go zap, Java Logback + logstash-encoder, dan Node pino) yang menghasilkan JSON tanpa overhead signifikan. Untuk aplikasi warisan (legacy) yang masih menghasilkan plain text, kita bisa melakukan parsing di log shipper dengan pipeline processor — tetapi itu merupakan workaround, bukan solusi jangka panjang.


Setup Filebeat sebagai Log Shipper #

Filebeat adalah agen ringan dari Elastic yang membaca file log dan mengirimkannya ke Elasticsearch atau Logstash. Pilihan ini masuk akal jika stack monitoring kita sudah berbasis Elastic — jika belum, silakan lompat ke bagian Setup Loki sebagai Backend di bawah.

# roles/filebeat/tasks/main.yml
---
- name: Tambahkan Elastic GPG key
  apt_key:
    url: https://artifacts.elastic.co/GPG-KEY-elasticsearch
    state: present

- name: Tambahkan repository Elastic
  apt_repository:
    repo: "deb https://artifacts.elastic.co/packages/8.x/apt stable main"
    state: present

- name: Install Filebeat
  apt:
    name: "filebeat={{ filebeat_version }}"
    state: present
    update_cache: true

- name: Deploy konfigurasi Filebeat
  template:
    src: filebeat.yml.j2
    dest: /etc/filebeat/filebeat.yml
    owner: root
    group: root
    mode: '0644'
  notify: Restart Filebeat

- name: Aktifkan dan jalankan Filebeat
  systemd:
    name: filebeat
    state: started
    enabled: true

Perhatikan checksum — di task install di bawah ini kita akan melihat bagaimana menambahkannya untuk keamanan. Konfigurasi Filebeat di bawah ini menambahkan field wajib ke setiap event log: hostname, environment, dan service name. Field-field ini adalah satu-satunya cara kita bisa memfilter log dari 200 server di fleet kita — jika tidak ada sejak awal, query service:api-gateway di Kibana tidak akan menemukan apa-apa:

{# roles/filebeat/templates/filebeat.yml.j2 #}
filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/*.log
      - /var/log/syslog
    fields:
      host: {{ inventory_hostname }}
      environment: {{ env }}
    fields_under_root: true

  - type: log
    enabled: true
    paths:
      - /var/log/app/*.log
    fields:
      service: {{ app_name }}
      host: {{ inventory_hostname }}
      environment: {{ env }}
    fields_under_root: true
    json.keys_under_root: true   # Parse JSON log secara otomatis
    json.overwrite_keys: true    # Field JSON naik ke root, bukan nested di 'message'
    json.add_error_key: true     # Track error parsing supaya gagal parse tidak silent

processors:
  - add_host_metadata:
      when.not.contains.tags: forwarded
  - add_cloud_metadata: ~
  - drop_fields:
      fields: ["agent.ephemeral_id", "agent.id"]
      ignore_missing: true

output.elasticsearch:
  hosts: {{ elasticsearch_hosts | to_json }}
  index: "logs-{{ '{{' }} fields.service {{ '}}' }}-%{+yyyy.MM.dd}"
  username: "{{ elasticsearch_username }}"
  password: "{{ vault_elasticsearch_password }}"
  ssl.certificate_authorities: ["/etc/filebeat/ca.crt"]
  ssl.verification_mode: full

setup.template.name: "logs"
setup.template.pattern: "logs-*"
setup.ilm.enabled: true   # Index Lifecycle Management — auto hapus log lama
Jangan lupa json.overwrite_keys: true saat input log berupa JSON. Tanpa flag tersebut, semua field JSON kita akan tersimpan di bawah key message sebagai satu objek, dan query level:error di Kibana tidak akan menemukan apa-apa. Ini adalah bug paling umum dalam konfigurasi Filebeat dengan proses debugging yang bisa memakan waktu berjam-jam.

Pair Anti-Pattern: Field Kosong vs Field Wajib #

Kasus paling umum yang sering muncul saat review konfigurasi logging di tim yang baru setup logging:

# ANTI-PATTERN: hanya membaca /var/log/*.log tanpa menambahkan field metadata
filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/*.log
# Masalah: saat ada 50 server mengirim log, kita tidak bisa memfilter
# "tampilkan error dari service X di production" karena tidak ada
# field service/host/environment. Setiap query harus melakukan scan terhadap seluruh dataset.

# BENAR: tambahkan fields di setiap input
filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/app/*.log
    fields:
      service: {{ app_name }}
      host: {{ inventory_hostname }}
      environment: {{ env }}
    fields_under_root: true
# Hasil: query di Kibana cukup `service:api-gateway AND level:error AND
# environment:production` — selesai dalam 2 detik di 50 server.

Memvalidasi Download Binary dengan Checksum #

Filebeat binary di-download langsung dari internet. Tanpa verifikasi checksum, satu compromised release server bisa menyisipkan backdoor ke seluruh fleet kita. Tambahkan verifikasi ini ke role:

# roles/filebeat/tasks/main.yml — tambahkan setelah apt install
- name: Verifikasi checksum binary Filebeat
  stat:
    path: /usr/share/filebeat/bin/filebeat
    checksum_algorithm: sha256
  register: filebeat_binary_stat

- name: Fail jika checksum tidak sesuai
  fail:
    msg: "Filebeat binary checksum mismatch — kemungkinan file korup atau compromised"
  when: filebeat_binary_stat.stat.checksum != filebeat_expected_sha256
  # Ambil nilai dari https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.x.x-linux-x86_64.tar.gz.sha512
# Verifikasi checksum bukan paranoia — supply chain attack terhadap tool monitoring
# adalah skenario nyata. Tambahkan checksum di defaults/main.yml dan validasi
# sebelum binary dieksekusi. Lima menit setup menghemat dari kompromi seluruh fleet.

Manajemen Log Rotation #

Log yang tidak di-rotate akan memenuhi disk. Setup logrotate untuk semua log penting — dan penting artinya: log aplikasi, log nginx, log PostgreSQL, bukan cuma /var/log/syslog:

# roles/logrotate/tasks/main.yml
---
- name: Deploy konfigurasi logrotate untuk aplikasi
  template:
    src: app-logrotate.j2
    dest: "/etc/logrotate.d/{{ app_name }}"
    owner: root
    group: root
    mode: '0644'

- name: Pastikan cron harian logrotate aktif
  cron:
    name: "logrotate daily"
    minute: "0"
    hour: "1"
    user: root
    job: "/usr/sbin/logrotate /etc/logrotate.conf"
    cron_file: ansible_logrotate
  when: logrotate_manage_cron | default(false)
{# roles/logrotate/templates/app-logrotate.j2 #}
{{ app_log_dir }}/*.log {
    daily
    rotate {{ log_rotate_days | default(14) }}
    dateext              # suffix tanggal, bukan .1.gz
    dateformat -%Y%m%d   # format YYYYMMDD
    compress
    delaycompress
    missingok
    notifempty
    sharedscripts
    copytruncate         # untuk aplikasi yang tidak bisa di-signal reopen
    postrotate
        systemctl reload {{ app_name }} > /dev/null 2>&1 || true
    endscript
}

copytruncate adalah opsi penting untuk aplikasi yang tidak menangani SIGHUP dengan benar (seperti beberapa aplikasi Java): daripada me-rename file log (yang membuat aplikasi tetap menulis ke file dengan inode lama), copytruncate menyalin isi file lalu memotong (truncate) file asli di tempat. Aplikasi tidak perlu tahu apa-apa.

Pair Anti-Pattern: Tanpa dateext vs Dengan dateext #

# ANTI-PATTERN: logrotate default rotation
/var/log/myapp/*.log {
    daily
    rotate 7
    compress
}
# Masalah: hasil file adalah myapp.log.1.gz, myapp.log.2.gz, ...
# Kita tidak bisa mengetahui kapan log tersebut di-rotate tanpa membuka isinya.
# Saat restore dari backup, urutan file .1 .2 .3 tidak informatif.

# BENAR: gunakan dateext + dateformat
/var/log/myapp/*.log {
    daily
    rotate 14
    dateext
    dateformat -%Y%m%d
    compress
}
# Hasil: myapp.log-20260607.gz, myapp.log-20260606.gz, ...
# Jelas terlihat: file ini di-rotate tanggal berapa. Pencarian di backup
# tool (atau `ls`) langsung jelas. Plus, urutan rotasi jadi tidak ambigu.

Setup Loki sebagai Backend Logging Ringan #

Untuk tim yang tidak ingin mengelola Elasticsearch, Grafana Loki adalah alternatif yang jauh lebih ringan. Loki menyimpan log dalam format yang mirip Prometheus (label-based, bukan full-text index), sehingga sangat cocok digunakan bersama /observability/monitoring/ yang sudah kita deploy:

# roles/loki/tasks/main.yml
---
- name: Buat user loki
  user:
    name: loki
    system: true
    shell: /usr/sbin/nologin
    home: /opt/loki
    create_home: true

- name: Download Loki binary
  get_url:
    url: "https://github.com/grafana/loki/releases/download/v{{ loki_version }}/loki-linux-amd64.zip"
    dest: /tmp/loki.zip
    mode: '0644'

- name: Extract Loki
  unarchive:
    src: /tmp/loki.zip
    dest: /usr/local/bin/
    remote_src: true
    creates: /usr/local/bin/loki-linux-amd64

- name: Rename binary Loki
  command: mv /usr/local/bin/loki-linux-amd64 /usr/local/bin/loki
  args:
    creates: /usr/local/bin/loki

- name: Deploy konfigurasi Loki
  template:
    src: loki-config.yml.j2
    dest: /etc/loki/config.yml
    owner: loki
    mode: '0640'
  notify: Restart Loki

- name: Deploy systemd unit file Loki
  template:
    src: loki.service.j2
    dest: /etc/systemd/system/loki.service
  notify:
    - Reload systemd
    - Restart Loki
{# roles/loki/templates/loki-config.yml.j2 #}
auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096
  log_level: info

common:
  path_prefix: /var/lib/loki
  storage:
    filesystem:
      chunks_directory: /var/lib/loki/chunks
      rules_directory: /var/lib/loki/rules
  replication_factor: 1
  ring:
    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: {{ loki_retention_days | default(30) }}d
  ingestion_rate_mb: 10
  ingestion_burst_size_mb: 20
  max_entries_limit_per_query: 5000

compactor:
  working_directory: /var/lib/loki/compactor
  retention_enabled: true
  delete_request_store: filesystem

ruler:
  alertmanager_url: http://alertmanager:9093
  storage:
    type: local
    local:
      directory: /var/lib/loki/rules

Perhatikan retention_period: 30d di limits_config — ini adalah kebijakan jangka waktu penyimpanan log. Atur sesuai dengan kebutuhan kepatuhan (compliance) bisnis kita. Industri fintech dan layanan kesehatan biasanya membutuhkan 1–7 tahun, sedangkan startup standar cukup 30–90 hari. Menghapus retention policy berarti menyimpan selamanya, yang berujung pada membengkaknya biaya penyimpanan.


Promtail: Log Shipper untuk Loki #

Promtail adalah agen Loki — lebih ringan dari Filebeat untuk use case yang tidak membutuhkan fitur Elastic. Yang paling penting: Promtail menghasilkan label Prometheus-style yang bisa kita query menggunakan LogQL dengan sintaksis yang mirip dengan PromQL:

# roles/promtail/tasks/main.yml
---
- name: Download dan install Promtail
  get_url:
    url: "https://github.com/grafana/loki/releases/download/v{{ promtail_version }}/promtail-linux-amd64.zip"
    dest: /tmp/promtail.zip

- name: Extract Promtail
  unarchive:
    src: /tmp/promtail.zip
    dest: /usr/local/bin/
    remote_src: true
    creates: /usr/local/bin/promtail-linux-amd64

- name: Rename Promtail binary
  command: mv /usr/local/bin/promtail-linux-amd64 /usr/local/bin/promtail
  args:
    creates: /usr/local/bin/promtail

- name: Deploy konfigurasi Promtail
  template:
    src: promtail-config.yml.j2
    dest: /etc/promtail/config.yml
  notify: Restart Promtail

- name: Deploy systemd unit Promtail
  template:
    src: promtail.service.j2
    dest: /etc/systemd/system/promtail.service
  notify:
    - Reload systemd
    - Restart Promtail
{# templates/promtail-config.yml.j2 #}
server:
  http_listen_port: 9080
  grpc_listen_port: 0
  log_level: info

positions:
  filename: /var/lib/promtail/positions.yaml

clients:
  - url: http://{{ loki_host }}:3100/loki/api/v1/push
    batchwait: 1s
    batchsize: 1048576
    backoff_config:
      min_period: 500ms
      max_period: 5m

scrape_configs:
  - job_name: system
    static_configs:
      - targets:
          - localhost
        labels:
          job: varlogs
          host: {{ inventory_hostname }}
          env: {{ env }}
          __path__: /var/log/*log

  - job_name: {{ app_name }}
    static_configs:
      - targets:
          - localhost
        labels:
          job: {{ app_name }}
          host: {{ inventory_hostname }}
          env: {{ env }}
          __path__: {{ app_log_dir }}/*.log
    pipeline_stages:
      - json:
          expressions:
            level: level
            message: message
            request_id: request_id
      - labels:
          level:
      - metrics:
          log_lines_total:
            type: Counter
            description: "Total log lines for {{ app_name }}"
            config:
              match_all: true
            action: inc
# Promtail menghasilkan metric `log_lines_total` dari jumlah baris log
# yang di-scrape. Metric ini bisa di-scrape Prometheus lewat
# `http://promtail-host:9080/metrics` — artinya kita bisa melakukan alerting saat
# log rate turun drastis (indikasi aplikasi crash tanpa menulis log).
# Bridge ini sangat powerful: metrik aplikasi hilang bisa berarti aplikasi hilang,
# atau bisa juga logging pipeline yang rusak.

Structured Logging dari Aplikasi #

Log shipper hanya bisa mengelompokkan log jika field-nya konsisten. Mendorong structured logging dari aplikasi jauh lebih efektif daripada melakukan parsing plain text di shipper — kita memiliki konteks penuh (trace ID, user ID, durasi) yang tidak akan pernah bisa diturunkan (derive) secara andal dari regex. Contoh untuk aplikasi Python:

# app/utils/logger.py
import logging
import sys
import json
from pythonjsonlogger import jsonlogger

class CustomJsonFormatter(jsonlogger.JsonFormatter):
    def add_fields(self, log_record, record, message_dict):
        super().add_fields(log_record, record, message_dict)
        log_record['timestamp'] = self.formatTime(record, self.datefmt)
        log_record['level'] = record.levelname
        log_record['logger'] = record.name
        log_record['service'] = '{{ app_name }}'  # di-inject oleh Ansible
        log_record['environment'] = '{{ env }}'

logger = logging.getLogger()
handler = logging.StreamHandler(sys.stdout)
formatter = CustomJsonFormatter('%(timestamp)s %(level)s %(name)s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)

# Penggunaan di aplikasi:
# logger.info("user_login", extra={"user_id": 42, "ip": "10.0.0.1"})
# Output: {"timestamp":"2026-06-07T08:01:23Z","level":"INFO","service":"api",
#          "environment":"production","message":"user_login",
#          "user_id":42,"ip":"10.0.0.1"}

Ansible bisa diintegrasikan untuk menyuntikkan nilai app_name dan env saat deploy — gunakan modul template untuk membuat file konfigurasi logger dengan nilai yang relevan untuk environment tersebut.


Decision Tree: Filebeat atau Promtail? #

Pemilihan log shipper menentukan backend dan biaya operasional. Gunakan decision tree ini sebagai panduan awal:

flowchart TD
    A["Stack monitoring<br/>sudah ada?"] -->|"Elasticsearch"| B["Gunakan Filebeat"]
    A -->|"Grafana + Prometheus"| C["Gunakan Promtail"]
    A -->|"Belum ada"| D{"Volume log<br/>per hari?"}

    D -->|"< 10 GB"| E["Promtail + Loki<br/>lebih ringan"]
    D -->|"> 50 GB"| F["Filebeat + Elasticsearch<br/>index lebih kuat"]

    B --> G{"Butuh parsing<br/>kompleks?"}
    C --> H{"Butuh integrasi<br/>dengan alertmanager?"}
    G -->|"Ya"| I["Filebeat + Logstash<br/>pipeline lebih kaya"]
    G -->|"Tidak"| J["Filebeat langsung<br/>ke Elasticsearch"]
    H -->|"Ya"| K["Promtail + Loki Ruler<br/>alerting built-in"]
    H -->|"Tidak"| L["Promtail standar"]

    style A stroke:#b45309,stroke-width:2px
    style E stroke:#15803d,stroke-width:2px
    style F stroke:#15803d,stroke-width:2px
    style I stroke:#1d4ed8,stroke-width:2px
    style J stroke:#1d4ed8,stroke-width:2px
Kriteria Filebeat + Elasticsearch Promtail + Loki
Resource per host ~50-100 MB RAM ~30-50 MB RAM
Storage cost Tinggi (full-text index) Rendah (label-based)
Query performance Sangat cepat untuk full-text search Optimal untuk query by label, lambat untuk full-text
Index Lifecycle ILM built-in Compactor + retention policy
Alerting dari log Logstash + ElastAlert Loki Ruler + Alertmanager
Learning curve Moderate (Elastic stack) Rendah (jika sudah pakai Grafana)
Best for Search-driven investigation, audit log Operational log, integration dengan metric

Pair Anti-Pattern: Password Plaintext vs Ansible Vault #

Credential backend seharusnya tidak pernah ada di file konfigurasi dalam bentuk plaintext. Pasangan ini menunjukkan perbedaan yang harus kita hindari dalam review:

{# ANTI-PATTERN: password hardcoded di template Jinja2 #}
{# roles/filebeat/templates/filebeat.yml.j2 #}
output.elasticsearch:
  hosts: ["{{ elasticsearch_host }}:9200"]
  username: "elastic"
  password: "MyS3cretP@ssw0rd"   # ✗ BAHAYA — ada di Git, terlihat di `ps`, bocor saat debug
  # Masalah: password tersimpan di Git history selamanya. Saat developer
  # menjalankan playbook di laptop, password muncul di log output. Saat
  # file di-share untuk debugging, credential ikut tersebar.

{# BENAR: credential dari Ansible Vault, di-inject saat render template #}
{# roles/filebeat/templates/filebeat.yml.j2 #}
output.elasticsearch:
  hosts: {{ elasticsearch_hosts | to_json }}
  username: "{{ elasticsearch_username }}"
  password: "{{ vault_elasticsearch_password }}"   # ✓ dari vault
  ssl.certificate_authorities: ["/etc/filebeat/ca.crt"]

Cara menyuntikkan vault ke template:

# Buat vault (dilakukan sekali, simpan vault password di password manager)
ansible-vault create group_vars/all/vault.yml
# Isi:
# vault_elasticsearch_password: "MyS3cretP@ssw0rd"

# Jalankan playbook dengan vault password
ansible-playbook site.yml --ask-vault-pass
# Atau otomatis dari CI:
ansible-playbook site.yml --vault-password-file ~/.vault_pass

Jangan pernah membiarkan log output menampilkan rendered template. Tambahkan no_log: true pada task yang merender template berisi credential:

- name: Deploy konfigurasi Filebeat
  template:
    src: filebeat.yml.j2
    dest: /etc/filebeat/filebeat.yml
  no_log: true   # ✓ menyembunyikan output yang bisa membocorkan credential

Tanpa no_log: true, password bisa muncul di log Ansible jika terjadi error. Untuk pipeline CI/CD yang menyimpan log artifact, ini setara dengan menuliskan password ke penyimpanan publik.


Mengintegrasikan Log dengan Metric Collection #

Log dan metrik saling melengkapi. Saat Prometheus alerting memberitahukan bahwa CPU usage naik di host-42, kita ingin langsung melompat ke log host tersebut untuk melihat apa yang sedang terjadi. Integrasi ini membutuhkan label yang konsisten di kedua belah pihak:

# roles/loki/tasks/integration.yml — label harus sama dengan
# yang dipakai Prometheus (lihat artikel metric-collection)
---
- name: Pastikan label Loki konsisten dengan Prometheus
  lineinfile:
    path: /etc/loki/config.yml
    regexp: '^  external_labels:'
    line: |
      external_labels:
        cluster: {{ cluster_name }}
        environment: {{ env }}      
    insertafter: 'common:'
  notify: Restart Loki

# Di Promtail scrape config, host label juga harus konsisten
# dengan `host` label di Prometheus node_exporter scrape

Query gabungan di Grafana — mengambil metrik dari Prometheus, melihat log yang relevan dari Loki di panel yang sama:

# LogQL query di Grafana Explore, filter menggunakan label yang sama dengan PromQL
{service="api-gateway", env="production", host="api-42"}
  | json
  | level="error"
  | line_format "{{.message}}"

Ringkasan #

  • Filebeat untuk ekosistem Elastic (Elasticsearch/Logstash); Promtail untuk ekosistem Grafana (Loki) — pilih berdasarkan stack monitoring yang sudah ada, jangan paksakan yang baru.
  • Selalu tambahkan fields di konfigurasi log shipper: host, environment, service — tanpa ini, query di backend pada 50+ server hampir mustahil dilakukan.
  • logrotate wajib di-setup untuk semua log aplikasi — log tanpa rotation akan menghabiskan disk dalam hitungan minggu di server produksi dan menyebabkan cascading failure.
  • Untuk log dalam format JSON, aktifkan json.keys_under_root: true (Filebeat) atau pipeline_stages: json (Promtail) agar field log bisa dicari dan difilter secara individual.
  • Gunakan Ansible Vault untuk menyimpan credential Elasticsearch atau Loki — password tidak boleh plaintext di file konfigurasi atau di Git history.
  • Pin versi Filebeat/Promtail/Loki di defaults/main.yml — update minor yang tidak terkontrol bisa mengubah format log, label, atau struktur index.
  • Aktifkan json.overwrite_keys: true saat input Filebeat adalah JSON — tanpa flag ini, field JSON akan nested di dalam message dan tidak bisa di-query per-field.
  • Tambahkan no_log: true di task yang render template berisi credential — mencegah kebocoran password di Ansible log atau CI artifact.

← Sebelumnya: Testing   Berikutnya: Monitoring →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact