refactor(server,logger): migrate to crowlog logger ecosystem (#135)

This commit is contained in:
Corentin Thomasset
2025-02-09 20:57:52 +01:00
committed by GitHub
parent c1f8507891
commit 0d1be0d3a5
13 changed files with 105 additions and 172 deletions

View File

@@ -8,7 +8,7 @@
"license": "AGPL-3.0-or-later",
"keywords": [],
"scripts": {
"dev": "tsx watch --env-file-if-exists=.env src/index.ts",
"dev": "tsx watch --env-file=.env src/index.ts | crowlog-pretty",
"build": "pnpm esbuild --bundle src/index.ts --platform=node --packages=external --format=esm --outfile=dist/index.js --minify",
"start": "node dist/index.js",
"start:with-migrations": "pnpm migrate:up && pnpm start",
@@ -31,6 +31,8 @@
"@aws-sdk/lib-storage": "^3.722.0",
"@corentinth/chisels": "^1.1.0",
"@corentinth/friendly-ids": "^0.0.1",
"@crowlog/async-context-plugin": "^1.0.0",
"@crowlog/logger": "^1.1.0",
"@hono/node-server": "^1.13.7",
"@hono/oauth-providers": "^0.6.2",
"@libsql/client": "^0.14.0",
@@ -50,6 +52,7 @@
},
"devDependencies": {
"@antfu/eslint-config": "catalog:",
"@crowlog/pretty": "^1.1.1",
"@total-typescript/ts-reset": "^0.6.1",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.10.2",

View File

@@ -1,5 +1,5 @@
import type { Config } from '../config/config.types';
import type { Logger } from '../shared/logger/logger.types';
import type { Logger } from '../shared/logger/logger';
import type { DocumentsRepository } from './documents.repository';
import type { DocumentStorageService } from './storage/documents.storage.services';
import { safely } from '@corentinth/chisels';

View File

@@ -1,4 +1,5 @@
import type { Config } from '../config/config.types';
import { createInMemoryLoggerTransport } from '@crowlog/logger';
import { pick } from 'lodash-es';
import { describe, expect, test } from 'vitest';
import { createInMemoryDatabase } from '../app/database/database.test-utils';
@@ -6,7 +7,6 @@ import { createDocumentsRepository } from '../documents/documents.repository';
import { documentsTable } from '../documents/documents.table';
import { createDocumentStorageService } from '../documents/storage/documents.storage.services';
import { createLogger } from '../shared/logger/logger';
import { createInMemoryLoggerTransport } from '../shared/logger/transports/in-memory.logger-transport';
import { createIntakeEmailsRepository } from './intake-emails.repository';
import { ingestEmailForRecipient, processIntakeEmailIngestion } from './intake-emails.usecases';

View File

@@ -1,6 +1,6 @@
import type { DocumentsRepository } from '../documents/documents.repository';
import type { DocumentStorageService } from '../documents/storage/documents.storage.services';
import type { Logger } from '../shared/logger/logger.types';
import type { Logger } from '../shared/logger/logger';
import type { IntakeEmailsRepository } from './intake-emails.repository';
import { safely } from '@corentinth/chisels';
import { createDocument } from '../documents/documents.usecases';

View File

@@ -1 +0,0 @@
export const logLevels = ['debug', 'info', 'warn', 'error'] as const;

View File

@@ -1,56 +1,7 @@
import type { LoggerTransport, LogLevel, LogMethodArguments } from './logger.types';
import { AsyncLocalStorage } from 'node:async_hooks';
import { logLevels } from './logger.constants';
import { createConsoleLoggerTransport } from './transports/console.logger-transport';
import { addLogContext, createAsyncContextPlugin, wrapWithLoggerContext } from '@crowlog/async-context-plugin';
import { createLoggerFactory, type Logger } from '@crowlog/logger';
const asyncLocalStorage = new AsyncLocalStorage<Record<string, unknown>>();
export type { Logger };
export { addLogContext, wrapWithLoggerContext };
export function addLogContext(context: Record<string, unknown>) {
const currentContext = asyncLocalStorage.getStore();
if (!currentContext) {
console.warn('Trying to add log context outside of logger middleware');
return;
}
Object.assign(currentContext, context);
}
export function wrapWithLoggerContext<T>(data: Record<string, unknown>, cb: () => T) {
return asyncLocalStorage.run({ ...data }, cb);
}
export function createLogger({
namespace,
transports = [createConsoleLoggerTransport()],
}: {
namespace: string;
transports?: LoggerTransport[];
}) {
const buildLogger = ({ level }: { level: LogLevel }) => {
return (...args: [data: Record<string, unknown>, message: string] | [message: string]) => {
const [data, message] = args.length === 1 ? [{}, args[0]] : args;
const loggerContext = asyncLocalStorage.getStore();
const timestampMs = Date.now();
transports.forEach((transport) => {
transport.log({
level,
message,
timestampMs,
namespace,
data: {
...loggerContext,
...data,
},
});
});
};
};
return logLevels.reduce((acc, level) => ({
...acc,
[level]: buildLogger({ level }),
}), {} as Record<LogLevel, (...args: LogMethodArguments) => void>);
}
export const createLogger = createLoggerFactory({ plugins: [createAsyncContextPlugin()] });

View File

@@ -1,19 +0,0 @@
import type { createLogger } from './logger';
import type { logLevels } from './logger.constants';
export type Logger = ReturnType<typeof createLogger>;
export type LogLevel = typeof logLevels[number];
export type LoggerTransportLogArgs = {
level: LogLevel;
message: string;
timestampMs: number;
namespace: string;
data: Record<string, unknown>;
};
export type LoggerTransport = {
log: (args: LoggerTransportLogArgs) => void;
};
export type LogMethodArguments = [data: Record<string, unknown>, message: string] | [message: string];

View File

@@ -1,19 +0,0 @@
import type { LoggerTransport, LogLevel } from '../logger.types';
export function createConsoleLoggerTransport(): LoggerTransport {
return {
log({ level, data, ...extra }) {
const consoleMethodMap: Record<LogLevel, 'log' | 'warn' | 'error'> = {
debug: 'log',
info: 'log',
warn: 'warn',
error: 'error',
};
const consoleMethod = consoleMethodMap[level];
// eslint-disable-next-line no-console
console[consoleMethod]({ ...data, ...extra, level });
},
};
}

View File

@@ -1,35 +0,0 @@
import { omit } from 'lodash-es';
import { describe, expect, test } from 'vitest';
import { createLogger } from '../logger';
import { createInMemoryLoggerTransport } from './in-memory.logger-transport';
describe('in-memory logger-transport', () => {
test('logged messages are accessible through getLogs', () => {
const transport = createInMemoryLoggerTransport();
const logger = createLogger({
namespace: 'test',
transports: [transport],
});
logger.info('Hello world');
logger.error({ error: new Error('An error occurred') }, 'An error occurred');
expect(transport.getLogs().map(log => omit(log, 'timestampMs'))).to.eql([
{
level: 'info',
message: 'Hello world',
namespace: 'test',
data: {},
},
{
level: 'error',
message: 'An error occurred',
namespace: 'test',
data: {
error: new Error('An error occurred'),
},
},
]);
});
});

View File

@@ -1,25 +0,0 @@
import type { LoggerTransport, LoggerTransportLogArgs } from '../logger.types';
type LogWithoutTimestamp = Omit<LoggerTransportLogArgs, 'timestampMs'>;
type InMemoryLoggerTransport = LoggerTransport & {
getLogs: ((options: { excludeTimestampMs: true }) => LogWithoutTimestamp[])
& ((options?: { excludeTimestampMs?: false }) => LoggerTransportLogArgs[]);
};
export function createInMemoryLoggerTransport(): InMemoryLoggerTransport {
const logs: LoggerTransportLogArgs[] = [];
return {
log: (args) => {
logs.push(args);
},
getLogs: (options) => {
if (options?.excludeTimestampMs === true) {
return logs.map(({ timestampMs: _, ...log }) => log) as ReturnType<InMemoryLoggerTransport['getLogs']>;
}
return logs;
},
};
}

View File

@@ -1,6 +1,6 @@
import type { Database } from '../app/database/database.types';
import type { Config } from '../config/config.types';
import type { Logger } from '../shared/logger/logger.types';
import type { Logger } from '../shared/logger/logger';
import { isFunction } from 'lodash-es';
import { createLogger } from '../shared/logger/logger';

View File

@@ -1,6 +1,6 @@
import type { Database } from '../../modules/app/database/database.types';
import type { Config } from '../../modules/config/config.types';
import type { Logger } from '../../modules/shared/logger/logger.types';
import type { Logger } from '../../modules/shared/logger/logger';
import process from 'node:process';
import { setupDatabase } from '../../modules/app/database/database';
import { parseConfig } from '../../modules/config/config';

104
pnpm-lock.yaml generated
View File

@@ -217,6 +217,12 @@ importers:
'@corentinth/friendly-ids':
specifier: ^0.0.1
version: 0.0.1
'@crowlog/async-context-plugin':
specifier: ^1.0.0
version: 1.0.0
'@crowlog/logger':
specifier: ^1.1.0
version: 1.1.0
'@hono/node-server':
specifier: ^1.13.7
version: 1.13.8(hono@4.6.20)
@@ -269,6 +275,9 @@ importers:
'@antfu/eslint-config':
specifier: 'catalog:'
version: 3.14.0(@typescript-eslint/utils@8.22.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.7.3))(@vue/compiler-sfc@3.5.13)(astro-eslint-parser@1.1.0(typescript@5.7.3))(eslint-plugin-astro@1.3.1(eslint@9.18.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.18.0(jiti@2.4.2))(typescript@5.7.3)(vitest@3.0.5(@types/debug@4.1.12)(@types/node@22.12.0)(jsdom@26.0.0))
'@crowlog/pretty':
specifier: ^1.1.1
version: 1.1.1
'@total-typescript/ts-reset':
specifier: ^0.6.1
version: 0.6.1
@@ -611,8 +620,8 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/parser@7.26.7':
resolution: {integrity: sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==}
'@babel/parser@7.26.8':
resolution: {integrity: sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -638,8 +647,8 @@ packages:
resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==}
engines: {node: '>=6.9.0'}
'@babel/types@7.26.7':
resolution: {integrity: sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==}
'@babel/types@7.26.8':
resolution: {integrity: sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA==}
engines: {node: '>=6.9.0'}
'@bcoe/v8-coverage@1.0.2':
@@ -669,6 +678,19 @@ packages:
peerDependencies:
solid-js: ^1.8
'@crowlog/async-context-plugin@1.0.0':
resolution: {integrity: sha512-i35Sd16EJ/4Qkr21EfN+uBFqOm3aHhxkpdA9ZIvxydM+9uA9Da/FtwtxEhxxuj/ZlyONcabmcGqqULpuvUVNvQ==}
engines: {node: '>=22.0.0'}
'@crowlog/logger@1.1.0':
resolution: {integrity: sha512-ofV3BxN468s/b4b1cdzXU103rfloTXHalm6hAU+BMnuXRTg3LHrxqwA42gjO+93efqT6zRCAGKQVi03k7W8MOw==}
engines: {node: '>=22.0.0'}
'@crowlog/pretty@1.1.1':
resolution: {integrity: sha512-ZkUXxDdoBXJ2Cl/xybyfqTscGZAhlZIUIMhCRgMFQMuFwir8XblQ8mAom3vlUv/OZdzH8zbGryFLvJPDwGScEw==}
engines: {node: '>=22.0.0'}
hasBin: true
'@csstools/color-helpers@5.0.1':
resolution: {integrity: sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==}
engines: {node: '>=18'}
@@ -3273,6 +3295,9 @@ packages:
decimal.js@10.4.3:
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
decimal.js@10.5.0:
resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==}
decode-named-character-reference@1.0.2:
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
@@ -4364,6 +4389,7 @@ packages:
libsql@0.4.7:
resolution: {integrity: sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==}
cpu: [x64, arm64, wasm32]
os: [darwin, linux, win32]
lines-and-columns@1.2.4:
@@ -5264,6 +5290,11 @@ packages:
engines: {node: '>=10'}
hasBin: true
semver@7.7.1:
resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
engines: {node: '>=10'}
hasBin: true
seroval-plugins@1.1.1:
resolution: {integrity: sha512-qNSy1+nUj7hsCOon7AO4wdAIo9P0jrzAMp18XhiOzA6/uO5TKtP7ScozVJ8T293oRIvi5wyCHSM4TrJo/c/GJA==}
engines: {node: '>=10'}
@@ -5563,10 +5594,17 @@ packages:
tldts-core@6.1.70:
resolution: {integrity: sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==}
tldts-core@6.1.77:
resolution: {integrity: sha512-bCaqm24FPk8OgBkM0u/SrEWJgHnhBWYqeBo6yUmcZJDCHt/IfyWBb+14CXdGi4RInMv4v7eUAin15W0DoA+Ytg==}
tldts@6.1.70:
resolution: {integrity: sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==}
hasBin: true
tldts@6.1.77:
resolution: {integrity: sha512-lBpoWgy+kYmuXWQ83+R7LlJCnsd9YW8DGpZSHhrMl4b8Ly/1vzOie3OdtmUJDkKxcgRGOehDu5btKkty+JEe+g==}
hasBin: true
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@@ -5583,6 +5621,10 @@ packages:
resolution: {integrity: sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==}
engines: {node: '>=16'}
tough-cookie@5.1.1:
resolution: {integrity: sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==}
engines: {node: '>=16'}
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
@@ -5602,6 +5644,12 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
ts-api-utils@2.0.1:
resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==}
engines: {node: '>=18.12'}
peerDependencies:
typescript: '>=4.8.4'
ts-pattern@5.6.2:
resolution: {integrity: sha512-d4IxJUXROL5NCa3amvMg6VQW2HVtZYmUTPfvVtO7zJWGYLJ+mry9v2OmYm+z67aniQoQ8/yFNadiEwtNS9qQiw==}
@@ -7011,9 +7059,9 @@ snapshots:
dependencies:
'@babel/types': 7.26.3
'@babel/parser@7.26.7':
'@babel/parser@7.26.8':
dependencies:
'@babel/types': 7.26.7
'@babel/types': 7.26.8
'@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)':
dependencies:
@@ -7047,7 +7095,7 @@ snapshots:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
'@babel/types@7.26.7':
'@babel/types@7.26.8':
dependencies:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
@@ -7082,6 +7130,14 @@ snapshots:
'@floating-ui/dom': 1.6.13
solid-js: 1.9.4
'@crowlog/async-context-plugin@1.0.0': {}
'@crowlog/logger@1.1.0': {}
'@crowlog/pretty@1.1.1':
dependencies:
picocolors: 1.1.1
'@csstools/color-helpers@5.0.1': {}
'@csstools/css-calc@2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
@@ -8954,8 +9010,8 @@ snapshots:
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.5
semver: 7.7.0
ts-api-utils: 2.0.0(typescript@5.7.3)
semver: 7.7.1
ts-api-utils: 2.0.1(typescript@5.7.3)
typescript: 5.7.3
transitivePeerDependencies:
- supports-color
@@ -9311,7 +9367,7 @@ snapshots:
'@vue/compiler-core@3.5.13':
dependencies:
'@babel/parser': 7.26.7
'@babel/parser': 7.26.8
'@vue/shared': 3.5.13
entities: 4.5.0
estree-walker: 2.0.2
@@ -9324,7 +9380,7 @@ snapshots:
'@vue/compiler-sfc@3.5.13':
dependencies:
'@babel/parser': 7.26.7
'@babel/parser': 7.26.8
'@vue/compiler-core': 3.5.13
'@vue/compiler-dom': 3.5.13
'@vue/compiler-ssr': 3.5.13
@@ -9940,6 +9996,9 @@ snapshots:
decimal.js@10.4.3: {}
decimal.js@10.5.0:
optional: true
decode-named-character-reference@1.0.2:
dependencies:
character-entities: 2.0.2
@@ -11472,7 +11531,7 @@ snapshots:
dependencies:
cssstyle: 4.2.1
data-urls: 5.0.0
decimal.js: 10.4.3
decimal.js: 10.5.0
form-data: 4.0.1
html-encoding-sniffer: 4.0.0
http-proxy-agent: 7.0.2
@@ -11483,7 +11542,7 @@ snapshots:
rrweb-cssom: 0.8.0
saxes: 6.0.0
symbol-tree: 3.2.4
tough-cookie: 5.0.0
tough-cookie: 5.1.1
w3c-xmlserializer: 5.0.0
webidl-conversions: 7.0.0
whatwg-encoding: 3.1.1
@@ -12822,6 +12881,8 @@ snapshots:
semver@7.7.0: {}
semver@7.7.1: {}
seroval-plugins@1.1.1(seroval@1.1.1):
dependencies:
seroval: 1.1.1
@@ -13192,10 +13253,18 @@ snapshots:
tldts-core@6.1.70: {}
tldts-core@6.1.77:
optional: true
tldts@6.1.70:
dependencies:
tldts-core: 6.1.70
tldts@6.1.77:
dependencies:
tldts-core: 6.1.77
optional: true
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
@@ -13210,6 +13279,11 @@ snapshots:
dependencies:
tldts: 6.1.70
tough-cookie@5.1.1:
dependencies:
tldts: 6.1.77
optional: true
tr46@0.0.3: {}
tr46@5.0.0:
@@ -13224,6 +13298,10 @@ snapshots:
dependencies:
typescript: 5.7.3
ts-api-utils@2.0.1(typescript@5.7.3):
dependencies:
typescript: 5.7.3
ts-pattern@5.6.2: {}
tsconfck@3.1.4(typescript@5.7.3):