{ "name": "Homelab Log Analyzer", "nodes": [ { "parameters": { "rule": { "interval": [ { "field": "hours", "hoursInterval": 6 } ] } }, "id": "schedule-trigger", "name": "Every 6 Hours", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.2, "position": [ 250, 400 ] }, { "parameters": { "command": "docker service logs --tail 100 --timestamps traefik_traefik 2>&1 || echo 'Service not found'" }, "id": "logs-traefik", "name": "Get Traefik Logs", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, "position": [ 500, 200 ], "continueOnFail": true }, { "parameters": { "command": "docker service logs --tail 100 --timestamps n8n_n8n 2>&1 || echo 'Service not found'" }, "id": "logs-n8n", "name": "Get n8n Logs", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, "position": [ 500, 350 ], "continueOnFail": true }, { "parameters": { "command": "docker service logs --tail 100 --timestamps ai_openwebui 2>&1 || echo 'Service not found'" }, "id": "logs-openwebui", "name": "Get OpenWebUI Logs", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, "position": [ 500, 500 ], "continueOnFail": true }, { "parameters": { "command": "docker service logs --tail 100 --timestamps infrastructure_komodo-core 2>&1 || echo 'Service not found'" }, "id": "logs-komodo", "name": "Get Komodo Logs", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, "position": [ 500, 650 ], "continueOnFail": true }, { "parameters": { "command": "docker service logs --tail 100 --timestamps monitoring_prometheus 2>&1 || echo 'Service not found'" }, "id": "logs-prometheus", "name": "Get Prometheus Logs", "type": "n8n-nodes-base.executeCommand", "typeVersion": 1, "position": [ 500, 800 ], "continueOnFail": true }, { "parameters": { "jsCode": "const items = $input.all();\n\nconst logAnalysis = {\n timestamp: new Date().toISOString(),\n services: [],\n errors: [],\n warnings: [],\n summary: {}\n};\n\nconst errorPatterns = [\n /ERROR/gi,\n /FATAL/gi,\n /CRITICAL/gi,\n /FAIL/gi,\n /panic:/gi,\n /exception/gi\n];\n\nconst warningPatterns = [\n /WARN/gi,\n /WARNING/gi,\n /deprecated/gi,\n /timeout/gi,\n /retry/gi\n];\n\nfor (const item of items) {\n const nodeName = item.json.node || 'unknown';\n const stdout = item.json.stdout || '';\n const lines = stdout.split('\\n').filter(l => l.trim());\n \n const serviceLog = {\n name: nodeName,\n totalLines: lines.length,\n errors: [],\n warnings: [],\n recentEntries: lines.slice(-10) // Last 10 lines\n };\n \n // Scan for errors and warnings\n lines.forEach(line => {\n const matchesError = errorPatterns.some(pattern => pattern.test(line));\n const matchesWarning = warningPatterns.some(pattern => pattern.test(line));\n \n if (matchesError) {\n const errorEntry = {\n service: nodeName,\n line: line,\n timestamp: line.match(/^\\d{4}-\\d{2}-\\d{2}T[\\d:]+\\.\\d+Z/) ? line.split(' ')[0] : null\n };\n serviceLog.errors.push(errorEntry);\n logAnalysis.errors.push(errorEntry);\n } else if (matchesWarning) {\n const warningEntry = {\n service: nodeName,\n line: line,\n timestamp: line.match(/^\\d{4}-\\d{2}-\\d{2}T[\\d:]+\\.\\d+Z/) ? line.split(' ')[0] : null\n };\n serviceLog.warnings.push(warningEntry);\n logAnalysis.warnings.push(warningEntry);\n }\n });\n \n logAnalysis.services.push(serviceLog);\n}\n\n// Generate summary\nlogAnalysis.summary = {\n totalServices: logAnalysis.services.length,\n totalErrors: logAnalysis.errors.length,\n totalWarnings: logAnalysis.warnings.length,\n servicesWithErrors: logAnalysis.services.filter(s => s.errors.length > 0).map(s => s.name),\n servicesWithWarnings: logAnalysis.services.filter(s => s.warnings.length > 0).map(s => s.name)\n};\n\nreturn [{ json: logAnalysis }];" }, "id": "parse-logs", "name": "Parse and Analyze Logs", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 750, 500 ] }, { "parameters": { "method": "POST", "url": "=http://lm-studio:1234/v1/chat/completions", "sendBody": true, "bodyParameters": { "parameters": [ { "name": "model", "value": "=qwen2.5-coder-7b-instruct" }, { "name": "messages", "value": "={{ [{\"role\":\"system\",\"content\":\"You are a Docker/Kubernetes expert and log analyzer. Analyze these Docker service logs and identify: 1) Critical issues requiring immediate attention 2) Performance concerns 3) Configuration problems 4) Recommended actions. Respond in JSON format with: critical_issues (array), performance_concerns (array), config_issues (array), recommendations (array).\"}, {\"role\":\"user\",\"content\":\"Analyze these homelab service logs:\\n\\nSummary: \" + JSON.stringify($json.summary, null, 2) + \"\\n\\nErrors Found: \" + JSON.stringify($json.errors.slice(0, 20), null, 2) + \"\\n\\nWarnings Found: \" + JSON.stringify($json.warnings.slice(0, 20), null, 2)}] }}" }, { "name": "temperature", "value": "=0.2" }, { "name": "max_tokens", "value": "=1500" } ] }, "options": { "timeout": 30000 } }, "id": "ai-log-analysis", "name": "AI Log Analysis", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 1000, 500 ] }, { "parameters": { "jsCode": "const logData = $('Parse and Analyze Logs').item.json;\nconst aiResponse = $json.choices[0].message.content;\n\nlet aiAnalysis;\ntry {\n // Extract JSON from response (AI might wrap it in markdown)\n const jsonMatch = aiResponse.match(/\\{[\\s\\S]*\\}/);\n aiAnalysis = jsonMatch ? JSON.parse(jsonMatch[0]) : { raw: aiResponse };\n} catch (e) {\n aiAnalysis = { raw: aiResponse };\n}\n\nconst report = {\n generated_at: new Date().toISOString(),\n period: '6 hours',\n summary: logData.summary,\n top_errors: logData.errors.slice(0, 10),\n top_warnings: logData.warnings.slice(0, 10),\n ai_analysis: aiAnalysis,\n action_required: logData.summary.totalErrors > 10 || (aiAnalysis.critical_issues && aiAnalysis.critical_issues.length > 0)\n};\n\nreturn [{ json: report }];" }, "id": "build-log-report", "name": "Build Log Report", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1250, 500 ] }, { "parameters": { "conditions": { "options": { "leftValue": "", "caseSensitive": true, "typeValidation": "strict" }, "combinator": "or", "conditions": [ { "id": "has-action-required", "leftValue": "={{ $json.action_required }}", "rightValue": true, "operator": { "type": "boolean", "operation": "true" } }, { "id": "many-errors", "leftValue": "={{ $json.summary.totalErrors }}", "rightValue": 5, "operator": { "type": "number", "operation": "gt" } } ] } }, "id": "should-alert-logs", "name": "Should Alert?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [ 1500, 500 ] } ], "pinData": {}, "connections": { "Every 6 Hours": { "main": [ [ { "node": "Get Traefik Logs", "type": "main", "index": 0 }, { "node": "Get n8n Logs", "type": "main", "index": 0 }, { "node": "Get OpenWebUI Logs", "type": "main", "index": 0 }, { "node": "Get Komodo Logs", "type": "main", "index": 0 }, { "node": "Get Prometheus Logs", "type": "main", "index": 0 } ] ] }, "Get Traefik Logs": { "main": [ [ { "node": "Parse and Analyze Logs", "type": "main", "index": 0 } ] ] }, "Get n8n Logs": { "main": [ [ { "node": "Parse and Analyze Logs", "type": "main", "index": 0 } ] ] }, "Get OpenWebUI Logs": { "main": [ [ { "node": "Parse and Analyze Logs", "type": "main", "index": 0 } ] ] }, "Get Komodo Logs": { "main": [ [ { "node": "Parse and Analyze Logs", "type": "main", "index": 0 } ] ] }, "Get Prometheus Logs": { "main": [ [ { "node": "Parse and Analyze Logs", "type": "main", "index": 0 } ] ] }, "Parse and Analyze Logs": { "main": [ [ { "node": "AI Log Analysis", "type": "main", "index": 0 } ] ] }, "AI Log Analysis": { "main": [ [ { "node": "Build Log Report", "type": "main", "index": 0 } ] ] }, "Build Log Report": { "main": [ [ { "node": "Should Alert?", "type": "main", "index": 0 } ] ] } }, "active": false, "settings": { "executionOrder": "v1" }, "versionId": "1", "meta": { "templateCredsSetupCompleted": true, "instanceId": "homelab" }, "id": "homelab-log-analyzer", "tags": [] }