Skip to content

Commit a74cd40

Browse files
committed
Add logging module filters and cleanup logs
Expose module-filter management in the API and UI, and reduce noisy internal logs to debug-level output.
1 parent 6f83590 commit a74cd40

67 files changed

Lines changed: 810 additions & 397 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
type: object
2+
properties:
3+
modules:
4+
type: array
5+
description: >
6+
List of module names to include in console output. An empty array
7+
means all modules are shown.
8+
items:
9+
type: string
10+
example:
11+
- dispatcher
12+
- model-metadata-manager

docs/openapi/openapi.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,8 @@ paths:
449449
$ref: paths/v0_management_metrics.yaml
450450
/v0/management/logging/level:
451451
$ref: paths/v0_management_logging_level.yaml
452+
/v0/management/logging/modules:
453+
$ref: paths/v0_management_logging_modules.yaml
452454
/v0/management/oauth/providers:
453455
$ref: paths/v0_management_oauth_providers.yaml
454456
/v0/management/oauth/sessions:
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
get:
2+
tags:
3+
- Management — Logging
4+
summary: Get module filter (admin only)
5+
description: |
6+
Returns the current module filter — the set of module names whose
7+
log entries are printed to the console.
8+
9+
When the list is empty (default), no filtering is applied and all
10+
modules' logs are shown on the console.
11+
12+
## Scope
13+
14+
This filter only affects the **console** transport. The streaming
15+
transport (used by the System Logs UI) and the StreamTransport used
16+
for log streaming always receive all entries regardless of this
17+
setting.
18+
19+
The filter can also be set at startup via the `LOG_MODULES` env var
20+
(comma-separated list of module names, e.g.
21+
`LOG_MODULES=dispatcher,model-metadata-manager`).
22+
23+
**Admin only** — limited principals receive 403.
24+
security:
25+
- AdminKey: []
26+
responses:
27+
'200':
28+
description: Current module filter.
29+
content:
30+
application/json:
31+
schema:
32+
$ref: ../components/schemas/ModuleFilterState.yaml
33+
'401':
34+
description: Authentication required or invalid credentials.
35+
operationId: getV0ManagementLoggingModules
36+
put:
37+
tags:
38+
- Management — Logging
39+
summary: Set module filter (admin only)
40+
description: |
41+
Replaces the current module filter with the provided list. Only
42+
logs from the listed modules will appear in console output.
43+
44+
Set to an empty array to show all modules (disable filtering).
45+
46+
**Admin only** — limited principals receive 403.
47+
security:
48+
- AdminKey: []
49+
requestBody:
50+
required: true
51+
content:
52+
application/json:
53+
schema:
54+
type: object
55+
properties:
56+
modules:
57+
type: array
58+
items:
59+
type: string
60+
description: >
61+
Module names to include. Empty array = show all modules.
62+
example:
63+
- dispatcher
64+
- model-metadata-manager
65+
required:
66+
- modules
67+
responses:
68+
'200':
69+
description: Updated module filter.
70+
content:
71+
application/json:
72+
schema:
73+
$ref: ../components/schemas/ModuleFilterState.yaml
74+
'400':
75+
description: Invalid body.
76+
content:
77+
application/json:
78+
schema:
79+
type: object
80+
properties:
81+
error:
82+
type: string
83+
'401':
84+
description: Authentication required or invalid credentials.
85+
operationId: putV0ManagementLoggingModules
86+
delete:
87+
tags:
88+
- Management — Logging
89+
summary: Clear module filter (admin only)
90+
description: |
91+
Clears the module filter so that all modules' logs appear in console
92+
output again.
93+
94+
**Admin only** — limited principals receive 403.
95+
security:
96+
- AdminKey: []
97+
responses:
98+
'200':
99+
description: Filter cleared (empty list).
100+
content:
101+
application/json:
102+
schema:
103+
$ref: ../components/schemas/ModuleFilterState.yaml
104+
'401':
105+
description: Authentication required or invalid credentials.
106+
operationId: deleteV0ManagementLoggingModules

packages/backend/src/cli/migrate-quota-snapshots.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ async function main() {
5151
const { inserted, skipped, totalSource } = await migrateLegacySnapshots();
5252

5353
if (totalSource === 0) {
54-
logger.info('Nothing to migrate.');
54+
logger.debug('Nothing to migrate.');
5555
} else {
56-
logger.info(`Migration complete. Inserted: ${inserted}, Skipped: ${skipped}`);
56+
logger.debug(`Migration complete. Inserted: ${inserted}, Skipped: ${skipped}`);
5757
}
5858
}
5959

packages/backend/src/cli/rekey.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ async function main() {
125125
.where(eq(schema.apiKeys.id, row.id));
126126
totalReKeyed++;
127127
}
128-
logger.info(`Re-keyed ${totalReKeyed} API key(s)`);
128+
logger.warn(`Re-keyed ${totalReKeyed} API key(s)`);
129129

