mirror of
https://github.com/decolua/9router.git
synced 2026-05-08 12:01:28 +00:00
feat(observability): add toggle for request detail recording (#122)
* feat(frontend): add toggle logic for observability configuration (cherry picked from commit 71cef26df6160290c980710ff4a0d6e7aa926105) * feat(backend): add toggle logic for observability configuration (cherry picked from commit fb1a8d90e24f041c41b3039f7189791458b87f87) --------- Co-authored-by: zx <me@char.moe>
This commit is contained in:
@@ -14,6 +14,7 @@ NODE_ENV=production
|
|||||||
API_KEY_SECRET=endpoint-proxy-api-key-secret
|
API_KEY_SECRET=endpoint-proxy-api-key-secret
|
||||||
MACHINE_ID_SALT=endpoint-proxy-salt
|
MACHINE_ID_SALT=endpoint-proxy-salt
|
||||||
ENABLE_REQUEST_LOGS=false
|
ENABLE_REQUEST_LOGS=false
|
||||||
|
OBSERVABILITY_ENABLED=true
|
||||||
AUTH_COOKIE_SECURE=false
|
AUTH_COOKIE_SECURE=false
|
||||||
REQUIRE_API_KEY=false
|
REQUIRE_API_KEY=false
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,23 @@ export default function ProfilePage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateObservabilityEnabled = async (enabled) => {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/settings", {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ observabilityEnabled: enabled }),
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
setSettings(prev => ({ ...prev, observabilityEnabled: enabled }));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to update observabilityEnabled:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const observabilityEnabled = settings.observabilityEnabled !== false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-2xl mx-auto">
|
<div className="max-w-2xl mx-auto">
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
@@ -358,6 +375,21 @@ export default function ProfilePage() {
|
|||||||
<h3 className="text-lg font-semibold">Observability</h3>
|
<h3 className="text-lg font-semibold">Observability</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">Enable Observability</p>
|
||||||
|
<p className="text-sm text-text-muted">
|
||||||
|
Turn request detail recording on/off globally
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Toggle
|
||||||
|
checked={observabilityEnabled}
|
||||||
|
onChange={updateObservabilityEnabled}
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cn("flex flex-col gap-4", !observabilityEnabled && "opacity-60")}>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">Max Records</p>
|
<p className="font-medium">Max Records</p>
|
||||||
@@ -372,7 +404,7 @@ export default function ProfilePage() {
|
|||||||
step="100"
|
step="100"
|
||||||
value={settings.observabilityMaxRecords || 1000}
|
value={settings.observabilityMaxRecords || 1000}
|
||||||
onChange={(e) => updateObservabilitySetting("observabilityMaxRecords", parseInt(e.target.value))}
|
onChange={(e) => updateObservabilitySetting("observabilityMaxRecords", parseInt(e.target.value))}
|
||||||
disabled={loading}
|
disabled={loading || !observabilityEnabled}
|
||||||
className="w-28 text-center"
|
className="w-28 text-center"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -391,7 +423,7 @@ export default function ProfilePage() {
|
|||||||
step="5"
|
step="5"
|
||||||
value={settings.observabilityBatchSize || 20}
|
value={settings.observabilityBatchSize || 20}
|
||||||
onChange={(e) => updateObservabilitySetting("observabilityBatchSize", parseInt(e.target.value))}
|
onChange={(e) => updateObservabilitySetting("observabilityBatchSize", parseInt(e.target.value))}
|
||||||
disabled={loading}
|
disabled={loading || !observabilityEnabled}
|
||||||
className="w-28 text-center"
|
className="w-28 text-center"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -410,7 +442,7 @@ export default function ProfilePage() {
|
|||||||
step="1000"
|
step="1000"
|
||||||
value={settings.observabilityFlushIntervalMs || 5000}
|
value={settings.observabilityFlushIntervalMs || 5000}
|
||||||
onChange={(e) => updateObservabilitySetting("observabilityFlushIntervalMs", parseInt(e.target.value))}
|
onChange={(e) => updateObservabilitySetting("observabilityFlushIntervalMs", parseInt(e.target.value))}
|
||||||
disabled={loading}
|
disabled={loading || !observabilityEnabled}
|
||||||
className="w-28 text-center"
|
className="w-28 text-center"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -429,7 +461,7 @@ export default function ProfilePage() {
|
|||||||
step="100"
|
step="100"
|
||||||
value={settings.observabilityMaxJsonSize || 1024}
|
value={settings.observabilityMaxJsonSize || 1024}
|
||||||
onChange={(e) => updateObservabilitySetting("observabilityMaxJsonSize", parseInt(e.target.value))}
|
onChange={(e) => updateObservabilitySetting("observabilityMaxJsonSize", parseInt(e.target.value))}
|
||||||
disabled={loading}
|
disabled={loading || !observabilityEnabled}
|
||||||
className="w-28 text-center"
|
className="w-28 text-center"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -437,6 +469,7 @@ export default function ProfilePage() {
|
|||||||
<p className="text-xs text-text-muted italic pt-2 border-t border-border/50">
|
<p className="text-xs text-text-muted italic pt-2 border-t border-border/50">
|
||||||
Current: Keeps {settings.observabilityMaxRecords || 1000} records, batches every {settings.observabilityBatchSize || 20} requests, max {settings.observabilityMaxJsonSize || 1024}KB per field
|
Current: Keeps {settings.observabilityMaxRecords || 1000} records, batches every {settings.observabilityBatchSize || 20} requests, max {settings.observabilityMaxJsonSize || 1024}KB per field
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ const defaultData = {
|
|||||||
cloudEnabled: false,
|
cloudEnabled: false,
|
||||||
stickyRoundRobinLimit: 3,
|
stickyRoundRobinLimit: 3,
|
||||||
requireLogin: true,
|
requireLogin: true,
|
||||||
|
observabilityEnabled: true,
|
||||||
observabilityMaxRecords: 1000,
|
observabilityMaxRecords: 1000,
|
||||||
observabilityBatchSize: 20,
|
observabilityBatchSize: 20,
|
||||||
observabilityFlushIntervalMs: 5000,
|
observabilityFlushIntervalMs: 5000,
|
||||||
@@ -71,6 +72,7 @@ function cloneDefaultData() {
|
|||||||
cloudEnabled: false,
|
cloudEnabled: false,
|
||||||
stickyRoundRobinLimit: 3,
|
stickyRoundRobinLimit: 3,
|
||||||
requireLogin: true,
|
requireLogin: true,
|
||||||
|
observabilityEnabled: true,
|
||||||
observabilityMaxRecords: 1000,
|
observabilityMaxRecords: 1000,
|
||||||
observabilityBatchSize: 20,
|
observabilityBatchSize: 20,
|
||||||
observabilityFlushIntervalMs: 5000,
|
observabilityFlushIntervalMs: 5000,
|
||||||
|
|||||||
@@ -17,8 +17,13 @@ async function getObservabilityConfig() {
|
|||||||
try {
|
try {
|
||||||
const { getSettings } = await import("@/lib/localDb");
|
const { getSettings } = await import("@/lib/localDb");
|
||||||
const settings = await getSettings();
|
const settings = await getSettings();
|
||||||
|
const envEnabled = process.env.OBSERVABILITY_ENABLED !== "false";
|
||||||
|
const enabled = typeof settings.observabilityEnabled === "boolean"
|
||||||
|
? settings.observabilityEnabled
|
||||||
|
: envEnabled;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
enabled,
|
||||||
maxRecords: settings.observabilityMaxRecords || parseInt(process.env.OBSERVABILITY_MAX_RECORDS || '1000', 10),
|
maxRecords: settings.observabilityMaxRecords || parseInt(process.env.OBSERVABILITY_MAX_RECORDS || '1000', 10),
|
||||||
batchSize: settings.observabilityBatchSize || parseInt(process.env.OBSERVABILITY_BATCH_SIZE || '20', 10),
|
batchSize: settings.observabilityBatchSize || parseInt(process.env.OBSERVABILITY_BATCH_SIZE || '20', 10),
|
||||||
flushIntervalMs: settings.observabilityFlushIntervalMs || parseInt(process.env.OBSERVABILITY_FLUSH_INTERVAL_MS || '5000', 10),
|
flushIntervalMs: settings.observabilityFlushIntervalMs || parseInt(process.env.OBSERVABILITY_FLUSH_INTERVAL_MS || '5000', 10),
|
||||||
@@ -27,6 +32,7 @@ async function getObservabilityConfig() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[requestDetailsDb] Failed to load observability config:", error);
|
console.error("[requestDetailsDb] Failed to load observability config:", error);
|
||||||
return {
|
return {
|
||||||
|
enabled: true,
|
||||||
maxRecords: 1000,
|
maxRecords: 1000,
|
||||||
batchSize: 20,
|
batchSize: 20,
|
||||||
flushIntervalMs: 5000,
|
flushIntervalMs: 5000,
|
||||||
@@ -37,6 +43,17 @@ async function getObservabilityConfig() {
|
|||||||
|
|
||||||
// Cache config to avoid repeated database reads
|
// Cache config to avoid repeated database reads
|
||||||
let cachedConfig = null;
|
let cachedConfig = null;
|
||||||
|
let cachedConfigTs = 0;
|
||||||
|
const CONFIG_CACHE_TTL_MS = 5000;
|
||||||
|
|
||||||
|
async function getCachedObservabilityConfig() {
|
||||||
|
if (!cachedConfig || (Date.now() - cachedConfigTs) > CONFIG_CACHE_TTL_MS) {
|
||||||
|
cachedConfig = await getObservabilityConfig();
|
||||||
|
cachedConfigTs = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cachedConfig;
|
||||||
|
}
|
||||||
|
|
||||||
let dbInstance = null;
|
let dbInstance = null;
|
||||||
|
|
||||||
@@ -317,13 +334,14 @@ function sanitizeHeaders(headers) {
|
|||||||
export async function saveRequestDetail(detail) {
|
export async function saveRequestDetail(detail) {
|
||||||
if (isCloud) return;
|
if (isCloud) return;
|
||||||
|
|
||||||
if (!cachedConfig) {
|
const config = await getCachedObservabilityConfig();
|
||||||
cachedConfig = await getObservabilityConfig();
|
if (!config.enabled) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeBuffer.push(detail);
|
writeBuffer.push(detail);
|
||||||
|
|
||||||
if (writeBuffer.length >= cachedConfig.batchSize) {
|
if (writeBuffer.length >= config.batchSize) {
|
||||||
await flushToDatabase();
|
await flushToDatabase();
|
||||||
|
|
||||||
if (flushTimer) {
|
if (flushTimer) {
|
||||||
@@ -334,7 +352,7 @@ export async function saveRequestDetail(detail) {
|
|||||||
flushTimer = setTimeout(() => {
|
flushTimer = setTimeout(() => {
|
||||||
flushToDatabase().catch(() => {});
|
flushToDatabase().catch(() => {});
|
||||||
flushTimer = null;
|
flushTimer = null;
|
||||||
}, cachedConfig.flushIntervalMs);
|
}, config.flushIntervalMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user