fix:修复队列没有日志

This commit is contained in:
lsy committed 2026-03-18 15:58:51 +08:00
1 parent dc74a5087a
commit 8ddc64becb
4 files changed
+26 -63

No files matched your search

+16
View File
@@ -2,6 +2,19 @@
## [Unreleased]
### ????
- **[proxy/usage]**: ???? usage ?????? ? by lsy
- ??: ??????????
- ??: apps/worker/src/routes/proxy.ts:70-560, .helloagents/modules/proxy.md:20-40, .helloagents/modules/usage.md:10-30
### ??
- **[proxy/usage]**: ?? usage ???????????? ? by lsy
- ??: [202603181533_usage-event-call-fix](archive/2026-03/202603181533_usage-event-call-fix/)
### 新增
- **[deploy-workflow]**: 新增本地一键部署脚本(init/update) — by lsy
@@ -227,6 +240,9 @@
### 快速修改
- **[deploy-workflow]**: 本地部署脚本移除 .env 自动生成与加载 — by lsy
- 类型: 快速修改(无方案包)
- 文件: scripts/deploy.mjs:1-292
- **[admin-ui/settings]**: 缓存 TTL 与版本成对展示为卡片 — by lsy
- 类型: 快速修改(无方案包)
- 文件: apps/ui/src/features/SettingsView.tsx:417-632
+1
View File
@@ -25,6 +25,7 @@
- 对 `/v1/responses` 且上游返回 400/404 时回退为 `/responses` 重试一次
- 可配置失败重试轮询(响应 5xx/429 时触发)
- 记录流式请求标记、首 token 延迟与推理强度到 usage_logs
- usage 事件调度仅传 event 对象,失败请求也必须记录 error usage
- 使用日志与模型能力写入可通过 `USAGE_QUEUE` 异步化(队列不可用时自动回退同步写入)
- 队列写入采用“两态简化逻辑”:未超限时按比例直写/队列,超限后全部直写
- 队列日限额由 `UsageLimiter` DO 维护,限制 `USAGE_QUEUE` 的日内发送量
+8 -8
View File
@@ -531,7 +531,7 @@ proxy.all("/*", tokenAuth, async (c) => {
}) => {
const latencyMs = Date.now() - requestStart;
const errorMessage = options.message ?? options.code;
scheduleUsageEvent(c, {
scheduleUsageEvent({
type: "usage",
payload: {
tokenId: tokenRecord.id,
@@ -938,7 +938,7 @@ proxy.all("/*", tokenAuth, async (c) => {
selectedUpstreamModel = upstreamModel;
lastErrorDetails = null;
if (recordModel) {
scheduleUsageEvent(c, {
scheduleUsageEvent({
type: "capability_upsert",
payload: {
channelId: channel.id,
@@ -960,7 +960,7 @@ proxy.all("/*", tokenAuth, async (c) => {
errorInfo.errorCode,
);
if (recordModel && cooldownSeconds > 0 && cooldownEligible) {
scheduleUsageEvent(c, {
scheduleUsageEvent({
type: "model_error",
payload: {
channelId: channel.id,
@@ -976,7 +976,7 @@ proxy.all("/*", tokenAuth, async (c) => {
cooldownSeconds > 0 &&
cooldownEligible
) {
scheduleUsageEvent(c, {
scheduleUsageEvent({
type: "model_error",
payload: {
channelId: channel.id,
@@ -1012,7 +1012,7 @@ proxy.all("/*", tokenAuth, async (c) => {
isTimeout ? "timeout" : "exception",
);
if (recordModel && cooldownSeconds > 0 && cooldownEligible) {
scheduleUsageEvent(c, {
scheduleUsageEvent({
type: "model_error",
payload: {
channelId: channel.id,
@@ -1028,7 +1028,7 @@ proxy.all("/*", tokenAuth, async (c) => {
cooldownSeconds > 0 &&
cooldownEligible
) {
scheduleUsageEvent(c, {
scheduleUsageEvent({
type: "model_error",
payload: {
channelId: channel.id,
@@ -1060,7 +1060,7 @@ proxy.all("/*", tokenAuth, async (c) => {
latency_ms: latencyMs,
last_channel_id: lastChannel?.id ?? null,
});
scheduleUsageEvent(c, {
scheduleUsageEvent({
type: "usage",
payload: {
tokenId: tokenRecord.id,
@@ -1113,7 +1113,7 @@ proxy.all("/*", tokenAuth, async (c) => {
? usageMissingReason ?? "usage_missing"
: null
: errorDetails?.errorMessage ?? null;
scheduleUsageEvent(c, {
scheduleUsageEvent({
type: "usage",
payload: {
tokenId: tokenRecord.id,
+1 -55
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env node
import { spawn } from "node:child_process";
import { access, readFile, writeFile } from "node:fs/promises";
import { access } from "node:fs/promises";
import { existsSync } from "node:fs";
import path from "node:path";
import { createInterface } from "node:readline/promises";
@@ -8,8 +8,6 @@ import { fileURLToPath } from "node:url";
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const DEFAULT_CONFIG = "apps/worker/wrangler.toml";
const DEFAULT_ENV_FILE = path.join(ROOT, ".env");
const DEFAULT_ENV_EXAMPLE = path.join(ROOT, ".env.example");
const BUN_CMD = (() => {
if (process.env.BUN_BIN && existsSync(process.env.BUN_BIN)) {
return process.env.BUN_BIN;
@@ -159,56 +157,6 @@ const runCapture = (command, args, options = {}) =>
const runBun = (args, options) => run(BUN_CMD, args, options);
const runBunx = (args, options) => run(BUN_CMD, ["x", ...args], options);
const parseEnvLine = (line) => {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) {
return null;
}
const idx = trimmed.indexOf("=");
if (idx === -1) {
return null;
}
const key = trimmed.slice(0, idx).trim();
let value = trimmed.slice(idx + 1).trim();
if (
(value.startsWith("\"") && value.endsWith("\"")) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
return { key, value };
};
const loadEnvFile = async (filePath) => {
if (!existsSync(filePath)) {
return [];
}
const raw = await readFile(filePath, "utf8");
const lines = raw.split(/\r?\n/);
for (const line of lines) {
const parsed = parseEnvLine(line);
if (!parsed) {
continue;
}
if (process.env[parsed.key] === undefined) {
process.env[parsed.key] = parsed.value;
}
}
return lines;
};
const ensureEnvFile = async (filePath, examplePath) => {
if (existsSync(filePath)) {
return;
}
if (existsSync(examplePath)) {
const template = await readFile(examplePath, "utf8");
await writeFile(filePath, template, "utf8");
return;
}
await writeFile(filePath, "# 本地部署环境变量(自动生成)\n", "utf8");
};
const promptChoice = async (rl, label, choices, defaultValue) => {
const choicesText = choices.join("/");
const defaultText = defaultValue ? ` [${defaultValue}]` : "";
@@ -340,8 +288,6 @@ const main = async () => {
const configPath = path.resolve(ROOT, options.config);
await access(configPath);
await ensureEnvFile(DEFAULT_ENV_FILE, DEFAULT_ENV_EXAMPLE);
await loadEnvFile(DEFAULT_ENV_FILE);
if (options.action === "init") {
options.target = "both";