Skip to content

Commit 7999ad7

Browse files
authored
Merge pull request #301 from IBM/logging
Logging examples
2 parents 0c8c361 + 89f9777 commit 7999ad7

50 files changed

Lines changed: 1191 additions & 50 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cos-to-sql/app.mjs

Lines changed: 66 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { stat } from "fs/promises";
2+
import crypto from "crypto";
23
import express from "express";
34
import favicon from "serve-favicon";
45
import path from "path";
@@ -15,8 +16,11 @@ import { ContainerAuthenticator } from "ibm-cloud-sdk-core";
1516
import { addUser, closeDBConnection, deleteUsers, getPgClient, listUsers } from "./utils/db.mjs";
1617
import { getObjectContent } from "./utils/cos.mjs";
1718
import { convertCsvToDataStruct } from "./utils/utils.mjs";
19+
import { getCodeEngineLogger } from "./utils/logging.mjs";
1820

19-
console.info("Starting the app ...");
21+
const appLogger = getCodeEngineLogger("app");
22+
23+
appLogger.info("Starting the app ...");
2024

2125
const requiredEnvVars = [
2226
"COS_REGION",
@@ -28,7 +32,7 @@ const requiredEnvVars = [
2832

2933
requiredEnvVars.forEach((envVarName) => {
3034
if (!process.env[envVarName]) {
31-
console.log(`Failed to start app. Missing '${envVarName}' environment variable`);
35+
appLogger.warn(`Failed to start app. Missing '${envVarName}' environment variable`);
3236
process.exit(1);
3337
}
3438
});
@@ -60,18 +64,42 @@ const app = express();
6064
app.use(express.json());
6165
app.use(favicon(path.join(__dirname, "public", "favicon.ico")));
6266

67+
// helper function to send JSON responses
68+
function sendJSONResponse(request, response, returnCode, jsonObject) {
69+
response.status(returnCode);
70+
response.setHeader("Content-Type", "application/json");
71+
response.end(JSON.stringify(jsonObject));
72+
const duration = Date.now() - request.startTime;
73+
request.logger.info(`handled ${request.method} request for '${request.path}' in ${duration}ms; rc: '${returnCode}'`, {
74+
returnCode,
75+
duration_ms: duration,
76+
});
77+
}
78+
6379
// use router to bundle all routes to /
6480
const router = express.Router();
81+
82+
// adding a middleware that extracts the correlation id and initializes the logger
83+
router.use((req, res, next) => {
84+
const correlationId = req.header("x-request-id") || crypto.randomBytes(8).toString("hex");
85+
req.startTime = Date.now();
86+
req.correlationId = correlationId;
87+
const operationId = `${req.method}:${req.path}`;
88+
req.operationId = operationId;
89+
req.logger = getCodeEngineLogger("handle-request").child({ correlationId, operationId });
90+
req.logger.info(`handling incoming ${req.method} request for '${req.path}'`);
91+
next();
92+
});
93+
6594
app.use("/", router);
6695

6796
//
6897
// Default http endpoint, which prints the list of all users in the database
6998
router.get("/", async (req, res) => {
70-
console.log(`handling / for '${req.url}'`);
7199
const pgClient = await getPgClient(secretsManager, smPgSecretId);
72100
const allUsers = await listUsers(pgClient);
73-
res.writeHead(200, { "Content-Type": "application/json" });
74-
res.end(JSON.stringify({ users: allUsers.rows }));
101+
102+
return sendJSONResponse(req, res, 200, { users: allUsers.rows });
75103
});
76104

77105
//
@@ -80,111 +108,102 @@ router.get("/readiness", async (req, res) => {
80108
console.log(`handling /readiness`);
81109
if (!(await stat("/var/run/secrets/codeengine.cloud.ibm.com/compute-resource-token/token"))) {
82110
console.error("Mounting the trusted profile compute resource token is not enabled");
83-
res.writeHead(500, { "Content-Type": "application/json" });
84-
res.end('{"error": "Mounting the trusted profile compute resource token is not enabled"}');
85-
return;
111+
112+
return sendJSONResponse(req, res, 500, {
113+
error: "Mounting the trusted profile compute resource token is not enabled",
114+
});
86115
}
87116

88-
res.writeHead(200, { "Content-Type": "application/json" });
89-
res.end('{"status": "ok"}');
90-
return;
117+
return sendJSONResponse(req, res, 200, { status: "ok" });
91118
});
92119

93120
//
94121
// Ingestion endpoint
95122
router.post("/cos-to-sql", async (req, res) => {
96-
console.log(`handling /cos-to-sql for '${req.url}'`);
97-
console.log(`request headers: '${JSON.stringify(req.headers)}`);
123+
req.logger.debug(`request headers: '${JSON.stringify(req.headers)}`);
98124
const event = req.body;
99-
console.log(`request body: '${event}'`);
125+
req.logger.debug(`request body: '${event}'`);
100126

101127
//
102128
// assess whether the request payload contains information about the COS file that got updated
103129
if (!event) {
104-
console.log("Request does not contain any event data");
105-
res.writeHead(400, { "Content-Type": "application/json" });
106-
res.end('{"error": "request does not contain any event data"}');
107-
return;
130+
req.logger.info("Request does not contain any event data");
131+
return sendJSONResponse(req, res, 400, { error: "request does not contain any event data" });
108132
}
109133

110134
//
111135
// make sure that the event relates to a COS write operation
112136
if (event.notification.event_type !== "Object:Write") {
113-
console.log(`COS operation '${event.notification.event_type}' does not match expectations 'Object:Write'`);
114-
res.writeHead(400, { "Content-Type": "application/json" });
115-
res.end(`{"error": "COS operation '${event.notification.event_type}' does not match expectations 'Object:Write'"}`);
116-
return;
137+
req.logger.info(`COS operation '${event.notification.event_type}' does not match expectations 'Object:Write'`);
138+
139+
return sendJSONResponse(req, res, 400, {
140+
error: `COS operation '${event.notification.event_type}' does not match expectations 'Object:Write'`,
141+
});
117142
}
118143
if (event.notification.content_type !== "text/csv") {
119-
console.log(
144+
req.logger.info(
120145
`COS update did happen on file '${event.key}' which is of type '${event.notification.content_type}' (expected type 'text/csv')`
121146
);
122-
res.writeHead(400, { "Content-Type": "application/json" });
123-
res.end(
124-
`{"error": "COS update did happen on file '${event.key}' which is of type '${event.notification.content_type}' (expected type 'text/csv')"}`
125-
);
126-
return;
147+
148+
return sendJSONResponse(req, res, 400, {
149+
error: `COS update did happen on file '${event.key}' which is of type '${event.notification.content_type}' (expected type 'text/csv')`,
150+
});
127151
}
128-
console.log(`Received a COS update event on the CSV file '${event.key}' in bucket '${event.bucket}'`);
152+
req.logger.info(`Received a COS update event on the CSV file '${event.key}' in bucket '${event.bucket}'`);
129153

130154
//
131155
// Retrieve the COS object that got updated
132-
console.log(`Retrieving file content of '${event.key}' from bucket ${event.bucket} ...`);
156+
req.logger.info(`Retrieving file content of '${event.key}' from bucket ${event.bucket} ...`);
133157
const fileContent = await getObjectContent(cosAuthenticator, cosRegion, event.bucket, event.key);
134158

135159
//
136160
// Convert CSV to a object structure representing an array of users
137-
console.log(`Converting CSV data to a data struct ...`);
161+
req.logger.info(`Converting CSV data to a data struct ...`);
138162
const users = await convertCsvToDataStruct(fileContent);
139-
console.log(`users: ${JSON.stringify(users)}`);
163+
req.logger.info(`users: ${JSON.stringify(users)}`);
140164

141165
const pgClient = await getPgClient(secretsManager, smPgSecretId);
142166

143167
//
144168
// Iterate through the list of users
145-
console.log(`Writing converted CSV data to the PostgreSQL database ...`);
169+
req.logger.info(`Writing converted CSV data to the PostgreSQL database ...`);
146170
let numberOfProcessedUsers = 0;
147171
for (const userToAdd of users) {
148172
try {
149173
// Perform a single SQL insert statement per user
150174
const result = await addUser(pgClient, userToAdd.Firstname, userToAdd.Lastname);
151-
console.log(`Added ${JSON.stringify(userToAdd)} -> ${JSON.stringify(result)}`);
175+
req.logger.info(`Added ${JSON.stringify(userToAdd)} -> ${JSON.stringify(result)}`);
152176
numberOfProcessedUsers++;
153177
} catch (err) {
154-
console.error(`Failed to add user '${JSON.stringify(userToAdd)}' to the database`, err);
178+
req.logger.error(`Failed to add user '${JSON.stringify(userToAdd)}' to the database`, err);
155179
}
156180
}
157181

158-
console.log(`Processed ${numberOfProcessedUsers} user records!`);
159-
res.writeHead(200, { "Content-Type": "application/json" });
160-
res.end(`{"status": "done"}`);
161-
return;
182+
req.logger.info(`Processed ${numberOfProcessedUsers} user records!`);
183+
return sendJSONResponse(req, res, 200, { status: "done" });
162184
});
163185

164186
//
165187
// Endpoint that drops the users table
166188
router.get("/clear", async (req, res) => {
167-
console.log(`handling /clear for '${req.url}'`);
168189
const pgClient = await getPgClient(secretsManager, smPgSecretId);
169190
await deleteUsers(pgClient);
170-
console.log(`Deletions done!`);
171-
res.writeHead(200, { "Content-Type": "application/json" });
172-
res.end(`{"status": "done"}`);
173-
return;
191+
req.logger.info(`Deletions done!`);
192+
return sendJSONResponse(req, res, 200, { status: "done" });
174193
});
175194

176195
// start server
177196
const port = 8080;
178197
const server = app.listen(port, () => {
179-
console.log(`Server is up and running on port ${port}!`);
198+
appLogger.info(`Server is up and running on port ${port}!`);
180199
});
181200

182201
process.on("SIGTERM", async () => {
183-
console.info("SIGTERM signal received.");
202+
appLogger.info("SIGTERM signal received.");
184203

185204
await closeDBConnection();
186205

187206
server.close(() => {
188-
console.log("Http server closed.");
207+
appLogger.info("Http server closed.");
189208
});
190209
});

0 commit comments

Comments
 (0)