130130
// ─── OAuth Credentials ──────────────────────────────────────────
131131
let oauthCount = 0;
@@ -146,7 +146,7 @@ async function main() {
146146
oauthCount++;
147147
}
148148
}
149-
logger.info(`Re-keyed ${oauthCount} OAuth credential(s)`);
149+
logger.warn(`Re-keyed ${oauthCount} OAuth credential(s)`);
150150

151151
// ─── Providers ──────────────────────────────────────────────────
152152
let providerCount = 0;
@@ -171,7 +171,7 @@ async function main() {
171171
providerCount++;
172172
}
173173
}
174-
logger.info(`Re-keyed ${providerCount} provider(s)`);
174+
logger.warn(`Re-keyed ${providerCount} provider(s)`);
175175

176176
// ─── MCP Servers ────────────────────────────────────────────────
177177
let mcpCount = 0;
@@ -188,9 +188,9 @@ async function main() {
188188
}
189189
}
190190
}
191-
logger.info(`Re-keyed ${mcpCount} MCP server(s)`);
191+
logger.warn(`Re-keyed ${mcpCount} MCP server(s)`);
192192

193-
logger.info(
193+
logger.warn(
194194
`Re-key complete. Total: ${totalReKeyed + oauthCount + providerCount + mcpCount} record(s). ` +
195195
'Update your ENCRYPTION_KEY env var to the new key before restarting.'
196196
);

packages/backend/src/db/encrypt-migration.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export async function runEncryptionMigration(): Promise<void> {
2424
return;
2525
}
2626

27-
logger.info('Starting encryption migration for existing plaintext data...');
27+
logger.debug('Starting encryption migration for existing plaintext data...');
2828

2929
const db = getDatabase();
3030
const schema = getSchema();
@@ -45,7 +45,7 @@ export async function runEncryptionMigration(): Promise<void> {
4545
migratedCount++;
4646
}
4747
}
48-
logger.info(`Encrypted ${migratedCount} API key(s)`);
48+
logger.debug(`Encrypted ${migratedCount} API key(s)`);
4949
} catch (error) {
5050
logger.error('Failed to encrypt API keys:', error);
5151
throw error;
@@ -71,7 +71,7 @@ export async function runEncryptionMigration(): Promise<void> {
7171
oauthCount++;
7272
}
7373
}
74-
logger.info(`Encrypted ${oauthCount} OAuth credential(s)`);
74+
logger.debug(`Encrypted ${oauthCount} OAuth credential(s)`);
7575
} catch (error) {
7676
logger.error('Failed to encrypt OAuth credentials:', error);
7777
throw error;
@@ -108,7 +108,7 @@ export async function runEncryptionMigration(): Promise<void> {
108108
providerCount++;
109109
}
110110
}
111-
logger.info(`Encrypted ${providerCount} provider(s)`);
111+
logger.debug(`Encrypted ${providerCount} provider(s)`);
112112
} catch (error) {
113113
logger.error('Failed to encrypt providers:', error);
114114
throw error;
@@ -130,11 +130,11 @@ export async function runEncryptionMigration(): Promise<void> {
130130
}
131131
}
132132
}
133-
logger.info(`Encrypted ${mcpCount} MCP server(s)`);
133+
logger.debug(`Encrypted ${mcpCount} MCP server(s)`);
134134
} catch (error) {
135135
logger.error('Failed to encrypt MCP servers:', error);
136136
throw error;
137137
}
138138

139-
logger.info('Encryption migration completed successfully');
139+
logger.debug('Encryption migration completed successfully');
140140
}

packages/backend/src/db/migrate.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ async function buildMigrations(journal: Journal, devDir: string): Promise<Migrat
6060
);
6161

