Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { PochiWebviewSidebar } from "./integrations/webview";
import { PochiTaskEditorProvider } from "./integrations/webview/webview-panel";
import { type AuthClient, createAuthClient } from "./lib/auth-client";
import { FileLogger } from "./lib/file-logger";
import { HeartbeatLogger } from "./lib/heartbeat-logger";
import { getLogger } from "./lib/logger";
import { PostInstallActions } from "./lib/post-install-actions";
import { WorkspaceScope } from "./lib/workspace-scoped";
Expand Down Expand Up @@ -79,6 +80,7 @@ export async function activate(context: vscode.ExtensionContext) {
container.resolve(CommandManager);
container.resolve(DiffOriginContentProvider);
container.resolve(PostInstallActions);
container.resolve(HeartbeatLogger);
container.resolve(FileLogger);
container.resolve(TerminalLinkProvider);
container.resolve(DiffChangesContentProvider);
Expand Down
1 change: 1 addition & 0 deletions packages/vscode/src/integrations/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ const PochiAdvanceSettings = z.object({
})
.optional(),
webviewLogLevel: z.string().optional(),
enabledFileLogger: z.boolean().optional(),
});

export type PochiAdvanceSettings = z.infer<typeof PochiAdvanceSettings>;
Expand Down
69 changes: 40 additions & 29 deletions packages/vscode/src/lib/file-logger.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,70 @@
import * as os from "node:os";
import path from "node:path";
// biome-ignore lint/style/useImportType: needed for dependency injection
import { PochiConfiguration } from "@/integrations/configuration";
import { getExtensionLogger } from "@vscode-logging/logger";
import { inject, injectable, singleton } from "tsyringe";
import * as vscode from "vscode";

const HeartbeatInterval = 120_000; // 2 minutes
import type * as vscode from "vscode";

@injectable()
@singleton()
export class FileLogger implements vscode.Disposable {
export class FileLogger {
private rootLogger;
private disposables: vscode.Disposable[] = [];

constructor(
@inject("vscode.ExtensionContext")
context: vscode.ExtensionContext,
private readonly pochiConfiguration: PochiConfiguration,
) {
const logFilePath = path.join(os.homedir(), ".pochi", "logs");
this.rootLogger = getExtensionLogger({
extName: context.extension.id,
level: "info",
logPath: context.logUri.fsPath,
logPath: logFilePath,
sourceLocationTracking: false,
logOutputChannel: undefined, // no log to output channel
logConsole: false, // no log to console
});

this.startHeartbeat();
}

getLogger(tag: string) {
return this.rootLogger.getChildLogger({ label: tag });
}

private startHeartbeat() {
const logger = this.getLogger("Heartbeat");
logger.info("Starting heartbeat check.", {
osHost: os.hostname(),
machineId: vscode.env.machineId,
remoteName: vscode.env.remoteName,
});
handleLog(name: string | undefined, level: string, args: unknown[]) {
const enabledFileLogger =
!!this.pochiConfiguration.advancedSettings.value.enabledFileLogger;
if (!enabledFileLogger) {
return;
}

const timmer = setInterval(() => {
logger.info("Heartbeat check.");
}, HeartbeatInterval);
this.disposables.push({
dispose: () => {
logger.info("Stopping heartbeat check.");
clearInterval(timmer);
},
});
}
const logger = this.getLogger(name || "FileLog");
const message =
typeof args[0] === "string" ? args[0] : JSON.stringify(args[0]);
const remainArgs = args.slice(1);

dispose() {
for (const d of this.disposables) {
d.dispose();
switch (level) {
case "INFO":
logger.info(message, ...remainArgs);
break;
case "WARN":
logger.warn(message, ...remainArgs);
break;
case "ERROR":
logger.error(message, ...remainArgs);
break;
case "FATAL":
logger.fatal(message, ...remainArgs);
break;
case "DEBUG":
logger.debug(message, ...remainArgs);
break;
case "TRACE":
logger.trace(message, ...remainArgs);
break;
default:
logger.info(message, ...remainArgs);
break;
}
this.disposables = [];
}
}
59 changes: 59 additions & 0 deletions packages/vscode/src/lib/heartbeat-logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as os from "node:os";
import { getExtensionLogger } from "@vscode-logging/logger";
import { inject, injectable, singleton } from "tsyringe";
import * as vscode from "vscode";

const HeartbeatInterval = 120_000; // 2 minutes

@injectable()
@singleton()
export class HeartbeatLogger implements vscode.Disposable {
private rootLogger;
private disposables: vscode.Disposable[] = [];

constructor(
@inject("vscode.ExtensionContext")
context: vscode.ExtensionContext,
) {
this.rootLogger = getExtensionLogger({
extName: context.extension.id,
level: "info",
logPath: context.logUri.fsPath,
sourceLocationTracking: false,
logOutputChannel: undefined, // no log to output channel
logConsole: false, // no log to console
});

this.startHeartbeat();
}

getLogger(tag: string) {
return this.rootLogger.getChildLogger({ label: tag });
}

private startHeartbeat() {
const logger = this.getLogger("Heartbeat");
logger.info("Starting heartbeat check.", {
osHost: os.hostname(),
machineId: vscode.env.machineId,
remoteName: vscode.env.remoteName,
});

const timmer = setInterval(() => {
logger.info("Heartbeat check.");
}, HeartbeatInterval);
this.disposables.push({
dispose: () => {
logger.info("Stopping heartbeat check.");
clearInterval(timmer);
},
});
}

dispose() {
for (const d of this.disposables) {
d.dispose();
}
this.disposables = [];
}
}
22 changes: 22 additions & 0 deletions packages/vscode/src/lib/logger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { window } from "vscode";
export { getLogger } from "@getpochi/common";
import { attachTransport } from "@getpochi/common";
import { container } from "tsyringe";
import { FileLogger } from "./file-logger";

const outputChannel = window.createOutputChannel("Pochi", { log: true });

Expand All @@ -11,6 +13,26 @@ attachTransport((args, meta) => {
message = `[${meta.name}] ${message}`;
}

// Check if the second argument contains { toFile: true }
const shouldLogToFile =
typeof args[1] === "object" &&
args[1] !== null &&
"toFile" in args[1] &&
args[1].toFile === true;

if (shouldLogToFile) {
try {
const fileLogger = container.resolve(FileLogger);
fileLogger.handleLog(meta.name, meta.logLevelName, [
message,
...remainArgs,
]);
} catch {
// ignore
}
return;
}

switch (meta.logLevelName) {
case "SILLY":
outputChannel.trace(message, ...remainArgs);
Expand Down