@bgord/bun 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/better-auth-logger.service.d.ts +3 -3
- package/dist/better-auth-logger.service.d.ts.map +1 -1
- package/dist/better-auth-logger.service.js +28 -6
- package/dist/better-auth-logger.service.js.map +1 -1
- package/dist/command-logger.service.d.ts +2 -2
- package/dist/command-logger.service.d.ts.map +1 -1
- package/dist/command-logger.service.js +1 -0
- package/dist/command-logger.service.js.map +1 -1
- package/dist/decorators.service.d.ts +2 -2
- package/dist/decorators.service.d.ts.map +1 -1
- package/dist/decorators.service.js +2 -0
- package/dist/decorators.service.js.map +1 -1
- package/dist/event-handler.service.d.ts +2 -2
- package/dist/event-handler.service.d.ts.map +1 -1
- package/dist/event-handler.service.js +4 -1
- package/dist/event-handler.service.js.map +1 -1
- package/dist/event-logger.service.d.ts +2 -2
- package/dist/event-logger.service.d.ts.map +1 -1
- package/dist/event-logger.service.js +1 -0
- package/dist/event-logger.service.js.map +1 -1
- package/dist/http-logger.middleware.d.ts +2 -2
- package/dist/http-logger.middleware.d.ts.map +1 -1
- package/dist/http-logger.middleware.js +3 -1
- package/dist/http-logger.middleware.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/jobs.service.d.ts +2 -2
- package/dist/jobs.service.d.ts.map +1 -1
- package/dist/jobs.service.js +7 -10
- package/dist/jobs.service.js.map +1 -1
- package/dist/logger-format-error.service.d.ts +3 -0
- package/dist/logger-format-error.service.d.ts.map +1 -0
- package/dist/logger-format-error.service.js +22 -0
- package/dist/logger-format-error.service.js.map +1 -0
- package/dist/logger-noop.adapter.d.ts +12 -0
- package/dist/logger-noop.adapter.d.ts.map +1 -0
- package/dist/logger-noop.adapter.js +11 -0
- package/dist/logger-noop.adapter.js.map +1 -0
- package/dist/logger-winston-local.adapter.d.ts +7 -0
- package/dist/logger-winston-local.adapter.d.ts.map +1 -0
- package/dist/logger-winston-local.adapter.js +18 -0
- package/dist/logger-winston-local.adapter.js.map +1 -0
- package/dist/logger-winston-production.adapter.d.ts +14 -0
- package/dist/logger-winston-production.adapter.d.ts.map +1 -0
- package/dist/logger-winston-production.adapter.js +32 -0
- package/dist/logger-winston-production.adapter.js.map +1 -0
- package/dist/logger-winston.adapter.d.ts +24 -0
- package/dist/logger-winston.adapter.d.ts.map +1 -0
- package/dist/logger-winston.adapter.js +28 -0
- package/dist/logger-winston.adapter.js.map +1 -0
- package/dist/logger.port.d.ts +71 -0
- package/dist/logger.port.d.ts.map +1 -0
- package/dist/logger.port.js +11 -0
- package/dist/logger.port.js.map +1 -0
- package/dist/mailer-noop.adapter.d.ts +2 -2
- package/dist/mailer-noop.adapter.d.ts.map +1 -1
- package/dist/mailer-noop.adapter.js +6 -1
- package/dist/mailer-noop.adapter.js.map +1 -1
- package/dist/mailer-smtp-with-logger.adapter.d.ts +2 -2
- package/dist/mailer-smtp-with-logger.adapter.d.ts.map +1 -1
- package/dist/mailer-smtp-with-logger.adapter.js +10 -2
- package/dist/mailer-smtp-with-logger.adapter.js.map +1 -1
- package/dist/prerequisites/log-file.d.ts +2 -2
- package/dist/prerequisites/log-file.d.ts.map +1 -1
- package/dist/prerequisites/log-file.js +2 -1
- package/dist/prerequisites/log-file.js.map +1 -1
- package/dist/setup.service.d.ts +2 -2
- package/dist/setup.service.d.ts.map +1 -1
- package/dist/setup.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/readme.md +6 -1
- package/src/better-auth-logger.service.ts +29 -6
- package/src/command-logger.service.ts +3 -2
- package/src/decorators.service.ts +4 -2
- package/src/event-handler.service.ts +6 -3
- package/src/event-logger.service.ts +3 -2
- package/src/http-logger.middleware.ts +5 -3
- package/src/index.ts +6 -1
- package/src/jobs.service.ts +10 -12
- package/src/logger-format-error.service.ts +24 -0
- package/src/logger-noop.adapter.ts +13 -0
- package/src/logger-winston-local.adapter.ts +17 -0
- package/src/logger-winston-production.adapter.ts +41 -0
- package/src/logger-winston.adapter.ts +46 -0
- package/src/logger.port.ts +69 -0
- package/src/mailer-noop.adapter.ts +8 -3
- package/src/mailer-smtp-with-logger.adapter.ts +12 -4
- package/src/prerequisites/log-file.ts +4 -3
- package/src/setup.service.ts +2 -2
- package/dist/logger.service.d.ts +0 -70
- package/dist/logger.service.d.ts.map +0 -1
- package/dist/logger.service.js +0 -92
- package/dist/logger.service.js.map +0 -1
- package/src/logger.service.ts +0 -168
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -66,7 +66,12 @@ src/
|
|
|
66
66
|
├── invariant-error-handler.service.ts
|
|
67
67
|
├── invariant.service.ts
|
|
68
68
|
├── jobs.service.ts
|
|
69
|
-
├── logger.service.ts
|
|
69
|
+
├── logger-format-error.service.ts
|
|
70
|
+
├── logger-noop.adapter.ts
|
|
71
|
+
├── logger-winston-local.adapter.ts
|
|
72
|
+
├── logger-winston-production.adapter.ts
|
|
73
|
+
├── logger-winston.adapter.ts
|
|
74
|
+
├── logger.port.ts
|
|
70
75
|
├── mailer-noop.adapter.ts
|
|
71
76
|
├── mailer-smtp-with-logger.adapter.ts
|
|
72
77
|
├── mailer-smtp.adapter.ts
|
|
@@ -1,19 +1,42 @@
|
|
|
1
1
|
import type { LogLevel } from "better-auth";
|
|
2
|
-
import { type
|
|
2
|
+
import { type LoggerPort, LogLevelEnum } from "./logger.port";
|
|
3
|
+
import { formatError } from "./logger-format-error.service";
|
|
3
4
|
|
|
4
5
|
export class BetterAuthLogger {
|
|
5
|
-
constructor(private readonly logger:
|
|
6
|
+
constructor(private readonly logger: LoggerPort) {}
|
|
6
7
|
|
|
7
8
|
attach() {
|
|
8
9
|
return {
|
|
9
10
|
disabled: false,
|
|
10
11
|
level: "debug",
|
|
11
|
-
log: (
|
|
12
|
-
this.
|
|
13
|
-
|
|
12
|
+
log: (lvl: LogLevel | undefined, message: string, ...args: unknown[]) => {
|
|
13
|
+
const level = this.mapLevel(lvl);
|
|
14
|
+
|
|
15
|
+
const base = {
|
|
16
|
+
component: "infra",
|
|
14
17
|
operation: "better-auth",
|
|
18
|
+
message,
|
|
15
19
|
metadata: { args },
|
|
16
|
-
}
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
switch (level) {
|
|
23
|
+
case LogLevelEnum.error: {
|
|
24
|
+
this.logger.error({
|
|
25
|
+
...base,
|
|
26
|
+
error: formatError(args.find((a) => a instanceof Error) ?? new Error(message)),
|
|
27
|
+
});
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
case LogLevelEnum.warn: {
|
|
31
|
+
this.logger.warn(base);
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
default: {
|
|
35
|
+
this.logger.info(base);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
17
40
|
} as const;
|
|
18
41
|
}
|
|
19
42
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LoggerPort } from "./logger.port";
|
|
2
2
|
|
|
3
3
|
export class CommandLogger {
|
|
4
|
-
constructor(private readonly logger:
|
|
4
|
+
constructor(private readonly logger: LoggerPort) {}
|
|
5
5
|
|
|
6
6
|
private _handle(
|
|
7
7
|
type: string,
|
|
@@ -15,6 +15,7 @@ export class CommandLogger {
|
|
|
15
15
|
|
|
16
16
|
this.logger.info({
|
|
17
17
|
message: `${commandName} emitted`,
|
|
18
|
+
component: "infra",
|
|
18
19
|
operation: "command_emitted",
|
|
19
20
|
metadata: commandData,
|
|
20
21
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as tools from "@bgord/tools";
|
|
2
|
-
import type {
|
|
2
|
+
import type { LoggerPort } from "./logger.port";
|
|
3
3
|
|
|
4
4
|
export class DecoratorTimeoutError extends Error {}
|
|
5
5
|
|
|
6
6
|
export class Decorators {
|
|
7
7
|
private readonly rounding = new tools.RoundToDecimal(2);
|
|
8
8
|
|
|
9
|
-
constructor(private readonly logger:
|
|
9
|
+
constructor(private readonly logger: LoggerPort) {}
|
|
10
10
|
|
|
11
11
|
duration() {
|
|
12
12
|
const that = this;
|
|
@@ -27,6 +27,7 @@ export class Decorators {
|
|
|
27
27
|
|
|
28
28
|
that.logger.info({
|
|
29
29
|
message: `${label} duration`,
|
|
30
|
+
component: "infra",
|
|
30
31
|
operation: "decorators_duration_ms",
|
|
31
32
|
metadata: { durationMs: that.rounding.round(after - before) },
|
|
32
33
|
});
|
|
@@ -53,6 +54,7 @@ export class Decorators {
|
|
|
53
54
|
|
|
54
55
|
that.logger.info({
|
|
55
56
|
message: `${label} inspector`,
|
|
57
|
+
component: "infra",
|
|
56
58
|
operation: "decorators_inspector",
|
|
57
59
|
metadata: { arguments: args, output: value },
|
|
58
60
|
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { z } from "zod/v4";
|
|
2
2
|
import type { GenericEventSchema } from "./event.types";
|
|
3
|
-
import type {
|
|
3
|
+
import type { LoggerPort } from "./logger.port";
|
|
4
|
+
import { formatError } from "./logger-format-error.service";
|
|
4
5
|
|
|
5
6
|
export class EventHandler {
|
|
6
|
-
constructor(private readonly logger:
|
|
7
|
+
constructor(private readonly logger: LoggerPort) {}
|
|
7
8
|
|
|
8
9
|
handle<T extends { name: z.infer<GenericEventSchema["shape"]["name"]> }>(fn: (event: T) => Promise<void>) {
|
|
9
10
|
return async (event: T) => {
|
|
@@ -12,8 +13,10 @@ export class EventHandler {
|
|
|
12
13
|
} catch (error) {
|
|
13
14
|
this.logger.error({
|
|
14
15
|
message: `Unknown ${event.name} event handler error`,
|
|
16
|
+
component: "infra",
|
|
15
17
|
operation: "unknown_event_handler_error",
|
|
16
|
-
|
|
18
|
+
error: formatError(error),
|
|
19
|
+
metadata: { name: event.name },
|
|
17
20
|
});
|
|
18
21
|
}
|
|
19
22
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LoggerPort } from "./logger.port";
|
|
2
2
|
|
|
3
3
|
export class EventLogger {
|
|
4
|
-
constructor(private readonly logger:
|
|
4
|
+
constructor(private readonly logger: LoggerPort) {}
|
|
5
5
|
|
|
6
6
|
private _handle(
|
|
7
7
|
type: string,
|
|
@@ -15,6 +15,7 @@ export class EventLogger {
|
|
|
15
15
|
|
|
16
16
|
this.logger.info({
|
|
17
17
|
message: `${eventName} emitted`,
|
|
18
|
+
component: "infra",
|
|
18
19
|
operation: "event_emitted",
|
|
19
20
|
metadata: eventData,
|
|
20
21
|
});
|
|
@@ -4,7 +4,7 @@ import _ from "lodash";
|
|
|
4
4
|
import { CacheHitEnum } from "./cache-resolver.service";
|
|
5
5
|
import { CacheResponse } from "./cache-response.middleware";
|
|
6
6
|
import type { CorrelationIdType } from "./correlation-id.vo";
|
|
7
|
-
import type {
|
|
7
|
+
import type { LoggerPort } from "./logger.port";
|
|
8
8
|
|
|
9
9
|
export class HttpLogger {
|
|
10
10
|
private static simplify(response: unknown) {
|
|
@@ -37,7 +37,7 @@ export class HttpLogger {
|
|
|
37
37
|
"if-none-match",
|
|
38
38
|
];
|
|
39
39
|
|
|
40
|
-
static build = (logger:
|
|
40
|
+
static build = (logger: LoggerPort) =>
|
|
41
41
|
createMiddleware(async (c, next) => {
|
|
42
42
|
const correlationId = c.get("requestId") as CorrelationIdType;
|
|
43
43
|
const info = getConnInfo(c);
|
|
@@ -64,6 +64,7 @@ export class HttpLogger {
|
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
logger.http({
|
|
67
|
+
component: "http",
|
|
67
68
|
operation: "http_request_before",
|
|
68
69
|
correlationId,
|
|
69
70
|
message: "request",
|
|
@@ -96,12 +97,13 @@ export class HttpLogger {
|
|
|
96
97
|
const durationMs = durationMsMatch?.[1] ? Number(durationMsMatch[1]) : undefined;
|
|
97
98
|
|
|
98
99
|
logger.http({
|
|
100
|
+
component: "http",
|
|
99
101
|
operation: "http_request_after",
|
|
100
102
|
correlationId,
|
|
101
103
|
message: "response",
|
|
102
104
|
method,
|
|
103
105
|
url,
|
|
104
|
-
|
|
106
|
+
status: c.res.status,
|
|
105
107
|
durationMs,
|
|
106
108
|
client,
|
|
107
109
|
metadata: HttpLogger.simplify(httpRequestAfterMetadata),
|
package/src/index.ts
CHANGED
|
@@ -39,7 +39,12 @@ export * from "./image-exif.service";
|
|
|
39
39
|
export * from "./invariant.service";
|
|
40
40
|
export * from "./invariant-error-handler.service";
|
|
41
41
|
export * from "./jobs.service";
|
|
42
|
-
export * from "./logger.
|
|
42
|
+
export * from "./logger.port";
|
|
43
|
+
export * from "./logger-format-error.service";
|
|
44
|
+
export * from "./logger-noop.adapter";
|
|
45
|
+
export * from "./logger-winston.adapter";
|
|
46
|
+
export * from "./logger-winston-local.adapter";
|
|
47
|
+
export * from "./logger-winston-production.adapter";
|
|
43
48
|
export * from "./mailer.port";
|
|
44
49
|
export * from "./mailer-noop.adapter";
|
|
45
50
|
export * from "./mailer-smtp.adapter";
|
package/src/jobs.service.ts
CHANGED
|
@@ -2,7 +2,8 @@ import * as tools from "@bgord/tools";
|
|
|
2
2
|
import type { Cron } from "croner";
|
|
3
3
|
import { CorrelationId } from "./correlation-id.vo";
|
|
4
4
|
import { CorrelationStorage } from "./correlation-storage.service";
|
|
5
|
-
import type {
|
|
5
|
+
import type { LoggerPort } from "./logger.port";
|
|
6
|
+
import { formatError } from "./logger-format-error.service";
|
|
6
7
|
|
|
7
8
|
export type JobNameType = string;
|
|
8
9
|
|
|
@@ -41,7 +42,7 @@ export type JobProcessorType = {
|
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
export class JobHandler {
|
|
44
|
-
constructor(private readonly logger:
|
|
45
|
+
constructor(private readonly logger: LoggerPort) {}
|
|
45
46
|
|
|
46
47
|
handle(jobProcessor: JobProcessorType) {
|
|
47
48
|
const correlationId = CorrelationId.parse(crypto.randomUUID());
|
|
@@ -55,6 +56,7 @@ export class JobHandler {
|
|
|
55
56
|
try {
|
|
56
57
|
that.logger.info({
|
|
57
58
|
message: `${jobProcessor.label} start`,
|
|
59
|
+
component: "infra",
|
|
58
60
|
operation: "job_start",
|
|
59
61
|
correlationId,
|
|
60
62
|
});
|
|
@@ -63,6 +65,7 @@ export class JobHandler {
|
|
|
63
65
|
|
|
64
66
|
that.logger.info({
|
|
65
67
|
message: `${jobProcessor.label} success`,
|
|
68
|
+
component: "infra",
|
|
66
69
|
operation: "job_success",
|
|
67
70
|
correlationId,
|
|
68
71
|
metadata: stopwatch.stop(),
|
|
@@ -70,12 +73,11 @@ export class JobHandler {
|
|
|
70
73
|
} catch (error) {
|
|
71
74
|
that.logger.error({
|
|
72
75
|
message: `${jobProcessor.label} error`,
|
|
76
|
+
component: "infra",
|
|
73
77
|
operation: "job_error",
|
|
74
78
|
correlationId,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
...stopwatch.stop(),
|
|
78
|
-
},
|
|
79
|
+
error: formatError(error),
|
|
80
|
+
metadata: { ...stopwatch.stop() },
|
|
79
81
|
});
|
|
80
82
|
}
|
|
81
83
|
};
|
|
@@ -85,11 +87,7 @@ export class JobHandler {
|
|
|
85
87
|
// biome-ignore lint: lint/complexity/noUselessThisAlias
|
|
86
88
|
const that = this;
|
|
87
89
|
|
|
88
|
-
return async () =>
|
|
89
|
-
that.logger.info({
|
|
90
|
-
message: `${cron.name} overrun`,
|
|
91
|
-
operation: "job_overrun",
|
|
92
|
-
});
|
|
93
|
-
};
|
|
90
|
+
return async () =>
|
|
91
|
+
that.logger.info({ message: `${cron.name} overrun`, component: "infra", operation: "job_overrun" });
|
|
94
92
|
}
|
|
95
93
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ErrorInfo } from "./logger.port";
|
|
2
|
+
|
|
3
|
+
export function formatError(err: unknown): ErrorInfo {
|
|
4
|
+
if (err instanceof Error) {
|
|
5
|
+
const code = (err as any)?.code;
|
|
6
|
+
const cause = (err as any)?.cause;
|
|
7
|
+
return {
|
|
8
|
+
name: err.name ?? "Error",
|
|
9
|
+
message: err.message ?? "Unknown error",
|
|
10
|
+
stack: err.stack, // always include if present
|
|
11
|
+
code: code != null ? String(code) : undefined,
|
|
12
|
+
cause:
|
|
13
|
+
cause instanceof Error
|
|
14
|
+
? { name: cause.name, message: cause.message }
|
|
15
|
+
: typeof cause === "string"
|
|
16
|
+
? { message: cause }
|
|
17
|
+
: undefined,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
name: "NonErrorThrown",
|
|
22
|
+
message: typeof err === "string" ? err : String(err),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LoggerPort } from "./logger.port";
|
|
2
|
+
|
|
3
|
+
export class LoggerNoopAdapter implements LoggerPort {
|
|
4
|
+
warn: LoggerPort["warn"] = (_log) => {};
|
|
5
|
+
error: LoggerPort["error"] = (_log) => {};
|
|
6
|
+
info: LoggerPort["info"] = (_log) => {};
|
|
7
|
+
http: LoggerPort["http"] = (_log) => {};
|
|
8
|
+
verbose: LoggerPort["verbose"] = (_log) => {};
|
|
9
|
+
debug: LoggerPort["debug"] = (_log) => {};
|
|
10
|
+
silly: LoggerPort["silly"] = (_log) => {};
|
|
11
|
+
|
|
12
|
+
setSilent: LoggerPort["setSilent"] = (_silent) => {};
|
|
13
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as winston from "winston";
|
|
2
|
+
import type { LogAppType, LoggerPort, LogLevelEnum } from "./logger.port";
|
|
3
|
+
import { LoggerWinstonAdapter } from "./logger-winston.adapter";
|
|
4
|
+
import { NodeEnvironmentEnum } from "./node-env.vo";
|
|
5
|
+
|
|
6
|
+
export class LoggerWinstonLocalAdapter {
|
|
7
|
+
constructor(private readonly app: LogAppType) {}
|
|
8
|
+
|
|
9
|
+
create(level: LogLevelEnum): LoggerPort {
|
|
10
|
+
return new LoggerWinstonAdapter({
|
|
11
|
+
app: this.app,
|
|
12
|
+
environment: NodeEnvironmentEnum.local,
|
|
13
|
+
level,
|
|
14
|
+
formats: [winston.format.prettyPrint()],
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { WinstonTransport as AxiomTransport } from "@axiomhq/winston";
|
|
2
|
+
import * as tools from "@bgord/tools";
|
|
3
|
+
import * as winston from "winston";
|
|
4
|
+
import type { LogAppType, LoggerPort, LogLevelEnum } from "./logger.port";
|
|
5
|
+
import { LoggerWinstonAdapter } from "./logger-winston.adapter";
|
|
6
|
+
import { NodeEnvironmentEnum } from "./node-env.vo";
|
|
7
|
+
|
|
8
|
+
type LoggerWinstonProductionAdapterConfigType = {
|
|
9
|
+
app: LogAppType;
|
|
10
|
+
AXIOM_API_TOKEN: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class LoggerWinstonProductionAdapter {
|
|
14
|
+
readonly prodLogFile: string;
|
|
15
|
+
|
|
16
|
+
constructor(private readonly config: LoggerWinstonProductionAdapterConfigType) {
|
|
17
|
+
this.prodLogFile = this.createProdLogFile();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
create(level: LogLevelEnum): LoggerPort {
|
|
21
|
+
const file = new winston.transports.File({
|
|
22
|
+
filename: `/var/log/${this.config.app}-${NodeEnvironmentEnum.production}.log`,
|
|
23
|
+
maxsize: tools.Size.toBytes({ unit: tools.SizeUnit.MB, value: 100 }),
|
|
24
|
+
maxFiles: 3,
|
|
25
|
+
tailable: true,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const axiom = new AxiomTransport({ token: this.config.AXIOM_API_TOKEN, dataset: this.config.app });
|
|
29
|
+
|
|
30
|
+
return new LoggerWinstonAdapter({
|
|
31
|
+
app: this.config.app,
|
|
32
|
+
environment: NodeEnvironmentEnum.production,
|
|
33
|
+
level,
|
|
34
|
+
transports: [file, axiom],
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private createProdLogFile() {
|
|
39
|
+
return `/var/log/${this.config.app}-${NodeEnvironmentEnum.production}.log`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as winston from "winston";
|
|
2
|
+
import { type LogAppType, type LoggerPort, LogLevelEnum } from "./logger.port";
|
|
3
|
+
import type { NodeEnvironmentEnum } from "./node-env.vo";
|
|
4
|
+
|
|
5
|
+
type WinstonLoggerOptions = {
|
|
6
|
+
app: LogAppType;
|
|
7
|
+
environment: NodeEnvironmentEnum;
|
|
8
|
+
level: LogLevelEnum;
|
|
9
|
+
formats?: winston.Logform.Format[];
|
|
10
|
+
transports?: winston.transport[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class LoggerWinstonAdapter implements LoggerPort {
|
|
14
|
+
private readonly logger: winston.Logger;
|
|
15
|
+
|
|
16
|
+
constructor(options: WinstonLoggerOptions) {
|
|
17
|
+
const format = winston.format.combine(
|
|
18
|
+
winston.format.errors({ stack: true }),
|
|
19
|
+
winston.format.timestamp(),
|
|
20
|
+
winston.format.json(),
|
|
21
|
+
...(options.formats ?? []),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
this.logger = winston.createLogger({
|
|
25
|
+
levels: winston.config.npm.levels,
|
|
26
|
+
level: options.level,
|
|
27
|
+
defaultMeta: { app: options.app, environment: options.environment },
|
|
28
|
+
handleExceptions: true,
|
|
29
|
+
handleRejections: true,
|
|
30
|
+
format,
|
|
31
|
+
transports: [new winston.transports.Console(), ...(options.transports ?? [])],
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
warn: LoggerPort["warn"] = (log) => this.logger.log({ level: LogLevelEnum.warn, ...log });
|
|
36
|
+
error: LoggerPort["error"] = (log) => this.logger.log({ level: LogLevelEnum.error, ...log });
|
|
37
|
+
info: LoggerPort["info"] = (log) => this.logger.log({ level: LogLevelEnum.info, ...log });
|
|
38
|
+
http: LoggerPort["http"] = (log) => this.logger.log({ level: LogLevelEnum.http, ...log });
|
|
39
|
+
verbose: LoggerPort["verbose"] = (log) => this.logger.log({ level: LogLevelEnum.verbose, ...log });
|
|
40
|
+
debug: LoggerPort["debug"] = (log) => this.logger.log({ level: LogLevelEnum.debug, ...log });
|
|
41
|
+
silly: LoggerPort["silly"] = (log) => this.logger.log({ level: LogLevelEnum.silly, ...log });
|
|
42
|
+
|
|
43
|
+
setSilent: LoggerPort["setSilent"] = (silent) => {
|
|
44
|
+
this.logger.silent = silent;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { CorrelationIdType } from "./correlation-id.vo";
|
|
2
|
+
import type { NodeEnvironmentEnum } from "./node-env.vo";
|
|
3
|
+
|
|
4
|
+
export type HttpClientInfo = { ip?: string; userAgent?: string };
|
|
5
|
+
|
|
6
|
+
export type LogAppType = string;
|
|
7
|
+
|
|
8
|
+
export type ErrorInfo = {
|
|
9
|
+
name?: string;
|
|
10
|
+
message?: string;
|
|
11
|
+
stack?: string;
|
|
12
|
+
code?: string;
|
|
13
|
+
cause?: { name?: string; message?: string };
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export enum LogLevelEnum {
|
|
17
|
+
error = "error",
|
|
18
|
+
warn = "warn",
|
|
19
|
+
info = "info",
|
|
20
|
+
http = "http",
|
|
21
|
+
verbose = "verbose",
|
|
22
|
+
debug = "debug",
|
|
23
|
+
silly = "silly",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type LogCoreType = {
|
|
27
|
+
/** ISO-8601 UTC string with milliseconds, e.g. 2025-08-23T18:42:31.123Z */
|
|
28
|
+
timestamp: string;
|
|
29
|
+
level: LogLevelEnum;
|
|
30
|
+
app: LogAppType;
|
|
31
|
+
environment: NodeEnvironmentEnum;
|
|
32
|
+
/** Bounded context / subsystem, e.g., "http" | "emotions" | "publishing" | "infra" */
|
|
33
|
+
component: string;
|
|
34
|
+
/** Machine-friendly operation name, e.g., "weekly_review_generate" */
|
|
35
|
+
operation: string;
|
|
36
|
+
/** Short human-readable sentence */
|
|
37
|
+
message: string;
|
|
38
|
+
correlationId?: CorrelationIdType;
|
|
39
|
+
metadata?: Record<string, any>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type LogHttpType = LogCoreType & {
|
|
43
|
+
level: LogLevelEnum.http;
|
|
44
|
+
component: "http";
|
|
45
|
+
method: string;
|
|
46
|
+
url: string;
|
|
47
|
+
status?: number;
|
|
48
|
+
durationMs?: number;
|
|
49
|
+
client: HttpClientInfo;
|
|
50
|
+
cacheHit?: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type LogErrorType = LogCoreType & { level: LogLevelEnum.error; error: ErrorInfo };
|
|
54
|
+
|
|
55
|
+
export type LogWarnType = LogCoreType & { level: LogLevelEnum.warn; error?: ErrorInfo };
|
|
56
|
+
|
|
57
|
+
export type AdapterInjectedFields = "timestamp" | "level" | "app" | "environment";
|
|
58
|
+
|
|
59
|
+
export interface LoggerPort {
|
|
60
|
+
error(entry: Omit<LogErrorType, AdapterInjectedFields>): void;
|
|
61
|
+
warn(entry: Omit<LogWarnType, AdapterInjectedFields>): void;
|
|
62
|
+
info(entry: Omit<LogCoreType, AdapterInjectedFields>): void;
|
|
63
|
+
http(entry: Omit<LogHttpType, AdapterInjectedFields>): void;
|
|
64
|
+
verbose(entry: Omit<LogCoreType, AdapterInjectedFields>): void;
|
|
65
|
+
debug(entry: Omit<LogCoreType, AdapterInjectedFields>): void;
|
|
66
|
+
silly(entry: Omit<LogCoreType, AdapterInjectedFields>): void;
|
|
67
|
+
|
|
68
|
+
setSilent(silent: boolean): void;
|
|
69
|
+
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import type { SendMailOptions } from "nodemailer";
|
|
2
|
-
import type {
|
|
2
|
+
import type { LoggerPort } from "./logger.port";
|
|
3
3
|
import type { MailerPort } from "./mailer.port";
|
|
4
4
|
|
|
5
5
|
type MailerSendOptionsType = SendMailOptions;
|
|
6
6
|
|
|
7
7
|
export class MailerNoopAdapter implements MailerPort {
|
|
8
|
-
constructor(private readonly logger:
|
|
8
|
+
constructor(private readonly logger: LoggerPort) {}
|
|
9
9
|
|
|
10
10
|
async send(message: MailerSendOptionsType): Promise<unknown> {
|
|
11
|
-
return this.logger.info({
|
|
11
|
+
return this.logger.info({
|
|
12
|
+
message: "[NOOP] Mailer adapter",
|
|
13
|
+
component: "mailer",
|
|
14
|
+
operation: "write",
|
|
15
|
+
metadata: message,
|
|
16
|
+
});
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
async verify() {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SendMailOptions } from "nodemailer";
|
|
2
|
-
import type {
|
|
2
|
+
import type { LoggerPort } from "./logger.port";
|
|
3
|
+
import { formatError } from "./logger-format-error.service";
|
|
3
4
|
import type { MailerPort } from "./mailer.port";
|
|
4
5
|
import type { MailerSmtpAdapter } from "./mailer-smtp.adapter";
|
|
5
6
|
|
|
@@ -7,7 +8,7 @@ type MailerSendOptionsType = SendMailOptions;
|
|
|
7
8
|
|
|
8
9
|
type SmtpMailerWithLoggerConfigType = {
|
|
9
10
|
smtpMailer: MailerSmtpAdapter;
|
|
10
|
-
logger:
|
|
11
|
+
logger: LoggerPort;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export class MailerSmtpWithLoggerAdapter implements MailerPort {
|
|
@@ -15,12 +16,18 @@ export class MailerSmtpWithLoggerAdapter implements MailerPort {
|
|
|
15
16
|
|
|
16
17
|
async send(message: MailerSendOptionsType): Promise<unknown> {
|
|
17
18
|
try {
|
|
18
|
-
this.config.logger.info({
|
|
19
|
+
this.config.logger.info({
|
|
20
|
+
message: "Mailer attempt",
|
|
21
|
+
component: "infra",
|
|
22
|
+
operation: "mailer",
|
|
23
|
+
metadata: message,
|
|
24
|
+
});
|
|
19
25
|
|
|
20
26
|
const result = await this.config.smtpMailer.send(message);
|
|
21
27
|
|
|
22
28
|
this.config.logger.info({
|
|
23
29
|
message: "Mailer success",
|
|
30
|
+
component: "infra",
|
|
24
31
|
operation: "mailer",
|
|
25
32
|
metadata: { message, result },
|
|
26
33
|
});
|
|
@@ -29,8 +36,9 @@ export class MailerSmtpWithLoggerAdapter implements MailerPort {
|
|
|
29
36
|
} catch (error) {
|
|
30
37
|
this.config.logger.error({
|
|
31
38
|
message: "Mailer error",
|
|
39
|
+
component: "infra",
|
|
32
40
|
operation: "mailer",
|
|
33
|
-
|
|
41
|
+
error: formatError(error),
|
|
34
42
|
});
|
|
35
43
|
|
|
36
44
|
throw error;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LoggerWinstonProductionAdapter } from "../logger-winston-production.adapter";
|
|
2
2
|
import * as prereqs from "../prerequisites.service";
|
|
3
3
|
|
|
4
4
|
type PrerequisiteLogFileConfigType = {
|
|
5
|
-
logger:
|
|
5
|
+
logger: LoggerWinstonProductionAdapter;
|
|
6
6
|
label: prereqs.PrerequisiteLabelType;
|
|
7
7
|
enabled?: boolean;
|
|
8
8
|
};
|
|
@@ -18,8 +18,9 @@ export class PrerequisiteLogFile extends prereqs.AbstractPrerequisite<Prerequisi
|
|
|
18
18
|
if (!this.enabled) return prereqs.PrerequisiteStatusEnum.undetermined;
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
|
-
const path = this.config.logger.
|
|
21
|
+
const path = this.config.logger.prodLogFile;
|
|
22
22
|
|
|
23
|
+
// TODO: adjust checks
|
|
23
24
|
const result = await Bun.file(path).exists();
|
|
24
25
|
|
|
25
26
|
return result ? this.pass() : this.reject();
|
package/src/setup.service.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { CorrelationStorage } from "./correlation-storage.service";
|
|
|
13
13
|
import { ETagExtractor } from "./etag-extractor.middleware";
|
|
14
14
|
import { HttpLogger } from "./http-logger.middleware";
|
|
15
15
|
import type { I18nConfigType } from "./i18n.service";
|
|
16
|
-
import type {
|
|
16
|
+
import type { LoggerPort } from "./logger.port";
|
|
17
17
|
import { TimeZoneOffset } from "./time-zone-offset.middleware";
|
|
18
18
|
import { WeakETagExtractor } from "./weak-etag-extractor.middleware";
|
|
19
19
|
|
|
@@ -28,7 +28,7 @@ type CorsOptions = Parameters<typeof cors>[0];
|
|
|
28
28
|
type SetupOverridesType = { cors?: CorsOptions };
|
|
29
29
|
|
|
30
30
|
export class Setup {
|
|
31
|
-
static essentials(logger:
|
|
31
|
+
static essentials(logger: LoggerPort, i18n: I18nConfigType, overrides?: SetupOverridesType) {
|
|
32
32
|
const corsOptions = overrides?.cors ?? { origin: "*" };
|
|
33
33
|
|
|
34
34
|
return [
|