All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 19m4s
575 lines
23 KiB
Plaintext
575 lines
23 KiB
Plaintext
# docker/logstash/bugsink.conf
|
|
# ============================================================================
|
|
# Logstash Pipeline Configuration for Bugsink Error Tracking
|
|
# ============================================================================
|
|
# This configuration aggregates logs from multiple sources and forwards errors
|
|
# to Bugsink (Sentry-compatible error tracking) in the dev container.
|
|
#
|
|
# Sources:
|
|
# - PM2 managed logs (/var/log/pm2/*.log) - API, Worker, Vite (ADR-014)
|
|
# - Pino application logs (/app/logs/*.log) - JSON format (fallback)
|
|
# - PostgreSQL logs (/var/log/postgresql/*.log) - Including fn_log() output
|
|
# - NGINX logs (/var/log/nginx/*.log) - Access and error logs
|
|
# - Redis logs (/var/log/redis/*.log) - Via shared volume (ADR-050)
|
|
#
|
|
# Bugsink Projects (3-project architecture):
|
|
# - Project 1: Backend API (Dev) - Pino/PM2 app errors, PostgreSQL errors
|
|
# DSN Key: cea01396c56246adb5878fa5ee6b1d22
|
|
# - Project 2: Frontend (Dev) - Configured via Sentry SDK in browser
|
|
# DSN Key: d92663cb73cf4145b677b84029e4b762
|
|
# - Project 4: Infrastructure (Dev) - Redis, NGINX, PM2 operational logs
|
|
# DSN Key: 14e8791da3d347fa98073261b596cab9
|
|
#
|
|
# Routing Logic:
|
|
# - Backend logs (type: pm2_api, pm2_worker, pino, postgres) -> Project 1
|
|
# - Infrastructure logs (type: redis, nginx_error, nginx_5xx) -> Project 4
|
|
# - Vite errors (type: pm2_vite with errors) -> Project 4 (build tooling)
|
|
#
|
|
# Related Documentation:
|
|
# - docs/adr/0050-postgresql-function-observability.md
|
|
# - docs/adr/0015-application-performance-monitoring-and-error-tracking.md
|
|
# - docs/operations/LOGSTASH-QUICK-REF.md
|
|
# ============================================================================
|
|
|
|
input {
|
|
# ============================================================================
|
|
# PM2 Managed Process Logs (ADR-014, ADR-050)
|
|
# ============================================================================
|
|
# PM2 manages all Node.js processes in the dev container, matching production.
|
|
# Logs are written to /var/log/pm2 for Logstash integration.
|
|
#
|
|
# Process logs:
|
|
# - api-out.log / api-error.log: API server (Pino JSON format)
|
|
# - worker-out.log / worker-error.log: Background worker (Pino JSON format)
|
|
# - vite-out.log / vite-error.log: Vite dev server (plain text)
|
|
# ============================================================================
|
|
|
|
# PM2 API Server Logs (Pino JSON format)
|
|
file {
|
|
path => "/var/log/pm2/api-out.log"
|
|
codec => json_lines
|
|
type => "pm2_api"
|
|
tags => ["app", "backend", "pm2", "api"]
|
|
start_position => "beginning"
|
|
sincedb_path => "/var/lib/logstash/sincedb_pm2_api_out"
|
|
}
|
|
|
|
file {
|
|
path => "/var/log/pm2/api-error.log"
|
|
codec => json_lines
|
|
type => "pm2_api"
|
|
tags => ["app", "backend", "pm2", "api", "stderr"]
|
|
start_position => "beginning"
|
|
sincedb_path => "/var/lib/logstash/sincedb_pm2_api_error"
|
|
}
|
|
|
|
# PM2 Worker Logs (Pino JSON format)
|
|
file {
|
|
path => "/var/log/pm2/worker-out.log"
|
|
codec => json_lines
|
|
type => "pm2_worker"
|
|
tags => ["app", "worker", "pm2"]
|
|
start_position => "beginning"
|
|
sincedb_path => "/var/lib/logstash/sincedb_pm2_worker_out"
|
|
}
|
|
|
|
file {
|
|
path => "/var/log/pm2/worker-error.log"
|
|
codec => json_lines
|
|
type => "pm2_worker"
|
|
tags => ["app", "worker", "pm2", "stderr"]
|
|
start_position => "beginning"
|
|
sincedb_path => "/var/lib/logstash/sincedb_pm2_worker_error"
|
|
}
|
|
|
|
# PM2 Vite Logs (plain text format)
|
|
file {
|
|
path => "/var/log/pm2/vite-out.log"
|
|
codec => plain
|
|
type => "pm2_vite"
|
|
tags => ["app", "frontend", "pm2", "vite"]
|
|
start_position => "beginning"
|
|
sincedb_path => "/var/lib/logstash/sincedb_pm2_vite_out"
|
|
}
|
|
|
|
file {
|
|
path => "/var/log/pm2/vite-error.log"
|
|
codec => plain
|
|
type => "pm2_vite"
|
|
tags => ["app", "frontend", "pm2", "vite", "stderr"]
|
|
start_position => "beginning"
|
|
sincedb_path => "/var/lib/logstash/sincedb_pm2_vite_error"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Pino Application Logs (Fallback Path)
|
|
# ============================================================================
|
|
# JSON-formatted logs from the Node.js application using Pino logger.
|
|
# Note: Primary logs now go to /var/log/pm2. This is a fallback.
|
|
# Log levels: 10=trace, 20=debug, 30=info, 40=warn, 50=error, 60=fatal
|
|
file {
|
|
path => "/app/logs/*.log"
|
|
codec => json
|
|
type => "pino"
|
|
tags => ["app", "backend"]
|
|
start_position => "beginning"
|
|
sincedb_path => "/var/lib/logstash/sincedb_pino"
|
|
}
|
|
|
|
# ============================================================================
|
|
# PostgreSQL Function Logs (ADR-050)
|
|
# ============================================================================
|
|
# Captures PostgreSQL log output including fn_log() structured JSON messages.
|
|
# PostgreSQL is configured to write logs to /var/log/postgresql/ (shared volume).
|
|
# Log format: "2026-01-22 14:30:00 PST [5724] postgres@flyer_crawler_dev LOG: message"
|
|
# Note: Timestamps are in PST (America/Los_Angeles) timezone as configured in compose.dev.yml
|
|
file {
|
|
path => "/var/log/postgresql/*.log"
|
|
type => "postgres"
|
|
tags => ["postgres", "database"]
|
|
start_position => "beginning"
|
|
sincedb_path => "/var/lib/logstash/sincedb_postgres"
|
|
}
|
|
|
|
# ============================================================================
|
|
# NGINX Logs
|
|
# ============================================================================
|
|
# Access logs for request monitoring and error logs for proxy/SSL issues.
|
|
file {
|
|
path => "/var/log/nginx/access.log"
|
|
type => "nginx_access"
|
|
tags => ["nginx", "access"]
|
|
start_position => "beginning"
|
|
sincedb_path => "/var/lib/logstash/sincedb_nginx_access"
|
|
}
|
|
|
|
file {
|
|
path => "/var/log/nginx/error.log"
|
|
type => "nginx_error"
|
|
tags => ["nginx", "error"]
|
|
start_position => "beginning"
|
|
sincedb_path => "/var/lib/logstash/sincedb_nginx_error"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Redis Logs (ADR-050)
|
|
# ============================================================================
|
|
# Redis logs from the shared volume. Redis uses its own log format:
|
|
# "<pid>:<role> <day> <month> <time> <loglevel> <message>"
|
|
# Example: "1:M 22 Jan 2026 14:30:00.123 * Ready to accept connections"
|
|
# Log levels: . (debug), - (verbose), * (notice), # (warning)
|
|
file {
|
|
path => "/var/log/redis/redis-server.log"
|
|
type => "redis"
|
|
tags => ["redis", "infra"]
|
|
start_position => "beginning"
|
|
sincedb_path => "/var/lib/logstash/sincedb_redis"
|
|
}
|
|
}
|
|
|
|
filter {
|
|
# ============================================================================
|
|
# PM2 API/Worker Log Processing (ADR-014)
|
|
# ============================================================================
|
|
# PM2 API and Worker logs are in Pino JSON format.
|
|
# Process them the same way as direct Pino logs.
|
|
if [type] in ["pm2_api", "pm2_worker"] {
|
|
# Tag errors (level 50 = error, 60 = fatal)
|
|
if [level] >= 50 {
|
|
mutate { add_tag => ["error", "pm2_pino_error"] }
|
|
|
|
# Map Pino level to Sentry level and set error_message field
|
|
if [level] == 60 {
|
|
mutate { add_field => { "sentry_level" => "fatal" } }
|
|
} else {
|
|
mutate { add_field => { "sentry_level" => "error" } }
|
|
}
|
|
|
|
# Copy msg to error_message for consistent access in output
|
|
mutate { add_field => { "error_message" => "%{msg}" } }
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# PM2 Vite Log Processing (ADR-014)
|
|
# ============================================================================
|
|
# Vite logs are plain text. Look for error patterns.
|
|
if [type] == "pm2_vite" {
|
|
# Detect Vite build/compilation errors
|
|
if [message] =~ /(?i)(error|failed|exception|cannot|enoent|eperm|EACCES|ECONNREFUSED)/ {
|
|
mutate { add_tag => ["error", "vite_error"] }
|
|
mutate { add_field => { "sentry_level" => "error" } }
|
|
mutate { add_field => { "error_message" => "Vite: %{message}" } }
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# Pino Log Processing (Fallback)
|
|
# ============================================================================
|
|
if [type] == "pino" {
|
|
# Tag errors (level 50 = error, 60 = fatal)
|
|
if [level] >= 50 {
|
|
mutate { add_tag => ["error", "pino_error"] }
|
|
|
|
# Map Pino level to Sentry level and set error_message field
|
|
if [level] == 60 {
|
|
mutate { add_field => { "sentry_level" => "fatal" } }
|
|
} else {
|
|
mutate { add_field => { "sentry_level" => "error" } }
|
|
}
|
|
|
|
# Copy msg to error_message for consistent access in output
|
|
mutate { add_field => { "error_message" => "%{msg}" } }
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# PostgreSQL Log Processing (ADR-050)
|
|
# ============================================================================
|
|
# PostgreSQL log format in dev container:
|
|
# "2026-01-22 14:30:00 PST [5724] postgres@flyer_crawler_dev LOG: message"
|
|
# "2026-01-22 15:06:03 PST [19851] postgres@flyer_crawler_dev ERROR: column "id" does not exist"
|
|
# Note: Timestamps are in PST (America/Los_Angeles) timezone
|
|
if [type] == "postgres" {
|
|
# Parse PostgreSQL log prefix with timezone (PST in dev, may vary in prod)
|
|
grok {
|
|
match => { "message" => "%{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{TIME} %{WORD:pg_timezone} \[%{POSINT:pg_pid}\] %{DATA:pg_user}@%{DATA:pg_database} %{WORD:pg_level}: ?%{GREEDYDATA:pg_message}" }
|
|
tag_on_failure => ["_postgres_grok_failure"]
|
|
}
|
|
|
|
# Check if this is a structured JSON log from fn_log()
|
|
if [pg_message] =~ /^\{.*"source":"postgresql".*\}$/ {
|
|
json {
|
|
source => "pg_message"
|
|
target => "fn_log"
|
|
}
|
|
|
|
# Mark as error if level is WARNING or ERROR
|
|
if [fn_log][level] in ["WARNING", "ERROR"] {
|
|
mutate { add_tag => ["error", "db_function"] }
|
|
mutate { add_field => { "sentry_level" => "warning" } }
|
|
if [fn_log][level] == "ERROR" {
|
|
mutate { replace => { "sentry_level" => "error" } }
|
|
}
|
|
# Use fn_log message for error_message
|
|
mutate { add_field => { "error_message" => "%{[fn_log][message]}" } }
|
|
}
|
|
}
|
|
|
|
# Catch native PostgreSQL errors (ERROR: or FATAL: prefix in pg_level)
|
|
if [pg_level] == "ERROR" or [pg_level] == "FATAL" {
|
|
mutate { add_tag => ["error", "postgres_native"] }
|
|
|
|
if [pg_level] == "FATAL" {
|
|
mutate { add_field => { "sentry_level" => "fatal" } }
|
|
} else {
|
|
mutate { add_field => { "sentry_level" => "error" } }
|
|
}
|
|
|
|
# Use the full pg_message for error_message
|
|
mutate { add_field => { "error_message" => "PostgreSQL %{pg_level}: %{pg_message}" } }
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# NGINX Access Log Processing
|
|
# ============================================================================
|
|
if [type] == "nginx_access" {
|
|
grok {
|
|
match => { "message" => '%{IPORHOST:client_ip} - %{DATA:user} \[%{HTTPDATE:timestamp}\] "%{WORD:method} %{URIPATHPARAM:request} HTTP/%{NUMBER:http_version}" %{NUMBER:status} %{NUMBER:bytes} "%{DATA:referrer}" "%{DATA:user_agent}"' }
|
|
tag_on_failure => ["_nginx_access_grok_failure"]
|
|
}
|
|
|
|
# Tag 5xx errors for Bugsink
|
|
if [status] =~ /^5/ {
|
|
mutate { add_tag => ["error", "nginx_5xx"] }
|
|
mutate { add_field => { "sentry_level" => "error" } }
|
|
mutate { add_field => { "error_message" => "HTTP %{status} %{method} %{request}" } }
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# NGINX Error Log Processing
|
|
# ============================================================================
|
|
# NGINX error log format: "2026/01/22 17:55:01 [error] 16#16: *3 message..."
|
|
if [type] == "nginx_error" {
|
|
grok {
|
|
match => { "message" => "%{YEAR}/%{MONTHNUM}/%{MONTHDAY} %{TIME} \[%{LOGLEVEL:nginx_level}\] %{POSINT:pid}#%{POSINT}: (\*%{POSINT:connection} )?%{GREEDYDATA:nginx_message}" }
|
|
tag_on_failure => ["_nginx_error_grok_failure"]
|
|
}
|
|
|
|
# Only process actual errors, not notices (like "signal process started")
|
|
if [nginx_level] in ["error", "crit", "alert", "emerg"] {
|
|
mutate { add_tag => ["error", "nginx_error"] }
|
|
|
|
# Map NGINX log level to Sentry level
|
|
if [nginx_level] in ["crit", "alert", "emerg"] {
|
|
mutate { add_field => { "sentry_level" => "fatal" } }
|
|
} else {
|
|
mutate { add_field => { "sentry_level" => "error" } }
|
|
}
|
|
|
|
mutate { add_field => { "error_message" => "NGINX [%{nginx_level}]: %{nginx_message}" } }
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# Redis Log Processing (ADR-050)
|
|
# ============================================================================
|
|
# Redis log format: "<pid>:<role> <day> <month> <time>.<ms> <level> <message>"
|
|
# Example: "1:M 22 Jan 14:30:00.123 * Ready to accept connections"
|
|
# Roles: M=master, S=slave, C=sentinel, X=cluster
|
|
# Levels: . (debug), - (verbose), * (notice), # (warning)
|
|
if [type] == "redis" {
|
|
# Parse Redis log format
|
|
grok {
|
|
match => { "message" => "%{POSINT:redis_pid}:%{WORD:redis_role} %{MONTHDAY} %{MONTH} %{YEAR}? ?%{TIME} %{DATA:redis_level_char} %{GREEDYDATA:redis_message}" }
|
|
tag_on_failure => ["_redis_grok_failure"]
|
|
}
|
|
|
|
# Map Redis level characters to human-readable levels
|
|
# . = debug, - = verbose, * = notice, # = warning
|
|
if [redis_level_char] == "#" {
|
|
mutate {
|
|
add_field => { "redis_level" => "warning" }
|
|
add_tag => ["error", "redis_warning"]
|
|
}
|
|
mutate { add_field => { "sentry_level" => "warning" } }
|
|
mutate { add_field => { "error_message" => "Redis WARNING: %{redis_message}" } }
|
|
} else if [redis_level_char] == "*" {
|
|
mutate { add_field => { "redis_level" => "notice" } }
|
|
} else if [redis_level_char] == "-" {
|
|
mutate { add_field => { "redis_level" => "verbose" } }
|
|
} else if [redis_level_char] == "." {
|
|
mutate { add_field => { "redis_level" => "debug" } }
|
|
}
|
|
|
|
# Also detect error keywords in message content (e.g., ECONNREFUSED, OOM, etc.)
|
|
if [redis_message] =~ /(?i)(error|failed|refused|denied|timeout|oom|crash|fatal|exception)/ {
|
|
if "error" not in [tags] {
|
|
mutate { add_tag => ["error", "redis_error"] }
|
|
mutate { add_field => { "sentry_level" => "error" } }
|
|
mutate { add_field => { "error_message" => "Redis ERROR: %{redis_message}" } }
|
|
}
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# Generate Sentry Event ID and Ensure Required Fields for all errors
|
|
# ============================================================================
|
|
# CRITICAL: sentry_level MUST be set for all errors before output.
|
|
# Bugsink's PostgreSQL schema limits level to varchar(7), so valid values are:
|
|
# fatal, error, warning, info, debug (all <= 7 chars)
|
|
# If sentry_level is not set, the literal "%{sentry_level}" (16 chars) is sent,
|
|
# causing PostgreSQL insertion failures.
|
|
# ============================================================================
|
|
if "error" in [tags] {
|
|
# Use Ruby for robust field handling - handles all edge cases
|
|
ruby {
|
|
code => '
|
|
require "securerandom"
|
|
|
|
# Generate unique event ID for Sentry
|
|
event.set("sentry_event_id", SecureRandom.hex(16))
|
|
|
|
# =====================================================================
|
|
# CRITICAL: Validate and set sentry_level
|
|
# =====================================================================
|
|
# Valid Sentry levels (max 7 chars for Bugsink PostgreSQL schema):
|
|
# fatal, error, warning, info, debug
|
|
# Default to "error" if missing, empty, or invalid.
|
|
# =====================================================================
|
|
valid_levels = ["fatal", "error", "warning", "info", "debug"]
|
|
current_level = event.get("sentry_level")
|
|
|
|
if current_level.nil? || current_level.to_s.strip.empty? || !valid_levels.include?(current_level.to_s.downcase)
|
|
event.set("sentry_level", "error")
|
|
else
|
|
# Normalize to lowercase
|
|
event.set("sentry_level", current_level.to_s.downcase)
|
|
end
|
|
|
|
# =====================================================================
|
|
# Ensure error_message has a fallback value
|
|
# =====================================================================
|
|
error_msg = event.get("error_message")
|
|
if error_msg.nil? || error_msg.to_s.strip.empty?
|
|
fallback_msg = event.get("message") || event.get("msg") || "Unknown error"
|
|
event.set("error_message", fallback_msg.to_s)
|
|
end
|
|
'
|
|
}
|
|
}
|
|
}
|
|
|
|
output {
|
|
# ============================================================================
|
|
# Forward Errors to Bugsink (Project Routing)
|
|
# ============================================================================
|
|
# Bugsink uses Sentry-compatible API. Events must include:
|
|
# - event_id: 32 hex characters (UUID without dashes)
|
|
# - message: Human-readable error description
|
|
# - level: fatal, error, warning, info, debug
|
|
# - timestamp: Unix epoch in seconds
|
|
# - platform: "node" for backend, "javascript" for frontend
|
|
#
|
|
# Authentication via X-Sentry-Auth header with project's public key.
|
|
#
|
|
# Project Routing:
|
|
# - Project 1 (Backend): Pino app logs, PostgreSQL errors
|
|
# - Project 4 (Infrastructure): Redis, NGINX, Vite build errors
|
|
# ============================================================================
|
|
|
|
# ============================================================================
|
|
# Infrastructure Errors -> Project 4
|
|
# ============================================================================
|
|
# Redis warnings/errors, NGINX errors, and Vite build errors go to
|
|
# the Infrastructure project for separation from application code errors.
|
|
if "error" in [tags] and ([type] == "redis" or [type] == "nginx_error" or [type] == "nginx_access" or [type] == "pm2_vite") {
|
|
http {
|
|
url => "http://localhost:8000/api/4/store/"
|
|
http_method => "post"
|
|
format => "json"
|
|
headers => {
|
|
"X-Sentry-Auth" => "Sentry sentry_key=14e8791da3d347fa98073261b596cab9, sentry_version=7"
|
|
"Content-Type" => "application/json"
|
|
}
|
|
mapping => {
|
|
"event_id" => "%{sentry_event_id}"
|
|
"timestamp" => "%{@timestamp}"
|
|
"level" => "%{sentry_level}"
|
|
"platform" => "other"
|
|
"logger" => "%{type}"
|
|
"message" => "%{error_message}"
|
|
"extra" => {
|
|
"hostname" => "%{[host][name]}"
|
|
"source_type" => "%{type}"
|
|
"tags" => "%{tags}"
|
|
"original_message" => "%{message}"
|
|
"project" => "infrastructure"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# Backend Application Errors -> Project 1
|
|
# ============================================================================
|
|
# Pino application logs (API, Worker), PostgreSQL function errors, and
|
|
# native PostgreSQL errors go to the Backend API project.
|
|
else if "error" in [tags] and ([type] in ["pm2_api", "pm2_worker", "pino", "postgres"]) {
|
|
http {
|
|
url => "http://localhost:8000/api/1/store/"
|
|
http_method => "post"
|
|
format => "json"
|
|
headers => {
|
|
"X-Sentry-Auth" => "Sentry sentry_key=cea01396c56246adb5878fa5ee6b1d22, sentry_version=7"
|
|
"Content-Type" => "application/json"
|
|
}
|
|
mapping => {
|
|
"event_id" => "%{sentry_event_id}"
|
|
"timestamp" => "%{@timestamp}"
|
|
"level" => "%{sentry_level}"
|
|
"platform" => "node"
|
|
"logger" => "%{type}"
|
|
"message" => "%{error_message}"
|
|
"extra" => {
|
|
"hostname" => "%{[host][name]}"
|
|
"source_type" => "%{type}"
|
|
"tags" => "%{tags}"
|
|
"original_message" => "%{message}"
|
|
"project" => "backend"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# Fallback: Any other errors -> Project 1
|
|
# ============================================================================
|
|
# Catch-all for any errors that don't match specific routing rules.
|
|
else if "error" in [tags] {
|
|
http {
|
|
url => "http://localhost:8000/api/1/store/"
|
|
http_method => "post"
|
|
format => "json"
|
|
headers => {
|
|
"X-Sentry-Auth" => "Sentry sentry_key=cea01396c56246adb5878fa5ee6b1d22, sentry_version=7"
|
|
"Content-Type" => "application/json"
|
|
}
|
|
mapping => {
|
|
"event_id" => "%{sentry_event_id}"
|
|
"timestamp" => "%{@timestamp}"
|
|
"level" => "%{sentry_level}"
|
|
"platform" => "node"
|
|
"logger" => "%{type}"
|
|
"message" => "%{error_message}"
|
|
"extra" => {
|
|
"hostname" => "%{[host][name]}"
|
|
"source_type" => "%{type}"
|
|
"tags" => "%{tags}"
|
|
"original_message" => "%{message}"
|
|
"project" => "backend-fallback"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# Store Operational Logs to Files (for debugging/audit)
|
|
# ============================================================================
|
|
# NGINX access logs (all requests, not just errors)
|
|
if [type] == "nginx_access" and "error" not in [tags] {
|
|
file {
|
|
path => "/var/log/logstash/nginx-access-%{+YYYY-MM-dd}.log"
|
|
codec => json_lines
|
|
}
|
|
}
|
|
|
|
# PostgreSQL operational logs (non-error)
|
|
if [type] == "postgres" and "error" not in [tags] {
|
|
file {
|
|
path => "/var/log/logstash/postgres-operational-%{+YYYY-MM-dd}.log"
|
|
codec => json_lines
|
|
}
|
|
}
|
|
|
|
# Redis operational logs (non-error)
|
|
if [type] == "redis" and "error" not in [tags] {
|
|
file {
|
|
path => "/var/log/logstash/redis-operational-%{+YYYY-MM-dd}.log"
|
|
codec => json_lines
|
|
}
|
|
}
|
|
|
|
# PM2 API operational logs (non-error) - ADR-014
|
|
if [type] == "pm2_api" and "error" not in [tags] {
|
|
file {
|
|
path => "/var/log/logstash/pm2-api-%{+YYYY-MM-dd}.log"
|
|
codec => json_lines
|
|
}
|
|
}
|
|
|
|
# PM2 Worker operational logs (non-error) - ADR-014
|
|
if [type] == "pm2_worker" and "error" not in [tags] {
|
|
file {
|
|
path => "/var/log/logstash/pm2-worker-%{+YYYY-MM-dd}.log"
|
|
codec => json_lines
|
|
}
|
|
}
|
|
|
|
# PM2 Vite operational logs (non-error) - ADR-014
|
|
if [type] == "pm2_vite" and "error" not in [tags] {
|
|
file {
|
|
path => "/var/log/logstash/pm2-vite-%{+YYYY-MM-dd}.log"
|
|
codec => json_lines
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# Debug Output (for development only)
|
|
# ============================================================================
|
|
# Uncomment to see all processed events in Logstash stdout
|
|
# stdout { codec => rubydebug }
|
|
}
|