← AIOps — Tuần 1: Detection & Triage

W1-D2: Log Mining + Parsing + Anomaly từ Log

Log Mining + Parsing + Anomaly từ Log

Hôm qua bạn đã học cách detect anomaly trên metric — số liệu dạng time series (CPU, latency, error rate). Metric cho biết “cái gì đang sai”: latency tăng, error rate tăng, throughput giảm. Nhưng metric không cho biết “tại sao”.

Để biết tại sao, bạn cần log. Log là text mà mỗi service ghi ra khi xử lý request, kết nối database, gặp lỗi, hoặc bất kỳ sự kiện nào. Khi metric báo “latency tăng”, bạn mở log và thấy: "Connection timeout to db-primary at 10.0.1.5:5432 after 30s" — bây giờ biết tại sao: database primary không phản hồi.

Vấn đề: 1 microservice production gen 10GB log/ngày. 100 service = 1TB/ngày. Mỗi dòng log khác nhau (IP khác, timestamp khác, parameter khác). grep "timeout" *.log cho ra 50,000 kết quả — cái nào quan trọng?

Buổi hôm nay giải quyết vấn đề này: biến triệu dòng log thành cấu trúc có thể phân tích bằng machine, rồi detect anomaly trên log giống như detect anomaly trên metric.


1. Nền Tảng: Log Trong Production Trông Như Thế Nào?

Log không phải chỉ là text

Trong textbook, log trông gọn gàng:

2024-01-15 10:23:45 ERROR Connection timeout to database
2024-01-15 10:23:46 INFO  Retry connection attempt 1
2024-01-15 10:23:47 INFO  Connection restored

Trong production, log trông như thế này:

[2024-01-15T10:23:45.123Z] [payment-service-7b4f8c-2xvnm] [tid:a8f2e1] ERROR com.company.payment.DatabaseConnector - Connection timeout to db-primary at 10.0.1.5:5432 after 30000ms. Retries remaining: 2. Context: orderId=ORD-8834721, amount=150000, currency=VND
[2024-01-15T10:23:45.456Z] [payment-service-7b4f8c-2xvnm] [tid:b7c3d2] WARN  com.company.payment.CircuitBreaker - Circuit breaker OPEN for db-primary. Failure count: 15/20. Last failure: java.net.SocketTimeoutException
[2024-01-15T10:23:45.789Z] [auth-service-3a2c1d-k9wnp] [tid:c1d4e5] INFO  com.company.auth.TokenValidator - Token validated successfully for userId=USR-442918 in 12ms
[2024-01-15T10:23:46.012Z] [payment-service-7b4f8c-9htqr] [tid:d2e5f6] ERROR com.company.payment.DatabaseConnector - Connection timeout to db-replica-2 at 10.0.2.3:5432 after 30000ms. Retries remaining: 1. Context: orderId=ORD-8834722, amount=250000, currency=VND

Mỗi dòng chứa:

  • Timestamp khác nhau (millisecond precision)
  • Pod name khác nhau (Kubernetes tạo random suffix)
  • Trace ID khác nhau (mỗi request 1 ID)
  • Class path (Java package name)
  • Dynamic parameters: IP, port, orderId, amount — mỗi dòng khác nhau
  • Static structure: “Connection timeout to … at … after …ms” — giống nhau

Vấn đề cốt lõi: Dòng 1 và dòng 4 ở trên thực chất là cùng 1 loại sự kiện (connection timeout tới DB), chỉ khác IP và orderId. Nhưng string match cho thấy chúng hoàn toàn khác nhau. Grep "Connection timeout" tìm được cả 2, nhưng grep không thể:

  • Gom chúng thành 1 nhóm “connection timeout events”
  • Đếm “bao nhiêu connection timeout / giờ?” (vì mỗi dòng khác nhau)
  • Phân biệt “connection timeout” vs “token validation” vs “circuit breaker” ở mức template

Structured vs Unstructured Log

Structured (JSON)Unstructured (Plain text)
Format{"level":"ERROR","msg":"Connection timeout","host":"10.0.1.5"}ERROR Connection timeout to 10.0.1.5:5432
ParseJSON.parse() — trivialRegex / ML parser — phức tạp
QueryFilter by field: level=ERROR AND host=10.0.1.5Full-text search: grep
StorageLớn hơn ~30% (field names lặp lại)Nhỏ hơn
AdoptionModern service (Go, Node.js thường log JSON)Legacy, Java (logback default), system log

