Add log level configuration support (#272)
Add log level configuration support via config.yaml and UI settings. --------- Co-authored-by: Shantur Rathore <i@shantur.com>
This commit is contained in:
@@ -26,6 +26,7 @@ const PreferencesSchema = z
|
||||
showUsageMetrics: z.boolean().default(true),
|
||||
autoCleanupBlankSessions: z.boolean().default(true),
|
||||
listeningMode: z.enum(["local", "all"]).default("local"),
|
||||
logLevel: z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).default("DEBUG"),
|
||||
|
||||
// OS notifications
|
||||
osNotificationsEnabled: z.boolean().default(false),
|
||||
|
||||
@@ -107,6 +107,10 @@ function mapLegacyToOwnerDocs(legacyConfig: unknown, legacyState: unknown): { co
|
||||
if (typeof listeningMode === "string") {
|
||||
serverConfig.listeningMode = listeningMode
|
||||
}
|
||||
const logLevel = preferences.logLevel
|
||||
if (typeof logLevel === "string") {
|
||||
serverConfig.logLevel = logLevel
|
||||
}
|
||||
const lastUsedBinary = preferences.lastUsedBinary
|
||||
if (typeof lastUsedBinary === "string") {
|
||||
serverConfig.opencodeBinary = lastUsedBinary
|
||||
@@ -135,6 +139,7 @@ function mapLegacyToOwnerDocs(legacyConfig: unknown, legacyState: unknown): { co
|
||||
const moved = new Set([
|
||||
"environmentVariables",
|
||||
"listeningMode",
|
||||
"logLevel",
|
||||
"lastUsedBinary",
|
||||
"modelRecents",
|
||||
"modelFavorites",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Logger } from "../logger"
|
||||
import type { EventBus } from "../events/bus"
|
||||
import type { ConfigLocation } from "../config/location"
|
||||
import { z } from "zod"
|
||||
import { YamlDocStore, type SettingsDoc } from "./yaml-doc-store"
|
||||
import { migrateSettingsLayout } from "./migrate"
|
||||
import type { WorkspaceEventPayload } from "../api-types"
|
||||
@@ -8,6 +9,54 @@ import { sanitizeConfigOwner } from "./public-config"
|
||||
|
||||
export type DocKind = "config" | "state"
|
||||
|
||||
const CanonicalLogLevelSchema = z.preprocess(
|
||||
(value) => (typeof value === "string" ? value.trim().toUpperCase() : value),
|
||||
z.enum(["DEBUG", "INFO", "WARN", "ERROR"]),
|
||||
)
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function isDeepEqual(a: unknown, b: unknown): boolean {
|
||||
if (a === b) return true
|
||||
try {
|
||||
return JSON.stringify(a) === JSON.stringify(b)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeServerConfigOwner(value: SettingsDoc): SettingsDoc {
|
||||
if (!isPlainObject(value)) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const next: SettingsDoc = { ...value }
|
||||
const parsedLogLevel = CanonicalLogLevelSchema.safeParse(next.logLevel)
|
||||
if (parsedLogLevel.success) {
|
||||
next.logLevel = parsedLogLevel.data
|
||||
} else if (next.logLevel !== undefined) {
|
||||
next.logLevel = "DEBUG"
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
function normalizeConfigDoc(doc: SettingsDoc): SettingsDoc {
|
||||
if (!isPlainObject(doc)) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (!isPlainObject(doc.server)) {
|
||||
return doc
|
||||
}
|
||||
|
||||
return {
|
||||
...doc,
|
||||
server: normalizeServerConfigOwner(doc.server as SettingsDoc),
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingsService {
|
||||
private readonly configStore: YamlDocStore
|
||||
private readonly stateStore: YamlDocStore
|
||||
@@ -23,22 +72,44 @@ export class SettingsService {
|
||||
}
|
||||
|
||||
getDoc(kind: DocKind): SettingsDoc {
|
||||
return kind === "config" ? this.configStore.get() : this.stateStore.get()
|
||||
if (kind !== "config") {
|
||||
return this.stateStore.get()
|
||||
}
|
||||
|
||||
const current = this.configStore.get()
|
||||
const normalized = normalizeConfigDoc(current)
|
||||
if (!isDeepEqual(current, normalized)) {
|
||||
this.configStore.replace(normalized)
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
mergePatchDoc(kind: DocKind, patch: unknown): SettingsDoc {
|
||||
const updated = kind === "config" ? this.configStore.mergePatch(patch) : this.stateStore.mergePatch(patch)
|
||||
const updated =
|
||||
kind === "config"
|
||||
? this.configStore.replace(normalizeConfigDoc(this.configStore.mergePatch(patch)))
|
||||
: this.stateStore.mergePatch(patch)
|
||||
this.publish(kind, "*")
|
||||
return updated
|
||||
}
|
||||
|
||||
getOwner(kind: DocKind, owner: string): SettingsDoc {
|
||||
return kind === "config" ? this.configStore.getOwner(owner) : this.stateStore.getOwner(owner)
|
||||
if (kind !== "config") {
|
||||
return this.stateStore.getOwner(owner)
|
||||
}
|
||||
|
||||
return owner === "server"
|
||||
? normalizeServerConfigOwner(this.getDoc("config").server as SettingsDoc)
|
||||
: this.getDoc("config")[owner] as SettingsDoc
|
||||
}
|
||||
|
||||
mergePatchOwner(kind: DocKind, owner: string, patch: unknown): SettingsDoc {
|
||||
const updated =
|
||||
kind === "config" ? this.configStore.mergePatchOwner(owner, patch) : this.stateStore.mergePatchOwner(owner, patch)
|
||||
kind === "config"
|
||||
? owner === "server"
|
||||
? this.configStore.replaceOwner(owner, normalizeServerConfigOwner(this.configStore.mergePatchOwner(owner, patch)))
|
||||
: this.configStore.mergePatchOwner(owner, patch)
|
||||
: this.stateStore.mergePatchOwner(owner, patch)
|
||||
this.publish(kind, owner, updated)
|
||||
return updated
|
||||
}
|
||||
|
||||
@@ -142,12 +142,15 @@ export class WorkspaceManager {
|
||||
[OPENCODE_SERVER_PASSWORD_ENV]: opencodePassword,
|
||||
}
|
||||
|
||||
const logLevel = (serverConfig as any)?.logLevel
|
||||
|
||||
try {
|
||||
const { pid, port, exitPromise, getLastOutput } = await this.runtime.launch({
|
||||
workspaceId: id,
|
||||
folder: workspacePath,
|
||||
binaryPath: resolvedBinaryPath,
|
||||
environment,
|
||||
logLevel,
|
||||
onExit: (info) => this.handleProcessExit(info.workspaceId, info),
|
||||
})
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ interface LaunchOptions {
|
||||
folder: string
|
||||
binaryPath: string
|
||||
environment?: Record<string, string>
|
||||
logLevel?: string
|
||||
onExit?: (info: ProcessExitInfo) => void
|
||||
}
|
||||
|
||||
@@ -139,7 +140,8 @@ export class WorkspaceRuntime {
|
||||
async launch(options: LaunchOptions): Promise<{ pid: number; port: number; exitPromise: Promise<ProcessExitInfo>; getLastOutput: () => string }> {
|
||||
this.validateFolder(options.folder)
|
||||
|
||||
const args = ["serve", "--port", "0", "--print-logs", "--log-level", "DEBUG"]
|
||||
const logLevel = typeof options.logLevel === "string" ? options.logLevel.toUpperCase() : "DEBUG"
|
||||
const args = ["serve", "--port", "0", "--print-logs", "--log-level", logLevel]
|
||||
const env = { ...process.env, ...(options.environment ?? {}) }
|
||||
|
||||
let exitResolve: ((info: ProcessExitInfo) => void) | null = null
|
||||
|
||||
Reference in New Issue
Block a user