mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 03:19:46 +00:00
Merge branch '10x-better-logging' into 'main'
Make logging 10x better See merge request soapbox-pub/ditto!734
This commit is contained in:
commit
1b3c22957e
4 changed files with 101 additions and 1 deletions
|
|
@ -238,6 +238,26 @@ export class DittoConf {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The logging configuration for the Ditto server. The config is derived from
|
||||||
|
* the DEBUG environment variable and it is parsed as follows:
|
||||||
|
*
|
||||||
|
* `DEBUG='<jsonl|pretty>:<minimum log level to show>:comma-separated scopes to show'`.
|
||||||
|
* If the scopes are empty (e.g. in 'pretty:warn:', then all scopes are shown.)
|
||||||
|
*/
|
||||||
|
get logConfig(): {
|
||||||
|
fmt: 'jsonl' | 'pretty';
|
||||||
|
level: string;
|
||||||
|
scopes: string[];
|
||||||
|
} {
|
||||||
|
const [fmt = 'jsonl', level = 'debug', scopes = ''] = (this.env.get('LOG_CONFIG') || '').split(':');
|
||||||
|
return {
|
||||||
|
fmt: fmt === 'jsonl' ? fmt : 'pretty',
|
||||||
|
level,
|
||||||
|
scopes: scopes.split(',').filter(Boolean),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/** nostr.build API endpoint when the `nostrbuild` uploader is used. */
|
/** nostr.build API endpoint when the `nostrbuild` uploader is used. */
|
||||||
get nostrbuildEndpoint(): string {
|
get nostrbuildEndpoint(): string {
|
||||||
return this.env.get('NOSTRBUILD_ENDPOINT') || 'https://nostr.build/api/v2/upload/files';
|
return this.env.get('NOSTRBUILD_ENDPOINT') || 'https://nostr.build/api/v2/upload/files';
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export const KyselyLogger: Logger = (event) => {
|
||||||
dbQueryDurationHistogram.observe(duration);
|
dbQueryDurationHistogram.observe(duration);
|
||||||
|
|
||||||
if (event.level === 'query') {
|
if (event.level === 'query') {
|
||||||
logi({ level: 'debug', ns: 'ditto.sql', sql, parameters: parameters as LogiValue, duration });
|
logi({ level: 'trace', ns: 'ditto.sql', sql, parameters: parameters as LogiValue, duration });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.level === 'error') {
|
if (event.level === 'error') {
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,8 @@ import dittoNamesRoute from '@/routes/dittoNamesRoute.ts';
|
||||||
import pleromaAdminPermissionGroupsRoute from '@/routes/pleromaAdminPermissionGroupsRoute.ts';
|
import pleromaAdminPermissionGroupsRoute from '@/routes/pleromaAdminPermissionGroupsRoute.ts';
|
||||||
import pleromaStatusesRoute from '@/routes/pleromaStatusesRoute.ts';
|
import pleromaStatusesRoute from '@/routes/pleromaStatusesRoute.ts';
|
||||||
import { DittoRelayStore } from '@/storages/DittoRelayStore.ts';
|
import { DittoRelayStore } from '@/storages/DittoRelayStore.ts';
|
||||||
|
import { logi } from '@soapbox/logi';
|
||||||
|
import { createLogiHandler } from '@/utils/logi.ts';
|
||||||
|
|
||||||
export interface AppEnv extends DittoEnv {
|
export interface AppEnv extends DittoEnv {
|
||||||
Variables: DittoEnv['Variables'] & {
|
Variables: DittoEnv['Variables'] & {
|
||||||
|
|
@ -179,6 +181,7 @@ type AppMiddleware = MiddlewareHandler<AppEnv>;
|
||||||
type AppController<P extends string = any> = Handler<AppEnv, P, HonoInput, Response | Promise<Response>>;
|
type AppController<P extends string = any> = Handler<AppEnv, P, HonoInput, Response | Promise<Response>>;
|
||||||
|
|
||||||
const conf = new DittoConf(Deno.env);
|
const conf = new DittoConf(Deno.env);
|
||||||
|
logi.handler = createLogiHandler(conf, logi.handler);
|
||||||
|
|
||||||
startSentry(conf);
|
startSentry(conf);
|
||||||
|
|
||||||
|
|
|
||||||
77
packages/ditto/utils/logi.ts
Normal file
77
packages/ditto/utils/logi.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import type { LogiHandler, LogiLog, LogiValue } from '@soapbox/logi';
|
||||||
|
import { DittoConf } from '@ditto/conf';
|
||||||
|
|
||||||
|
type Level = LogiLog['level'];
|
||||||
|
|
||||||
|
const levels: Level[] = ['trace', 'debug', 'info', 'warn', 'error', 'fatal', 'critical'];
|
||||||
|
|
||||||
|
const lowerLevels: Record<Level, Level[]> = levels.reduce((acc, level, index) => {
|
||||||
|
acc[level] = levels.slice(index);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<Level, Level[]>);
|
||||||
|
|
||||||
|
const colors: Record<Level, string> = {
|
||||||
|
trace: 'grey',
|
||||||
|
debug: 'white',
|
||||||
|
info: 'blue',
|
||||||
|
warn: 'yellow',
|
||||||
|
error: 'orange',
|
||||||
|
fatal: 'red',
|
||||||
|
critical: 'red',
|
||||||
|
};
|
||||||
|
|
||||||
|
const levelSet = new Set(levels);
|
||||||
|
const isLevel = (str: string): str is Level => levelSet.has(str as Level);
|
||||||
|
|
||||||
|
const prettyPrint = (msg: LogiValue): string => {
|
||||||
|
const message = msg || '';
|
||||||
|
const type = typeof message;
|
||||||
|
switch (type) {
|
||||||
|
case 'string':
|
||||||
|
case 'bigint':
|
||||||
|
case 'number':
|
||||||
|
case 'boolean':
|
||||||
|
return message.toString();
|
||||||
|
case 'function':
|
||||||
|
case 'symbol':
|
||||||
|
case 'undefined':
|
||||||
|
return `<${type}>`;
|
||||||
|
case 'object':
|
||||||
|
if (message === null) return '<null>';
|
||||||
|
return JSON.stringify(message, (_, v) => {
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
return `[${v.map((itm) => JSON.stringify(itm)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (typeof v === 'string') return `\`${v}\``;
|
||||||
|
return v;
|
||||||
|
}, 2)
|
||||||
|
.replaceAll('\\"', '"')
|
||||||
|
.replace(/^"/, '')
|
||||||
|
.replace(/"$/, '');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pair = (key: string, value: LogiValue | undefined) => {
|
||||||
|
return `${key}: ${prettyPrint(value || '')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createLogiHandler = (conf: DittoConf, defaultHandler: LogiHandler) => (log: LogiLog) => {
|
||||||
|
const { fmt, level, scopes } = conf.logConfig;
|
||||||
|
if (fmt === 'jsonl') return defaultHandler(log);
|
||||||
|
if (!isLevel(level)) throw new Error(`Invalid log level ${level} specified`);
|
||||||
|
if (!lowerLevels[level].includes(log.level)) return;
|
||||||
|
if (scopes.length && !scopes.some((scope) => scope.startsWith(log.ns))) return;
|
||||||
|
const message = prettyPrint(log.message || log.msg || '');
|
||||||
|
const remaining = Object.entries(log)
|
||||||
|
.filter(([key]) => !['ns', 'level', 'message', 'msg'].includes(key));
|
||||||
|
|
||||||
|
console.group(
|
||||||
|
`%c${log.level.toUpperCase()} %c${log.ns} %c${message || ''}`,
|
||||||
|
`color: ${colors[log.level]}; font-weight: bold`,
|
||||||
|
'font-weight: normal; color: yellow',
|
||||||
|
'color: unset',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (remaining.length) console.log(remaining.map((itm) => pair(...itm)).join('\n'));
|
||||||
|
console.groupEnd();
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue