Skip to content

Commit 1b1ff2b

Browse files
authored
[OGUI-1748] QC should be using logging that has facility set per process env (#3052)
* [OGUI-1748] Logging to use process.env in QCG and documentation (#3008)
1 parent ce942da commit 1b1ff2b

14 files changed

Lines changed: 85 additions & 28 deletions

QualityControl/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ QCG is a **Web-based graphical user interface (GUI)** designed for [**O<sup>2</s
1212

1313
## Table of Contents
1414
- [Installation](docs/Installation.md)
15+
- [QCG Developer Logging Conventions](./docs/QCG_Developer_Logging_Conventions.md)
1516
- [Architecture](docs/Architecture.md)
1617
- [Testing](docs/Testing.md)
1718
- [Features](docs/Features.md)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# QCG Developer Logging Conventions
2+
3+
## Introduction
4+
5+
QCG (Quality Control GUI) is deployed in multiple production instances, each serving a separate QCDB database. To ensure clear separation of logs and traceability across deployments, developers must follow specific logging conventions.
6+
7+
## Logging with LogManager and LOG_FACILITY
8+
9+
Logging in QCG should use the O2 Logging application from the `@aliceo2/web-ui` framework. This wraps the Winston library and O2's InfoLogger project, providing a unified logging interface.
10+
11+
### Why Use LOG_FACILITY?
12+
13+
Each QCG deployment (e.g., `qcg`, `qcg-mc`, `qcg-async`) should have its own log label. This allows logs to be easily attributed to the correct deployment and QCDB instance.
14+
The standard for the log_facility is: `<application_name>/<file>`
15+
The standard for the log_facility is: `<application_name>/<service>`
16+
17+
> **Note:** The `<service>` portion should be a descriptive name for the service, module, or controller (e.g., `bkp-service`, `obj-controller`). This helps make logs more readable and traceable. Avoid using generic filenames; prefer meaningful service names as shown in the examples below.
18+
### Setting the Log Facility
19+
20+
The log facility string should be built using the `process.env.npm_config_log_label` environment variable. If not set, a sensible default (e.g., `qcg`) should be used.
21+
22+
### Example: Setting Up Logging in BookkeepingService
23+
24+
```js
25+
import { LogManager } from '@aliceo2/web-ui';
26+
27+
// Use the environment variable or a default value
28+
const LOG_FACILITY = `${process.env.npm_config_log_label ?? 'qcg'}/bkp-service`;
29+
30+
// Initialize the logger for this service
31+
this._logger = LogManager.getLogger(LOG_FACILITY);
32+
33+
// Usage example
34+
this._logger.infoMessage('BookkeepingService started');
35+
```
36+
37+
### Best Practices
38+
39+
- **Always use the provided logger:** Do not use `console.log` or other logging libraries directly.
40+
- **Set the log label via environment variable:** This ensures logs are correctly attributed in multi-instance deployments.
41+
- **Log meaningful events:** Include context in log messages to aid debugging and monitoring.

QualityControl/lib/controllers/ObjectController.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
'use strict';
1515
import { LogManager, updateAndSendExpressResponseFromNativeError } from '@aliceo2/web-ui';
1616

17+
const LOG_FACILITY = `${process.env.npm_config_log_label ?? 'qcg'}/obj-controller`;
18+
1719
/**
1820
* Gateway for all QC Objects requests
1921
* @class
@@ -35,7 +37,7 @@ export class ObjectController {
3537
* @type {RunMonitoringService}
3638
*/
3739
this._runModeService = runModeService;
38-
this._logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'qcg'}/object-ctrl`);
40+
this._logger = LogManager.getLogger(LOG_FACILITY);
3941
}
4042

4143
/**

QualityControl/lib/controllers/UserController.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
*/
1414

1515
import assert from 'assert';
16-
import { LogManager } from '@aliceo2/web-ui';
16+
import { LogLevel, LogManager } from '@aliceo2/web-ui';
17+
18+
const LOG_FACILITY = `${process.env.npm_config_log_label ?? 'qcg'}/user-controller`;
1719

1820
/**
1921
* @typedef {import('../repositories/UserRepository.js').UserRepository} UserRepository
2022
*/
2123

22-
const logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'qcg'}/user`);
23-
2424
/**
2525
* Gateway for all User data calls
2626
*/
@@ -32,6 +32,7 @@ export class UserController {
3232
*/
3333
constructor(userRepository) {
3434
assert(userRepository, 'Missing User Repository');
35+
this._logger = LogManager.getLogger(LOG_FACILITY);
3536

3637
/**
3738
* User repository for interacting with user data.
@@ -56,9 +57,11 @@ export class UserController {
5657
res.status(200).json({ ok: true });
5758
} catch (err) {
5859
if (err.stack) {
59-
logger.trace(err);
60+
this._logger.trace(err);
6061
}
61-
logger.error('Unable to add user to memory');
62+
this._logger.errorMessage('Unable to add user to memory', {
63+
level: LogLevel.SUPPORT,
64+
});
6265
res.status(502).json({ ok: false, message: 'Unable to add user to memory' });
6366
}
6467
}

QualityControl/lib/database/SequelizeDatabase.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ import { getDbConfig } from '../config/database.js';
2121
import models from './models/index.js';
2222
import { SequelizeStorage } from 'umzug';
2323

24+
const LOG_FACILITY = `${process.env.npm_config_log_label ?? 'qcg'}/database`;
25+
2426
/**
2527
* Sequelize implementation of the Database.
2628
*/
2729
export class SequelizeDatabase {
2830
constructor(config) {
29-
this._logger = LogManager.getLogger('qcg/database');
31+
this._logger = LogManager.getLogger(LOG_FACILITY);
3032

3133
if (!config) {
3234
this._logger.warnMessage('No configuration provided for SequelizeDatabase. Using default configuration.');

QualityControl/lib/database/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414

1515
import { LogManager } from '@aliceo2/web-ui';
1616

17+
const LOG_FACILITY = `${process.env.npm_config_log_label ?? 'qcg'}/database`;
18+
1719
/**
1820
* Initializes the database connection and runs migrations.
1921
* @param {object} sequelizeDatabase - The Sequelize database instance.
2022
* @returns {Promise<void>} A promise that resolves when the database is initialized.
2123
*/
2224
export const initDatabase = async (sequelizeDatabase) => {
23-
const _logger = LogManager.getLogger('qcg/database');
25+
const _logger = LogManager.getLogger(LOG_FACILITY);
2426
try {
2527
await sequelizeDatabase.connect();
2628
await sequelizeDatabase.migrate();

QualityControl/lib/database/migrations/20250424113921-add-default-options.mjs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
/**
1616
* Creates default options for drawing and display hints in the database.
1717
* @param {object} queryInterface - The Sequelize query interface.
18-
* @param {Sequelize} Sequelize - The Sequelize library.
1918
* @returns {Promise<void>} A promise that resolves when the options are created.
2019
*/
2120
export const up = async (queryInterface) => {

QualityControl/lib/services/BookkeepingService.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ import { RunStatus } from '../../common/library/runStatus.enum.js';
1616
import { httpGetJson } from '../utils/httpRequests.js';
1717
import { LogManager } from '@aliceo2/web-ui';
1818

19-
const logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'bkp-service'}`);
2019
const GET_BKP_DATABASE_STATUS_PATH = '/api/status/database';
2120
const GET_RUN_TYPES_PATH = '/api/runTypes';
2221
const GET_RUN_PATH = '/api/runs';
2322

23+
const LOG_FACILITY = `${process.env.npm_config_log_label ?? 'qcg'}/bkp-service`;
24+
2425
/**
2526
* BookkeepingService class to be used to retrieve data from Bookkeeping
2627
*/
@@ -34,6 +35,8 @@ export class BookkeepingService {
3435
this._port = null;
3536
this._token = '';
3637
this._protocol = '';
38+
39+
this._logger = LogManager.getLogger(LOG_FACILITY);
3740
}
3841

3942
/**
@@ -69,12 +72,12 @@ export class BookkeepingService {
6972
*/
7073
async connect() {
7174
if (!this.validateConfig()) {
72-
logger.infoMessage(`Bookkeeping service will not be used. Reason: ${this.error}`);
75+
this._logger.infoMessage(`Bookkeeping service will not be used. Reason: ${this.error}`);
7376
return;
7477
}
7578
this.active = await this.simulateConnection();
7679
if (!this.active) {
77-
logger.infoMessage(`Bookkeeping service will not be used. Reason: ${this.error}`);
80+
this._logger.infoMessage(`Bookkeeping service will not be used. Reason: ${this.error}`);
7881
}
7982
}
8083

@@ -94,7 +97,7 @@ export class BookkeepingService {
9497
},
9598
);
9699
if (data && data?.status?.ok && data?.status?.configured) {
97-
logger.infoMessage('Successfully connected to Bookkeeping');
100+
this._logger.infoMessage('Successfully connected to Bookkeeping');
98101
return true;
99102
} else {
100103
this.error = 'Bookkeeping service is not configured or status is not OK';
@@ -133,7 +136,7 @@ export class BookkeepingService {
133136
*/
134137
async retrieveRunStatus(runNumber) {
135138
if (!this.active) {
136-
logger.warnMessage('Could not connect to bookkeeping');
139+
this._logger.warnMessage('Could not connect to bookkeeping');
137140
return RunStatus.BOOKKEEPING_UNAVAILABLE;
138141
}
139142

@@ -151,10 +154,10 @@ export class BookkeepingService {
151154
} catch (error) {
152155
const msg = error?.message ?? String(error);
153156
if (msg.includes('404')) {
154-
logger.warnMessage(`Run number ${runNumber} not found in bookkeeping`);
157+
this._logger.warnMessage(`Run number ${runNumber} not found in bookkeeping`);
155158
return RunStatus.NOT_FOUND;
156159
}
157-
logger.errorMessage(`Error fetching run status: ${error.message || error}`);
160+
this._logger.errorMessage(`Error fetching run status: ${error.message || error}`);
158161
return RunStatus.UNKNOWN;
159162
}
160163
}

QualityControl/lib/services/FilterService.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414

1515
import { LogManager } from '@aliceo2/web-ui';
1616
import { RunStatus } from '../../common/library/runStatus.enum.js';
17-
const LOG_FACILITY = `${process.env.npm_config_log_label ?? 'qcg'}/filter-svc`;
17+
18+
const LOG_FACILITY = `${process.env.npm_config_log_label ?? 'qcg'}/filter-service`;
1819

1920
/**
2021
* High level service that composes, processes and maps data from the bookkeeping service
@@ -29,6 +30,7 @@ export class FilterService {
2930
this._logger = LogManager.getLogger(LOG_FACILITY);
3031
this._bookkeepingService = bookkeepingService;
3132
this._runTypes = [];
33+
this.initFilters();
3234

3335
this._runTypesRefreshInterval = config?.bookkeeping?.runTypesRefreshInterval ??
3436
(config?.bookkeeping ? 24 * 60 * 60 * 1000 : -1);

QualityControl/lib/services/JsonFileService.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313
*/
1414

1515
import { LogManager } from '@aliceo2/web-ui';
16-
const logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'qcg'}/json`);
1716
import fs from 'fs';
1817
import path from 'path';
1918

19+
const LOG_FACILITY = `${process.env.npm_config_log_label ?? 'qcg'}/json-service`;
20+
2021
/**
2122
* Store layouts inside JSON based file with atomic write
2223
*/
@@ -26,6 +27,8 @@ export class JsonFileService {
2627
* @param {string} pathname - path to JSON DB file
2728
*/
2829
constructor(pathname) {
30+
this._logger = LogManager.getLogger(LOG_FACILITY);
31+
2932
// Path of the file to store data
3033
this.pathname = path.join(pathname);
3134
this.pathnameTmp = `${this.pathname}~tmp`;
@@ -41,7 +44,7 @@ export class JsonFileService {
4144
async _syncFileAndInternalState() {
4245
await this._readFromFile();
4346
await this.writeToFile();
44-
logger.infoMessage(`Preferences will be saved in ${this.pathname}`);
47+
this._logger.infoMessage(`Preferences will be saved in ${this.pathname}`);
4548
}
4649

4750
/**
@@ -54,7 +57,7 @@ export class JsonFileService {
5457
if (err) {
5558
// File does not exist, it's ok, we will create it
5659
if (err.code === 'ENOENT') {
57-
logger.info('DB file does not exist, will create one');
60+
this._logger.infoMessage('DB file does not exist, will create one');
5861
return resolve();
5962
}
6063

@@ -93,9 +96,9 @@ export class JsonFileService {
9396
const dataToFile = JSON.stringify(this.data, null, 1);
9497
await fs.promises.writeFile(this.pathnameTmp, dataToFile);
9598
await fs.promises.rename(this.pathnameTmp, this.pathname);
96-
logger.infoMessage('DB file updated');
99+
this._logger.infoMessage('DB file updated');
97100
} catch (err) {
98-
logger.errorMessage('Error writing to DB file:', err);
101+
this._logger.errorMessage('Error writing to DB file:', err);
99102
throw err;
100103
} finally {
101104
this.lock.release();

0 commit comments

Comments
 (0)