feat(10.2-01): add hidden debug commands and error ring buffer foundation
- Add 4 new keyword routes: /errors, /clear-errors, /debug, /trace - Create Process Debug Command code node with unified command handling - Initialize workflow static data structure (errorLog with debug, errors, traces) - Implement /errors command to display recent errors (default 5, max 50) - Implement /clear-errors command to reset error buffer - Implement /debug on|off|status for debug mode toggle - Implement /trace <correlationId> for correlation-based query - Add Send Debug Response Telegram node with HTML formatting - Wire Keyword Router -> Process Debug Command -> Send Debug Response - Commands remain hidden (not listed in /start menu) - Node count: 168 -> 170 (+2 nodes)
This commit is contained in:
+175
-8
@@ -363,6 +363,98 @@
|
|||||||
},
|
},
|
||||||
"renameOutput": true,
|
"renameOutput": true,
|
||||||
"outputKey": "status"
|
"outputKey": "status"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "keyword-errors",
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": false,
|
||||||
|
"typeValidation": "loose"
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"id": "starts-with-errors",
|
||||||
|
"leftValue": "={{ $json.message.text }}",
|
||||||
|
"rightValue": "/errors",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "startsWith"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "errors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "keyword-clear-errors",
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": false,
|
||||||
|
"typeValidation": "loose"
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"id": "starts-with-clear",
|
||||||
|
"leftValue": "={{ $json.message.text }}",
|
||||||
|
"rightValue": "/clear",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "startsWith"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "clear-errors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "keyword-debug",
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": false,
|
||||||
|
"typeValidation": "loose"
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"id": "starts-with-debug",
|
||||||
|
"leftValue": "={{ $json.message.text }}",
|
||||||
|
"rightValue": "/debug",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "startsWith"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "keyword-trace",
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": false,
|
||||||
|
"typeValidation": "loose"
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"id": "starts-with-trace",
|
||||||
|
"leftValue": "={{ $json.message.text }}",
|
||||||
|
"rightValue": "/trace",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "startsWith"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "trace"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1531,7 +1623,7 @@
|
|||||||
"resource": "message",
|
"resource": "message",
|
||||||
"operation": "sendMessage",
|
"operation": "sendMessage",
|
||||||
"chatId": "={{ $json.message.chat.id }}",
|
"chatId": "={{ $json.message.chat.id }}",
|
||||||
"text": "<b>Commands:</b>\n\n• status\n• start [name]\n• stop [name]\n• restart [name]\n• update [name]\n• logs [name]",
|
"text": "<b>Commands:</b>\n\n\u2022 status\n\u2022 start [name]\n\u2022 stop [name]\n\u2022 restart [name]\n\u2022 update [name]\n\u2022 logs [name]",
|
||||||
"additionalFields": {
|
"additionalFields": {
|
||||||
"parse_mode": "HTML"
|
"parse_mode": "HTML"
|
||||||
}
|
}
|
||||||
@@ -2831,7 +2923,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "// Build confirmation message for update all\nconst data = $json;\nconst containers = data.containersToUpdate || [];\nconst count = data.count || 0;\n\n// Build container list (max 10 for display)\nconst displayContainers = containers.slice(0, 10);\nconst containerList = displayContainers.map(c => `• ${c.name}`).join('\\n');\nconst moreText = count > 10 ? `\\n...and ${count - 10} more` : '';\n\nconst message = `Update ${count} container${count !== 1 ? 's' : ''}?\\n\\n${containerList}${moreText}`;\n\n// Create inline keyboard\nconst timestamp = Math.floor(Date.now() / 1000);\nconst containerNames = containers.map(c => c.name).join(',');\n\n// Encode container names in callback (will need to lookup IDs later)\nreturn {\n json: {\n chatId: data.chatId,\n messageId: data.messageId,\n message: message,\n keyboard: {\n inline_keyboard: [\n [\n { text: '✅ Confirm', callback_data: `uall:confirm:${timestamp}` },\n { text: '❌ Cancel', callback_data: 'uall:cancel' }\n ]\n ]\n },\n containerNames: containerNames,\n containers: containers,\n timestamp: timestamp\n }\n};"
|
"jsCode": "// Build confirmation message for update all\nconst data = $json;\nconst containers = data.containersToUpdate || [];\nconst count = data.count || 0;\n\n// Build container list (max 10 for display)\nconst displayContainers = containers.slice(0, 10);\nconst containerList = displayContainers.map(c => `\u2022 ${c.name}`).join('\\n');\nconst moreText = count > 10 ? `\\n...and ${count - 10} more` : '';\n\nconst message = `Update ${count} container${count !== 1 ? 's' : ''}?\\n\\n${containerList}${moreText}`;\n\n// Create inline keyboard\nconst timestamp = Math.floor(Date.now() / 1000);\nconst containerNames = containers.map(c => c.name).join(',');\n\n// Encode container names in callback (will need to lookup IDs later)\nreturn {\n json: {\n chatId: data.chatId,\n messageId: data.messageId,\n message: message,\n keyboard: {\n inline_keyboard: [\n [\n { text: '\u2705 Confirm', callback_data: `uall:confirm:${timestamp}` },\n { text: '\u274c Cancel', callback_data: 'uall:cancel' }\n ]\n ]\n },\n containerNames: containerNames,\n containers: containers,\n timestamp: timestamp\n }\n};"
|
||||||
},
|
},
|
||||||
"id": "code-build-update-all-confirmation",
|
"id": "code-build-update-all-confirmation",
|
||||||
"name": "Build Update All Confirmation",
|
"name": "Build Update All Confirmation",
|
||||||
@@ -2872,7 +2964,7 @@
|
|||||||
"resource": "message",
|
"resource": "message",
|
||||||
"operation": "sendMessage",
|
"operation": "sendMessage",
|
||||||
"chatId": "={{ $json.chatId }}",
|
"chatId": "={{ $json.chatId }}",
|
||||||
"text": "All containers are up to date! 🎉",
|
"text": "All containers are up to date! \ud83c\udf89",
|
||||||
"options": {}
|
"options": {}
|
||||||
},
|
},
|
||||||
"id": "telegram-send-all-up-to-date",
|
"id": "telegram-send-all-up-to-date",
|
||||||
@@ -2895,7 +2987,7 @@
|
|||||||
"resource": "callback",
|
"resource": "callback",
|
||||||
"operation": "answerQuery",
|
"operation": "answerQuery",
|
||||||
"queryId": "={{ $json.queryId }}",
|
"queryId": "={{ $json.queryId }}",
|
||||||
"text": "⏱️ Confirmation expired (30s timeout)",
|
"text": "\u23f1\ufe0f Confirmation expired (30s timeout)",
|
||||||
"options": {
|
"options": {
|
||||||
"showAlert": true
|
"showAlert": true
|
||||||
}
|
}
|
||||||
@@ -2942,7 +3034,7 @@
|
|||||||
"resource": "callback",
|
"resource": "callback",
|
||||||
"operation": "answerQuery",
|
"operation": "answerQuery",
|
||||||
"queryId": "={{ $json.queryId }}",
|
"queryId": "={{ $json.queryId }}",
|
||||||
"text": "❌ Update cancelled"
|
"text": "\u274c Update cancelled"
|
||||||
},
|
},
|
||||||
"id": "telegram-answer-update-all-cancel",
|
"id": "telegram-answer-update-all-cancel",
|
||||||
"name": "Answer Update All Cancel",
|
"name": "Answer Update All Cancel",
|
||||||
@@ -3030,7 +3122,7 @@
|
|||||||
"resource": "callback",
|
"resource": "callback",
|
||||||
"operation": "answerQuery",
|
"operation": "answerQuery",
|
||||||
"queryId": "={{ $json.queryId }}",
|
"queryId": "={{ $json.queryId }}",
|
||||||
"text": "✅ Starting batch update..."
|
"text": "\u2705 Starting batch update..."
|
||||||
},
|
},
|
||||||
"id": "telegram-answer-update-all-confirm",
|
"id": "telegram-answer-update-all-confirm",
|
||||||
"name": "Answer Update All Confirm",
|
"name": "Answer Update All Confirm",
|
||||||
@@ -3498,7 +3590,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "// Format logs result for inline keyboard display\nconst result = $json;\nconst data = $('Prepare Inline Logs Input').item.json;\n\nconst containerName = result.containerName;\n\n// Add timestamp to prevent 'message not modified' error on refresh\nconst timestamp = new Date().toLocaleTimeString('en-US', {\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false\n});\n\n// Build inline keyboard\nconst keyboard = [\n [\n { text: '🔄 Refresh Logs', callback_data: `action:logs:${containerName}` },\n { text: '⬆️ Update', callback_data: `action:update:${containerName}` }\n ],\n [\n { text: '◀️ Back to List', callback_data: 'list:0' }\n ]\n];\n\n// Append timestamp to message\nconst messageWithTimestamp = result.message + `\\n\\n<i>Updated: ${timestamp}</i>`;\n\nreturn {\n json: {\n chatId: data.chatId,\n messageId: data.messageId,\n text: messageWithTimestamp,\n reply_markup: { inline_keyboard: keyboard }\n }\n};"
|
"jsCode": "// Format logs result for inline keyboard display\nconst result = $json;\nconst data = $('Prepare Inline Logs Input').item.json;\n\nconst containerName = result.containerName;\n\n// Add timestamp to prevent 'message not modified' error on refresh\nconst timestamp = new Date().toLocaleTimeString('en-US', {\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false\n});\n\n// Build inline keyboard\nconst keyboard = [\n [\n { text: '\ud83d\udd04 Refresh Logs', callback_data: `action:logs:${containerName}` },\n { text: '\u2b06\ufe0f Update', callback_data: `action:update:${containerName}` }\n ],\n [\n { text: '\u25c0\ufe0f Back to List', callback_data: 'list:0' }\n ]\n];\n\n// Append timestamp to message\nconst messageWithTimestamp = result.message + `\\n\\n<i>Updated: ${timestamp}</i>`;\n\nreturn {\n json: {\n chatId: data.chatId,\n messageId: data.messageId,\n text: messageWithTimestamp,\n reply_markup: { inline_keyboard: keyboard }\n }\n};"
|
||||||
},
|
},
|
||||||
"id": "b1800598-1ff6-4da3-8506-4e4e8127f902",
|
"id": "b1800598-1ff6-4da3-8506-4e4e8127f902",
|
||||||
"name": "Format Inline Logs Result",
|
"name": "Format Inline Logs Result",
|
||||||
@@ -4813,6 +4905,42 @@
|
|||||||
2110,
|
2110,
|
||||||
-300
|
-300
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const staticData = $getWorkflowStaticData('global');\nconst input = $input.item.json;\nconst command = (input.message?.text || '').toLowerCase().trim();\nconst chatId = input.message.chat.id;\n\n// Initialize static data structure if missing\nif (!staticData.errorLog) {\n staticData.errorLog = {\n debug: { enabled: false, executionCount: 0 },\n errors: { buffer: [], nextId: 1, count: 0, lastCleared: new Date().toISOString() },\n traces: { buffer: [], nextId: 1 }\n };\n}\n\nlet text = '';\n\n// Parse command and arguments\nconst parts = command.split(' ');\nconst cmd = parts[0];\nconst arg1 = parts[1] || '';\n\nif (cmd === '/errors') {\n // Show recent errors (default 5, max 50)\n const count = parseInt(arg1) || 5;\n const requestedCount = Math.min(count, 50);\n const errors = staticData.errorLog.errors.buffer || [];\n const recentErrors = errors.slice(-requestedCount).reverse();\n \n if (recentErrors.length === 0) {\n text = 'No errors recorded.';\n } else {\n text = `<b>Recent Errors (${recentErrors.length})</b>\\n\\n`;\n recentErrors.forEach(err => {\n const timestamp = new Date(err.timestamp).toISOString().replace('T', ' ').substring(0, 19);\n text += `<b>#${err.id}</b> - ${timestamp}\\n`;\n text += `Workflow: ${err.workflow} > ${err.node}\\n`;\n text += `<pre>${err.userMessage}</pre>\\n`;\n if (err.error?.httpCode) {\n text += `HTTP: ${err.error.httpCode}\\n`;\n }\n text += '\\n';\n });\n \n text += `<i>Total errors: ${staticData.errorLog.errors.count}\\n`;\n text += `Debug mode: ${staticData.errorLog.debug.enabled ? 'ON' : 'OFF'}</i>`;\n }\n \n} else if (cmd === '/clear' || cmd === '/clear-errors') {\n // Clear error buffer\n const count = staticData.errorLog.errors.buffer.length;\n staticData.errorLog.errors.buffer = [];\n staticData.errorLog.errors.nextId = 1;\n staticData.errorLog.errors.lastCleared = new Date().toISOString();\n text = `<b>Error log cleared.</b> ${count} entries removed.`;\n \n} else if (cmd === '/debug') {\n // Toggle debug mode\n if (arg1 === 'on') {\n staticData.errorLog.debug.enabled = true;\n staticData.errorLog.debug.executionCount = 0;\n text = '<b>Debug mode enabled.</b>\\nTracing sub-workflow boundaries and callback routing.';\n } else if (arg1 === 'off') {\n staticData.errorLog.debug.enabled = false;\n text = '<b>Debug mode disabled.</b>';\n } else {\n // Status (default)\n const debug = staticData.errorLog.debug;\n const errors = staticData.errorLog.errors;\n const traces = staticData.errorLog.traces;\n text = `<b>Debug Status</b>\\n\\n`;\n text += `Debug mode: ${debug.enabled ? '<b>ON</b>' : 'OFF'}\\n`;\n text += `Execution count: ${debug.executionCount || 0}\\n`;\n text += `Error buffer: ${errors.buffer.length} entries\\n`;\n text += `Trace buffer: ${traces.buffer.length} entries\\n`;\n text += `Total errors: ${errors.count}`;\n }\n \n} else if (cmd === '/trace') {\n // Trace by correlation ID\n const correlationId = arg1;\n \n if (!correlationId) {\n text = '<b>Usage:</b> /trace <correlationId>';\n } else {\n const errors = staticData.errorLog.errors.buffer || [];\n const traces = staticData.errorLog.traces.buffer || [];\n \n // Find all entries matching correlation ID\n const errorMatches = errors.filter(e => e.correlationId === correlationId);\n const traceMatches = traces.filter(t => t.correlationId === correlationId);\n \n // Combine and sort chronologically\n const allMatches = [...errorMatches, ...traceMatches].sort((a, b) => \n new Date(a.timestamp) - new Date(b.timestamp)\n );\n \n if (allMatches.length === 0) {\n text = `No entries found for correlation ID: <code>${correlationId}</code>`;\n } else {\n text = `<b>Trace for ${correlationId}</b>\\n\\n`;\n allMatches.forEach(entry => {\n const timestamp = new Date(entry.timestamp).toISOString().replace('T', ' ').substring(0, 19);\n const type = entry.id.startsWith('err') ? 'ERROR' : 'TRACE';\n text += `<b>[${type}]</b> ${timestamp}\\n`;\n text += `${entry.workflow || 'main'} > ${entry.node}\\n`;\n \n if (type === 'ERROR') {\n text += `<pre>${entry.userMessage}</pre>\\n`;\n } else if (entry.event) {\n text += `Event: ${entry.event}\\n`;\n }\n text += '\\n';\n });\n }\n }\n}\n\nreturn { json: { chatId, text } };"
|
||||||
|
},
|
||||||
|
"id": "code-process-debug-command",
|
||||||
|
"name": "Process Debug Command",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1120,
|
||||||
|
-200
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"chatId": "={{ $json.chatId }}",
|
||||||
|
"text": "={{ $json.text }}",
|
||||||
|
"additionalFields": {
|
||||||
|
"parse_mode": "HTML"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "telegram-send-debug-response",
|
||||||
|
"name": "Send Debug Response",
|
||||||
|
"type": "n8n-nodes-base.telegram",
|
||||||
|
"typeVersion": 1.2,
|
||||||
|
"position": [
|
||||||
|
1340,
|
||||||
|
-200
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"telegramApi": {
|
||||||
|
"id": "I0xTTiASl7C1NZhJ",
|
||||||
|
"name": "Telegram account"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"connections": {
|
"connections": {
|
||||||
@@ -5285,6 +5413,34 @@
|
|||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Process Debug Command",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Process Debug Command",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Process Debug Command",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Process Debug Command",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -6669,6 +6825,17 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"Process Debug Command": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Debug Response",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pinData": {},
|
"pinData": {},
|
||||||
@@ -6679,4 +6846,4 @@
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"triggerCount": 1,
|
"triggerCount": 1,
|
||||||
"active": false
|
"active": false
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user