6262
const sources = new Set(results.map((r) => r.meta.source));
63-
logger.info(
63+
logger.debug(
6464
`Loaded ${results.length} migrations from ${sources.size === 1 ? [...sources][0] : 'mixed'} source`
6565
);
6666

@@ -246,7 +246,7 @@ export async function runMigrations() {
246246
const db = getDatabase();
247247
const dialect = getCurrentDialect();
248248

249-
logger.info(`Running ${dialect} migrations...`);
249+
logger.debug(`Running ${dialect} migrations...`);
250250

251251
if (dialect === 'sqlite') {
252252
// In dev/source mode, re-read the journal from disk so that migrations
@@ -325,7 +325,7 @@ export async function runMigrations() {
325325
}
326326
}
327327

328-
logger.info('Migrations completed successfully');
328+
logger.debug('Migrations completed successfully');
329329
} catch (error: any) {
330330
logger.error('Migration failed', error);
331331
throw error;

packages/backend/src/index.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ if (!process.env.DATABASE_URL) {
166166
}
167167

168168
// Log startup configuration
169-
logger.info(`DATABASE_URL: ${process.env.DATABASE_URL}`);
170-
logger.info(`PORT: ${process.env.PORT || '4000'}`);
169+
logger.debug(`DATABASE_URL: ${process.env.DATABASE_URL}`);
170+
logger.debug(`PORT: ${process.env.PORT || '4000'}`);
171171

172172
const fastify = Fastify({
173173
logger: false, // We use a custom winston-based logger
@@ -207,7 +207,7 @@ SelectorFactory.setUsageStorage(usageStorage);
207207
// Enable debug mode if DEBUG=true environment variable is set
208208
if (process.env.DEBUG === 'true') {
209209
DebugManager.getInstance().setEnabled(true);
210-
logger.info('Debug mode auto-enabled via DEBUG=true environment variable');
210+
logger.warn('Debug mode auto-enabled via DEBUG=true environment variable');
211211
}
212212

213213
// --- Database Initialization ---
@@ -233,7 +233,7 @@ try {
233233
const configService = ConfigService.getInstance();
234234

235235
if (await configService.isFirstLaunch()) {
236-
logger.info('First launch detected — checking for existing config files to import');
236+
logger.debug('First launch detected — checking for existing config files to import');
237237

238238
// Import from plexus.yaml if it exists
239239
// Try CONFIG_FILE env var first, then check common locations
@@ -251,23 +251,23 @@ try {
251251
if (configPath && fs.existsSync(configPath)) {
252252
const yamlContent = fs.readFileSync(configPath, 'utf-8');
253253
await configService.importFromYaml(yamlContent);
254-
logger.info(`Imported configuration from ${configPath} into database`);
254+
logger.debug(`Imported configuration from ${configPath} into database`);
255255
} else {
256-
logger.info('No plexus.yaml found — starting with empty configuration');
256+
logger.debug('No plexus.yaml found — starting with empty configuration');
257257
}
258258

259259
// Import from auth.json if it exists
260260
const authJsonPath = process.env.AUTH_JSON || './auth.json';
261261
if (fs.existsSync(authJsonPath)) {
262262
const authContent = fs.readFileSync(authJsonPath, 'utf-8');
263263
await configService.importFromAuthJson(authContent);
264-
logger.info(`Imported OAuth credentials from ${authJsonPath} into database`);
264+
logger.debug(`Imported OAuth credentials from ${authJsonPath} into database`);
265265
}
266266

267267
// Mark bootstrap as complete so a future restart (even with an empty
268268
// providers table) does not re-import from the YAML file.
269269
await configService.getRepository().markBootstrapped();
270-
logger.info('Bootstrap complete — marked database as bootstrapped');
270+
logger.debug('Bootstrap complete — marked database as bootstrapped');
271271
} catch (importError) {
272272
logger.error(
273273
'Failed to import config — clearing partial data for clean retry on next launch',
@@ -279,7 +279,7 @@ try {
279279
}
280280

281281
await configService.initialize();
282-
logger.info('Configuration loaded from database');
282+
logger.debug('Configuration loaded from database');
283283

284284
// Eagerly initialize OAuth auth manager so auth.json schema migration
285285
// runs during startup (instead of waiting for first OAuth request).
@@ -322,7 +322,7 @@ try {
322322
let quotaEnforcer: QuotaEnforcer | undefined;
323323
try {
324324
quotaEnforcer = new QuotaEnforcer();
325-
logger.info('User quota enforcer initialized');
325+
logger.debug('User quota enforcer initialized');
326326
} catch (e) {
327327
logger.error('Failed to initialize user quota enforcer', e);
328328
}
@@ -438,7 +438,7 @@ const embeddedByName = new Map<string, EmbeddedFile>(
438438
(Bun.embeddedFiles as EmbeddedFile[]).map((f) => [f.name, f])
439439
);
440440

441-
logger.info(`Serving frontend from: ${frontendDistDir}`);
441+
logger.debug(`Serving frontend from: ${frontendDistDir}`);
442442

443443
const serveAsset = async (reply: FastifyReply, filePath: string, ext: string) => {
444444
const mimeType = mimeTypes[ext] ?? 'application/octet-stream';

packages/backend/src/middleware/log.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ export const requestLogger = async (request: FastifyRequest, reply: FastifyReply
77
if (method === 'GET' || method === 'POST') {
88
logger.debug(`${method} ${path}`);
99
} else {
10-
logger.info(`${method} ${path}`);
10+
logger.debug(`${method} ${path}`);
1111
}
1212
};

packages/backend/src/routes/management/_principal.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export async function resolvePrincipal(request: FastifyRequest): Promise<Princip
112112
comment: cfg.comment ?? null,
113113
};
114114
} catch (err) {
115-
logger.silly(`[AUTH] api_keys lookup failed: ${(err as Error).message}`);
115+
logger.silly(`api_keys lookup failed: ${(err as Error).message}`);
116116
return null;
117117
}
118118
}
@@ -124,7 +124,7 @@ export async function resolvePrincipal(request: FastifyRequest): Promise<Princip
124124
export async function authenticate(request: FastifyRequest, _reply: FastifyReply): Promise<void> {
125125
const principal = await resolvePrincipal(request);
126126
if (!principal) {
127-
logger.silly(`[ADMIN AUTH] Rejected request to ${request.url} - invalid or missing credential`);
127+
logger.silly(`Rejected request to ${request.url} - invalid or missing credential`);
128128
throw new ManagementAuthError(401, 'Unauthorized', 'auth_error');
129129
}
130130
request.principal = principal;

0 commit comments

Comments
 (0)