Thực tế: Hầu hết tổ chức có cả 2. Service mới log JSON, service cũ log plain text, system log (syslog, kernel) log format riêng. AIOps cần handle cả 2 — đây là lý do cần log parser.

Log Volume — Tại sao scale quan trọng

Theo Splunk State of Observability 2024, enterprise trung bình ingest 1-5 TB log/ngày. Banking, telecom có thể lên 10+ TB/ngày.

ScaleLog volume/ngàySố dòng/ngàyGrep có đủ?
Startup (10 service)10-50 GB~100M dòngChậm nhưng OK
Mid-size (100 service)100-500 GB~1B dòngKhông — cần ELK/Loki
Enterprise (1000 service)1-10 TB~10B dòngKhông — cần streaming pipeline
Banking/Telecom10+ TB~100B dòngCần sampling + tiered storage

Chi phí: Splunk tính $150-200/GB ingested/tháng. 1TB/ngày × 30 ngày = 30TB = $4.5M-6M/năm chỉ cho log storage + indexing. Đây là lý do nhiều company chuyển sang Loki (cheaper, log-based) hoặc build custom pipeline.


2. Log Parsing — Biến Text Thành Structure

Vấn đề: Tại sao cần parse?

Bạn có 1 triệu dòng log. Mỗi dòng khác nhau vì IP, timestamp, orderId, amount… khác nhau. Nhưng pattern lặp lại. “Connection timeout to X at Y:Z” xuất hiện 5000 lần với X, Y, Z khác nhau.

Log parsing = tách mỗi dòng log thành 2 phần:

  1. Template (static part): "Connection timeout to <*> at <*>:<*> after <*>ms" — phần giống nhau
  2. Parameters (dynamic part): ["10.0.1.5", "5432", "30000"] — phần thay đổi

Sau khi parse, 5000 dòng “Connection timeout…” trở thành 1 template với 5000 instances. Bây giờ có thể:

  • Đếm: “Template này xuất hiện 5000 lần/giờ — bình thường là 10 lần/giờ = anomaly
  • So sánh: “Hôm qua template này không tồn tại — new template = có gì đó mới xảy ra
  • Cluster: “5 template liên quan tới DB, 3 template liên quan tới network”

2.1 Drain3 — Parser Phổ Biến Nhất

Drain3 là implementation Python của thuật toán Drain, được dùng rộng rãi nhất cho log parsing trong AIOps. Logpai (đội nghiên cứu log parsing hàng đầu) rank Drain là parser tốt nhất về accuracy + speed.

Ý tưởng thuật toán:

Drain dùng fixed-depth parse tree để nhóm log nhanh chóng. Mỗi dòng log đi qua tree từ root → leaf, tại mỗi level tree quyết định nhóm dòng này vào đâu:

  • Level 1: Log length — nhóm theo số từ trong dòng. Dòng 15 từ và dòng 8 từ hiếm khi cùng template.
  • Level 2: First token — nhóm theo từ đầu tiên (thường là level: ERROR, INFO, WARN).
  • Level 3+: Similarity matching — so sánh với các template đã biết. Nếu giống ≥ threshold → gộp vào template đó. Nếu không giống → tạo template mới.
Parse Tree:

Root
├── Length=15
│   ├── "ERROR"
│   │   ├── Template-1: "Connection timeout to <*> at <*>:<*> after <*>ms"
│   │   └── Template-2: "Failed to process payment <*> amount <*>"
│   └── "WARN"
│       └── Template-3: "Circuit breaker OPEN for <*> failure count <*>"
├── Length=12
│   ├── "INFO"
│   │   ├── Template-4: "Token validated for userId=<*> in <*>ms"
│   │   └── Template-5: "Request completed <*> status=<*> duration=<*>ms"
│   └── ...

Khi log mới đến, Drain đi từ root → length → first token → check similarity với templates ở leaf. Nếu match → gộp vào template (cập nhật count). Nếu không match → tạo template mới.

Thời gian: O(1) per log line (fixed depth tree, không scan toàn bộ). Parse 1 triệu dòng trong vài giây.

from drain3 import TemplateMiner
from drain3.template_miner_config import TemplateMinerConfig

config = TemplateMinerConfig()
config.drain_sim_th = 0.4        # similarity threshold: 0.0-1.0
                                  # thấp = ít template (gộp nhiều), cao = nhiều template (tách nhiều)
