Qwik Audit Trail
Qwik Resumability Audit Trail Logging server$ routeAction$ Middleware Structured Logging Compliance SOC 2 ISO 27001 PDPA Immutable
| Event Type | Action | Log Fields | Compliance |
|---|---|---|---|
| Authentication | login logout register | actor_id ip user_agent result | All Standards |
| Data CRUD | create read update delete | resource_type resource_id changes | SOC 2 ISO PDPA |
| Permission | grant revoke change_role | target_user old_role new_role | SOC 2 ISO |
| Settings | config_change feature_toggle | setting_key old_value new_value | SOC 2 |
| API Access | api_call webhook | endpoint method status_code | All Standards |
Database Schema & Implementation
# === Audit Trail Database Schema ===
# CREATE TABLE audit_logs (
# id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
# timestamp TIMESTAMPTZ NOT NULL DEFAULT now(),
# actor_id VARCHAR(255),
# actor_ip INET,
# actor_email VARCHAR(255),
# session_id VARCHAR(255),
# user_agent TEXT,
# action VARCHAR(100) NOT NULL,
# resource_type VARCHAR(100) NOT NULL,
# resource_id VARCHAR(255),
# result VARCHAR(20) NOT NULL, -- success, failure, error
# details JSONB,
# correlation_id VARCHAR(255),
# created_at TIMESTAMPTZ NOT NULL DEFAULT now()
# ) PARTITION BY RANGE (timestamp);
#
# -- Partitions by month
# CREATE TABLE audit_logs_2024_01 PARTITION OF audit_logs
# FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
#
# -- Indexes
# CREATE INDEX idx_audit_actor ON audit_logs (actor_id, timestamp);
# CREATE INDEX idx_audit_resource ON audit_logs (resource_type, resource_id);
# CREATE INDEX idx_audit_action ON audit_logs (action, timestamp);
# CREATE INDEX idx_audit_correlation ON audit_logs (correlation_id);
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class AuditLogEntry:
timestamp: str
actor_id: str
actor_ip: str
action: str
resource_type: str
resource_id: str
result: str
details: dict = field(default_factory=dict)
# Example audit entries for a Qwik e-commerce app
entries = [
AuditLogEntry("2024-01-15T10:00:00Z", "user_123", "203.0.113.50",
"auth.login", "session", "sess_abc", "success",
{"method": "password", "mfa": True}),
AuditLogEntry("2024-01-15T10:05:00Z", "user_123", "203.0.113.50",
"order.create", "order", "ord_456", "success",
{"total": 1500, "items": 3, "payment": "credit_card"}),
AuditLogEntry("2024-01-15T10:10:00Z", "admin_001", "10.0.0.5",
"user.update_role", "user", "user_789", "success",
{"old_role": "user", "new_role": "admin", "reason": "promotion"}),
AuditLogEntry("2024-01-15T10:15:00Z", "user_456", "198.51.100.20",
"product.delete", "product", "prod_321", "failure",
{"error": "permission_denied", "required_role": "admin"}),
]
print("=== Audit Log Entries ===")
for e in entries:
print(f"\n [{e.timestamp}] {e.action} → {e.result}")
print(f" Actor: {e.actor_id} ({e.actor_ip})")
print(f" Resource: {e.resource_type}/{e.resource_id}")
print(f" Details: {e.details}")
Qwik Middleware
# === Qwik City Audit Middleware ===
# // src/routes/layout.tsx - Global Audit Middleware
# import { type RequestHandler } from '@builder.io/qwik-city';
#
# export const onRequest: RequestHandler = async ({ request, next, sharedMap }) => {
# const correlationId = crypto.randomUUID();
# sharedMap.set('correlationId', correlationId);
# sharedMap.set('requestStart', Date.now());
#
# await next();
#
# // Log after response
# const duration = Date.now() - (sharedMap.get('requestStart') as number);
# console.log(JSON.stringify({
# timestamp: new Date().toISOString(),
# correlation_id: correlationId,
# method: request.method,
# url: request.url,
# duration_ms: duration,
# user_agent: request.headers.get('user-agent'),
# }));
# };
# // src/lib/audit.ts - Audit Logger
# import { server$ } from '@builder.io/qwik-city';
#
# export const auditLog = server$(async function(params: {
# action: string;
# resourceType: string;
# resourceId: string;
# result: 'success' | 'failure';
# details?: Record;
# }) {
# const session = this.sharedMap.get('session');
# await db.insert('audit_logs', {
# actor_id: session?.userId,
# actor_ip: this.request.headers.get('x-forwarded-for'),
# correlation_id: this.sharedMap.get('correlationId'),
# ...params,
# });
# });
@dataclass
class MiddlewareConfig:
hook: str
purpose: str
data_captured: str
example: str
configs = [
MiddlewareConfig("onRequest",
"ดักจับทุก Request เข้า Server",
"method url timestamp duration user_agent correlation_id",
"Global Audit Logging ทุก Route"),
MiddlewareConfig("routeAction$",
"ดักจับ Form Submission / Mutation",
"action resource changes result actor_id",
"order.create user.update product.delete"),
MiddlewareConfig("routeLoader$",
"ดักจับ Data Loading (อ่าน)",
"resource_type query_params result actor_id",
"user.list order.detail product.search"),
MiddlewareConfig("server$",
"ดักจับ Server Function Call",
"function_name params result actor_id",
"Custom API calls, Business Logic"),
]
print("=== Middleware Configs ===")
for c in configs:
print(f" [{c.hook}] {c.purpose}")
print(f" Data: {c.data_captured}")
print(f" Example: {c.example}")
Monitoring & Compliance
# === Audit Monitoring & Compliance ===
@dataclass
class ComplianceCheck:
standard: str
requirement: str
implementation: str
monitoring: str
compliance = [
ComplianceCheck("SOC 2 (CC6.1)",
"Log all access to systems",
"onRequest middleware logs ทุก Request, routeAction$ logs ทุก Mutation",
"Dashboard: Request Count per User, Failed Auth Attempts"),
ComplianceCheck("ISO 27001 (A.12.4)",
"Event logging and monitoring",
"Structured JSON Logs → ELK Stack, Retention 1 ปี",
"Alert: Unusual Activity, Mass Delete, Permission Change"),
ComplianceCheck("PDPA (Section 37)",
"Record personal data processing",
"Log ทุกครั้งที่ Access ข้อมูลส่วนบุคคล resource_type=user",
"Report: Data Access Log per User สำหรับ Data Subject Request"),
ComplianceCheck("PCI-DSS (10.2)",
"Audit trail for payment",
"Log ทุก Access ไป Order Payment Transaction",
"Alert: Payment Data Access นอกเวลาทำการ"),
ComplianceCheck("Immutability",
"Log ห้ามแก้ไข ลบ",
"PostgreSQL ไม่มี UPDATE DELETE Policy + S3 Object Lock Archive",
"Alert: ถ้ามีความพยายาม DELETE audit_logs"),
]
print("=== Compliance Checks ===")
for c in compliance:
print(f"\n [{c.standard}] {c.requirement}")
print(f" Implementation: {c.implementation}")
print(f" Monitoring: {c.monitoring}")
เคล็ดลับ
- server$: ใช้ server$ สำหรับ Audit ทุก Action ปลอดภัยกว่า Client-side
- Correlation ID: ใส่ Correlation ID ทุก Request ติดตามข้าม Service ได้
- Partition: Partition audit_logs ตาม Month สำหรับ Performance
- Immutable: ห้าม UPDATE DELETE ใน audit_logs ใช้ DB Policy
- Async: ส่ง Log ผ่าน Queue ไม่บล็อก Request Response
Audit Trail ใน Qwik คืออะไร
บันทึกทุก Action server$ routeAction$ ใครทำอะไรเมื่อไหร่ Resumability Server-side Log ELK Loki Compliance SOC 2 ISO PDPA
ออกแบบ Schema อย่างไร
UUID timestamp actor_id actor_ip action resource_type resource_id result details JSONB Partition Month Index Immutable No UPDATE DELETE
Implement อย่างไร
onRequest Middleware routeAction$ server$ auditLog function Structured JSON Correlation ID Message Queue Async Unit Test Middleware Pattern
Compliance ต้องทำอะไร
SOC 2 ISO 27001 PDPA PCI-DSS GDPR Immutable Log Retention 1-7 ปี Access Control Review Monitoring Alert Dashboard Report
สรุป
Qwik Audit Trail server$ routeAction$ Middleware Structured JSON Correlation ID PostgreSQL Partition Immutable Compliance SOC 2 ISO PDPA ELK
