fix: wire OTLP logs for diagnostics
This commit is contained in:
101
src/telegram/bot-message.test.ts
Normal file
101
src/telegram/bot-message.test.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const buildTelegramMessageContext = vi.hoisted(() => vi.fn());
|
||||
const dispatchTelegramMessage = vi.hoisted(() => vi.fn());
|
||||
const logMessageQueued = vi.hoisted(() => vi.fn());
|
||||
const logMessageProcessed = vi.hoisted(() => vi.fn());
|
||||
const logSessionStateChange = vi.hoisted(() => vi.fn());
|
||||
const diagnosticLogger = vi.hoisted(() => ({
|
||||
info: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
error: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./bot-message-context.js", () => ({
|
||||
buildTelegramMessageContext,
|
||||
}));
|
||||
|
||||
vi.mock("./bot-message-dispatch.js", () => ({
|
||||
dispatchTelegramMessage,
|
||||
}));
|
||||
|
||||
vi.mock("../logging/diagnostic.js", () => ({
|
||||
diagnosticLogger,
|
||||
logMessageQueued,
|
||||
logMessageProcessed,
|
||||
logSessionStateChange,
|
||||
}));
|
||||
|
||||
import { createTelegramMessageProcessor } from "./bot-message.js";
|
||||
|
||||
describe("telegram bot message diagnostics", () => {
|
||||
beforeEach(() => {
|
||||
buildTelegramMessageContext.mockReset();
|
||||
dispatchTelegramMessage.mockReset();
|
||||
logMessageQueued.mockReset();
|
||||
logMessageProcessed.mockReset();
|
||||
logSessionStateChange.mockReset();
|
||||
diagnosticLogger.info.mockReset();
|
||||
diagnosticLogger.debug.mockReset();
|
||||
diagnosticLogger.error.mockReset();
|
||||
});
|
||||
|
||||
const baseDeps = {
|
||||
bot: {},
|
||||
cfg: {},
|
||||
account: {},
|
||||
telegramCfg: {},
|
||||
historyLimit: 0,
|
||||
groupHistories: {},
|
||||
dmPolicy: {},
|
||||
allowFrom: [],
|
||||
groupAllowFrom: [],
|
||||
ackReactionScope: "none",
|
||||
logger: {},
|
||||
resolveGroupActivation: () => true,
|
||||
resolveGroupRequireMention: () => false,
|
||||
resolveTelegramGroupConfig: () => ({}),
|
||||
runtime: {},
|
||||
replyToMode: "auto",
|
||||
streamMode: "auto",
|
||||
textLimit: 4096,
|
||||
opts: {},
|
||||
resolveBotTopicsEnabled: () => false,
|
||||
};
|
||||
|
||||
it("decrements queue depth after successful processing", async () => {
|
||||
buildTelegramMessageContext.mockResolvedValue({
|
||||
route: { sessionKey: "agent:main:main" },
|
||||
});
|
||||
|
||||
const processMessage = createTelegramMessageProcessor(baseDeps);
|
||||
await processMessage({ message: { chat: { id: 123 }, message_id: 456 } }, [], [], {});
|
||||
|
||||
expect(logMessageQueued).toHaveBeenCalledTimes(1);
|
||||
expect(logSessionStateChange).toHaveBeenCalledWith({
|
||||
sessionKey: "agent:main:main",
|
||||
state: "idle",
|
||||
reason: "message_completed",
|
||||
});
|
||||
});
|
||||
|
||||
it("decrements queue depth after processing error", async () => {
|
||||
buildTelegramMessageContext.mockResolvedValue({
|
||||
route: { sessionKey: "agent:main:main" },
|
||||
});
|
||||
dispatchTelegramMessage.mockRejectedValue(new Error("boom"));
|
||||
|
||||
const processMessage = createTelegramMessageProcessor(baseDeps);
|
||||
|
||||
await expect(
|
||||
processMessage({ message: { chat: { id: 123 }, message_id: 456 } }, [], [], {}),
|
||||
).rejects.toThrow("boom");
|
||||
|
||||
expect(logMessageQueued).toHaveBeenCalledTimes(1);
|
||||
expect(logSessionStateChange).toHaveBeenCalledWith({
|
||||
sessionKey: "agent:main:main",
|
||||
state: "idle",
|
||||
reason: "message_error",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,12 @@
|
||||
// @ts-nocheck
|
||||
import { buildTelegramMessageContext } from "./bot-message-context.js";
|
||||
import { dispatchTelegramMessage } from "./bot-message-dispatch.js";
|
||||
import {
|
||||
diagnosticLogger as diag,
|
||||
logMessageProcessed,
|
||||
logMessageQueued,
|
||||
logSessionStateChange,
|
||||
} from "../logging/diagnostic.js";
|
||||
|
||||
export const createTelegramMessageProcessor = (deps) => {
|
||||
const {
|
||||
@@ -27,37 +33,122 @@ export const createTelegramMessageProcessor = (deps) => {
|
||||
} = deps;
|
||||
|
||||
return async (primaryCtx, allMedia, storeAllowFrom, options) => {
|
||||
const context = await buildTelegramMessageContext({
|
||||
primaryCtx,
|
||||
allMedia,
|
||||
storeAllowFrom,
|
||||
options,
|
||||
bot,
|
||||
cfg,
|
||||
account,
|
||||
historyLimit,
|
||||
groupHistories,
|
||||
dmPolicy,
|
||||
allowFrom,
|
||||
groupAllowFrom,
|
||||
ackReactionScope,
|
||||
logger,
|
||||
resolveGroupActivation,
|
||||
resolveGroupRequireMention,
|
||||
resolveTelegramGroupConfig,
|
||||
});
|
||||
if (!context) return;
|
||||
await dispatchTelegramMessage({
|
||||
context,
|
||||
bot,
|
||||
cfg,
|
||||
runtime,
|
||||
replyToMode,
|
||||
streamMode,
|
||||
textLimit,
|
||||
telegramCfg,
|
||||
opts,
|
||||
resolveBotTopicsEnabled,
|
||||
});
|
||||
const chatId = primaryCtx?.message?.chat?.id ?? primaryCtx?.chat?.id ?? "unknown";
|
||||
const messageId = primaryCtx?.message?.message_id ?? "unknown";
|
||||
const startTime = Date.now();
|
||||
|
||||
diag.info(
|
||||
`process message start: channel=telegram chatId=${chatId} messageId=${messageId} mediaCount=${
|
||||
allMedia?.length ?? 0
|
||||
}`,
|
||||
);
|
||||
|
||||
let sessionKey: string | undefined;
|
||||
|
||||
try {
|
||||
const context = await buildTelegramMessageContext({
|
||||
primaryCtx,
|
||||
allMedia,
|
||||
storeAllowFrom,
|
||||
options,
|
||||
bot,
|
||||
cfg,
|
||||
account,
|
||||
historyLimit,
|
||||
groupHistories,
|
||||
dmPolicy,
|
||||
allowFrom,
|
||||
groupAllowFrom,
|
||||
ackReactionScope,
|
||||
logger,
|
||||
resolveGroupActivation,
|
||||
resolveGroupRequireMention,
|
||||
resolveTelegramGroupConfig,
|
||||
});
|
||||
if (!context) {
|
||||
const durationMs = Date.now() - startTime;
|
||||
diag.debug(
|
||||
`process message skipped: channel=telegram chatId=${chatId} messageId=${messageId} reason=no_context`,
|
||||
);
|
||||
logMessageProcessed({
|
||||
channel: "telegram",
|
||||
chatId,
|
||||
messageId,
|
||||
durationMs,
|
||||
outcome: "skipped",
|
||||
reason: "no_context",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
sessionKey = context?.route?.sessionKey;
|
||||
diag.info(
|
||||
`process message dispatching: channel=telegram chatId=${chatId} messageId=${messageId} sessionKey=${
|
||||
sessionKey ?? "unknown"
|
||||
}`,
|
||||
);
|
||||
if (sessionKey) {
|
||||
logMessageQueued({ sessionKey, channel: "telegram", source: "telegram" });
|
||||
}
|
||||
|
||||
await dispatchTelegramMessage({
|
||||
context,
|
||||
bot,
|
||||
cfg,
|
||||
runtime,
|
||||
replyToMode,
|
||||
streamMode,
|
||||
textLimit,
|
||||
telegramCfg,
|
||||
opts,
|
||||
resolveBotTopicsEnabled,
|
||||
});
|
||||
|
||||
const durationMs = Date.now() - startTime;
|
||||
logMessageProcessed({
|
||||
channel: "telegram",
|
||||
chatId,
|
||||
messageId,
|
||||
sessionKey,
|
||||
durationMs,
|
||||
outcome: "completed",
|
||||
});
|
||||
if (sessionKey) {
|
||||
logSessionStateChange({
|
||||
sessionKey,
|
||||
state: "idle",
|
||||
reason: "message_completed",
|
||||
});
|
||||
}
|
||||
diag.info(
|
||||
`process message complete: channel=telegram chatId=${chatId} messageId=${messageId} sessionKey=${
|
||||
sessionKey ?? "unknown"
|
||||
} durationMs=${durationMs}`,
|
||||
);
|
||||
} catch (err) {
|
||||
const durationMs = Date.now() - startTime;
|
||||
logMessageProcessed({
|
||||
channel: "telegram",
|
||||
chatId,
|
||||
messageId,
|
||||
sessionKey,
|
||||
durationMs,
|
||||
outcome: "error",
|
||||
error: String(err),
|
||||
});
|
||||
if (sessionKey) {
|
||||
logSessionStateChange({
|
||||
sessionKey,
|
||||
state: "idle",
|
||||
reason: "message_error",
|
||||
});
|
||||
}
|
||||
diag.error(
|
||||
`process message error: channel=telegram chatId=${chatId} messageId=${messageId} durationMs=${durationMs} error="${String(
|
||||
err,
|
||||
)}"`,
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,6 +5,13 @@ import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import {
|
||||
logWebhookError,
|
||||
logWebhookProcessed,
|
||||
logWebhookReceived,
|
||||
startDiagnosticHeartbeat,
|
||||
stopDiagnosticHeartbeat,
|
||||
} from "../logging/diagnostic.js";
|
||||
import { resolveTelegramAllowedUpdates } from "./allowed-updates.js";
|
||||
import { createTelegramBot } from "./bot.js";
|
||||
|
||||
@@ -38,6 +45,8 @@ export async function startTelegramWebhook(opts: {
|
||||
secretToken: opts.secret,
|
||||
});
|
||||
|
||||
startDiagnosticHeartbeat();
|
||||
|
||||
const server = createServer((req, res) => {
|
||||
if (req.url === healthPath) {
|
||||
res.writeHead(200);
|
||||
@@ -49,13 +58,29 @@ export async function startTelegramWebhook(opts: {
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
const startTime = Date.now();
|
||||
logWebhookReceived({ channel: "telegram", updateType: "telegram-post" });
|
||||
const handled = handler(req, res);
|
||||
if (handled && typeof (handled as Promise<void>).catch === "function") {
|
||||
void (handled as Promise<void>).catch((err) => {
|
||||
runtime.log?.(`webhook handler failed: ${formatErrorMessage(err)}`);
|
||||
if (!res.headersSent) res.writeHead(500);
|
||||
res.end();
|
||||
});
|
||||
void (handled as Promise<void>)
|
||||
.then(() => {
|
||||
logWebhookProcessed({
|
||||
channel: "telegram",
|
||||
updateType: "telegram-post",
|
||||
durationMs: Date.now() - startTime,
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
const errMsg = formatErrorMessage(err);
|
||||
logWebhookError({
|
||||
channel: "telegram",
|
||||
updateType: "telegram-post",
|
||||
error: errMsg,
|
||||
});
|
||||
runtime.log?.(`webhook handler failed: ${errMsg}`);
|
||||
if (!res.headersSent) res.writeHead(500);
|
||||
res.end();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -73,6 +98,7 @@ export async function startTelegramWebhook(opts: {
|
||||
const shutdown = () => {
|
||||
server.close();
|
||||
void bot.stop();
|
||||
stopDiagnosticHeartbeat();
|
||||
};
|
||||
if (opts.abortSignal) {
|
||||
opts.abortSignal.addEventListener("abort", shutdown, { once: true });
|
||||
|
||||
Reference in New Issue
Block a user