process.env.DEBUG_HIDE_DATE = "yes";
import debug, { Debugger } from "debug";
import { mkdirSync, createWriteStream, statSync, ftruncate } from "fs";
import { format } from "util";
import { dirname, join } from "path";
import { logs } from "./data";
const MAX_FILE_SIZE_BYTES = 20 * 1024 * 1024;
const COCALC = debug("cocalc");
let _trimLogFileSizePath = "";
export function trimLogFileSize() {
return;
if (!_trimLogFileSizePath) return;
let stats;
try {
stats = statSync(_trimLogFileSizePath);
} catch(_) {
return;
}
if (stats.size > MAX_FILE_SIZE_BYTES) {
const fileStream = createWriteStream(_trimLogFileSizePath, { flags: "r+" });
fileStream.on("open", (fd) => {
ftruncate(fd, MAX_FILE_SIZE_BYTES, (truncateErr) => {
if (truncateErr) {
console.error(truncateErr);
return;
}
fileStream.close();
});
});
}
}
function myFormat(...args): string {
if (args.length > 1 && typeof args[0] == "string" && !args[0].includes("%")) {
const v: string[] = [];
for (const x of args) {
try {
v.push(typeof x == "object" ? JSON.stringify(x) : `${x}`);
} catch (_) {
v.push(`${x}`);
}
}
return v.join(" ");
}
return format(...args);
}
function defaultTransports(): { console?: boolean; file?: string } {
if (process.env.SMC_TEST) {
return {};
} else if (process.env.COCALC_DOCKER) {
return { file: "/var/log/hub/log" };
} else if (process.env.NODE_ENV == "production") {
return { console: true };
} else {
return { file: join(logs, "log") };
}
}
function initTransports() {
if (!process.env.DEBUG) {
return;
}
const transports = defaultTransports();
if (process.env.DEBUG_CONSOLE) {
transports.console =
process.env.DEBUG_CONSOLE != "no" && process.env.DEBUG_CONSOLE != "false";
}
if (process.env.DEBUG_FILE != null) {
transports.file = process.env.DEBUG_FILE;
}
let fileStream;
if (transports.file) {
const { file } = transports;
mkdirSync(dirname(file), { recursive: true });
fileStream = createWriteStream(file, {
flags: "a",
});
_trimLogFileSizePath = file;
trimLogFileSize();
}
let firstLog: boolean = true;
COCALC.log = (...args) => {
if (!transports.file && !transports.console) return;
if (firstLog && transports.file) {
const announce = `***\n\nLogging to "${transports.file}"${
transports.console ? " and console.log" : ""
} via the debug module\nwith DEBUG='${
process.env.DEBUG
}'.\nUse DEBUG_FILE='path' and DEBUG_CONSOLE=[yes|no] to override.\nUsing e.g., something like DEBUG='cocalc:*,-cocalc:silly:*' to control log levels.\n\n***`;
console.log(announce);
if (transports.file) {
fileStream.write(announce);
}
firstLog = false;
}
const line = `${new Date().toISOString()}: ${myFormat(...args)}\n`;
if (transports.console) {
console.log(line);
}
if (transports.file) {
fileStream.write(line);
}
};
}
initTransports();
const DEBUGGERS = {
error: COCALC.extend("error"),
warn: COCALC.extend("warn"),
info: COCALC.extend("info"),
http: COCALC.extend("http"),
verbose: COCALC.extend("verbose"),
debug: COCALC.extend("debug"),
silly: COCALC.extend("silly"),
};
type Level = keyof typeof DEBUGGERS;
const LEVELS: Level[] = [
"error",
"warn",
"info",
"http",
"verbose",
"debug",
"silly",
];
class Logger {
private name: string;
private debuggers: { [level: string]: Debugger } = {};
constructor(name: string) {
this.name = name;
for (const level of LEVELS) {
this.debuggers[level] = DEBUGGERS[level].extend(name);
this[level] = (...args) => {
this.counter(level);
this.debuggers[level](...args);
};
}
}
public isEnabled(level: Level): boolean {
return this.debuggers[level].enabled;
}
public extend(name: string) {
return new Logger(`${this.name}:${name}`);
}
private counter(level: Level): void {
if (counter == null) return;
counter.labels(this.name, level).inc(1);
}
}
export interface WinstonLogger {
error: Function;
warn: Function;
info: Function;
http: Function;
verbose: Function;
debug: Function;
silly: Function;
extend: (name: string) => WinstonLogger;
isEnabled: (level: Level) => boolean;
}
const cache: { [name: string]: WinstonLogger } = {};
export default function getLogger(name: string): WinstonLogger {
if (cache[name] != null) {
return cache[name];
}
return (cache[name] = new Logger(name) as unknown as WinstonLogger);
}
export { getLogger };
let counter: any = undefined;
export function setCounter(f) {
counter = f;
}