From c39c08537ca572d26d4d2aea4b3bb2b3b5290c23 Mon Sep 17 00:00:00 2001 From: Enrico Regge Date: Fri, 27 Mar 2026 17:56:24 +0100 Subject: [PATCH] Addressed bugs outlined #321 --- cloudant-change-listener/Dockerfile | 6 +-- cloudant-change-listener/job/ce-api-utils.mjs | 35 +++++++-------- cloudant-change-listener/job/job.mjs | 44 +++++++++---------- cloudant-change-listener/job/package.json | 12 ++--- fotobox/frontend-app/Dockerfile | 2 +- 5 files changed, 45 insertions(+), 54 deletions(-) diff --git a/cloudant-change-listener/Dockerfile b/cloudant-change-listener/Dockerfile index d2a4f8650..97f2c0ab1 100644 --- a/cloudant-change-listener/Dockerfile +++ b/cloudant-change-listener/Dockerfile @@ -1,10 +1,10 @@ -FROM registry.access.redhat.com/ubi9/nodejs-22:latest AS build-env +FROM registry.access.redhat.com/ubi9/nodejs-24:latest AS build-env WORKDIR /opt/app-root/src COPY --chown=default:root job/* . RUN npm install # Use a small distroless image for as runtime image -FROM gcr.io/distroless/nodejs22 +FROM gcr.io/distroless/nodejs24 COPY --from=build-env /opt/app-root/src /app WORKDIR /app -ENTRYPOINT ["job.mjs"] \ No newline at end of file +CMD ["job.mjs"] \ No newline at end of file diff --git a/cloudant-change-listener/job/ce-api-utils.mjs b/cloudant-change-listener/job/ce-api-utils.mjs index b6a077605..1a31c07ef 100644 --- a/cloudant-change-listener/job/ce-api-utils.mjs +++ b/cloudant-change-listener/job/ce-api-utils.mjs @@ -1,25 +1,20 @@ -import log4js from "log4js"; import { IamAuthenticator } from "ibm-cloud-sdk-core"; import CodeEngineV2 from "@ibm-cloud/ibm-code-engine-sdk/dist/code-engine/v2.js"; +import winston from "winston"; +const { combine, json } = winston.format; -// -// use a formatted logger to have timestamps in the log output -log4js.configure({ - appenders: { - out: { type: "stdout" }, - }, - categories: { - default: { appenders: ["out"], level: process.env.LOGLEVEL || "debug" }, - }, +const logger = winston.createLogger({ + level: "debug", + transports: [new winston.transports.Console()], + format: combine(json()), }); -const logger = log4js.getLogger(); let codeEngineApi; function getCodeEngineApi() { if (!codeEngineApi) { // Extracting the region from the CE_DOMAIN (us-south.codeengine.appdomain.cloud) const region = process.env.CE_DOMAIN.substring(0, process.env.CE_DOMAIN.indexOf(".")); - logger.trace(`CE_DOMAIN: '${process.env.CE_DOMAIN}', region: '${region}'`); + logger.debug(`CE_DOMAIN: '${process.env.CE_DOMAIN}', region: '${region}'`); // Construct the Code Engine client using the IAM authenticator. // see: https://cloud.ibm.com/apidocs/codeengine/v2?code=node#authentication @@ -36,17 +31,17 @@ function getCodeEngineApi() { export async function getJobConfig(projectId, configMapName) { const fn = "getJobConfig "; - logger.trace(`${fn}> configMapName: '${configMapName}'`); + logger.debug(`${fn}> configMapName: '${configMapName}'`); const configMap = await getConfigMap(projectId, configMapName); - logger.trace(`${fn}< configmap '${configMapName}' exists `); + logger.debug(`${fn}< configmap '${configMapName}' exists `); return configMap; } export async function updateJobConfig(projectId, configMapName, configDataToUpdate) { const fn = "updateJobConfig "; - logger.trace(`${fn}> configMapName: '${configMapName}', configDataToUpdate: '${JSON.stringify(configDataToUpdate)}'`); + logger.debug(`${fn}> configMapName: '${configMapName}', configDataToUpdate: '${JSON.stringify(configDataToUpdate)}'`); const updatedConfigMap = await updateConfigMap(projectId, configMapName, configDataToUpdate); - logger.trace(`${fn}< updated configmap '${JSON.stringify(updatedConfigMap)}'`); + logger.debug(`${fn}< updated configmap '${JSON.stringify(updatedConfigMap)}'`); } export async function isJobAlreadyRunning(projectId, jobName) { @@ -55,7 +50,7 @@ export async function isJobAlreadyRunning(projectId, jobName) { try { const jobRunList = await listJobRuns(projectId, jobName); - logger.trace(`${fn}- jobRunList: '${JSON.stringify(jobRunList)}'`); + logger.debug(`${fn}- jobRunList: '${JSON.stringify(jobRunList)}'`); if (!jobRunList || !Array.isArray(jobRunList)) { logger.debug(`${fn}< false - unexpected response`); @@ -115,7 +110,7 @@ async function listJobRuns(projectId, jobName) { logger.debug(`Successfully listed all runs of job '${jobName}' - duration: ${Date.now() - startTime}ms`); return allResults; } catch (err) { - logger.error(`Failed to list runs of job '${jobName}', err =`, err); + logger.error(`Failed to list runs of job '${jobName}'`, err); throw err; } } @@ -142,7 +137,7 @@ async function updateConfigMap(projectId, name, configData) { logger.debug(`Successfully updated ConfigMap '${name}' - duration: ${Date.now() - startTime}ms`); return res.result; } catch (err) { - logger.error(`Failed to update ConfigMap '${JSON.stringify(params)}', err =`, err); + logger.error(`Failed to update ConfigMap '${JSON.stringify(params)}'`, err); throw err; } } @@ -166,7 +161,7 @@ async function getConfigMap(projectId, name) { logger.debug(`Successfully read ConfigMap '${name}' - duration: ${Date.now() - startTime}ms`); return res.result; } catch (err) { - logger.error(`Failed to read ConfigMap '${JSON.stringify(params)}', err =`, err); + logger.error(`Failed to read ConfigMap '${JSON.stringify(params)}'`, err); throw err; } } diff --git a/cloudant-change-listener/job/job.mjs b/cloudant-change-listener/job/job.mjs index e1e66863e..94a33bf9e 100644 --- a/cloudant-change-listener/job/job.mjs +++ b/cloudant-change-listener/job/job.mjs @@ -62,7 +62,7 @@ * - CLOUDANTNOSQLDB_PORT (optional) DB port number (default: 443) * - CLOUDANTNOSQLDB_APIKEY The IBM Cloud IAM APIkey if using IAMAuthentication on DB connection * - DB_NAME The name of the DB to listen on. e.g "MyTestDB" - * - DB_LAST_SEQ (optional) last_seq value to use as start identifier for db changes feed. + * - DB_LAST_SEQ (optional) lastSeq value to use as start identifier for db changes feed. * - CE_TARGET Full URL of the target Code Engine function or application that should receive events * - DB_POST_CHANGES_TIMEOUT Max wait-time in milliseconds on each long-polling call to the DB (default: 8000) * The poll timeout can be used to adapt the listening timeout to the settings defined on the cloudant DB @@ -73,21 +73,17 @@ *************************************************************************************/ import { CloudantV1 } from "@ibm-cloud/cloudant"; import { BasicAuthenticator, IamAuthenticator } from "ibm-cloud-sdk-core"; -import log4js from "log4js"; import { LRUCache } from "lru-cache"; import { isJobAlreadyRunning, updateJobConfig, getJobConfig } from "./ce-api-utils.mjs"; +import winston from "winston"; +const { combine, json } = winston.format; -// -// use a formatted logger to have timestamps in the log output -log4js.configure({ - appenders: { - out: { type: "stdout" }, - }, - categories: { - default: { appenders: ["out"], level: process.env.LOGLEVEL || "info" }, - }, +const logger = winston.createLogger({ + level: "debug", + transports: [new winston.transports.Console()], + format: combine(json()), }); -const logger = log4js.getLogger(); + logger.info('Starting Cloudant DB change listener ...'); @@ -358,7 +354,7 @@ function dbChangeHandler(change) { logger.info(`Successfully called CE URL of app or function with response: '${httpRes.status}' - duration: ${Date.now() - startTime}ms`); }) .catch((err) => { - logger.error(`Failed to call CE URL of app or function'${targetCEUrl}', err =`, err); + logger.error(`Failed to call CE URL of app or function '${targetCEUrl}'`, err); }); } @@ -433,7 +429,7 @@ async function doListen() { //* change from which the listening starts. This since option is controlled in //* this loop by following rules: //* - use DB_LAST_SEQ value on first cycle of loop - //* - if waitForDbChanges() call provides a valid last_seq value in response, then + //* - if waitForDbChanges() call provides a valid lastSeq value in response, then //* use this value for the next run of the loop //* - if waitForDbChanges() call fails with timeout or retryalbe error, //* then re-run with same seq value as in the previous loop cycle @@ -454,7 +450,7 @@ async function doListen() { // // Response exist when waitForDbChanges returns OK - // If response.result.results == 0 = { "results": [] , "last_seq" : value } means timeout occur + // If response.result.results == 0 = { "results": [] , "lastSeq" : value } means timeout occur // If response = Doc change received ( feed.on(data,..)) if (Object.keys(response).length === 0) { logger.info("Cloudant-SDK provided an unexpected empty response object on postChanges() call. Continue listening"); @@ -464,15 +460,15 @@ async function doListen() { if (!response || !response.result) { sinceToken = "now"; - logger.info(`Got Ok postChanges result, but not a valid last_seq value. Start fresh by pulling only new changes.`); + logger.info(`Got Ok postChanges result, but not a valid lastSeq value. Start fresh by pulling only new changes.`); continue; // run waitForDbChanges with new since token } - if (Array.isArray(response.result.results) && response.result.results.length === 0 && response.result.last_seq) { + if (Array.isArray(response.result.results) && response.result.results.length === 0 && response.result.lastSeq) { // // Wait timed out and delivered the lastSeq value as start point for the next loop - sinceToken = response.result.last_seq; - lastHandledSeq = response.result.last_seq; + sinceToken = response.result.lastSeq; + lastHandledSeq = response.result.lastSeq; logger.info(`No changes detected in the given wait period. Assigned new since token from result.`); continue; // run waitForDbChanges with updated since token } @@ -486,9 +482,9 @@ async function doListen() { }); // - // Get the last_seq value to use in the next postChanges() query - sinceToken = response.result.last_seq; - lastHandledSeq = response.result.last_seq; + // Get the lastSeq value to use in the next postChanges() query + sinceToken = response.result.lastSeq; + lastHandledSeq = response.result.lastSeq; continue; // run waitForDbChanges with updated since token } @@ -497,7 +493,7 @@ async function doListen() { // valid lastSeq number then continue the wrapper loop with // since = now sinceToken = "now"; - logger.info(`Got Ok postChanges result, but not a valid last_seq value. Start fresh by pulling only new changes.`); + logger.info(`Got Ok postChanges result, but not a valid lastSeq value. Start fresh by pulling only new changes.`); continue; } catch (err) { // Handle how to proceed loop in case of WRAPPER_TIMEOUT @@ -512,7 +508,7 @@ async function doListen() { continue; } - logger.error("Error in waitForDbChanges loop, err = ", err); + logger.error("Error in waitForDbChanges loop", err); // In case of unexpected error occur, then continue // with wrapper loop and since = 'now' diff --git a/cloudant-change-listener/job/package.json b/cloudant-change-listener/job/package.json index 0c7c5d146..a269fe69e 100644 --- a/cloudant-change-listener/job/package.json +++ b/cloudant-change-listener/job/package.json @@ -4,7 +4,7 @@ "description": "Change event listener for cloudant DB", "main": "job.mjs", "engines": { - "node": "^22", + "node": "^24", "npm": "^11" }, "scripts": { @@ -14,10 +14,10 @@ "author": "", "license": "ISC", "dependencies": { - "@ibm-cloud/cloudant": "^0.12.7", - "@ibm-cloud/ibm-code-engine-sdk": "^4.0.0", - "ibm-cloud-sdk-core": "^5.4.2", - "lru-cache": "^10.0.3", - "log4js": "^6.9.1" + "@ibm-cloud/cloudant": "^0.12.16", + "@ibm-cloud/ibm-code-engine-sdk": "^5.0.0", + "ibm-cloud-sdk-core": "^5.4.9", + "lru-cache": "^11.2.7", + "winston": "^3.19.0" } } \ No newline at end of file diff --git a/fotobox/frontend-app/Dockerfile b/fotobox/frontend-app/Dockerfile index 6d78977ab..5d1a5c0f6 100644 --- a/fotobox/frontend-app/Dockerfile +++ b/fotobox/frontend-app/Dockerfile @@ -20,6 +20,6 @@ RUN npm ci --omit dev EXPOSE 3000 -ENTRYPOINT [ "node", "build" ] +CMD [ "node", "build" ]