config.drain_depth = 4            # độ sâu parse tree: 3-6
                                  # sâu hơn = chính xác hơn nhưng chậm hơn

miner = TemplateMiner(config=config)

# Parse từng dòng log
log_lines = [
    "Connection timeout to db-primary at 10.0.1.5:5432 after 30000ms",
    "Connection timeout to db-replica at 10.0.2.3:5432 after 25000ms",
    "Token validated for userId=USR-442918 in 12ms",
    "Connection timeout to db-primary at 10.0.1.5:5432 after 30000ms",
    "Request completed /api/payment status=500 duration=30125ms",
]

for line in log_lines:
    result = miner.add_log_message(line)
    print(f"Cluster ID: {result['cluster_id']}")
    print(f"Template:   {result['template_mined']}")
    print(f"Change:     {result['change_type']}")
    print()

# Xem tất cả templates
print("=== All Templates ===")
for cluster in miner.drain.clusters:
    print(f"  [{cluster.cluster_id}] (count={cluster.size}): {cluster.get_template()}")

Output:

Cluster ID: 1
Template:   Connection timeout to <*> at <*> after <*>
Count:      1

Cluster ID: 1
Template:   Connection timeout to <*> at <*> after <*>
Count:      2

Cluster ID: 2
Template:   Token validated for <*> in <*>
Count:      1

Cluster ID: 1
Template:   Connection timeout to <*> at <*> after <*>
Count:      3

Cluster ID: 3
Template:   Request completed <*> <*> <*>
Count:      1

3 dòng “Connection timeout…” → cùng Cluster 1 (template giống nhau, chỉ khác parameter).

Tuning drain_sim_th (similarity threshold):

sim_thÝ nghĩaKhi nào dùng
0.2-0.3Gộp aggressive — nhiều log khác nhau thành 1 templateLog rất diverse, muốn ít template
0.4-0.5Balance (default)Hầu hết use case
0.6-0.8Tách nhiều — mỗi variation thành template riêngLog structured, muốn chính xác

Common mistake: sim_th quá cao (0.8+) → mỗi dòng log thành 1 template riêng → hàng triệu template → vô dụng. sim_th quá thấp (0.1) → gộp hết → 5 template cho 1 triệu dòng → mất thông tin.

2.2 So Sánh Parsers

ParserApproachSpeedAccuracyKhi nào dùng
Drain3Fixed-depth treeRất nhanh (O(1)/line)CaoDefault choice. Production, streaming.
SpellLongest common subsequenceTrung bìnhCaoLog có cấu trúc ổn định
LenmaLength + token matchingNhanhTrung bìnhSimple use case
LLM-basedPrompt LLM phân loại logRất chậm (API call)Rất caoKhi cần semantic understanding, offline analysis
RegexViết tay regex cho mỗi pattern-Phụ thuộc regexLog ít pattern, biết trước format

Tại sao Drain3 thắng: Theo benchmark của Logpai trên 16 datasets, Drain đạt accuracy cao nhất (average F1 > 0.9) với speed nhanh nhất. Spell gần bằng accuracy nhưng chậm hơn 5-10x. LLM-based chính xác nhất nhưng không thể chạy real-time trên 1TB log/ngày.

LLM-based parsing — tương lai nhưng chưa production-ready:

# Ý tưởng: dùng LLM phân loại log
prompt = """
Classify this log line into a template. Replace dynamic values with <*>.

Log: "Connection timeout to db-primary at 10.0.1.5:5432 after 30000ms"
Template: "Connection timeout to <*> at <*>:<*> after <*>ms"

Log: "Failed to authenticate user admin from IP 192.168.1.100"
Template:
"""
# LLM trả lời: "Failed to authenticate user <*> from IP <*>"

Chính xác hơn Drain vì hiểu semantic (“admin” là username, “192.168.1.100” là IP). Nhưng:

  • 1 API call / log line → $$$$ ở TB scale
  • Latency: 200ms-2s / call → không real-time
  • Dùng cho: offline analysis, bootstrap template list, hard-to-parse log

2.3 Drain3 Internals — Similarity Matching

Khi 1 log line mới đến leaf node của parse tree, Drain so sánh nó với các template đã tồn tại ở leaf đó. Cách so sánh:

Token-by-token matching:

Drain tokenize cả log line mới và template hiện tại theo khoảng trắng, rồi so sánh từng token:

  • Token giống nhau → match
  • Token <*> trong template → luôn match (wildcard)
  • Token khác nhau → mismatch

