guanghulab/scripts/server-diagnose-report.js

202 lines
7.0 KiB
JavaScript
Raw Permalink Normal View History

// scripts/server-diagnose-report.js
// 铸渊 · PM2 服务诊断报告 → Notion 工单
//
// 将 PM2 诊断和系统健康检查结果写入 Notion 工单队列。
// 由 pm2-server-diagnose.yml 工作流调用。
//
// 环境变量:
// NOTION_TOKEN Notion API token
// NOTION_TICKET_DB_ID 工单队列数据库 ID
// PM2_REPORT PM2 诊断输出
// CLEANUP_REPORT 清理操作输出(可选)
// HEALTH_REPORT 健康检查输出
'use strict';
const https = require('https');
const NOTION_TOKEN = process.env.NOTION_TOKEN || '';
const NOTION_TICKET_DB_ID = process.env.NOTION_TICKET_DB_ID || '';
const PM2_REPORT = process.env.PM2_REPORT || '';
const CLEANUP_REPORT = process.env.CLEANUP_REPORT || '';
const HEALTH_REPORT = process.env.HEALTH_REPORT || '';
const NOTION_VERSION = '2022-06-28';
const NOTION_API_HOSTNAME = 'api.notion.com';
const NOTION_RICH_TEXT_MAX = 2000;
const MAX_PM2_REPORT_LENGTH = 3000;
const MAX_CLEANUP_REPORT_LENGTH = 2000;
const MAX_HEALTH_REPORT_LENGTH = 3000;
// ══════════════════════════════════════════════════════════
// Notion API 工具
// ══════════════════════════════════════════════════════════
function notionPost(endpoint, body) {
return new Promise(function (resolve, reject) {
var payload = JSON.stringify(body);
var opts = {
hostname: NOTION_API_HOSTNAME,
port: 443,
path: endpoint,
method: 'POST',
headers: {
'Authorization': 'Bearer ' + NOTION_TOKEN,
'Content-Type': 'application/json',
'Notion-Version': NOTION_VERSION,
'Content-Length': Buffer.byteLength(payload),
},
};
var req = https.request(opts, function (res) {
var data = '';
res.on('data', function (chunk) { data += chunk; });
res.on('end', function () {
try {
var parsed = JSON.parse(data);
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(parsed);
} else {
reject(new Error('Notion API ' + res.statusCode + ': ' + (parsed.message || data)));
}
} catch (e) {
reject(new Error('Notion API parse error: ' + data));
}
});
});
req.on('error', reject);
req.write(payload);
req.end();
});
}
function richTextChunks(content) {
var str = String(content || '');
var chunks = [];
for (var i = 0; i < str.length; i += NOTION_RICH_TEXT_MAX) {
chunks.push({ type: 'text', text: { content: str.slice(i, i + NOTION_RICH_TEXT_MAX) } });
}
if (chunks.length === 0) {
chunks.push({ type: 'text', text: { content: '' } });
}
return chunks;
}
// ══════════════════════════════════════════════════════════
// 构建报告内容
// ══════════════════════════════════════════════════════════
function buildReportContent() {
var now = new Date().toISOString();
var dateStr = now.slice(0, 10);
var sections = [];
sections.push('## 📋 PM2 服务排查报告 · ' + dateStr);
sections.push('');
sections.push('| 字段 | 值 |');
sections.push('|------|-----|');
sections.push('| 执行者 | 铸渊Agent |');
sections.push('| 执行时间 | ' + now + ' |');
sections.push('| 来源 | GitHub Actions · pm2-server-diagnose |');
sections.push('');
if (PM2_REPORT) {
sections.push('## 阶段一 · PM2 诊断结果');
sections.push('');
sections.push(PM2_REPORT.slice(0, MAX_PM2_REPORT_LENGTH));
sections.push('');
}
if (CLEANUP_REPORT) {
sections.push('## 清理操作结果');
sections.push('');
sections.push(CLEANUP_REPORT.slice(0, MAX_CLEANUP_REPORT_LENGTH));
sections.push('');
}
if (HEALTH_REPORT) {
sections.push('## 阶段二 · 全系统健康检查');
sections.push('');
sections.push(HEALTH_REPORT.slice(0, MAX_HEALTH_REPORT_LENGTH));
sections.push('');
}
return sections.join('\n');
}
// ══════════════════════════════════════════════════════════
// 主函数 — 创建 Notion 诊断工单
// ══════════════════════════════════════════════════════════
async function createDiagnoseTicket() {
if (!NOTION_TOKEN || !NOTION_TICKET_DB_ID) {
console.log('⚠️ Notion credentials not configured, skipping ticket creation');
console.log(' NOTION_TOKEN: ' + (NOTION_TOKEN ? '✅' : '❌'));
console.log(' NOTION_TICKET_DB_ID: ' + (NOTION_TICKET_DB_ID ? '✅' : '❌'));
process.exit(0);
}
var now = new Date();
var dateStr = now.toISOString().slice(0, 10);
var title = 'PM2服务排查报告 · ' + dateStr;
var contentText = buildReportContent();
// 将内容拆分成多个段落块Notion 单个 rich_text 限制 2000 字符)
var contentBlocks = [];
var remaining = contentText;
while (remaining.length > 0) {
var chunk = remaining.slice(0, NOTION_RICH_TEXT_MAX);
remaining = remaining.slice(NOTION_RICH_TEXT_MAX);
contentBlocks.push({
object: 'block',
type: 'paragraph',
paragraph: {
rich_text: [{ type: 'text', text: { content: chunk } }],
},
});
}
// 确保至少有一个内容块
if (contentBlocks.length === 0) {
contentBlocks.push({
object: 'block',
type: 'paragraph',
paragraph: {
rich_text: [{ type: 'text', text: { content: '诊断报告(无详细输出)' } }],
},
});
}
var body = {
parent: { database_id: NOTION_TICKET_DB_ID },
properties: {
'标题': { title: [{ type: 'text', text: { content: title } }] },
'操作类型': { select: { name: '其他' } },
'提交者': { rich_text: [{ type: 'text', text: { content: '铸渊Agent' } }] },
'状态': { select: { name: '已完成' } },
'优先级': { select: { name: 'P1' } },
},
children: contentBlocks,
};
console.log('📋 创建 PM2 诊断工单...');
console.log(' 标题: ' + title);
console.log(' 数据库: ' + NOTION_TICKET_DB_ID);
console.log(' 内容长度: ' + contentText.length + ' 字符');
console.log(' 内容块数: ' + contentBlocks.length);
try {
var result = await notionPost('/v1/pages', body);
console.log('✅ Notion 诊断工单已创建: ' + result.id);
console.log(' URL: ' + (result.url || 'N/A'));
} catch (err) {
console.error('❌ 工单创建失败: ' + err.message);
// 不以失败退出,诊断本身已成功
console.log('⚠️ 诊断结果已输出到 Actions 日志Notion 写入失败不影响诊断');
process.exit(0);
}
}
createDiagnoseTicket();