mirror of
https://github.com/nestriness/nestri.git
synced 2026-08-02 01:35:18 +03:00
feat: Change logs a lil bit
This commit is contained in:
@@ -20,9 +20,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"drizzle-orm": "^0.44.6",
|
||||
"hono": "catalog:",
|
||||
"ioredis": "^5.8.2",
|
||||
"postgres": "^3.4.7",
|
||||
"ulid": "^3.0.1",
|
||||
"xdg-basedir": "^5.1.0",
|
||||
"zod": "catalog:",
|
||||
"zod-openapi": "^5.4.3"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod/v4";
|
||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
|
||||
/**
|
||||
* Standard error response schema used for OpenAPI documentation
|
||||
@@ -108,7 +109,7 @@ export class VisibleError extends Error {
|
||||
/**
|
||||
* Convert this error to an HTTP status code
|
||||
*/
|
||||
public statusCode(): number {
|
||||
public statusCode(): ContentfulStatusCode {
|
||||
switch (this.type) {
|
||||
case "validation":
|
||||
return 400;
|
||||
|
||||
50
packages/core/src/global/index.ts
Normal file
50
packages/core/src/global/index.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir";
|
||||
|
||||
const app = "nestri";
|
||||
|
||||
const data = path.join(xdgData!, app);
|
||||
const cache = path.join(xdgCache!, app);
|
||||
const config = path.join(xdgConfig!, app);
|
||||
const state = path.join(xdgState!, app);
|
||||
|
||||
export namespace Global {
|
||||
export const Path = {
|
||||
data,
|
||||
bin: path.join(data, "bin"),
|
||||
log: path.join(data, "log"),
|
||||
cache,
|
||||
config,
|
||||
state,
|
||||
} as const;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
fs.mkdir(Global.Path.data, { recursive: true }),
|
||||
fs.mkdir(Global.Path.config, { recursive: true }),
|
||||
fs.mkdir(Global.Path.state, { recursive: true }),
|
||||
fs.mkdir(Global.Path.log, { recursive: true }),
|
||||
fs.mkdir(Global.Path.bin, { recursive: true }),
|
||||
]);
|
||||
|
||||
const CACHE_VERSION = "0";
|
||||
|
||||
const version = await Bun.file(path.join(Global.Path.cache, "version"))
|
||||
.text()
|
||||
.catch(() => "0");
|
||||
|
||||
if (version !== CACHE_VERSION) {
|
||||
try {
|
||||
const contents = await fs.readdir(Global.Path.cache);
|
||||
await Promise.all(
|
||||
contents.map((item) =>
|
||||
fs.rm(path.join(Global.Path.cache, item), {
|
||||
recursive: true,
|
||||
force: true,
|
||||
}),
|
||||
),
|
||||
);
|
||||
} catch (e) {}
|
||||
await Bun.file(path.join(Global.Path.cache, "version")).write(CACHE_VERSION);
|
||||
}
|
||||
@@ -1,55 +1,192 @@
|
||||
import z from "zod/v4";
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import { Context } from "../context.js";
|
||||
import { Global } from "../global/index.js";
|
||||
|
||||
export namespace Log {
|
||||
const ctx = Context.create<{
|
||||
tags: Record<string, any>;
|
||||
}>();
|
||||
|
||||
export const Level = z
|
||||
.enum(["DEBUG", "INFO", "WARN", "ERROR"])
|
||||
.meta({ ref: "LogLevel", description: "Log level" });
|
||||
export type Level = z.infer<typeof Level>;
|
||||
|
||||
const levelPriority: Record<Level, number> = {
|
||||
DEBUG: 0,
|
||||
INFO: 1,
|
||||
WARN: 2,
|
||||
ERROR: 3,
|
||||
};
|
||||
|
||||
let level: Level = "INFO";
|
||||
|
||||
function shouldLog(input: Level): boolean {
|
||||
return levelPriority[input] >= levelPriority[level];
|
||||
}
|
||||
|
||||
export type Logger = {
|
||||
debug(message?: any, extra?: Record<string, any>): void;
|
||||
info(message?: any, extra?: Record<string, any>): void;
|
||||
error(message?: any, extra?: Record<string, any>): void;
|
||||
warn(message?: any, extra?: Record<string, any>): void;
|
||||
tag(key: string, value: string): Logger;
|
||||
clone(): Logger;
|
||||
time(
|
||||
message: string,
|
||||
extra?: Record<string, any>,
|
||||
): {
|
||||
stop(): void;
|
||||
[Symbol.dispose](): void;
|
||||
};
|
||||
};
|
||||
|
||||
const loggers = new Map<string, Logger>();
|
||||
|
||||
export const Default = create({ service: "default" });
|
||||
|
||||
export interface Options {
|
||||
print: boolean;
|
||||
dev?: boolean;
|
||||
level?: Level;
|
||||
}
|
||||
|
||||
let logpath = "";
|
||||
export function file() {
|
||||
return logpath;
|
||||
}
|
||||
|
||||
export async function init(options: Options) {
|
||||
if (options.level) level = options.level;
|
||||
cleanup(Global.Path.log);
|
||||
if (options.print) return;
|
||||
logpath = path.join(
|
||||
Global.Path.log,
|
||||
options.dev
|
||||
? "dev.log"
|
||||
: `${new Date().toISOString().split(".")[0]?.replace(/:/g, "")}.log`,
|
||||
);
|
||||
const logfile = Bun.file(logpath);
|
||||
await fs.truncate(logpath).catch(() => {});
|
||||
const writer = logfile.writer();
|
||||
process.stderr.write = (msg) => {
|
||||
writer.write(msg);
|
||||
writer.flush();
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
async function cleanup(dir: string) {
|
||||
const glob = new Bun.Glob("????-??-??T??????.log");
|
||||
const files = await Array.fromAsync(
|
||||
glob.scan({
|
||||
cwd: dir,
|
||||
absolute: true,
|
||||
}),
|
||||
);
|
||||
if (files.length <= 5) return;
|
||||
|
||||
const filesToDelete = files.slice(0, -10);
|
||||
await Promise.all(
|
||||
filesToDelete.map((file) => fs.unlink(file).catch(() => {})),
|
||||
);
|
||||
}
|
||||
|
||||
function formatError(error: Error, depth = 0): string {
|
||||
const result = error.message;
|
||||
return error.cause instanceof Error && depth < 10
|
||||
? result + " Caused by: " + formatError(error.cause, depth + 1)
|
||||
: result;
|
||||
}
|
||||
|
||||
let last = Date.now();
|
||||
export function create(tags?: Record<string, any>) {
|
||||
tags = tags || {};
|
||||
|
||||
const result = {
|
||||
info(msg: string, extra?: Record<string, any>) {
|
||||
const prefix = Object.entries({
|
||||
...use().tags,
|
||||
...tags,
|
||||
...extra,
|
||||
const service = tags["service"];
|
||||
if (service && typeof service === "string") {
|
||||
const cached = loggers.get(service);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
function build(message: any, extra?: Record<string, any>) {
|
||||
const prefix = Object.entries({
|
||||
...use().tags,
|
||||
...tags,
|
||||
...extra,
|
||||
})
|
||||
.filter(([_, value]) => value !== undefined && value !== null)
|
||||
.map(([key, value]) => {
|
||||
const prefix = `${key}=`;
|
||||
if (value instanceof Error) return prefix + formatError(value);
|
||||
if (typeof value === "object") return prefix + JSON.stringify(value);
|
||||
return prefix + value;
|
||||
})
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join(" ");
|
||||
console.log(prefix, msg);
|
||||
return result;
|
||||
.join(" ");
|
||||
const next = new Date();
|
||||
const diff = next.getTime() - last;
|
||||
last = next.getTime();
|
||||
return (
|
||||
[next.toISOString().split(".")[0], "+" + diff + "ms", prefix, message]
|
||||
.filter(Boolean)
|
||||
.join(" ") + "\n"
|
||||
);
|
||||
}
|
||||
const result: Logger = {
|
||||
debug(message?: any, extra?: Record<string, any>) {
|
||||
if (shouldLog("DEBUG")) {
|
||||
process.stderr.write("DEBUG " + build(message, extra));
|
||||
}
|
||||
},
|
||||
warn(msg: string, extra?: Record<string, any>) {
|
||||
const prefix = Object.entries({
|
||||
...use().tags,
|
||||
...tags,
|
||||
...extra,
|
||||
})
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join(" ");
|
||||
console.warn(prefix, msg);
|
||||
return result;
|
||||
info(message?: any, extra?: Record<string, any>) {
|
||||
if (shouldLog("INFO")) {
|
||||
process.stderr.write("INFO " + build(message, extra));
|
||||
}
|
||||
},
|
||||
error(error: Error) {
|
||||
const prefix = Object.entries({
|
||||
...use().tags,
|
||||
...tags,
|
||||
})
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join(" ");
|
||||
console.error(prefix, error);
|
||||
return result;
|
||||
error(message?: any, extra?: Record<string, any>) {
|
||||
if (shouldLog("ERROR")) {
|
||||
process.stderr.write("ERROR " + build(message, extra));
|
||||
}
|
||||
},
|
||||
warn(message?: any, extra?: Record<string, any>) {
|
||||
if (shouldLog("WARN")) {
|
||||
process.stderr.write("WARN " + build(message, extra));
|
||||
}
|
||||
},
|
||||
tag(key: string, value: string) {
|
||||
tags[key] = value;
|
||||
if (tags) tags[key] = value;
|
||||
return result;
|
||||
},
|
||||
clone() {
|
||||
return Log.create({ ...tags });
|
||||
},
|
||||
time(message: string, extra?: Record<string, any>) {
|
||||
const now = Date.now();
|
||||
result.info(message, { status: "started", ...extra });
|
||||
function stop() {
|
||||
result.info(message, {
|
||||
status: "completed",
|
||||
duration: Date.now() - now,
|
||||
...extra,
|
||||
});
|
||||
}
|
||||
return {
|
||||
stop,
|
||||
[Symbol.dispose]() {
|
||||
stop();
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
if (service && typeof service === "string") {
|
||||
loggers.set(service, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user