Similarity score = số token match / tổng số token. Nếu score ≥ sim_th → gộp vào template (token mismatch trở thành <*>). Nếu score < sim_th → tạo template mới.

Ví dụ step-by-step:

Template hiện tại: "Connection timeout to <*> at <*> after <*>"
Log mới:          "Connection timeout to db-replica at 10.0.2.3 after 25000ms"

So sánh token:
  "Connection"  vs "Connection"   → match
  "timeout"     vs "timeout"      → match
  "to"          vs "to"           → match
  "<*>"         vs "db-replica"   → match (wildcard)
  "at"          vs "at"           → match
  "<*>"         vs "10.0.2.3"     → match (wildcard)
  "after"       vs "after"        → match
  "<*>"         vs "25000ms"      → match (wildcard)

Score = 8/8 = 1.0 ≥ 0.4 → MATCH → gộp vào template, count += 1

Một ví dụ khác:

Template hiện tại: "Connection timeout to <*> at <*> after <*>"
Log mới:          "Disk usage exceeded threshold on volume /data at 95%"

So sánh token:
  "Connection" vs "Disk"      → mismatch
  "timeout"    vs "usage"     → mismatch
  "to"         vs "exceeded"  → mismatch
  ...

Score = 1/9 = 0.11 < 0.4 → NO MATCH → tạo template mới

Tại sao fixed-depth tree?

Drain dùng tree có depth cố định (thường 3-4 level) thay vì tree depth không giới hạn. Lý do:

  • Speed: Depth cố định → O(1) lookup per log line. Không cần scan toàn bộ tree.
  • Memory: Giới hạn số node → memory bounded. Với variable depth, tree có thể grow vô hạn.
  • Trade-off: Shallow tree (depth 3) → nhanh hơn, ít chính xác. Deep tree (depth 6) → chậm hơn, chính xác hơn. Depth 4 là balance tốt cho hầu hết log format.

Drain paper (He et al., ICWS 2017) chứng minh rằng fixed-depth tree với depth 4 đạt parsing accuracy tương đương variable-depth approach, nhưng nhanh hơn 10x.

2.4 Multiline Log — Edge Case Quan Trọng

Hầu hết log parser (kể cả Drain3) giả định 1 event = 1 dòng. Nhưng trong Java/Python, stack trace có thể là 20-50 dòng cho 1 event:

2024-01-15 10:23:45 ERROR PaymentService - Failed to process payment
java.sql.SQLException: Connection pool exhausted
    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:155)
    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:128)
    at com.company.payment.DatabaseConnector.execute(DatabaseConnector.java:42)
    at com.company.payment.PaymentProcessor.process(PaymentProcessor.java:89)
Caused by: java.net.SocketTimeoutException: connect timed out
    at java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:103)
    ... 15 more

Đây là 1 event nhưng 10 dòng. Nếu feed từng dòng vào Drain3, mỗi dòng at com.xxx.yyy trở thành template riêng → hàng nghìn template rác từ stack trace.

Giải pháp:

  1. Pre-processing: Trước khi feed vào Drain3, merge multiline thành 1 dòng. Regex: dòng bắt đầu bằng whitespace hoặc at hoặc Caused by → append vào dòng trước.
  2. Log framework config: Cấu hình log framework (Logback, Log4j) output 1 event = 1 dòng JSON (structured logging).
  3. Fluentd/Fluent Bit multiline parser: Cấu hình ở collection layer, merge trước khi gửi tới storage.

Paper reference: Zhu et al., “Tools and Benchmarks for Automated Log Parsing” (ICSE 2019) — benchmark đầu tiên đo multiline handling, hầu hết parser score dưới 0.5 accuracy trên multiline log.


3. Từ Templates Tới Anomaly Detection

Sau khi parse log thành templates, có nhiều cách detect anomaly — không chỉ đếm.

3.1 Template Count Time Series

Cách đơn giản nhất: đếm số lần mỗi template xuất hiện per time window (VD: mỗi 5 phút), tạo time series, rồi dùng anomaly detector từ D1.

Đếm số lần mỗi template xuất hiện per time window (VD: mỗi 5 phút):

import pandas as pd
from collections import Counter

