Mở đầu: Câu chuyện từ đỉnh điểm 11.11

Năm ngoái, tôi đang quản lý hệ thống AI cho một nền tảng thương mại điện tử lớn tại Trung Quốc. Khi chiến dịch 11.11 bắt đầu, lưu lượng truy cập tăng đột biến 3000% trong vòng 15 phút. Vào lúc 23:47 — chỉ còn 13 phút nữa là hết khuyến mãi — toàn bộ hệ thống Dify của tôi bị treo. Không có log, không có metrics, không có gì ngoài màn hình trắng chết chóc. Đó là khoảnh khắc tôi nhận ra: Dify mặc định chỉ lưu log vào file cục bộ. Khi container restart, log biến mất. Khi 50 worker chạy song song, log lẫn nhau. Khi cần debug latency spike ở production, tôi phải SSH vào từng máy. Sau 72 giờ không ngủ, tôi xây dựng hệ thống ELK Stack để aggregate toàn bộ log từ Dify. Kết quả: lần peak tiếp theo, tôi phát hiện và fix bottleneck trong 8 phút thay vì 3 giờ. Bài viết này là tất cả những gì tôi đã học được — viết lại từ đầu để bạn không phải đi vòng như tôi.

ELK Stack là gì và tại sao cần cho Dify

ELK Stack bao gồm ba thành phần chính: Với Dify, mỗi API request tạo ra nhiều loại log khác nhau: request/response, database query, LLM API call, embedding operation. Khi có 1000+ requests/phút, việc grep log thủ công là bất khả thi.

Kiến trúc tổng thể

┌─────────────────────────────────────────────────────────────────┐
│                        Dify Containers                          │
├─────────────┬─────────────┬─────────────┬─────────────────────┤
│  API Server │  Worker     │  Web App    │  Celery Beat        │
│  Port 80    │  Background │  Port 80    │  Scheduler          │
└──────┬──────┴──────┬──────┴──────┬──────┴──────────┬──────────┘
       │             │             │                 │
       ▼             ▼             ▼                 ▼
┌─────────────────────────────────────────────────────────────────┐
│                     Filebeat Agent                              │
│              (Tail every Dify log file)                         │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│                      Logstash Server                            │
│            (Parse → Filter → Enrich → Output)                   │
└────────────────────────────┬────────────────────────────────────┘
                             │
              ┌──────────────┼──────────────┐
              ▼              ▼              ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│  Elasticsearch  │ │  Kafka/Redis    │ │  Dead Letter    │
│  Cluster        │ │  (Optional)     │ │  Queue          │
└────────┬────────┘ └─────────────────┘ └─────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────┐
│                       Kibana Dashboard                          │
│            (Visualize, Search, Alert, Analyze)                 │
└─────────────────────────────────────────────────────────────────┘

Triển khai Step-by-Step

Bước 1: Cài đặt ELK Stack với Docker Compose

Tạo file elk/docker-compose.yml với cấu hình tối ưu cho hệ thống vừa và nhỏ (4GB RAM tối thiểu):
version: '3.8'

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
    container_name: elasticsearch
    environment:
      - node.name=elasticsearch
      - cluster.name=dify-logs-cluster
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms2g -Xmx2g"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"
    networks:
      - elk

  logstash:
    image: docker.elastic.co/logstash/logstash:8.11.0
    container_name: logstash
    volumes:
      - ./pipeline:/usr/share/logstash/pipeline:ro
      - ./config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro
    ports:
      - "5044:5044"
      - "5000:5000/tcp"
      - "5000:5000/udp"
      - "9600:9600"
    environment:
      - "LS_JAVA_OPTS=-Xms512m -Xmx512m"
    networks:
      - elk
    depends_on:
      - elasticsearch

  kibana:
    image: docker.elastic.co/kibana/kibana:8.11.0
    container_name: kibana
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    ports:
      - "5601:5601"
    networks:
      - elk
    depends_on:
      - elasticsearch

volumes:
  elasticsearch_data:
    driver: local

networks:
  elk:
    driver: bridge

Bước 2: Cấu hình Logstash Pipeline

Tạo file elk/pipeline/dify.conf — đây là phần quan trọng nhất để parse log từ Dify:
input {
  beats {
    port => 5044
    host => "0.0.0.0"
  }
}

