chore: migrate host logging to shared Logger service (#8820)

* chore: migrate host logging to shared Logger service

- Replace HostProvider.logToChannel usage with Logger.log/error
  in controller, webview, and checkpoint migration code
- Remove redundant, low-value log statements from Cline API
  methods to reduce noise
- Centralize logging through shared Logger service for more
  consistent, structured logging and easier maintenance
- Remove redundant , low-value log statements from StateManager where
  we logged error that would be throw and get logged again

* Update src/integrations/checkpoints/CheckpointMigration.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix

* update tests

* update tests

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Bee
2026-01-23 15:27:11 -08:00
committed by GitHub
co-authored by Copilot
parent 4d6f908fbd
commit 3ff63562c8
13 changed files with 37 additions and 85 deletions
@@ -25,7 +25,7 @@ async function handleInstallWithCline(
await controller.postStateToWebview()
await controller.initTask(installTask)
HostProvider.get().logToChannel(`Started task to install ${dependencyName}`)
Logger.log(`[handleInstallWithCline] Started task to install ${dependencyName}`)
}
/**
+2 -1
View File
@@ -119,7 +119,6 @@ export class Controller {
constructor(readonly context: vscode.ExtensionContext) {
PromptRegistry.getInstance() // Ensure prompts and tools are registered
HostProvider.get().logToChannel("ClineProvider instantiated")
this.stateManager = StateManager.get()
StateManager.get().registerCallbacks({
onPersistenceError: async ({ error }: PersistenceErrorEvent) => {
@@ -154,6 +153,8 @@ export class Controller {
// Check CLI installation status once on startup
checkCliInstallation(this)
Logger.log("[Controller] ClineProvider instantiated")
}
/*
@@ -120,7 +120,7 @@ export async function refreshVercelAiGatewayModels(_controller: Controller): Pro
}
await fs.writeFile(vercelAiGatewayModelsFilePath, JSON.stringify(models))
Logger.log("Vercel AI Gateway models fetched and saved", JSON.stringify(models).slice(0, 300))
Logger.log("Vercel AI Gateway models fetched and saved")
} else {
Logger.error("Invalid response from Vercel AI Gateway API")
}
@@ -1,5 +1,4 @@
import { Boolean } from "@shared/proto/cline/common"
import { Logger } from "@/shared/services/Logger"
import { isClineCliInstalled } from "@/utils/cli-detector"
import { Controller } from ".."
@@ -12,8 +11,7 @@ export async function checkCliInstallation(_controller: Controller): Promise<Boo
try {
const isInstalled = await isClineCliInstalled()
return Boolean.create({ value: isInstalled })
} catch (error) {
Logger.error("Failed to check CLI installation:", error)
} catch {
return Boolean.create({ value: false })
}
}
+1 -12
View File
@@ -17,8 +17,6 @@ import {
} from "@shared/storage/state-keys"
import chokidar, { FSWatcher } from "chokidar"
import type { ExtensionContext } from "vscode"
import { HostProvider } from "@/hosts/host-provider"
import { ShowMessageType } from "@/shared/proto/index.host"
import { Logger } from "@/shared/services/Logger"
import {
getTaskHistoryStateFilePath,
@@ -267,12 +265,7 @@ export class StateManager {
Object.assign(this.taskStateCache, taskSettings)
} catch (error) {
// If reading fails, just use empty cache
Logger.error("[StateManager] Failed to load task settings:", error)
HostProvider.window.showMessage({
type: ShowMessageType.ERROR,
message: `Failed to load task settings, defaulting to globally selected settings.`,
})
Logger.error("[StateManager] Failed to load task settings, defaulting to globally selected settings.", error)
}
}
@@ -735,7 +728,6 @@ export class StateManager {
}),
)
} catch (error) {
Logger.error("[StateManager] Failed to persist global state batch:", error)
throw error
}
}
@@ -765,7 +757,6 @@ export class StateManager {
}),
)
} catch (error) {
Logger.error("[StateManager] Failed to persist task settings batch:", error)
throw error
}
}
@@ -786,7 +777,6 @@ export class StateManager {
}),
)
} catch (error) {
Logger.error("Failed to persist secrets batch:", error)
throw error
}
}
@@ -803,7 +793,6 @@ export class StateManager {
}),
)
} catch (error) {
Logger.error("Failed to persist workspace state batch:", error)
throw error
}
}
+4 -13
View File
@@ -1,48 +1,39 @@
import { Controller } from "@core/controller"
import { sendChatButtonClickedEvent } from "@core/controller/ui/subscribeToChatButtonClicked"
import { HostProvider } from "@/hosts/host-provider"
import { Logger } from "@/shared/services/Logger"
import { ClineAPI } from "./cline"
export function createClineAPI(sidebarController: Controller): ClineAPI {
const api: ClineAPI = {
startNewTask: async (task?: string, images?: string[]) => {
HostProvider.get().logToChannel("Starting new task")
await sidebarController.clearTask()
await sidebarController.postStateToWebview()
await sendChatButtonClickedEvent()
await sidebarController.initTask(task, images)
HostProvider.get().logToChannel(
`Task started with message: ${task ? `"${task}"` : "undefined"} and ${images?.length || 0} image(s)`,
)
},
sendMessage: async (message?: string, images?: string[]) => {
HostProvider.get().logToChannel(
`Sending message: ${message ? `"${message}"` : "undefined"} with ${images?.length || 0} image(s)`,
)
if (sidebarController.task) {
await sidebarController.task.handleWebviewAskResponse("messageResponse", message || "", images || [])
} else {
HostProvider.get().logToChannel("No active task to send message to")
Logger.error("No active task to send message to")
}
},
pressPrimaryButton: async () => {
HostProvider.get().logToChannel("Pressing primary button")
if (sidebarController.task) {
await sidebarController.task.handleWebviewAskResponse("yesButtonClicked", "", [])
} else {
HostProvider.get().logToChannel("No active task to press button for")
Logger.error("No active task to press button for")
}
},
pressSecondaryButton: async () => {
HostProvider.get().logToChannel("Pressing secondary button")
if (sidebarController.task) {
await sidebarController.task.handleWebviewAskResponse("noButtonClicked", "", [])
} else {
HostProvider.get().logToChannel("No active task to press button for")
Logger.error("No active task to press button for")
}
},
}
+1 -1
View File
@@ -113,7 +113,7 @@ export class VscodeWebviewProvider extends WebviewProvider implements vscode.Web
// if the extension is starting a new session, clear previous task state
this.controller.clearTask()
HostProvider.get().logToChannel("Webview view resolved")
Logger.log("[VscodeWebviewProvider] Webview view resolved")
// Title setting logic removed to allow VSCode to use the container title primarily.
}
@@ -12,8 +12,6 @@ import { Logger } from "@/shared/services/Logger"
*/
export async function cleanupLegacyCheckpoints(): Promise<void> {
try {
HostProvider.get().logToChannel("Checking for legacy checkpoints...")
const tasksDir = path.join(HostProvider.get().globalStorageFsPath, "tasks")
// Check if tasks directory exists
@@ -45,29 +43,27 @@ export async function cleanupLegacyCheckpoints(): Promise<void> {
const checkpointsDir = path.join(mostRecentFolder.path, "checkpoints")
if (await fileExistsAtPath(checkpointsDir)) {
HostProvider.get().logToChannel("Found legacy checkpoints directory, cleaning up...")
const results = { deleted: [] as string[], failed: [] as string[] }
// Legacy checkpoints found, delete checkpoints directories in all task folders
for (const folder of folderStats) {
const folderCheckpointsDir = path.join(folder.path, "checkpoints")
if (await fileExistsAtPath(folderCheckpointsDir)) {
HostProvider.get().logToChannel(`Deleting legacy checkpoints in ${folder.folder}`)
try {
await fs.rm(folderCheckpointsDir, { recursive: true, force: true })
results.deleted.push(folder.folder)
} catch (_error) {
// Ignore error if directory removal fails
HostProvider.get().logToChannel(
`Warning: Failed to delete checkpoints in ${folder.folder}, continuing...`,
)
results.failed.push(folder.folder)
}
}
}
HostProvider.get().logToChannel("Legacy checkpoints cleanup completed")
Logger.info(
`Legacy checkpoints cleanup completed. Deleted: ${results.deleted.length}, Failed: ${results.failed.length}`,
)
}
}
} catch (error) {
HostProvider.get().logToChannel(`Error cleaning up legacy checkpoints: ${error}`)
Logger.error("Error cleaning up legacy checkpoints:", error)
throw new Error("Error cleaning up legacy checkpoints.", { cause: error })
}
}
+4 -4
View File
@@ -32,7 +32,7 @@ export async function initializeDistinctId(context: ExtensionContext, uuid: () =
setDistinctId(distinctId)
Logger.log("Telemetry distinct ID initialized:", distinctId)
Logger.log("[DistinctId] initialized:", distinctId)
}
/*
@@ -46,7 +46,7 @@ async function getMachineId(): Promise<string | undefined> {
const id = await machineId()
return id
} catch (error) {
Logger.log("Failed to get machine ID from node-machine-id", error)
Logger.log("[DistinctId] Failed to get machine ID from node-machine-id", error)
return undefined
}
}
@@ -57,7 +57,7 @@ async function getMachineId(): Promise<string | undefined> {
*/
export function setDistinctId(newId: string) {
if (_distinctId && _distinctId !== newId) {
Logger.log(`Changing telemetry ID from ${_distinctId} to ${newId}.`)
Logger.log("[DistinctId] Updating...", `From ${_distinctId} to ${newId}`)
}
_distinctId = newId
}
@@ -69,7 +69,7 @@ export function setDistinctId(newId: string) {
*/
export function getDistinctId() {
if (!_distinctId) {
Logger.error("Telemetry ID is not initialized. Call initializeDistinctId() first.")
Logger.debug("[DistinctId] Not initialized. Call initializeDistinctId() first.")
}
return _distinctId
}
+3 -5
View File
@@ -78,7 +78,7 @@ export class ClineBlobStorage extends ClineStorage {
this.adapter = adapter
this.settings = settings
this.initialized = true
Logger.log("[ClineBlobStorage] Adapter created")
Logger.log(`[ClineBlobStorage] Adapter created for ${settings.adapterType}`)
}
} catch (error) {
// Log but don't throw - allow startup to continue
@@ -114,8 +114,8 @@ export class ClineBlobStorage extends ClineStorage {
}
try {
return await this.adapter!.read(key)
} catch (error) {
Logger.error(`[ClineBlobStorage] failed to get '${key}':`, error)
} catch {
// Silently return undefined on read errors
return undefined
}
}
@@ -128,7 +128,6 @@ export class ClineBlobStorage extends ClineStorage {
try {
await this.adapter!.write(key, value)
} catch (error) {
Logger.error(`[ClineBlobStorage] failed to store '${key}':`, error)
throw error
}
}
@@ -141,7 +140,6 @@ export class ClineBlobStorage extends ClineStorage {
try {
await this.adapter!.remove(key)
} catch (error) {
Logger.error(`[ClineBlobStorage] failed to delete '${key}':`, error)
throw error
}
}
+2 -4
View File
@@ -36,8 +36,7 @@ export class ClineSecretStorage extends ClineStorage {
protected async _get(key: string): Promise<string | undefined> {
try {
return key ? await this.storage.get(key) : undefined
} catch (error) {
Logger.error("[ClineSecretStorage]", error)
} catch {
return undefined
}
}
@@ -51,12 +50,11 @@ export class ClineSecretStorage extends ClineStorage {
await this.storage.store(key, value)
}
} catch (error) {
Logger.error("[ClineSecretStorage]", error)
Logger.error("[ClineSecretStorage] Failed to store", error)
}
}
protected async _delete(key: string): Promise<void> {
Logger.info("[ClineSecretStorage] deleting secret")
await this.storage.delete(key)
}
}
+3 -4
View File
@@ -48,8 +48,7 @@ export abstract class ClineStorage {
public async get(key: string): Promise<string | undefined> {
try {
return await this._get(key)
} catch (error) {
Logger.error(`[${this.name}] failed to get '${key}':`, error)
} catch {
return undefined
}
}
@@ -77,8 +76,8 @@ export abstract class ClineStorage {
try {
await this._delete(key)
await this.fire(key)
} catch (error) {
Logger.error(`[${this.name}] failed to delete '${key}':`, error)
} catch {
// Silently fail on delete errors
}
}
+7 -25
View File
@@ -2,22 +2,23 @@ import { afterEach, beforeEach, describe, it } from "mocha"
import * as should from "should"
import * as sinon from "sinon"
import { createClineAPI } from "@/exports"
import { Logger } from "@/shared/services/Logger"
import type { ClineAPI } from "../exports/cline"
import { setVscodeHostProviderMock } from "./host-provider-test-utils"
describe("ClineAPI Core Functionality", () => {
let api: ClineAPI
let mockController: any
let mockLogToChannel: sinon.SinonStub<[string], void>
let mockLoggerError: sinon.SinonStub
let sandbox: sinon.SinonSandbox
let _getGlobalStateStub: sinon.SinonStub
beforeEach(async () => {
sandbox = sinon.createSandbox()
// Create mock log function
mockLogToChannel = sandbox.stub<[string], void>()
setVscodeHostProviderMock({ logToChannel: mockLogToChannel })
// Stub Logger.error
mockLoggerError = sandbox.stub(Logger, "error")
setVscodeHostProviderMock({})
// Create a mock controller that matches what the real createClineAPI expects
// We don't import the real Controller to avoid the webview dependencies
@@ -64,11 +65,6 @@ describe("ClineAPI Core Functionality", () => {
sinon.assert.called(mockController.clearTask)
sinon.assert.called(mockController.postStateToWebview)
sinon.assert.calledWith(mockController.initTask, taskDescription, images)
// Verify logging - first it logs "Starting new task"
sinon.assert.calledWith(mockLogToChannel, "Starting new task")
// Then it logs the task details
sinon.assert.calledWith(mockLogToChannel, `Task started with message: "Create a test function" and 2 image(s)`)
})
it("should handle undefined task description", async () => {
@@ -76,16 +72,12 @@ describe("ClineAPI Core Functionality", () => {
sinon.assert.called(mockController.clearTask)
sinon.assert.calledWith(mockController.initTask, undefined, [])
sinon.assert.calledWith(mockLogToChannel, "Task started with message: undefined and 0 image(s)")
})
it("should handle task with no images", async () => {
await api.startNewTask("Task without images")
sinon.assert.calledWith(mockController.initTask, "Task without images", undefined)
sinon.assert.calledWith(mockLogToChannel, `Task started with message: "Task without images" and 0 image(s)`)
})
})
@@ -99,16 +91,12 @@ describe("ClineAPI Core Functionality", () => {
await api.sendMessage("Test message", ["image.png"])
sinon.assert.calledWith(mockTask.handleWebviewAskResponse, "messageResponse", "Test message", ["image.png"])
sinon.assert.calledWith(mockLogToChannel, `Sending message: "Test message" with 1 image(s)`)
})
it("should handle no active task gracefully", async () => {
mockController.task = undefined
await api.sendMessage("Message to nowhere", [])
sinon.assert.calledWith(mockLogToChannel, "No active task to send message to")
})
it("should handle empty message", async () => {
@@ -131,8 +119,6 @@ describe("ClineAPI Core Functionality", () => {
await api.sendMessage(undefined, [])
sinon.assert.calledWith(mockTask.handleWebviewAskResponse, "messageResponse", "", [])
sinon.assert.calledWith(mockLogToChannel, `Sending message: undefined with 0 image(s)`)
})
})
@@ -147,8 +133,6 @@ describe("ClineAPI Core Functionality", () => {
await api.pressPrimaryButton()
sinon.assert.calledWith(mockTask.handleWebviewAskResponse, "yesButtonClicked", "", [])
sinon.assert.calledWith(mockLogToChannel, "Pressing primary button")
})
it("should handle primary button press with no active task", async () => {
@@ -156,7 +140,7 @@ describe("ClineAPI Core Functionality", () => {
await api.pressPrimaryButton()
sinon.assert.calledWith(mockLogToChannel, "No active task to press button for")
sinon.assert.calledWith(mockLoggerError, "No active task to press button for")
})
})
@@ -170,8 +154,6 @@ describe("ClineAPI Core Functionality", () => {
await api.pressSecondaryButton()
sinon.assert.calledWith(mockTask.handleWebviewAskResponse, "noButtonClicked", "", [])
sinon.assert.calledWith(mockLogToChannel, "Pressing secondary button")
})
it("should handle secondary button press with no active task", async () => {
@@ -179,7 +161,7 @@ describe("ClineAPI Core Functionality", () => {
await api.pressSecondaryButton()
sinon.assert.calledWith(mockLogToChannel, "No active task to press button for")
sinon.assert.calledWith(mockLoggerError, "No active task to press button for")
})
})
})