def create_template_timeseries(log_entries, window='5min'):
    """
    Biến parsed log thành time series per template.
    
    Args:
        log_entries: list of (timestamp, template_id) tuples
        window: aggregation window
    
    Returns:
        DataFrame: columns = template IDs, rows = time windows, values = count
    """
    df = pd.DataFrame(log_entries, columns=['timestamp', 'template_id'])
    df['timestamp'] = pd.to_datetime(df['timestamp'])
    
    # Group by time window + template → count
    ts = df.groupby([pd.Grouper(key='timestamp', freq=window), 'template_id']).size()
    ts = ts.unstack(fill_value=0)  # pivot: rows=time, cols=template
    
    return ts

# Kết quả trông như:
#                     T-001  T-002  T-003  T-042
# 2024-01-15 10:00     120     45     30      5
# 2024-01-15 10:05     115     42     28      8
# 2024-01-15 10:10     118     48     31    200  ← T-042 spike!
# 2024-01-15 10:15     122     44     29    185  ← still high

Bây giờ T-042 (“Connection timeout to <*>”) có time series: [5, 8, 200, 185, ...]. Apply anomaly detector từ D1:

  • 3σ: T-042 trung bình 5-10/window, std ~3. 200 = 63σ → anomaly
  • Isolation Forest: Feed tất cả template counts cùng lúc → multivariate anomaly (T-042 spike + T-001 stable = bất thường)

3.2 New Template Detection

Một trong những signal mạnh nhất: template chưa từng xuất hiện trước đó.

known_templates = set()  # templates đã thấy trong training period

def detect_new_template(result):
    """
    Detect khi Drain3 tạo template mới.
    Template mới = có gì đó mới xảy ra trong hệ thống.
    """
    template = result.get_template()
    if template not in known_templates:
        known_templates.add(template)
        return True, template  # NEW TEMPLATE — investigate!
    return False, template

Tại sao quan trọng:

  • Deploy mới → log message mới → new template. Nếu deploy gây lỗi, new template là signal đầu tiên.
  • Attack mới → log pattern chưa từng thấy. VD: SQL injection attempt tạo log line mà template chưa tồn tại.
  • Config change → service behavior thay đổi → log thay đổi.

Common mistake: Không filter template mới trong 1 giờ đầu sau deploy. Deploy mới luôn tạo new template (startup log, health check log). Cần grace period.

3.3 Log Sequence Anomaly Detection

Template count phát hiện “cái gì tăng/giảm bất thường”. Nhưng đôi khi số lượng bình thường, thứ tự bất thường.

Bình thường, 1 request đi qua sequence: Login → Validate → Process → Commit → Response. Nếu bạn thấy Login → Validate → Commit → Process → Response — số lượng mỗi template không đổi, nhưng thứ tự sai → có thể race condition hoặc bug.

Kỹ thuật:

  • N-gram trên template sequence: Biến chuỗi template ID thành n-gram (VD: bigram [T1→T2], [T2→T3], [T3→T4]). Training: đếm frequency mỗi n-gram. Detection: n-gram chưa từng thấy = anomaly.
  • LSTM trên sequence: Train LSTM predict “template tiếp theo given N template trước”. Nếu prediction confidence thấp → sequence bất thường. Đây là approach của DeepLog (Du et al., CCS 2017) — paper đầu tiên dùng DL cho log anomaly detection.
  • Finite State Machine (FSM): Build FSM từ log sequence bình thường. Transition không tồn tại trong FSM = anomaly. Đơn giản hơn LSTM, nhưng chỉ phát hiện transition mới, không phát hiện “transition hiếm”.

Paper: Du et al., “DeepLog: Anomaly Detection and Diagnosis from System Logs through Deep Learning” (CCS 2017). Paper này introduce ý tưởng dùng LSTM predict next log template — nếu actual template khác predicted → anomaly. Cited 2000+ lần.

3.4 Inter-arrival Time Anomaly

Mỗi template có rhythm tự nhiên. VD: health check log xuất hiện mỗi 30 giây. Garbage collection log xuất hiện mỗi 5-10 phút. Nếu:

  • Health check ngừng xuất hiện → service có thể đã crash
  • GC log từ mỗi 5 phút → mỗi 10 giây → memory pressure, GC chạy liên tục

Kỹ thuật: Tính inter-arrival time (thời gian giữa 2 lần xuất hiện liên tiếp) cho mỗi template. Build distribution. Detect khi inter-arrival time nằm ngoài expected range.

Ưu điểm so với template count: phát hiện được absence (template biến mất) — template count = 0 có thể là bình thường (nếu window quá ngắn) hoặc bất thường (service crash). Inter-arrival time detect “template lẽ ra phải xuất hiện 30 giây trước mà chưa thấy”.

