Skip to content

Commit 8baa0dc

Browse files
committed
refactor: update logger service integration across the application
- Renamed logger variable to loggerService for consistency. - Updated logging calls to use the new loggerService structure. - Replaced winston with @omdxp/jslog for improved logging capabilities. - Added graceful shutdown handling for logger file streams. - Enhanced log messages with additional context and structured attributes.
1 parent eb07b06 commit 8baa0dc

11 files changed

Lines changed: 147 additions & 355 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ api/fetch_cache
2020
api/postgres_db
2121
api/meilisearch_db
2222
api/nodemon.json
23+
api/logs
2324

2425
# web
2526
bundle

api/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"@dzcode.io/data": "*",
1111
"@dzcode.io/models": "*",
1212
"@dzcode.io/utils": "*",
13+
"@omdxp/jslog": "^1.7.1",
1314
"@sentry/node": "^8.28.0",
1415
"@sentry/profiling-node": "^8.28.0",
1516
"@types/make-fetch-happen": "^10.0.4",
@@ -31,8 +32,7 @@
3132
"postgres": "^3.4.4",
3233
"reflect-metadata": "^0.2.2",
3334
"routing-controllers": "^0.10.4",
34-
"typedi": "^0.10.0",
35-
"winston": "^3.3.3"
35+
"typedi": "^0.10.0"
3636
},
3737
"devDependencies": {
3838
"@dzcode.io/tooling": "*",

api/src/ai/service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type OpenAIResponse = {
1818
export class AIService {
1919
constructor(
2020
private readonly configService: ConfigService,
21-
private readonly logger: LoggerService,
21+
private readonly loggerService: LoggerService,
2222
private readonly fetchService: FetchService,
2323
private readonly aiPromptRepository: AiPromptRepository,
2424
) {}
@@ -44,7 +44,7 @@ export class AIService {
4444

4545
const body = { model: "gpt-4o", messages: payloadWithValidationPrompt };
4646

47-
this.logger.info({ message: "Cached response not found, querying AI..." });
47+
this.loggerService.logger.info("Cached response not found, querying AI...");
4848
// todo-zm: change to captureEvent
4949
captureException("AI Query", { tags: { type: "CRON" }, extra: { body } });
5050

api/src/app/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,23 @@ useContainer(Container); // eslint-disable-line react-hooks/rules-of-hooks
5555
};
5656
const app: Application = createExpressServer(routingControllersOptions);
5757

58-
const logger = Container.get(LoggerService);
58+
const loggerService = Container.get(LoggerService);
5959

6060
Sentry.setupExpressErrorHandler(app);
6161

62+
// Graceful shutdown handler for logger file streams
63+
const shutdown = (signal: string) => {
64+
loggerService.logger.info("Received signal, closing logger streams", "signal", signal);
65+
loggerService.close();
66+
process.exit(0);
67+
};
68+
69+
process.on("SIGTERM", () => shutdown("SIGTERM"));
70+
process.on("SIGINT", () => shutdown("SIGINT"));
71+
6272
// Start it
6373
app.listen(PORT, () => {
6474
const commonConfig = fsConfig(NODE_ENV);
65-
logger.info({ message: `API Server up on: ${commonConfig.api.url}/` });
75+
loggerService.logger.info("API Server started", "url", commonConfig.api.url);
6676
});
6777
})();

api/src/app/middlewares/logger.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,44 @@
11
import { RequestHandler } from "express";
22
import { ExpressMiddlewareInterface, Middleware } from "routing-controllers";
3-
import { LoggerService, LogLevel } from "src/logger/service";
3+
import { LoggerService } from "src/logger/service";
44
import { Service } from "typedi";
5+
import { HttpReq, HttpRes, Level } from "@omdxp/jslog";
56

67
@Service()
78
@Middleware({ type: "after" })
89
export class LoggerMiddleware implements ExpressMiddlewareInterface {
910
constructor(private loggerService: LoggerService) {}
1011

1112
use: RequestHandler = (req, res, next) => {
12-
let logLevel: LogLevel = "info";
13+
let logLevel = Level.INFO;
1314
const { statusCode } = res;
14-
if (statusCode < 100 && statusCode >= 400) {
15-
logLevel = "error";
15+
16+
if (statusCode >= 500) {
17+
logLevel = Level.ERROR;
18+
} else if (statusCode >= 400) {
19+
logLevel = Level.WARN;
20+
} else if (statusCode >= 300) {
21+
logLevel = Level.DEBUG;
1622
}
1723

18-
this.loggerService.log(logLevel, {
19-
message: `${res.statusCode} ${req.method} ${req.url}`,
24+
const logger = this.loggerService.logger;
25+
const message = `${req.method} ${req.url}`;
26+
27+
const requestAttrs = HttpReq({
28+
method: req.method,
29+
url: req.url,
30+
ip: req.ip,
31+
userAgent: req.headers["user-agent"],
32+
});
33+
34+
const responseAttrs = HttpRes({
35+
status: statusCode,
2036
});
37+
38+
const logAttrs = [...requestAttrs, ...responseAttrs];
39+
40+
logger.log(logLevel, message, ...logAttrs);
41+
2142
next();
2243
};
2344
}

api/src/digest/cron.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class DigestCron {
4646
private isRunning = false;
4747

4848
constructor(
49-
private readonly logger: LoggerService,
49+
private readonly loggerService: LoggerService,
5050
private readonly dataService: DataService,
5151
private readonly githubService: GithubService,
5252
private readonly projectsRepository: ProjectRepository,
@@ -64,7 +64,7 @@ export class DigestCron {
6464
this.schedule,
6565
async () => {
6666
if (this.isRunning) {
67-
logger.warn({ message: "Digest cron already running" });
67+
loggerService.logger.warn("Digest cron already running");
6868
return;
6969
}
7070

@@ -75,10 +75,7 @@ export class DigestCron {
7575
this.isRunning = false;
7676
console.error(error);
7777
captureException(error, { tags: { type: "CRON" } });
78-
logger.error({
79-
message: `Digest cron failed: ${error}`,
80-
meta: { error },
81-
});
78+
loggerService.logger.error("Digest cron failed", "error", error);
8279
}
8380
this.isRunning = false;
8481
},
@@ -90,20 +87,20 @@ export class DigestCron {
9087
undefined,
9188
true,
9289
);
93-
logger.info({ message: "Digest cron initialized" });
90+
loggerService.logger.info("Digest cron initialized");
9491
}
9592

9693
/**
9794
* Generate a random runId, use it to tag all newly fetched data, persist it to the database, then delete all data that doesn't have that runId.
9895
*/
9996
private async run() {
10097
const runId = Math.random().toString(36).slice(2);
101-
this.logger.info({ message: `Digest cron started, runId: ${runId}` });
98+
this.loggerService.logger.info("Digest cron started", "runId", runId);
10299

103100
let projectsFromDataFolder = await this.dataService.listProjects();
104101

105102
if (this.configService.env().NODE_ENV === "development") {
106-
this.logger.info({ message: `Running in development mode, filtering projects` });
103+
this.loggerService.logger.info("Running in development mode, filtering projects");
107104
projectsFromDataFolder = projectsFromDataFolder.filter((p) =>
108105
["Open-listings", "dzcode.io website", "Mishkal", "System Monitor"].includes(p.name),
109106
);
@@ -326,7 +323,7 @@ it may contain non-translatable parts like acronyms, keep them as is.`;
326323
captureException(error, { tags: { type: "CRON" } });
327324
}
328325

329-
this.logger.info({ message: `Digest cron finished, runId: ${runId}` });
326+
this.loggerService.logger.info("Digest cron finished", "runId", runId);
330327
}
331328

332329
private async getRepoInfo(

api/src/fetch/service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { FetchConfig } from "./types";
99
export class FetchService {
1010
constructor(
1111
private readonly configService: ConfigService,
12-
private readonly logger: LoggerService,
12+
private readonly loggerService: LoggerService,
1313
) {
1414
const { FETCH_CACHE_PATH } = this.configService.env();
1515

@@ -46,10 +46,10 @@ export class FetchService {
4646

4747
private makeFetchHappenInstance;
4848
private async fetch<T>(url: string, options: FetchOptions) {
49-
this.logger.info({ message: `Fetching ${url}` });
49+
this.loggerService.logger.info("Fetching URL", "url", url);
5050
const response = await this.makeFetchHappenInstance(url, options);
5151
if (!response.ok) {
52-
this.logger.error({ message: `Failed to fetch ${url}`, meta: { status: response.status } });
52+
this.loggerService.logger.error("Failed to fetch URL", "url", url, "status", response.status);
5353
throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
5454
}
5555
const jsonResponse = (await response.json()) as T;

api/src/logger/service.ts

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,66 @@
11
import { Service } from "typedi";
2-
import winston from "winston";
2+
import {
3+
Logger,
4+
New,
5+
MultiHandler,
6+
PrettyHandler,
7+
ColorHandler,
8+
JSONHandler,
9+
FileHandler,
10+
Level,
11+
} from "@omdxp/jslog";
12+
import * as path from "path";
13+
import * as fs from "fs";
314

415
@Service()
516
export class LoggerService {
17+
private _logger: Logger;
18+
private fileHandler?: FileHandler;
19+
620
constructor() {
7-
this.logger = winston.createLogger({
8-
level: "info",
9-
format: winston.format.json(),
10-
transports: [
11-
new winston.transports.Console({
12-
format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
13-
}),
14-
],
15-
});
16-
}
21+
const isDev = process.env.NODE_ENV === "development";
22+
const logDir = path.join(process.cwd(), "logs");
1723

18-
public log(level: LogLevel, logInfo: LogObject) {
19-
this.logger.log(level, logInfo.message, logInfo.meta);
20-
}
24+
if (!fs.existsSync(logDir)) {
25+
fs.mkdirSync(logDir, { recursive: true });
26+
}
2127

22-
public info(logInfo: LogObject) {
23-
this.log("info", logInfo);
24-
}
28+
if (isDev) {
29+
const devHandler = new PrettyHandler({
30+
handler: new ColorHandler({
31+
level: Level.DEBUG,
32+
addSource: true,
33+
}),
34+
indent: 2,
35+
compactArrays: true,
36+
});
37+
this._logger = New(devHandler);
38+
} else {
39+
this.fileHandler = new FileHandler({
40+
filepath: path.join(logDir, "api.log"),
41+
maxSize: 50 * 1024 * 1024,
42+
maxFiles: 10,
43+
format: "json",
44+
level: Level.INFO,
45+
addSource: false,
46+
});
2547

26-
public error(logInfo: LogObject) {
27-
this.log("error", logInfo);
28-
}
48+
const productionHandler = new MultiHandler([
49+
new JSONHandler({ level: Level.INFO }),
50+
this.fileHandler,
51+
]);
2952

30-
public debug(logInfo: LogObject) {
31-
this.log("debug", logInfo);
53+
this._logger = New(productionHandler);
54+
}
3255
}
3356

34-
public warn(logInfo: LogObject) {
35-
this.log("warn", logInfo);
57+
public get logger(): Logger {
58+
return this._logger;
3659
}
3760

38-
private logger;
61+
public close(): void {
62+
if (this.fileHandler) {
63+
this.fileHandler.close();
64+
}
65+
}
3966
}
40-
41-
export type LogLevel = "info" | "error" | "debug" | "warn";
42-
type LogObject = {
43-
message: string;
44-
meta?: unknown;
45-
};

api/src/postgres/service.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,20 @@ export class PostgresService {
2222
private readonly configService: ConfigService,
2323
private readonly loggerService: LoggerService,
2424
) {
25-
this.loggerService.info({ message: "Initializing Postgres database" });
25+
this.loggerService.logger.info("Initializing Postgres database");
2626
const { POSTGRES_URI } = this.configService.env();
2727

2828
const queryClient = postgres(POSTGRES_URI);
2929
this.drizzleDB = drizzle(queryClient);
30-
this.loggerService.info({ message: "Database migration started" });
30+
this.loggerService.logger.info("Database migration started");
3131
}
3232

3333
public async migrate() {
3434
if (this.isReady) throw new Error("Database is already ready");
3535

36-
this.loggerService.info({ message: "Database migration started" });
36+
this.loggerService.logger.info("Database migration started");
3737
await migrate(this.drizzleDB, { migrationsFolder: join(__dirname, "../../db/migrations") });
38-
this.loggerService.info({ message: "Database migration complete" });
38+
this.loggerService.logger.info("Database migration complete");
3939

4040
this.isReady = true;
4141
}

0 commit comments

Comments
 (0)