11import { stat } from "fs/promises" ;
2+ import crypto from "crypto" ;
23import express from "express" ;
34import favicon from "serve-favicon" ;
45import path from "path" ;
@@ -15,8 +16,11 @@ import { ContainerAuthenticator } from "ibm-cloud-sdk-core";
1516import { addUser , closeDBConnection , deleteUsers , getPgClient , listUsers } from "./utils/db.mjs" ;
1617import { getObjectContent } from "./utils/cos.mjs" ;
1718import { 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
2125const requiredEnvVars = [
2226 "COS_REGION" ,
@@ -28,7 +32,7 @@ const requiredEnvVars = [
2832
2933requiredEnvVars . 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();
6064app . use ( express . json ( ) ) ;
6165app . 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 /
6480const 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+
6594app . use ( "/" , router ) ;
6695
6796//
6897// Default http endpoint, which prints the list of all users in the database
6998router . 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
95122router . 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
166188router . 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
177196const port = 8080 ;
178197const 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
182201process . 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