3.5 Parameter Value Anomaly

Cùng 1 template nhưng parameter khác thường. VD:

  • Template: "Query completed in <*>ms"
  • Bình thường: parameter = 5, 12, 8, 15 (milliseconds)
  • Bất thường: parameter = 45000 (45 giây!)

Template count không thay đổi (vẫn 1 query/request). Nhưng giá trị parameter bất thường.

Kỹ thuật: Sau khi Drain3 parse, extract parameter values. Với parameter dạng số, build distribution (mean, std) → detect outlier. Với parameter dạng string (IP, hostname), build frequency table → detect rare value (IP chưa từng thấy = possible new host hoặc attack).

Paper: He et al., “Experience Report: System Log Analysis for Anomaly Detection” (ISSRE 2016) — so sánh parameter-based vs template-based anomaly detection, kết luận: kết hợp cả 2 cho F1 tăng 15-20%.

3.6 So Sánh Các Technique

TechniqueDetect gìMiss gìComplexity
Template countTemplate spike/dropThứ tự sai, parameter saiThấp
New templateBehavior mớiAnomaly trong template cũThấp
Sequence (n-gram)Thứ tự bất thườngParameter sai, count bình thườngTrung bình
Sequence (LSTM)Pattern phức tạp, temporalCần data lớn, khó debugCao
Inter-arrival timeTemplate biến mất, rhythm thay đổiTemplate mớiTrung bình
Parameter valueGiá trị bất thường trong template quenTemplate mớiTrung bình

Trong production, kết hợp template count + new template + parameter value cover được 80%+ use case mà không cần DL.

3.7 Log Embedding — TF-IDF và Semantic

Ngoài đếm, có thể embed template thành vector rồi tính similarity:

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

templates = [
    "Connection timeout to <*> at <*>:<*> after <*>ms",
    "Connection refused to <*> at <*>:<*>",
    "Token validated for userId=<*> in <*>ms",
    "Request completed <*> status=<*> duration=<*>ms",
    "Disk usage at <*>% on volume <*>",
]

# TF-IDF: biến mỗi template thành vector dựa trên word frequency
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(templates)

# Similarity matrix: template nào giống template nào?
sim_matrix = cosine_similarity(tfidf_matrix)

# Template 0 và 1 giống nhau (cả 2 về connection) → similarity cao
# Template 0 và 2 khác nhau (connection vs token) → similarity thấp

Khi nào dùng TF-IDF:

  • Cluster templates thành nhóm (DB-related, auth-related, network-related)
  • Tìm template tương tự khi gặp template mới (“template mới này giống group nào?”)
  • Visualization: plot templates trên 2D (t-SNE/UMAP) để thấy cluster

Sentence-Transformers — semantic embedding:

from sentence_transformers import SentenceTransformer

model = SentenceTransformer('all-MiniLM-L6-v2')
embeddings = model.encode(templates)
# Hiểu semantic: "Connection timeout" và "Connection refused" gần nhau
# dù TF-IDF có thể cho similarity thấp vì từ khác nhau

4. Kết Hợp Metric + Log — Cross-Signal Analysis

Đây là lúc AIOps thực sự mạnh. Metric cho biết “cái gì”, log cho biết “tại sao”. Kết hợp = narrow down root cause nhanh gấp nhiều lần.

Workflow thực tế

1. Metric anomaly detector trigger: "latency p99 tăng từ 200ms lên 1.2s"
   → Biết: có vấn đề. Không biết: vấn đề gì.

2. Filter log trong time window anomaly (±5 phút)
   → Thu hẹp: từ 1 triệu dòng log → 50,000 dòng

3. Parse log → template count
   → Tìm: template nào spike cùng lúc với metric anomaly?
   → Kết quả: T-042 "Connection timeout to <*>" spike 40x

4. Drill down template T-042
   → Parameters: IP 10.0.1.5 xuất hiện 80% → DB primary
   → Context: orderId cho thấy payment service bị ảnh hưởng

5. Kết luận: "Latency tăng vì DB primary (10.0.1.5) timeout.
   Payment service bị ảnh hưởng nặng nhất."

Code kết hợp

