Getting the client to run (#12)
* Clean up workbench and integrate initialization data * Uncomment Electron fill * Run server & client together * Clean up Electron fill & patch * Bind fs methods This makes them usable with the promise form: `promisify(access)(...)`. * Add space between tag and title to browser logger * Add typescript dep to server and default __dirname for path * Serve web files from server * Adjust some dev options * Rework workbench a bit to use a class and catch unexpected errors * No mkdirs for now, fix util fill, use bash with exec * More fills, make general client abstract * More fills * Fix cp.exec * Fix require calls in fs fill being aliased * Create data and storage dir * Implement fs.watch Using exec for now. * Implement storage database fill * Fix os export and homedir * Add comment to use navigator.sendBeacon * Fix fs callbacks (some args are optional) * Make sure data directory exists when passing it back * Update patch * Target es5 * More fills * Add APIs required for bootstrap-fork to function (#15) * Add bootstrap-fork execution * Add createConnection * Bundle bootstrap-fork into cli * Remove .node directory created from spdlog * Fix npm start * Remove unnecessary comment * Add webpack-hot-middleware if CLI env is not set * Add restarting to shared process * Fix starting with yarn
This commit is contained in:
186
packages/vscode/src/fill/spdlog.ts
Normal file
186
packages/vscode/src/fill/spdlog.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import { appendFile, stat, readdir } from "fs";
|
||||
import { RotatingLogger as NodeRotatingLogger } from "spdlog";
|
||||
import { logger, field } from "@coder/logger";
|
||||
import { escapePath } from "@coder/protocol";
|
||||
|
||||
// TODO: It would be better to spawn an actual spdlog instance on the server and
|
||||
// use that for the logging. Or maybe create an instance when the server starts,
|
||||
// and just always use that one (make it part of the protocol).
|
||||
export class RotatingLogger implements NodeRotatingLogger {
|
||||
|
||||
private format = true;
|
||||
private buffer = "";
|
||||
private flushPromise: Promise<void> | undefined;
|
||||
private name: string;
|
||||
private logDirectory: string;
|
||||
private escapedLogDirectory: string;
|
||||
private fullFilePath: string;
|
||||
private fileName: string;
|
||||
private fileExt: string | undefined;
|
||||
private escapedFilePath: string;
|
||||
private filesize: number;
|
||||
private filecount: number;
|
||||
|
||||
public constructor(name: string, filePath: string, filesize: number, filecount: number) {
|
||||
this.name = name;
|
||||
this.filesize = filesize;
|
||||
this.filecount = filecount;
|
||||
|
||||
this.fullFilePath = filePath;
|
||||
const slashIndex = filePath.lastIndexOf("/");
|
||||
const dotIndex = filePath.lastIndexOf(".");
|
||||
this.logDirectory = slashIndex !== -1 ? filePath.substring(0, slashIndex) : "/";
|
||||
this.fileName = filePath.substring(slashIndex + 1, dotIndex !== -1 ? dotIndex : undefined);
|
||||
this.fileExt = dotIndex !== -1 ? filePath.substring(dotIndex + 1) : undefined;
|
||||
|
||||
this.escapedLogDirectory = escapePath(this.logDirectory);
|
||||
this.escapedFilePath = escapePath(filePath);
|
||||
|
||||
this.flushPromise = new Promise((resolve): void => {
|
||||
exec(`mkdir -p ${this.escapedLogDirectory}; touch ${this.escapedFilePath}`, async (error) => {
|
||||
if (!error) {
|
||||
try {
|
||||
await this.doFlush();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
logger.error(error.message, field("error", error));
|
||||
}
|
||||
this.flushPromise = undefined;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public trace(message: string): void {
|
||||
this.write("trace", message);
|
||||
}
|
||||
|
||||
public debug(message: string): void {
|
||||
this.write("debug", message);
|
||||
}
|
||||
|
||||
public info(message: string): void {
|
||||
this.write("info", message);
|
||||
}
|
||||
|
||||
public warn(message: string): void {
|
||||
this.write("warn", message);
|
||||
}
|
||||
|
||||
public error(message: string): void {
|
||||
this.write("error", message);
|
||||
}
|
||||
|
||||
public critical(message: string): void {
|
||||
this.write("critical", message);
|
||||
}
|
||||
|
||||
public setLevel(): void {
|
||||
// Should output everything.
|
||||
}
|
||||
|
||||
public clearFormatters(): void {
|
||||
this.format = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the buffer. Only one process runs at a time to prevent race
|
||||
* conditions.
|
||||
*/
|
||||
public flush(): Promise<void> {
|
||||
if (!this.flushPromise) {
|
||||
this.flushPromise = this.doFlush().then(() => {
|
||||
this.flushPromise = undefined;
|
||||
}).catch((error) => {
|
||||
this.flushPromise = undefined;
|
||||
logger.error(error.message, field("error", error));
|
||||
});
|
||||
}
|
||||
|
||||
return this.flushPromise;
|
||||
}
|
||||
|
||||
public drop(): void {
|
||||
this.buffer = "";
|
||||
}
|
||||
|
||||
private pad(num: number, length: number = 2, prefix: string = "0"): string {
|
||||
const str = num.toString();
|
||||
|
||||
return (length > str.length ? prefix.repeat(length - str.length) : "") + str;
|
||||
}
|
||||
|
||||
private write(severity: string, message: string): void {
|
||||
if (this.format) {
|
||||
const date = new Date();
|
||||
const dateStr = `${date.getFullYear()}-${this.pad(date.getMonth() + 1)}-${this.pad(date.getDate())}`
|
||||
+ ` ${this.pad(date.getHours())}:${this.pad(date.getMinutes())}:${this.pad(date.getSeconds())}.${this.pad(date.getMilliseconds(), 3)}`;
|
||||
this.buffer += `[${dateStr}] [${this.name}] [${severity}] `;
|
||||
}
|
||||
this.buffer += message;
|
||||
if (this.format) {
|
||||
this.buffer += "\n";
|
||||
}
|
||||
this.flush();
|
||||
}
|
||||
|
||||
private async rotate(): Promise<void> {
|
||||
const stats = await promisify(stat)(this.fullFilePath);
|
||||
if (stats.size < this.filesize) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reExt = typeof this.fileExt !== "undefined" ? `\\.${this.fileExt}` : "";
|
||||
const re = new RegExp(`^${this.fileName}(?:\\.(\\d+))?${reExt}$`);
|
||||
const orderedFiles: string[] = [];
|
||||
(await promisify(readdir)(this.logDirectory)).forEach((file) => {
|
||||
const match = re.exec(file);
|
||||
if (match) {
|
||||
orderedFiles[typeof match[1] !== "undefined" ? parseInt(match[1], 10) : 0] = file;
|
||||
}
|
||||
});
|
||||
|
||||
// Rename in reverse so we don't overwrite before renaming something.
|
||||
let count = 0;
|
||||
const command = orderedFiles.map((file) => {
|
||||
const fileExt = typeof this.fileExt !== "undefined" ? `.${this.fileExt}` : "";
|
||||
const newFile = `${this.logDirectory}/${this.fileName}.${++count}${fileExt}`;
|
||||
|
||||
return count >= this.filecount
|
||||
? `rm ${escapePath(this.logDirectory + "/" + file)}`
|
||||
: `mv ${escapePath(this.logDirectory + "/" + file)} ${escapePath(newFile)}`;
|
||||
}).reverse().concat([
|
||||
`touch ${escapePath(this.fullFilePath)}`,
|
||||
]).join(";");
|
||||
|
||||
await promisify(exec)(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the entire buffer, including anything added in the meantime, and
|
||||
* rotates the log if necessary.
|
||||
*/
|
||||
private async doFlush(): Promise<void> {
|
||||
const writeBuffer = async (): Promise<void> => {
|
||||
const toWrite = this.buffer;
|
||||
this.buffer = "";
|
||||
|
||||
await promisify(appendFile)(this.fullFilePath, toWrite);
|
||||
};
|
||||
|
||||
while (this.buffer.length > 0) {
|
||||
await writeBuffer();
|
||||
await this.rotate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const setAsyncMode = (): void => {
|
||||
// Nothing to do.
|
||||
};
|
||||
Reference in New Issue
Block a user