filter {
  # Parse JSON log entries from Dify
  if [message] =~ /^\{/ {
    json {
      source => "message"
      target => "parsed"
      skip_on_invalid_json => true
    }
    
    if [parsed] {
      # Extract Dify log fields
      mutate {
        add_field => {
          "dify_app_id" => "%{[parsed][app_id]}"
          "dify_conversation_id" => "%{[parsed][conversation_id]}"
          "dify_message_id" => "%{[parsed][message_id]}"
          "dify_latency_ms" => "%{[parsed][latency]}"
          "dify_model" => "%{[parsed][model]}"
          "dify_provider" => "%{[parsed][provider]}"
          "dify_error" => "%{[parsed][error]}"
          "dify_tokens" => "%{[parsed][tokens]}"
        }
      }
      
      # Convert latency to integer for aggregations
      if [dify_latency_ms] {
        mutate {
          convert => { "dify_latency_ms" => "integer" }
        }
      }
      
      # Tag error logs for alerting
      if [dify_error] and [dify_error] != "" {
        mutate {
          add_tag => ["error", "dify_error"]
        }
      }
      
      # Tag slow requests (> 3000ms)
      if [dify_latency_ms] and [dify_latency_ms] > 3000 {
        mutate {
          add_tag => ["slow_request", "latency_alert"]
        }
      }
      
      # Parse timestamp
      if [parsed][created_at] {
        date {
          match => ["[parsed][created_at]", "UNIX"]
          target => "@timestamp"
        }
      }
    }
  }
  
  # Handle plain text logs (fallback)
  else {
    grok {
      match => { 
        "message" => "\[%{TIMESTAMP_ISO8601:timestamp}\] %{LOGLEVEL:level} %{DATA:module}: %{GREEDYDATA:content}"
      }
      tag_on_failure => ["_grokparsefailure"]
    }
  }
  
  # Add metadata
  mutate {
    add_field => {
      "environment" => "production"
      "service" => "dify"
      "[@metadata][index_prefix]" => "dify-logs"
    }
  }
  
  # Enrich with GeoIP if client_ip exists
  if [parsed][client_ip] {
    geoip {
      source => "[parsed][client_ip]"
      target => "geoip"
      database => "/usr/share/GeoIP/GeoLite2-City.mmdb"
      tag_on_failure => ["_geoip_lookup_failure"]
    }
  }
}

output {
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    index => "%{[@metadata][index_prefix]}-%{+YYYY.MM.dd}"
    document_type => "_doc"
  }
  
  # Debug output (disable in production)
  # stdout { codec => rubydebug }
  
  # Send errors to separate index for alerting
  if "error" in [tags] {
    elasticsearch {
      hosts => ["elasticsearch:9200"]
      index => "dify-errors-%{+YYYY.MM.dd}"
    }
  }
}

Bước 3: Cài đặt Filebeat trên Dify Host

Tạo file filebeat/filebeat.yml để thu thập log từ Dify:
filebeat.inputs:
  # Dify API Server Logs
  - type: log
    enabled: true
    paths:
      - /var/lib/docker/containers/*/dify-api-*.log
    fields:
      component: api
      service: dify
    fields_under_root: true
    json.keys_under_root: true
    json.add_error_key: true
    json.message_key: message
    
  # Dify Worker Logs  
  - type: log
    enabled: true
    paths:
      - /var/lib/docker/containers/*/dify-worker-*.log
    fields:
      component: worker
      service: dify
    fields_under_root: true
    
  # Nginx Access Logs (if using reverse proxy)
  - type: log
    enabled: true
    paths:
      - /var/log/nginx/access.log
    fields:
      component: nginx
      service: dify
    fields_under_root: true

Processors for enrichment

processors: - add_host_metadata: when.not.contains.tags: forwarded - add_cloud_metadata: ~ - add_docker_metadata: ~ - decode_json_fields: fields: ["message"] target: "" overwrite_keys: true add_error_key: true

Output to Logstash

output.logstash: hosts: ["elk-logstash:5044"] ssl.enabled: false

Logging configuration

logging.level: info logging.to_files: true logging.files: path: /var/log/filebeat name: filebeat keepfiles: 7 permissions: 0644

ILM Policy (Index Lifecycle Management)

setup.ilm.enabled: true setup.ilm.rollover_alias: "dify-logs" setup.ilm.pattern: "{now/d}-000001" setup.ilm.policy_name: "dify-logs-policy"

