mirror of
https://github.com/Silentely/eSIM-Tools.git
synced 2026-09-03 06:24:20 +08:00
🔧 chore: 为 BFF 代理和 GraphQL 函数添加调试日志以排查 500 错误
- Edge Function (bff-proxy): 添加请求入口、ACCESS_KEY 检查、body 读取、 转发目标、fetch 结果等关键节点日志 - Function (giffgaff-graphql): 添加操作名、参数完整性、上游调用状态、 token 刷新链路等全链路日志 - 修复: fetch 异常时不再向客户端暴露内部错误详情(安全加固) - 所有日志仅记录布尔态和状态码,不泄露 token/签名等敏感信息
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
|
||||
export default async (request, context) => {
|
||||
const url = new URL(request.url);
|
||||
const ts = new Date().toISOString();
|
||||
|
||||
// 仅处理 /bff/* 路径
|
||||
if (!url.pathname.startsWith('/bff/')) {
|
||||
@@ -18,6 +19,8 @@ export default async (request, context) => {
|
||||
return new Response('Bad Request', { status: 400 });
|
||||
}
|
||||
|
||||
console.log(`[BFF] ${ts} | ${request.method} ${url.pathname} → target=${targetName}`);
|
||||
|
||||
// 目标 Netlify Function URL(同域)
|
||||
const functionUrl = new URL(`/.netlify/functions/${targetName}` , request.url);
|
||||
|
||||
@@ -25,11 +28,13 @@ export default async (request, context) => {
|
||||
// Netlify Edge 使用 Deno 运行时
|
||||
const accessKey = (typeof Deno !== 'undefined' && Deno.env && Deno.env.get('ACCESS_KEY')) || '';
|
||||
if (!accessKey) {
|
||||
console.error(`[BFF] ${ts} | ACCESS_KEY missing in Edge env`);
|
||||
return new Response(JSON.stringify({ error: 'Server Misconfigured', message: 'ACCESS_KEY not configured' }), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
console.log(`[BFF] ${ts} | ACCESS_KEY present, proceeding`);
|
||||
|
||||
// 复制请求头并添加服务端密钥头(仅内部互调使用,不影响浏览器 CORS)
|
||||
const headers = new Headers(request.headers);
|
||||
@@ -41,7 +46,16 @@ export default async (request, context) => {
|
||||
let body = undefined;
|
||||
const isMutating = request.method !== 'GET' && request.method !== 'HEAD';
|
||||
if (isMutating) {
|
||||
body = await request.arrayBuffer();
|
||||
try {
|
||||
body = await request.arrayBuffer();
|
||||
console.log(`[BFF] ${ts} | body read OK, size=${body.byteLength} bytes`);
|
||||
} catch (bodyErr) {
|
||||
console.error(`[BFF] ${ts} | body read FAILED: ${bodyErr.message}`);
|
||||
return new Response(JSON.stringify({ error: 'Bad Request', message: 'Failed to read request body' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const proxiedRequest = new Request(functionUrl.toString(), {
|
||||
@@ -50,14 +64,20 @@ export default async (request, context) => {
|
||||
body
|
||||
});
|
||||
|
||||
const response = await fetch(proxiedRequest);
|
||||
|
||||
// 简短日志头(不包含敏感信息)
|
||||
console.log(`[BFF] ${ts} | forwarding to ${functionUrl.pathname}`);
|
||||
let response;
|
||||
try {
|
||||
const ok = response.ok;
|
||||
const status = response.status;
|
||||
console.log(`[BFF] ${request.method} ${url.pathname} -> ${functionUrl.pathname} ${status}${ok ? '' : ' (fail)'}`);
|
||||
} catch (_) {}
|
||||
response = await fetch(proxiedRequest);
|
||||
} catch (fetchErr) {
|
||||
console.error(`[BFF] ${ts} | fetch to Function FAILED: ${fetchErr.message}`);
|
||||
return new Response(JSON.stringify({ error: 'Bad Gateway', message: 'Failed to reach upstream function' }), {
|
||||
status: 502,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
// 响应日志(不包含敏感信息)
|
||||
console.log(`[BFF] ${ts} | response from ${functionUrl.pathname}: status=${response.status} ok=${response.ok}`);
|
||||
|
||||
// 直接透传响应
|
||||
return response;
|
||||
|
||||
@@ -21,6 +21,9 @@ const graphqlSchema = {
|
||||
};
|
||||
|
||||
exports.handler = withAuth(async (event, context, { auth, body }) => {
|
||||
const ts = new Date().toISOString();
|
||||
console.log(`[GGQL] ${ts} | invoked, operationName=${body.operationName || 'none'}, hasAccessToken=${!!body.accessToken}, hasMfaSignature=${!!body.mfaSignature}, hasCookie=${!!body.cookie}`);
|
||||
|
||||
// 解析请求体
|
||||
const { mfaSignature, mfaRef, query, variables, operationName, cookie } = body;
|
||||
|
||||
@@ -125,6 +128,7 @@ exports.handler = withAuth(async (event, context, { auth, body }) => {
|
||||
const verifyCookieUrl = hostHdr ? `${protoHdr}://${hostHdr}/.netlify/functions/verify-cookie` : ((process.env.URL || '').replace(/\/$/, '') + '/.netlify/functions/verify-cookie');
|
||||
|
||||
// 调用Giffgaff GraphQL API
|
||||
console.log(`[GGQL] ${ts} | calling upstream: op=${opName}, isSwap=${isSwap}, hasMfaSignature=${!!mfaSignature}, hasResolvedMfaRef=${!!resolvedMfaRef}`);
|
||||
let response;
|
||||
try {
|
||||
response = await axios.post(
|
||||
@@ -132,13 +136,16 @@ exports.handler = withAuth(async (event, context, { auth, body }) => {
|
||||
graphqlBody,
|
||||
{ headers: requestHeaders, timeout: 30000 }
|
||||
);
|
||||
console.log(`[GGQL] ${ts} | upstream OK: status=${response.status}, hasData=${!!response.data}, hasErrors=${!!response.data?.errors}`);
|
||||
} catch (err) {
|
||||
const status = err.response?.status;
|
||||
const data = err.response?.data || {};
|
||||
const isUnauthorized = status === 401 || data?.error === 'unauthorized' || /invalid_token/i.test(String(data?.error || ''));
|
||||
console.error(`[GGQL] ${ts} | upstream FAILED: status=${status}, isUnauthorized=${isUnauthorized}, errMsg=${err.message}`);
|
||||
|
||||
// 失败 401 时尝试用 cookie 刷新后重试一次
|
||||
if (isUnauthorized && cookie) {
|
||||
console.log(`[GGQL] ${ts} | attempting cookie-based token refresh`);
|
||||
try {
|
||||
const r = await axios.post(verifyCookieUrl, { cookie }, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -148,15 +155,19 @@ exports.handler = withAuth(async (event, context, { auth, body }) => {
|
||||
if (r.data?.success && r.data?.accessToken) {
|
||||
accessToken = r.data.accessToken;
|
||||
requestHeaders['Authorization'] = `Bearer ${accessToken}`;
|
||||
console.log(`[GGQL] ${ts} | token refreshed, retrying upstream call`);
|
||||
response = await axios.post(
|
||||
'https://publicapi.giffgaff.com/gateway/graphql',
|
||||
graphqlBody,
|
||||
{ headers: requestHeaders, timeout: 30000 }
|
||||
);
|
||||
console.log(`[GGQL] ${ts} | retry OK: status=${response.status}`);
|
||||
} else {
|
||||
console.error(`[GGQL] ${ts} | cookie refresh failed: success=${r.data?.success}`);
|
||||
throw err;
|
||||
}
|
||||
} catch (reErr) {
|
||||
console.error(`[GGQL] ${ts} | token refresh/retry FAILED: ${reErr.message}`);
|
||||
throw new AuthError('Access token expired. Please re-login with cookie.', 401);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -92,23 +92,40 @@ class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出环境信息(启动时调用一次,仅开发环境)
|
||||
* 输出环境信息(启动时调用一次)
|
||||
* 生产环境:仅输出关键排错信息(不含敏感数据)
|
||||
* 开发环境:输出完整信息
|
||||
*/
|
||||
static env() {
|
||||
if (!isDev || typeof window === 'undefined') return;
|
||||
console.groupCollapsed('%c[ENV] 环境信息', 'color: #6366f1; font-weight: bold');
|
||||
console.log('时间:', new Date().toISOString());
|
||||
console.log('主机:', window.location.hostname);
|
||||
console.log('协议:', window.location.protocol);
|
||||
console.log('语言:', navigator.language);
|
||||
console.log('平台:', navigator.platform);
|
||||
console.log('UserAgent:', navigator.userAgent);
|
||||
console.log('屏幕:', `${screen.width}x${screen.height}`);
|
||||
console.log('视口:', `${window.innerWidth}x${window.innerHeight}`);
|
||||
console.log('Cookie:', navigator.cookieEnabled ? '启用' : '禁用');
|
||||
console.log('ServiceWorker:', 'serviceWorker' in navigator ? '支持' : '不支持');
|
||||
console.log('NODE_ENV:', typeof process !== 'undefined' ? process.env?.NODE_ENV : 'N/A');
|
||||
console.groupEnd();
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
if (isDev) {
|
||||
// 开发环境:完整信息
|
||||
console.groupCollapsed('%c[ENV] 环境信息', 'color: #6366f1; font-weight: bold');
|
||||
console.log('时间:', new Date().toISOString());
|
||||
console.log('主机:', window.location.hostname);
|
||||
console.log('协议:', window.location.protocol);
|
||||
console.log('语言:', navigator.language);
|
||||
console.log('平台:', navigator.platform);
|
||||
console.log('UserAgent:', navigator.userAgent);
|
||||
console.log('屏幕:', `${screen.width}x${screen.height}`);
|
||||
console.log('视口:', `${window.innerWidth}x${window.innerHeight}`);
|
||||
console.log('Cookie:', navigator.cookieEnabled ? '启用' : '禁用');
|
||||
console.log('ServiceWorker:', 'serviceWorker' in navigator ? '支持' : '不支持');
|
||||
console.log('NODE_ENV:', typeof process !== 'undefined' ? process.env?.NODE_ENV : 'N/A');
|
||||
console.groupEnd();
|
||||
} else {
|
||||
// 生产环境:关键排错信息(折叠分组,不占空间)
|
||||
console.groupCollapsed('%c[ENV] 运行环境', 'color: #6b7280; font-size: 11px');
|
||||
console.log('主机:', window.location.hostname);
|
||||
console.log('协议:', window.location.protocol);
|
||||
console.log('平台:', navigator.platform);
|
||||
console.log('语言:', navigator.language);
|
||||
console.log('Cookie:', navigator.cookieEnabled ? '启用' : '禁用');
|
||||
console.log('ServiceWorker:', 'serviceWorker' in navigator ? '支持' : '不支持');
|
||||
console.log('视口:', `${window.innerWidth}x${window.innerHeight}`);
|
||||
console.groupEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user