From 2ffed91cec022c6449e8f732b4fd5e3a0b2eed4f Mon Sep 17 00:00:00 2001 From: piyook Date: Sun, 26 May 2024 11:57:18 +0100 Subject: [PATCH] feat(): add logger store and view functionality --- .dockerignore | 1 + .env | 6 ++- .gitignore | 3 +- docker-compose.yml | 1 + package-lock.json | 6 +++ package.json | 1 + src/server.ts | 7 ++- src/utilities/log-page.ts | 78 +++++++++++++++++++++++++++++++ src/utilities/logger.ts | 23 +++++++++ src/utilities/server-page.ts | 6 ++- src/utilities/validate-request.ts | 7 +++ 11 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 src/utilities/log-page.ts create mode 100644 src/utilities/logger.ts diff --git a/.dockerignore b/.dockerignore index 6d68aea..8e3f511 100644 --- a/.dockerignore +++ b/.dockerignore @@ -22,3 +22,4 @@ **/values.dev.yaml LICENSE README.md +/src/logs/* \ No newline at end of file diff --git a/.env b/.env index 7334e6f..8b230ee 100644 --- a/.env +++ b/.env @@ -1,10 +1,12 @@ -PROJECT_NAME= mock-api-framework +PROJECT_NAME= llm-mock SERVER_PORT=8000 LLM_URL_ENDPOINT=chatgpt/chat/completions ## MOCK_LLM_RESPONSE_TYPE can be 'lorem' or 'stored' -MOCK_LLM_RESPONSE_TYPE=lorem +MOCK_LLM_RESPONSE_TYPE=stored MAX_LOREM_PARAS=8 # SET DEBUG TO * START DETAILED DEBUGGING LOGS AND OFF TO STOP DEBUG=OFF LLM_NAME=chatgpt VALIDATE_REQUESTS=ON +# SET LOG_REQUESTS TO ON TO CONSOLE LOG DETAILS OF ALL INCOMING REQUESTS (VALIDATE REQUESTS MUST ALSO BE ON) +LOG_REQUESTS=ON diff --git a/.gitignore b/.gitignore index ab05030..d48436f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules -*.log \ No newline at end of file +*.log +/src/logs/* \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index cc87b5c..60b6fb7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,5 +14,6 @@ services: DEBUG: ${DEBUG} LLM_NAME: ${LLM_NAME} VALIDATE_REQUESTS: ${VALIDATE_REQUESTS} + LOG_REQUESTS: ${LOG_REQUESTS} ports: - 8000:8000 diff --git a/package-lock.json b/package-lock.json index 8efecb8..0988565 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@types/express": "^4.17.21", "dotenv": "^16.4.5", "msw": "^2.3.0", + "pretty-print-json": "^3.0.1", "tsx": "^4.10.2", "typescript": "^5.4.5", "xo": "^0.58.0" @@ -7578,6 +7579,11 @@ "node": ">=6.0.0" } }, + "node_modules/pretty-print-json": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pretty-print-json/-/pretty-print-json-3.0.1.tgz", + "integrity": "sha512-AA2h0aq55ygYn0+Igpdw59YTa+TEvMr4X8U3WSsWIQIjiwxeSH1YspVd/xFf1X/B8WxtmjPDbX+h7eGHQS/whw==" + }, "node_modules/proto-props": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/proto-props/-/proto-props-2.0.0.tgz", diff --git a/package.json b/package.json index 94d8ff6..67fb251 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@types/express": "^4.17.21", "dotenv": "^16.4.5", "msw": "^2.3.0", + "pretty-print-json": "^3.0.1", "tsx": "^4.10.2", "typescript": "^5.4.5", "xo": "^0.58.0" diff --git a/src/server.ts b/src/server.ts index 5f4b5c5..c30e046 100644 --- a/src/server.ts +++ b/src/server.ts @@ -3,10 +3,15 @@ import { createServer } from '@mswjs/http-middleware'; import * as seeders from './seeders/index.js'; import getApiPaths from './utilities/file-scan.js'; import serverPage from './utilities/server-page.js'; +import logPage from './utilities/log-page.js'; const { apiHandlers, apiPaths } = await getApiPaths(); -const httpServer = createServer(...apiHandlers, ...serverPage(apiPaths)); +const httpServer = createServer( + ...apiHandlers, + ...serverPage(apiPaths), + ...logPage(), +); httpServer.listen(process.env?.SERVER_PORT ?? 8000); diff --git a/src/utilities/log-page.ts b/src/utilities/log-page.ts new file mode 100644 index 0000000..5fce780 --- /dev/null +++ b/src/utilities/log-page.ts @@ -0,0 +1,78 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { http, HttpResponse } from 'msw'; +import { prettyPrintJson, FormatOptions } from 'pretty-print-json'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const createHtml = () => { + function readLogs() { + const logFolder = `${__dirname}/../logs`; + const logPath = path.join(logFolder, 'api_request_log.json'); + let logs = ''; + // Append the log entry to the log file + try { + logs = fs.readFileSync(logPath, 'utf8'); + } catch { + logs = '{"message": "No logs found"}'; + } + + return logs; + } + + const htmlString = ` + +
+ +
+ +

Last POST Request Made

+

File can be viewed in /src/logs folder in container or local machine

+
+${prettyPrintJson.toHtml(JSON.parse(readLogs()), { indent: 4, lineNumbers: true })} +
+ + + + + + `; + + return htmlString; +}; + +const logPage = () => { + return [ + http.get(`/logs`, () => { + return new HttpResponse(createHtml(), { + status: 200, + headers: { + 'Content-Type': 'text/html', + }, + }); + }), + ]; +}; + +export default logPage; diff --git a/src/utilities/logger.ts b/src/utilities/logger.ts new file mode 100644 index 0000000..54fbf12 --- /dev/null +++ b/src/utilities/logger.ts @@ -0,0 +1,23 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { type DefaultBodyType } from 'msw'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export default function logger(logItem: DefaultBodyType) { + const logFolder = `${__dirname}/../logs`; + const logPath = path.join(logFolder, 'api_request_log.json'); + + // Convert the object to a string + const logEntry = `[${JSON.stringify(logItem).trim().replaceAll('\n', '')}]\n`; + + // Append the log entry to the log file + + fs.writeFile(logPath, logEntry, (err: any) => { + if (err) { + console.error('Error writing to log file:', err); + } + }); +} diff --git a/src/utilities/server-page.ts b/src/utilities/server-page.ts index a779d8d..324297b 100644 --- a/src/utilities/server-page.ts +++ b/src/utilities/server-page.ts @@ -6,7 +6,7 @@ const prefix = process.env?.LLM_URL_ENDPOINT ?? ''; const homePage = (apiPaths: string[]) => { const htmlString = ` - +

Mock LLM Server: Running

@@ -18,7 +18,9 @@ const homePage = (apiPaths: string[]) => {

Mocked LLM: ${process.env?.LLM_NAME?.toUpperCase() ?? 'NONE'}

Response Type: ${process.env.MOCK_LLM_RESPONSE_TYPE?.toUpperCase() ?? 'NONE'}

${process.env.MOCK_LLM_RESPONSE_TYPE === 'lorem' ? ` Maximum sentences: ${process.env?.MAX_LOREM_PARAS} ` : ''}

-

POST Request Validation: ${process.env?.VALIDATE_REQUESTS?.toUpperCase() ?? 'NONE'}

+

LLM Request Validation: ${process.env?.VALIDATE_REQUESTS?.toUpperCase() ?? 'NONE'}

+

Http Request Log: ${process.env?.LOG_REQUESTS?.toUpperCase() ?? 'NONE'}

+

View Logs url: localhost:${process.env?.SERVER_PORT}/logs

Debug Mode: ${process.env.DEBUG === '*' ? 'ON' : 'OFF'}

API endpoint (GET & POST): ${apiPaths.map((path) => '/' + prefix + '').join('')}

diff --git a/src/utilities/validate-request.ts b/src/utilities/validate-request.ts index 9788486..e915988 100644 --- a/src/utilities/validate-request.ts +++ b/src/utilities/validate-request.ts @@ -1,4 +1,5 @@ import { type DefaultBodyType, type StrictRequest } from 'msw'; +import logger from './logger.js'; const logRequestBody = (data: Record) => { console.log('Request Body:', data); @@ -36,6 +37,12 @@ export const validateRequest = async ( return request .json() .then((data) => { + // Log Request if Debug is set to on + if (process.env?.LOG_REQUESTS?.toUpperCase() === 'ON') { + console.log('REQUEST:', data); + logger(data); + } + const requiredKeys = Object.keys(requestTemplate.default[0]); if (typeof data !== 'object' || data === null) {