mirror of
https://github.com/d0zingcat/alert-message-center.git
synced 2026-05-13 15:09:19 +00:00
feat: fix ws error and unify logger
Signed-off-by: d0zingcat <iamtangli42@gmail.com>
This commit is contained in:
@@ -14,12 +14,14 @@
|
||||
"@larksuiteoapi/node-sdk": "^1.56.1",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"hono": "^4.11.3",
|
||||
"pino": "^10.1.1",
|
||||
"postgres": "^3.4.8",
|
||||
"zod": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"bun-types": "latest",
|
||||
"drizzle-kit": "^0.31.8"
|
||||
"drizzle-kit": "^0.31.8",
|
||||
"pino-pretty": "^13.1.3"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Hono } from 'hono';
|
||||
import { logger } from './lib/logger';
|
||||
import { setCookie, getCookie } from 'hono/cookie';
|
||||
import { db } from './db';
|
||||
import { users } from './db/schema';
|
||||
@@ -100,7 +101,7 @@ auth.get('/callback', async (c) => {
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('OAuth callback error:', error);
|
||||
logger.error({ err: error }, 'OAuth callback error');
|
||||
return c.json({ error: 'Authentication failed' }, 500);
|
||||
}
|
||||
});
|
||||
@@ -125,7 +126,7 @@ auth.get('/me', (c) => {
|
||||
};
|
||||
return c.json({ user });
|
||||
} catch (error) {
|
||||
console.error('[Auth] Failed to parse session cookie:', error);
|
||||
logger.error({ err: error }, '[Auth] Failed to parse session cookie');
|
||||
return c.json({ error: 'Invalid session' }, 401);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { db } from './db';
|
||||
import { knownGroupChats } from './db/schema';
|
||||
import { knownGroupChats, topicGroupChats } from './db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import * as lark from '@larksuiteoapi/node-sdk';
|
||||
import { logger } from './lib/logger';
|
||||
|
||||
export const eventDispatcher = new lark.EventDispatcher({}).register({
|
||||
'im.chat.member.bot.added_v1': async (data) => {
|
||||
const payload = data as any;
|
||||
const { chat_id, name } = payload.chat || payload.message?.chat || {};
|
||||
console.log(`[Feishu Event] Bot added to group: ${name} (${chat_id})`);
|
||||
const { chat_id, name } = data as any;
|
||||
logger.info({ chat_id, name }, '[Feishu Event] Bot added to group');
|
||||
|
||||
if (chat_id) {
|
||||
await db.insert(knownGroupChats).values({
|
||||
@@ -23,4 +23,13 @@ export const eventDispatcher = new lark.EventDispatcher({}).register({
|
||||
});
|
||||
}
|
||||
},
|
||||
'im.chat.member.bot.deleted_v1': async (data) => {
|
||||
const { chat_id } = data as any;
|
||||
logger.info({ chat_id }, '[Feishu Event] Bot removed from group');
|
||||
|
||||
if (chat_id) {
|
||||
await db.delete(knownGroupChats).where(eq(knownGroupChats.chatId, chat_id));
|
||||
await db.delete(topicGroupChats).where(eq(topicGroupChats.chatId, chat_id));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import * as lark from '@larksuiteoapi/node-sdk';
|
||||
import { logger } from './lib/logger';
|
||||
|
||||
export class FeishuClient {
|
||||
public client: lark.Client;
|
||||
private appId: string;
|
||||
public appId: string;
|
||||
public appSecret: string;
|
||||
|
||||
constructor(appId: string, appSecret: string) {
|
||||
this.appId = appId;
|
||||
this.appSecret = appSecret;
|
||||
this.client = new lark.Client({
|
||||
appId: appId,
|
||||
appSecret: appSecret,
|
||||
@@ -31,7 +34,7 @@ export class FeishuClient {
|
||||
});
|
||||
|
||||
if (response.code !== 0) {
|
||||
console.error('Feishu send message error:', response);
|
||||
logger.error({ response }, 'Feishu send message error');
|
||||
throw new Error(`Failed to send message: ${response.msg}`);
|
||||
}
|
||||
return response.data;
|
||||
@@ -51,7 +54,7 @@ export class FeishuClient {
|
||||
});
|
||||
|
||||
if (response.code !== 0) {
|
||||
console.error('Feishu get user access token error:', response);
|
||||
logger.error({ response }, 'Feishu get user access token error');
|
||||
throw new Error(`Failed to get user access token: ${response.msg}`);
|
||||
}
|
||||
return response.data;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Hono } from 'hono';
|
||||
import { logger } from './lib/logger';
|
||||
import { cors } from 'hono/cors';
|
||||
import { serveStatic } from 'hono/bun';
|
||||
import { db } from './db';
|
||||
@@ -30,7 +31,7 @@ app.use('/*', serveStatic({ root: './public' }));
|
||||
app.get('*', serveStatic({ path: './public/index.html' }));
|
||||
|
||||
app.onError((err, c) => {
|
||||
console.error(`[Global Error] ${c.req.method} ${c.req.url}:`, err);
|
||||
logger.error({ err, method: c.req.method, url: c.req.url }, 'Global Error');
|
||||
return c.json({ error: err.message || 'Internal Server Error' }, 500);
|
||||
});
|
||||
|
||||
|
||||
19
apps/server/src/lib/logger.ts
Normal file
19
apps/server/src/lib/logger.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import pino from 'pino';
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
|
||||
export const logger = pino({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
transport: isDevelopment
|
||||
? {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: 'HH:MM:ss Z',
|
||||
ignore: 'pid,hostname',
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
||||
export default logger;
|
||||
@@ -3,13 +3,14 @@ import { eq } from 'drizzle-orm';
|
||||
import { db } from './db';
|
||||
import { topics, alertTasks, alertLogs, users } from './db/schema';
|
||||
import { feishuClient } from './feishu';
|
||||
import { logger } from './lib/logger';
|
||||
|
||||
const webhook = new Hono();
|
||||
|
||||
webhook.post('/:token/topic/:slug', async (c) => {
|
||||
const token = c.req.param('token');
|
||||
const slug = c.req.param('slug');
|
||||
console.log(`[Webhook] Received request for token: ${token}, slug: ${slug}`);
|
||||
logger.info({ token, slug }, '[Webhook] Received request');
|
||||
|
||||
// 0. Find the User by Token
|
||||
const user = await db.query.users.findFirst({
|
||||
@@ -17,19 +18,19 @@ webhook.post('/:token/topic/:slug', async (c) => {
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
console.warn(`[Webhook] Invalid personal token: ${token}`);
|
||||
logger.warn({ token }, '[Webhook] Invalid personal token');
|
||||
return c.json({ error: 'Invalid personal token' }, 401);
|
||||
}
|
||||
let body;
|
||||
try {
|
||||
const rawBody = await c.req.text();
|
||||
console.log(`[Webhook] Raw body length: ${rawBody.length}, content: "${rawBody}"`);
|
||||
logger.debug({ bodyLength: rawBody.length }, '[Webhook] Received raw body');
|
||||
if (!rawBody || rawBody.trim() === '') {
|
||||
return c.json({ error: 'Empty body' }, 400);
|
||||
}
|
||||
body = JSON.parse(rawBody);
|
||||
} catch (e) {
|
||||
console.error(`[Webhook] Failed to parse JSON body:`, e);
|
||||
logger.error({ err: e }, '[Webhook] Failed to parse JSON body');
|
||||
return c.json({ error: 'Invalid JSON body' }, 400);
|
||||
}
|
||||
|
||||
@@ -47,11 +48,11 @@ webhook.post('/:token/topic/:slug', async (c) => {
|
||||
});
|
||||
|
||||
if (!topic) {
|
||||
console.warn(`[Webhook] Topic not found: ${slug}`);
|
||||
logger.warn({ slug }, '[Webhook] Topic not found');
|
||||
return c.json({ error: 'Topic not found' }, 404);
|
||||
}
|
||||
|
||||
console.log(`[Webhook] Found topic: ${topic.name}`);
|
||||
logger.info({ topicName: topic.name }, '[Webhook] Found topic');
|
||||
|
||||
// 2. Collect recipients
|
||||
const userRecipients = topic.subscriptions
|
||||
@@ -96,7 +97,11 @@ webhook.post('/:token/topic/:slug', async (c) => {
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`[Webhook] Task ${task.id}: Dispatching to ${userRecipients.length} users and ${groupRecipients.length} groups`);
|
||||
logger.info({
|
||||
taskId: task.id,
|
||||
userCount: userRecipients.length,
|
||||
groupCount: groupRecipients.length
|
||||
}, '[Webhook] Dispatching alerts');
|
||||
|
||||
// 4. Send Private Messages asynchronously
|
||||
Promise.allSettled(allRecipients.map(async (recipient) => {
|
||||
@@ -126,7 +131,11 @@ webhook.post('/:token/topic/:slug', async (c) => {
|
||||
|
||||
return { recipientId: recipient.id, status: 'sent', error: null };
|
||||
} catch (error: any) {
|
||||
console.error(`Failed to send to ${recipient.type} ${recipient.name}:`, error);
|
||||
logger.error({
|
||||
err: error,
|
||||
recipientType: recipient.type,
|
||||
recipientName: recipient.name
|
||||
}, 'Failed to send alert');
|
||||
return { recipientId: recipient.id, status: 'failed', error: error.message };
|
||||
}
|
||||
})).then(async (results) => {
|
||||
@@ -173,7 +182,12 @@ webhook.post('/:token/topic/:slug', async (c) => {
|
||||
await db.insert(alertLogs).values(logs as any);
|
||||
}
|
||||
|
||||
console.log(`[Webhook] Task ${task.id}: Sent ${successCount}/${allRecipients.length} alerts for topic ${slug}`);
|
||||
logger.info({
|
||||
taskId: task.id,
|
||||
successCount,
|
||||
totalCount: allRecipients.length,
|
||||
slug
|
||||
}, '[Webhook] Task processed');
|
||||
});
|
||||
|
||||
return c.json({
|
||||
@@ -186,7 +200,7 @@ webhook.post('/:token/topic/:slug', async (c) => {
|
||||
|
||||
webhook.post('/:token/dm', async (c) => {
|
||||
const token = c.req.param('token');
|
||||
console.log(`[Webhook] Received DM request for token: ${token}`);
|
||||
logger.info({ token }, '[Webhook] Received DM request');
|
||||
|
||||
// 0. Find the User by Token
|
||||
const user = await db.query.users.findFirst({
|
||||
@@ -194,7 +208,7 @@ webhook.post('/:token/dm', async (c) => {
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
console.warn(`[Webhook] Invalid personal token: ${token}`);
|
||||
logger.warn({ token }, '[Webhook] Invalid personal token');
|
||||
return c.json({ error: 'Invalid personal token' }, 401);
|
||||
}
|
||||
|
||||
@@ -260,7 +274,7 @@ webhook.post('/:token/dm', async (c) => {
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error(`Failed to send DM to user ${user.name}:`, error);
|
||||
logger.error({ err: error, userName: user.name }, 'Failed to send DM');
|
||||
await db.update(alertTasks).set({
|
||||
status: 'failed',
|
||||
updatedAt: new Date(),
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import * as lark from '@larksuiteoapi/node-sdk';
|
||||
import { feishuClient } from './feishu';
|
||||
import { eventDispatcher } from './event-handler';
|
||||
import { logger } from './lib/logger';
|
||||
|
||||
export const startWebSocket = async () => {
|
||||
if (process.env.FEISHU_USE_WS !== 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Feishu WS] Starting WebSocket connection...');
|
||||
logger.info('[Feishu WS] Starting WebSocket connection...');
|
||||
try {
|
||||
await (feishuClient.client as any).ws.start(eventDispatcher);
|
||||
console.log('[Feishu WS] Connected successfully');
|
||||
const wsClient = new lark.WSClient({
|
||||
appId: feishuClient.appId,
|
||||
appSecret: feishuClient.appSecret,
|
||||
});
|
||||
await wsClient.start({ eventDispatcher });
|
||||
logger.info('[Feishu WS] Connected successfully');
|
||||
} catch (e) {
|
||||
console.error('[Feishu WS] Connection failed:', e);
|
||||
logger.error({ err: e }, '[Feishu WS] Connection failed');
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user