def cross_signal_analysis(metric_anomalies, log_entries, miner, window_minutes=5):
    """
    Kết hợp metric anomaly với log analysis.
    
    Khi metric anomaly xảy ra, tìm log template nào spike cùng lúc.
    """
    results = []
    
    for anomaly_time in metric_anomalies:
        # Filter log trong window quanh anomaly
        start = anomaly_time - pd.Timedelta(minutes=window_minutes)
        end = anomaly_time + pd.Timedelta(minutes=window_minutes)
        
        window_logs = [
            (ts, line) for ts, line in log_entries
            if start <= ts <= end
        ]
        
        # Parse và đếm template
        template_counts = Counter()
        for ts, line in window_logs:
            result = miner.add_log_message(line)
            template_counts[result.get_template()] += 1
        
        # Top template = most likely related to anomaly
        top_templates = template_counts.most_common(5)
        
        results.append({
            'anomaly_time': anomaly_time,
            'log_count': len(window_logs),
            'top_templates': top_templates,
        })
    
    return results

5. Production Considerations

5.1 Log Sampling

Ở TB scale, không thể parse 100% log. Cần sampling strategy:

StrategyMô tảTrade-off
Random samplingLấy random 10% logĐơn giản, nhưng miss anomaly hiếm
Head-basedLấy 100% log từ 10% requestGiữ full context per request, miss 90%
Tail-basedChỉ giữ log từ request lỗi/chậmTốt cho debug, miss slow-burn issue
AdaptiveTăng sampling rate khi detect anomalyTốt nhất, phức tạp implement

5.2 Log Pipeline Architecture

Trong production, log không đi thẳng từ service vào parser. Có cả 1 pipeline:

Service → Agent → Aggregator → Transport → Processing → Storage → Query

Collection layer — Agent:

  • Fluent Bit (C, lightweight, ~450KB memory): chạy trên mỗi node/pod, collect log từ stdout/file, forward tới aggregator. CNCF graduated project. Dùng cho: Kubernetes DaemonSet, IoT, edge.
  • Fluentd (Ruby, plugin-rich): mạnh hơn Fluent Bit về plugin ecosystem (700+ plugins). Dùng cho: aggregator layer, complex routing.
  • Vector (Rust, high-performance): mới hơn, performance tốt hơn Fluentd 5-10x. Datadog develop. Dùng cho: high-throughput pipeline.
  • OpenTelemetry Collector: collect metric + log + trace cùng 1 agent. Vendor-neutral. Xu hướng mới — thay thế dedicated log agent.

Paper reference: Schipper et al., “A Benchmark for Log Data Processing Pipelines” (ICPE 2024) — benchmark Fluent Bit vs Fluentd vs Vector trên throughput, latency, resource usage.

Transport layer:

  • Direct push: Service → Storage. Đơn giản nhưng coupling cao, storage crash = mất log.
  • Kafka: Buffer giữa collection và processing. Decouple producer/consumer. Replay capability (re-process log nếu parser bug). Dùng cho: > 100 service, cần durability.
  • NATS: Nhẹ hơn Kafka, không persist by default. Dùng cho: low-latency, fire-and-forget log.

Storage layer:

StorageƯuNhượcCost
Elasticsearch (ELK)Full-text search mạnh, query linh hoạtResource heavy, đắt ở scale$$$$
Loki (Grafana)Chỉ index label, không full-text → rẻ hơn 10xQuery hạn chế hơn ES$
ClickHouseColumn-oriented, query aggregate cực nhanhKhông tốt cho full-text search$$
S3 + ParquetRẻ nhất, lưu lâu dàiKhông query real-time, cần Athena/Spark$ (storage only)

Retention strategy — Hot/Warm/Cold:

  • Hot (0-7 ngày): Elasticsearch/Loki — query nhanh, đắt
  • Warm (7-30 ngày): Cheaper storage tier, slower query
  • Cold (30-365 ngày): S3 Glacier — chỉ dùng khi cần forensic, query mất phút-giờ
  • Delete (>365 ngày): Trừ khi compliance yêu cầu giữ lâu hơn

Doanh nghiệp lớn thường kết hợp: Fluent Bit → Kafka → Flink (parse + enrich) → Elasticsearch (7 ngày hot) + S3 (archive). Tổng cost cho 1TB/ngày: ~$15,000-30,000/tháng (tuỳ query volume).

5.3 Structured Logging Best Practice

Nếu bạn control service code, log structured (JSON) thay vì plain text:

# BAD: plain text, khó parse
logger.info(f"Payment processed for order {order_id} amount {amount} in {duration}ms")