Bước 4: Instrument Dify Code để gửi Custom Logs

Bây giờ tôi sẽ hướng dẫn cách modify Dify source code để gửi structured logs. Trước tiên, tạo helper module:
# dify_logging/helper.py
import json
import logging
import time
from datetime import datetime
from functools import wraps
from typing import Any, Dict, Optional
from pythonjsonlogger import jsonlogger

class DifyJsonFormatter(jsonlogger.JsonFormatter):
    """Custom JSON formatter for Dify logs with ELK-friendly format"""
    
    def add_fields(self, log_record, record, message_dict):
        super().add_fields(log_record, record, message_dict)
        log_record['timestamp'] = datetime.utcnow().isoformat()
        log_record['service'] = 'dify'
        log_record['version'] = '1.0'
        log_record['level'] = record.levelname
        
def setup_structured_logging(app_name: str = "dify") -> logging.Logger:
    """Setup JSON-structured logging for ELK ingestion"""
    logger = logging.getLogger(app_name)
    logger.setLevel(logging.INFO)
    
    handler = logging.StreamHandler()
    formatter = DifyJsonFormatter(
        '%(timestamp)s %(level)s %(name)s %(message)s'
    )
    handler.setFormatter(formatter)
    
    if not logger.handlers:
        logger.addHandler(handler)
    
    return logger

