Skip to content

Commit b2967f2

Browse files
committedDec 16, 2024
add direct metrics pushed to datadog
1 parent f416fd8 commit b2967f2

File tree

10 files changed

+257
-16
lines changed

10 files changed

+257
-16
lines changed
 

Diff for: ‎api/index.js

+7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { validateEnvironmentVariables } from './src/shared/infrastructure/valida
44

55
validateEnvironmentVariables();
66

7+
import metrics from 'datadog-metrics';
8+
79
import { disconnect, prepareDatabaseConnection } from './db/knex-database-connection.js';
810
import { createServer } from './server.js';
911
import { config } from './src/shared/config.js';
@@ -42,6 +44,11 @@ async function _exitOnSignal(signal) {
4244
logger.info(`Received signal: ${signal}.`);
4345
logger.info('Stopping HAPI server...');
4446
await server.stop({ timeout: 30000 });
47+
if (server.pixCustomIntervals) {
48+
logger.info('Closing metrics interval and flush metrics');
49+
server.pixCustomIntervals.forEach(clearInterval);
50+
metrics.flush();
51+
}
4552
if (server.oppsy) {
4653
logger.info('Stopping HAPI Oppsy server...');
4754
await server.oppsy.stop();

Diff for: ‎api/package-lock.json

+104
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: ‎api/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"bcrypt": "^5.0.1",
3737
"cron-parser": "^4.9.0",
3838
"dayjs": "^1.11.5",
39+
"datadog-metrics": "^0.12.0",
3940
"debug": "^4.3.4",
4041
"dotenv": "^16.0.1",
4142
"fast-levenshtein": "^3.0.0",

Diff for: ‎api/sample.env

+8
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ REDIS_URL=redis://localhost:6379
4040
# default: none
4141
# sample (everyday at 06:30 UTC): CACHE_RELOAD_TIME=30 6 * * *
4242

43+
# =====
44+
# DATADOG
45+
# =====
46+
47+
# Datadog access context
48+
#
49+
DATADOG_API_KEY=nokey
50+
4351
# =========
4452
# DATABASES
4553
# =========

Diff for: ‎api/server.js

+62-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Oppsy from '@1024pix/oppsy';
22
import Hapi from '@hapi/hapi';
3+
import metrics from 'datadog-metrics';
34
import { parse } from 'neoqs';
45

56
import { setupErrorHandling } from './config/server-setup-error-handling.js';
@@ -35,6 +36,7 @@ import { config } from './src/shared/config.js';
3536
import { monitoringTools } from './src/shared/infrastructure/monitoring-tools.js';
3637
import { plugins } from './src/shared/infrastructure/plugins/index.js';
3738
import { deserializer } from './src/shared/infrastructure/serializers/jsonapi/deserializer.js';
39+
import { logger } from './src/shared/infrastructure/utils/logger.js';
3840
// bounded context migration
3941
import { sharedRoutes } from './src/shared/routes.js';
4042
import { swaggers } from './src/shared/swaggers.js';
@@ -69,7 +71,25 @@ const { logOpsMetrics, port, logging } = config;
6971
const createServer = async () => {
7072
const server = createBareServer();
7173

72-
if (logOpsMetrics) await enableOpsMetrics(server);
74+
// initialisation of Datadog link for metrics publication
75+
if (config.environment !== 'development' || config.featureToggles.isDirectMetricsEnabled) {
76+
logger.info('Metric initialisation : linked to Datadog');
77+
metrics.init({
78+
host: config.infra.containerName,
79+
prefix: 'tests.',
80+
defaultTags: [`service:${config.infra.appName}`],
81+
});
82+
} else {
83+
logger.info('Metric initialisation : no reporter => no metrics sent');
84+
metrics.init({ reporter: metrics.NullReporter() });
85+
}
86+
87+
if (logOpsMetrics) {
88+
// OPS metrics via direct metrics
89+
if (config.featureToggles.isDirectMetricsEnabled) await enableOpsMetrics(server);
90+
// OPS metrics via Oppsy
91+
if (!config.featureToggles.isOppsyDisabled) await enableLegacyOpsMetrics(server);
92+
}
7393

7494
setupErrorHandling(server);
7595

@@ -125,6 +145,47 @@ const createBareServer = function () {
125145
};
126146

127147
const enableOpsMetrics = async function (server) {
148+
function collectMemoryStats() {
149+
const memUsage = process.memoryUsage();
150+
metrics.gauge(`captain.api.memory.rss`, memUsage.rss);
151+
metrics.gauge('captain.api.memory.heapTotal', memUsage.heapTotal);
152+
metrics.gauge('captain.api.memory.heapUsed', memUsage.heapUsed);
153+
}
154+
155+
const metricsInterval = setInterval(collectMemoryStats, 5000);
156+
server.pixCustomIntervals = [metricsInterval];
157+
158+
const gaugeConnections = (pool) => () => {
159+
logger.info('publie les metrics de knex');
160+
metrics.gauge('captain.api.knex.db_connections_used', pool.numUsed());
161+
metrics.gauge('captain.api.knex.db_connections_free', pool.numFree());
162+
metrics.gauge('captain.api.knex.db_connections_pending_creation', pool.numPendingCreates());
163+
metrics.gauge('captain.api.knex.db_connections_pending_detroy', pool['pendingDestroys'].length);
164+
};
165+
166+
const client = knex.client;
167+
gaugeConnections(client.pool)();
168+
169+
client.pool.on('createSuccess', gaugeConnections(client.pool));
170+
client.pool.on('acquireSuccess', gaugeConnections(client.pool));
171+
client.pool.on('release', gaugeConnections(client.pool));
172+
client.pool.on('destroySuccess', gaugeConnections(client.pool));
173+
174+
server.events.on('response', (request) => {
175+
const info = request.info;
176+
177+
const statusCode = request.raw.res.statusCode;
178+
const responseTime = (info.completed !== undefined ? info.completed : info.responded) - info.received;
179+
180+
metrics.histogram('captain.api.duration', responseTime, [
181+
`method:${request.route.method}`,
182+
`route:${request.route.path}`,
183+
`statusCode:${statusCode}`,
184+
]);
185+
});
186+
};
187+
188+
const enableLegacyOpsMetrics = async function (server) {
128189
const oppsy = new Oppsy(server);
129190

130191
oppsy.on('ops', (data) => {

Diff for: ‎api/src/shared/config.js

+9
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,12 @@ const configuration = (function () {
204204
),
205205
isAsyncQuestRewardingCalculationEnabled: toBoolean(process.env.FT_ENABLE_ASYNC_QUESTS_REWARDS_CALCULATION),
206206
isCertificationTokenScopeEnabled: toBoolean(process.env.FT_ENABLE_CERTIF_TOKEN_SCOPE),
207+
isDirectMetricsEnabled: toBoolean(process.env.FT_ENABLE_DIRECT_METRICS),
207208
isNeedToAdjustCertificationAccessibilityEnabled: toBoolean(
208209
process.env.FT_ENABLE_NEED_TO_ADJUST_CERTIFICATION_ACCESSIBILITY,
209210
),
210211
isNewAuthenticationDesignEnabled: toBoolean(process.env.FT_NEW_AUTHENTICATION_DESIGN_ENABLED),
212+
isOppsyDisabled: toBoolean(process.env.FT_OPPSY_DISABLED),
211213
isPix1dEnabled: toBoolean(process.env.FT_PIX_1D_ENABLED),
212214
isPixCompanionEnabled: toBoolean(process.env.FT_PIX_COMPANION_ENABLED),
213215
isSelfAccountDeletionEnabled: toBoolean(process.env.FT_SELF_ACCOUNT_DELETION),
@@ -223,6 +225,8 @@ const configuration = (function () {
223225
enableRequestMonitoring: toBoolean(process.env.ENABLE_REQUEST_MONITORING),
224226
},
225227
infra: {
228+
appName: process.env.APP,
229+
containerName: process.env.CONTAINER,
226230
concurrencyForHeavyOperations: _getNumber(process.env.INFRA_CONCURRENCY_HEAVY_OPERATIONS, 2),
227231
chunkSizeForCampaignResultProcessing: _getNumber(process.env.INFRA_CHUNK_SIZE_CAMPAIGN_RESULT_PROCESSING, 10),
228232
chunkSizeForOrganizationLearnerDataProcessing: _getNumber(
@@ -290,6 +294,9 @@ const configuration = (function () {
290294
},
291295
},
292296
},
297+
metrics: {
298+
flushIntervalSeconds: _getNumber(process.env.DIRECT_METRICS_FLUSH_INTERVAL, 5),
299+
},
293300
partner: {
294301
fetchTimeOut: ms(process.env.FETCH_TIMEOUT_MILLISECONDS || '20s'),
295302
},
@@ -412,7 +419,9 @@ const configuration = (function () {
412419
config.featureToggles.deprecatePoleEmploiPushNotification = false;
413420
config.featureToggles.isAlwaysOkValidateNextChallengeEndpointEnabled = false;
414421
config.featureToggles.isCertificationTokenScopeEnabled = false;
422+
config.featureToggles.isDirectMetricsEnabled = false;
415423
config.featureToggles.isNeedToAdjustCertificationAccessibilityEnabled = false;
424+
config.featureToggles.isOppsyDisabled = false;
416425
config.featureToggles.isPix1dEnabled = true;
417426
config.featureToggles.isPixCompanionEnabled = false;
418427
config.featureToggles.isSelfAccountDeletionEnabled = false;

Diff for: ‎api/src/shared/infrastructure/plugins/pino.js

+14-9
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,20 @@ const plugin = {
7171

7272
server.events.on('response', (request) => {
7373
const info = request.info;
74-
logger.info(
75-
{
76-
queryParams: request.query,
77-
req: request,
78-
res: request.raw.res,
79-
responseTime: (info.completed !== undefined ? info.completed : info.responded) - info.received,
80-
},
81-
'request completed',
82-
);
74+
75+
const shouldLog = !config.featureToggles.isDirectMetricsEnabled;
76+
77+
if (shouldLog || request.raw.res.statusCode != 200) {
78+
logger.info(
79+
{
80+
queryParams: request.query,
81+
req: request,
82+
res: request.raw.res,
83+
responseTime: (info.completed !== undefined ? info.completed : info.responded) - info.received,
84+
},
85+
'request completed',
86+
);
87+
}
8388
});
8489
},
8590
};

Diff for: ‎api/tests/shared/acceptance/application/feature-toggles/feature-toggle-controller_test.js

+2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ describe('Acceptance | Shared | Application | Controller | feature-toggle', func
2424
'is-always-ok-validate-next-challenge-endpoint-enabled': false,
2525
'is-async-quest-rewarding-calculation-enabled': false,
2626
'is-certification-token-scope-enabled': false,
27+
'is-direct-metrics-enabled': false,
2728
'is-need-to-adjust-certification-accessibility-enabled': false,
2829
'is-new-authentication-design-enabled': false,
30+
'is-oppsy-disabled': false,
2931
'is-pix1d-enabled': true,
3032
'is-pix-companion-enabled': false,
3133
'is-quest-enabled': false,

0 commit comments

Comments
 (0)