# GOOD: structured JSON, dễ query
logger.info("Payment processed", extra={
    "order_id": order_id,
    "amount": amount,
    "duration_ms": duration,
    "currency": "VND",
})

Structured log → không cần Drain3 parse → trực tiếp query/aggregate. Nhưng legacy system không có structured log → vẫn cần parser.


6. Chọn Approach

ScenarioApproach
Có structured log (JSON)Trực tiếp aggregate + anomaly detect trên fields
Có unstructured log, cần real-timeDrain3 parse → template count → anomaly detect
Cần semantic understandingTF-IDF hoặc sentence-transformer cluster templates
Cần detect new behaviorNew template detection (Drain3 tạo cluster mới)
Debug specific incidentCross-signal: metric anomaly time → filter log → find template spike
Offline analysis, accuracy caoLLM-based parsing + deep analysis

KPI Đo Lường

KPICông thứcTargetGiải thích
Parsing accuracyĐúng template / tổng log> 0.85Template extracted có đúng không (so với ground truth)
Template countSố template unique100-500 cho 1 serviceQuá ít = gộp quá nhiều, quá nhiều = tách quá nhiều
Grouping accuracyPrecision/recall của clusteringF1 > 0.8Log cùng loại có được gộp cùng template không
Cross-signal TTDMetric anomaly → tìm root cause trong log< 5 phútThời gian từ “biết có vấn đề” → “biết tại sao”

References


Assignment — Chiều Nay

Trong repo aiops-{name}/w1/day-b/:

Phase 1: Parse Log với Drain3

  1. Download 1 dataset từ Loghub — gợi ý: HDFS (có label) hoặc BGL (supercomputer log, phức tạp hơn)
  2. Trong assignment.ipynb:
    • Load log file, đếm tổng số dòng
    • Parse toàn bộ log với Drain3
    • Liệt kê tất cả templates, đếm số dòng mỗi template
    • Export top-10 templates ra results/top_templates.csv (columns: template_id, template, count)
    • Tune drain_sim_th: thử 0.3, 0.5, 0.7 → ghi lại số template mỗi giá trị, chọn giá trị tốt nhất

Phase 2: Anomaly Detection trên Log

  1. Tạo template count time series (window 5 phút hoặc tùy dataset)
  2. Apply anomaly detector từ D1 (3σ hoặc Isolation Forest) trên template count
  3. Detect: template nào spike bất thường? Template mới xuất hiện khi nào?
  4. Nếu dataset có label (HDFS): tính precision/recall của log-based anomaly detection

Phase 3: Embedding + Cross-signal

  1. TF-IDF trên templates → tính similarity matrix → tìm template clusters
  2. Inject 1 dòng log “lạ” (tự viết, khác tất cả template) → Drain3 tạo new template → detect

Phase 4: Challenge — Build Mini Log Analyzer

  1. Viết 1 script log_analyzer.py nhận input là 1 log file, output ra:
    • Tổng số dòng, số template unique
    • Top-5 template (count + % tổng)
    • Template nào tăng đột biến trong 1 giờ gần nhất (so với trung bình)
    • New templates (chưa xuất hiện trước giờ gần nhất)
    • Script phải chạy được: python log_analyzer.py <logfile> → in kết quả ra stdout
  2. Test script trên 2 dataset khác nhau (VD: HDFS + BGL, hoặc HDFS + Spark) → so sánh output: dataset nào có nhiều template hơn, tại sao?

Bonus

  • Parse log từ 1 ứng dụng thật mà bạn có (Docker log, nginx log, application log) — không dùng Loghub
  • So sánh Drain3 output khi dùng structured JSON log vs unstructured plain text log trên cùng 1 service
  • Viết regex parser cho 1 log format cụ thể, so sánh output với Drain3

SUBMIT.md phải chứa:

  • Screenshots: plot template count time series, anomaly highlighted
  • Log: output Drain3 (số template, top-10), tuning log (sim_th values + kết quả)
  • Reflection: Drain3 parse tốt không, template nào cho insight, metric vs log khác gì

Knowledge Check (viết tay)

  1. Giải thích Drain3 parse tree hoạt động thế nào (vẽ sơ đồ đơn giản)
  2. Tại sao cần log parsing thay vì grep — cho ví dụ cụ thể
  3. Template count time series là gì, tại sao dùng nó để detect anomaly
  4. New template detection: tại sao template mới là signal quan trọng
  5. Metric cho biết gì, log cho biết gì, kết hợp 2 cái thì được gì