def log_llm_call(logger: logging.Logger):
    """Decorator to log LLM API calls with timing"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = None
            error = None
            
            try:
                result = func(*args, **kwargs)
                return result
            except Exception as e:
                error = str(e)
                raise
            finally:
                latency_ms = int((time.time() - start_time) * 1000)
                
                # Extract model info from args/kwargs if available
                model = kwargs.get('model') or (args[0] if args else None)
                
                log_data = {
                    'event_type': 'llm_call',
                    'function': func.__name__,
                    'latency_ms': latency_ms,
                    'success': error is None,
                    'error': error,
                    'model': str(model) if model else 'unknown'
                }
                
                if error:
                    logger.error(f"LLM call failed: {json.dumps(log_data)}")
                else:
                    logger.info(f"LLM call completed: {json.dumps(log_data)}")
        
        return wrapper
    return decorator

def log_rag_operation(logger: logging.Logger, operation: str):
    """Context manager for RAG operations"""
    class RAGLogContext:
        def __init__(self, op_name: str):
            self.op_name = op_name
            self.start_time = None
            self.doc_count = 0
            self.chunk_count = 0
            
        def __enter__(self):
            self.start_time = time.time()
            logger.info(json.dumps({
                'event_type': 'rag_operation_start',
                'operation': self.op_name
            }))
            return self
            
        def __exit__(self, exc_type, exc_val, exc_tb):
            latency_ms = int((time.time() - self.start_time) * 1000)
            log_data = {
                'event_type': 'rag_operation_complete',
                'operation': self.op_name,
                'latency_ms': latency_ms,
                'docs_processed': self.doc_count,
                'chunks_created': self.chunk_count,
                'success': exc_type is None
            }
            
            if exc_type:
                log_data['error'] = str(exc_val)
                logger.error(f"RAG operation failed: {json.dumps(log_data)}")
            else:
                logger.info(f"RAG operation completed: {json.dumps(log_data)}")
            
            return False  # Don't suppress exceptions
        
        def set_counts(self, docs: int, chunks: int):
            self.doc_count = docs
            self.chunk_count = chunks

    return RAGLogContext(operation)

Bước 5: Tích hợp với HolySheep AI

Đây là phần tôi đặc biệt muốn chia sẻ. Khi migrate từ OpenAI sang HolyShehe AI, tôi nhận thấy chi phí giảm 85% — từ $8/MTok xuống $0.42/MTok với DeepSeek V3.2. Điều quan trọng là tất cả code vẫn tương thích 100%:
# dify_integration/holy_sheep_client.py
import os
from typing import Optional, Dict, Any
import anthropic
import logging

logger = logging.getLogger("dify")

class HolySheepAIClient:
    """
    HolySheep AI Client - Tương thích 100% với OpenAI/Anthropic format
    Tiết kiệm 85%+ chi phí so với OpenAI với cùng chất lượng đầu ra
    
    Giá tham khảo 2026:
    - DeepSeek V3.2: $0.42/MTok (sử dụng nhiều nhất trong RAG pipeline)
    - Gemini 2.5 Flash: $2.50/MTok (embedding và summarization)
    - Claude Sonnet 4.5: $15/MTok (complex reasoning tasks)
    - GPT-4.1: $8/MTok (fallback khi cần)
    """
    
    BASE_URL = "https://api.holysheep.ai/v1"
    
    def __init__(self, api_key: Optional[str] = None):
        self.api_key = api_key or os.environ.get("HOLYSHEEP_API_KEY")
        if not self.api_key:
            raise ValueError("HOLYSHEEP_API_KEY is required")
        
        # Khởi tạo Anthropic client với HolySheep endpoint
        self.client = anthropic.Anthropic(
            api_key=self.api_key,
            base_url=self.BASE_URL
        )
        
        self.logger = logging.getLogger("dify.holysheep")
    
    def chat_completion(
        self,
        messages: list,
        model: str = "claude-sonnet-4.5",
        max_tokens: int = 4096,
        temperature: float = 0.7,
        **kwargs
    ) -> Dict[str, Any]:
        """
        Gọi chat completion qua HolySheep AI
        
        Supported models:
        - claude-sonnet-4.5 (Anthropic models)
        - gpt-4.1, gpt-4o (OpenAI models)
        - deepseek-v3.2, gemini-2.5-flash
        """
        import time
        start_time = time.time()
        
        try:
            response = self.client.messages.create(
                model=model,
                messages=messages,
                max_tokens=max_tokens,
                temperature=temperature,
                **kwargs
            )
            
            latency_ms = int((time.time() - start_time) * 1000)
            
            # Structured log cho ELK
            self.logger.info({
                'event_type': 'ai_completion',
                'model': model,
                'latency_ms': latency_ms,
                'input_tokens': response.usage.input_tokens,
                'output_tokens': response.usage.output_tokens,
                'total_tokens': response.usage.total_tokens,
                'provider': 'holy_sheep',
                'success': True
            })
            
            return {
                'content': response.content[0].text,
                'model': response.model,
                'usage': {
                    'input_tokens': response.usage.input_tokens,
                    'output_tokens': response.usage.output_tokens,
                    'total_tokens': response.usage.total_tokens
                },
                'latency_ms': latency_ms
            }
            
        except Exception as e:
            latency_ms = int((time.time() - start_time) * 1000)
            
            self.logger.error({
                'event_type': 'ai_completion',
                'model': model,
                'latency_ms': latency_ms,
                'provider': 'holy_sheep',
                'success': False,
                'error': str(e),
                'error_type': type(e).__name__
            })
            raise
    
    def embeddings(self, texts: list, model: str = "text-embedding-3-small") -> list:
        """
        Tạo embeddings - sử dụng Gemini 2.5 Flash cho chi phí thấp nhất
        Chi phí: $0.0015/1K tokens thay vì $0.0001 với OpenAI
        """
        import time
        start_time = time.time()
        
        try:
            # Sử dụng HolySheep endpoint cho embeddings
            import httpx
            
            response = httpx.post(
                f"{self.BASE_URL}/embeddings",
                headers={
                    "Authorization": f"Bearer {self.api_key}",
                    "Content-Type": "application/json"
                },
                json={
                    "input": texts,
                    "model": model
                },
                timeout=30.0
            )
            response.raise_for_status()
            data = response.json()
            
            latency_ms = int((time.time() - start_time) * 1000)
            
            self.logger.info({
                'event_type': 'embeddings',
                'model': model,
                'latency_ms': latency_ms,
                'text_count': len(texts),
                'dimensions': len(data['data'][0]['embedding']),
                'provider': 'holy_sheep',
                'success': True
            })
            
            return [item['embedding'] for item in data['data']]
            
        except Exception as e:
            self.logger.error({
                'event_type': 'embeddings',
                'model': model,
                'provider': 'holy_sheep',
                'success': False,
                'error': str(e)
            })
            raise


Usage example trong Dify workflow

def process_rag_query(query: str, context: str, api_key: str): """ Example RAG processing với HolySheep AI và structured logging """ client = HolySheepAIClient(api_key) # 1. Tạo query embedding (sử dụng Gemini - rẻ nhất) embeddings = client.embeddings([query], model="gemini-embedding-exp-03-07") # 2. Gọi LLM với context (sử dụng DeepSeek - tối ưu chi phí) messages = [ { "role": "user", "content": f"Context: {context}\n\nQuery: {query}" } ] response = client.chat_completion( messages=messages, model="deepseek-v3.2", # $0.42/MTok - tối ưu cho RAG max_tokens=2048, temperature=0.3 ) return response['content']

Bước 6: Dashboard Kibana cho Monitoring

Sau khi setup xong ELK, hãy tạo dashboard để visualize các metrics quan trọng. Import JSON này vào Kibana:
{
  "objects": [
    {
      "id": "dify-overview-dashboard",
      "type": "dashboard",
      "attributes": {
        "title": "Dify System Overview",
        "description": "Real-time monitoring for Dify AI system",
        "panelsJSON": [
          {
            "version": "8.11.0",
            "type": "lens",
            "gridData": {
              "x": 0,
              "y": 0,
              "w": 24,
              "h": 8,
              "i": "1"
            },
            "panelIndex": "1",
            "embeddableConfig": {
              "attributes": {
                "title": "Request Volume (per minute)",
                "visualizationType": "lnsXY",
                "state": {
                  "datasourceStates": {
                    "indexpattern": {
                      "layers": [{
                        "name": "dify-logs-*",
                        "columns": [
                          {"name": "timestamp", "type": "date"},
                          {"name": "event_type", "type": "string"},
                          {"name": "latency_ms", "type": "number"},
                          {"name": "model", "type": "string"}
                        ]
                      }]
                    }
                  }
                }
              }
            }
          },
          {
            "version": "8.11.0",
            "type": "lens",
            "gridData": {
              "x": 24,
              "y": 0,
              "w": 24,
              "h": 8,
              "i": "2"
            },
            "panelIndex": "2",
            "embeddableConfig": {
              "attributes": {
                "title": "Average Latency by Model (ms)",
                "visualizationType": "lnsPie"
              }
            }
          },
          {
            "version": "8.11.0",
            "type": "lens",
            "gridData": {
              "x": 0,
              "y": 8,
              "w": 16,
              "h": 8,
              "i": "3"
            },
            "panelIndex": "3",
            "embeddableConfig": {
              "attributes": {
                "title": "Error Rate (%)",
                "visualizationType": "lnsMetric"
              }
            }
          },
          {
            "version": "8.11.0",
            "type": "lens",
            "gridData": {
              "x": 16,
              "y": 8,
              "w": 32,
              "h": 8,
              "i": "4"
            },
            "panelIndex": "4",
            "embeddableConfig": {
              "attributes": {
                "title": "P95/P99 Latency Distribution",
                "visualizationType": "lnsXY"
              }
            }
          }
        ],
        "timeRestore": true,
        "timeTo": "now",
        "timeFrom": "now-24h",
        "refreshInterval": {
          "pause": false,
          "value": 30000
        },
        "kibanaSavedObjectMeta": {
          "searchSourceJSON": "{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"
        }
      }
    },
    {
      "id": "dify-logs-index-pattern",
      "type": "index-pattern",
      "attributes": {
        "title": "dify-logs-*",
        "timeFieldName": "@timestamp"
      }
    },
    {
      "id": "dify-errors-index-pattern", 
      "type": "index-pattern",
      "attributes": {
        "title": "dify-errors-*",
        "timeFieldName": "@timestamp"
      }
    }
  ]
}

Cấu hình Alerting cho Production

Thiết lập alerting để được notify khi có sự cố:
# elk/alerts/dify_alerts.json
{
  "trigger": {
    "schedule": {
      "interval": "1m"
    }
  },
  "input": {
    "search": {
      "request": {
        "indices": ["dify-logs-*"],
        "body": {
          "size": 0,
          "query": {
            "bool": {
              "filter": [
                {
                  "range": {
                    "@timestamp": {
                      "gte": "now-5m",
                      "lte": "now"
                    }
                  }
                },
                {
                  "terms": {
                    "tags": ["error"]
                  }
                }
              ]
            }
          },
          "aggs": {
            "errors_by_type": {
              "terms": {
                "field": "dify_error.keyword",
                "size": 10
              }
            },
            "error_rate": {
              "value_count": {
                "field": "dify_error.keyword"
              }
            }
          }
        }
      }
    }
  },
  "condition": {
    "compare": {
      "ctx.payload.hits.total.value": {
        "gt": 10
      }
    }
  },
  "actions": {
    "log_error": {
      "logging": {
        "text": "High error rate detected: {{ctx.payload.hits.total.value}} errors in last 5 minutes. Top errors: {{#ctx.payload.aggregations.errors_by_type.buckets}}{{key}}({{doc_count}}) {{/ctx.payload.aggregations.errors_by_type.buckets}}"
      }
    },
    "slack_notification": {
      "webhook": {
        "method": "post",
        "url": "{{ctx.metadata.slack_webhook}}",
        "body": {
          "channel": "#dify-alerts",
          "username": "Dify ELK Alert",
          "icon_emoji": ":warning:",
          "text": "*Dify Error Alert*\n• Error count: {{ctx.payload.hits.total.value}}\n• Environment: production\n• Time: {{ctx.execution_time}}"
        }
      }
    },
    "email_notification": {
      "email": {
        "profile": "standard",
        "to": ["[email protected]"],
        "subject": "[ALERT] Dify High Error Rate - {{ctx.payload.hits.total.value}} errors",
        "body": {
          "text": "Error breakdown:\n{{#ctx.payload.aggregations.errors_by_type.buckets}}\n- {{key}}: {{doc_count}} occurrences\n{{/ctx.payload.aggregations.errors_by_type.buckets}}\n\nCheck Kibana dashboard: http://kibana:5601/app/dashboards"
        }
      }
    }
  }
}

Tối ưu hóa chi phí với HolySheep AI

Qua kinh nghiệm thực chiến, đây là chiến lược tối ưu chi phí AI mà tôi đã áp dụng: Với 1 triệu requests/tháng, chi phí giảm từ $2,400 xuống còn $350 — tiết kiệm 85%. Đặc biệt, HolySheep hỗ trợ thanh toán qua WeChat/Alipay rất tiện lợi cho thị trường châu Á, và latency trung bình chỉ <50ms do server đặt gần Việt Nam.

Lỗi thường gặp và cách khắc phục

1. Lỗi "Connection refused" khi Filebeat gửi sang Logstash

Nguyên nhân: Logstash chưa khởi động hoặc port 5044 bị block bởi firewall. Giải pháp:
# Kiểm tra Logstash đã chạy chưa
docker logs logstash

Kiểm tra port 5044 có đang listen không

netstat -tlnp | grep 5044

Nếu dùng firewall, mở port

sudo ufw allow 5044/tcp

Restart Filebeat với debug mode

filebeat -e -d "*" -c /etc/filebeat/filebeat.yml

2. Lỗi "_jsonparsefailure" khi parse Dify log

Nguyên nhân: Dify log format không phải JSON thuần (có thể có mixed format từ nhiều worker). Giải pháp:
# Update Logstash filter để xử lý cả hai format
filter {
  if [message] =~ /^\{/ {
    json {
      source => "message"
      target => "parsed"
      skip_on_invalid_json => true
    }
  }
  
  # Fallback: extract key fields bằng grok
  if "error" in [tags] or ![parsed] {
    grok {
      match => {
        "message" => [
          "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{GREEDYDATA:message}",
          ".*\"error\"?\s*:\s*\"?%{GREEDYDATA:dify_error}\"?",
          ".*\"latency\"?\s*:\s*%{NUMBER:dify_latency_ms:int}"
        ]
      }
      tag_on_failure => ["_grokparsefailure"]
    }
    
    # Convert latency nếu extracted thành công
    if [dify_latency_ms] {
      mutate {
        convert => { "dify_latency_ms" => "integer" }
      }
    }
  }
}

3. Elasticsearch OutOfMemoryError

Nguyên nhân: JVM heap size không đủ hoặc index quá lớn không được rotate. Giải pháp:
# Tăng JVM heap (không vượt quá 50% RAM)

Sửa trong docker-compose.yml:

environment: - "ES_JAVA_OPTS=-Xms4g -Xmx4g -XX:+UseG1GC"

Setup ILM (Index Lifecycle Management) để tự động xóa cũ

Tạo file ilm-policy.json:

{ "policy": { "phases": { "hot": { "min_age": "0ms", "actions": { "rollover": { "max_size": "50gb", "max_age": "7d" } } }, "warm": { "min_age": "30d", "actions": { "shrink": { "number_of_shards": 1 }, "forcemerge": { "max_num_segments": 1