added logger

This commit is contained in:
Bojan Kucera 2025-06-03 18:31:02 -04:00
parent dd27f3bf2c
commit 58ae897e90
13 changed files with 1493 additions and 12 deletions

View file

@ -2,19 +2,18 @@
"name": "data-processor",
"version": "1.0.0",
"description": "Data processing and pipeline orchestration service",
"main": "src/index.ts",
"scripts": {
"main": "src/index.ts", "scripts": {
"dev": "bun run --watch src/index.ts",
"start": "bun run src/index.ts",
"build": "bun build src/index.ts --outdir=dist",
"build": "bun build src/index.ts --outdir=dist --target=bun",
"test": "bun test",
"lint": "eslint src/**/*.ts",
"type-check": "tsc --noEmit"
},
"dependencies": {
},"dependencies": {
"@stock-bot/types": "*",
"@stock-bot/event-bus": "*",
"@stock-bot/utils": "*",
"@stock-bot/logger": "*",
"@stock-bot/api-client": "*",
"hono": "^4.6.3",
"ioredis": "^5.4.1",

View file

@ -1,6 +1,6 @@
import { Hono } from 'hono';
import { serve } from 'bun';
import { logger } from '@stock-bot/utils';
import { getLogger, loggingMiddleware, errorLoggingMiddleware } from '@stock-bot/logger';
import { DataPipelineOrchestrator } from './core/DataPipelineOrchestrator';
import { DataQualityService } from './services/DataQualityService';
import { DataIngestionService } from './services/DataIngestionService';
@ -12,6 +12,18 @@ import { JobController } from './controllers/JobController';
const app = new Hono();
// Initialize logger
const logger = getLogger('data-processor');
// Add logging middleware
app.use('*', loggingMiddleware({
serviceName: 'data-processor',
skipPaths: ['/health']
}));
// Add error logging middleware
app.use('*', errorLoggingMiddleware(logger));
// Services
const dataQualityService = new DataQualityService();
const dataIngestionService = new DataIngestionService();

View file

@ -11,12 +11,12 @@
"types": ["bun-types"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"],
"references": [
"exclude": ["node_modules", "dist"], "references": [
{ "path": "../../../libs/api-client" },
{ "path": "../../../libs/config" },
{ "path": "../../../libs/event-bus" },
{ "path": "../../../libs/http-client" },
{ "path": "../../../libs/logger" },
{ "path": "../../../libs/types" },
{ "path": "../../../libs/utils" },
]

140
bun.lock
View file

@ -93,6 +93,7 @@
"dependencies": {
"@stock-bot/api-client": "*",
"@stock-bot/event-bus": "*",
"@stock-bot/logger": "*",
"@stock-bot/types": "*",
"@stock-bot/utils": "*",
"axios": "^1.6.2",
@ -286,6 +287,23 @@
"typescript": "^5.3.0",
},
},
"libs/logger": {
"name": "@stock-bot/logger",
"version": "1.0.0",
"dependencies": {
"@stock-bot/config": "workspace:*",
"@stock-bot/types": "workspace:*",
"winston": "^3.11.0",
"winston-daily-rotate-file": "^4.7.1",
"winston-loki": "^6.0.8",
},
"devDependencies": {
"@types/jest": "^29.5.2",
"@types/node": "^20.5.0",
"jest": "^29.5.0",
"typescript": "^5.4.5",
},
},
"libs/types": {
"name": "@stock-bot/types",
"version": "1.0.0",
@ -417,7 +435,9 @@
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="],
"@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="],
"@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="],
"@dabh/diagnostics": ["@dabh/diagnostics@2.0.3", "", { "dependencies": { "colorspace": "1.1.x", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="],
@ -629,6 +649,32 @@
"@napi-rs/nice-win32-x64-msvc": ["@napi-rs/nice-win32-x64-msvc@1.0.1", "", { "os": "win32", "cpu": "x64" }, "sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg=="],
"@napi-rs/snappy-android-arm-eabi": ["@napi-rs/snappy-android-arm-eabi@7.2.2", "", { "os": "android", "cpu": "arm" }, "sha512-H7DuVkPCK5BlAr1NfSU8bDEN7gYs+R78pSHhDng83QxRnCLmVIZk33ymmIwurmoA1HrdTxbkbuNl+lMvNqnytw=="],
"@napi-rs/snappy-android-arm64": ["@napi-rs/snappy-android-arm64@7.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-2R/A3qok+nGtpVK8oUMcrIi5OMDckGYNoBLFyli3zp8w6IArPRfg1yOfVUcHvpUDTo9T7LOS1fXgMOoC796eQw=="],
"@napi-rs/snappy-darwin-arm64": ["@napi-rs/snappy-darwin-arm64@7.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-USgArHbfrmdbuq33bD5ssbkPIoT7YCXCRLmZpDS6dMDrx+iM7eD2BecNbOOo7/v1eu6TRmQ0xOzeQ6I/9FIi5g=="],
"@napi-rs/snappy-darwin-x64": ["@napi-rs/snappy-darwin-x64@7.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-0APDu8iO5iT0IJKblk2lH0VpWSl9zOZndZKnBYIc+ei1npw2L5QvuErFOTeTdHBtzvUHASB+9bvgaWnQo4PvTQ=="],
"@napi-rs/snappy-freebsd-x64": ["@napi-rs/snappy-freebsd-x64@7.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mRTCJsuzy0o/B0Hnp9CwNB5V6cOJ4wedDTWEthsdKHSsQlO7WU9W1yP7H3Qv3Ccp/ZfMyrmG98Ad7u7lG58WXA=="],
"@napi-rs/snappy-linux-arm-gnueabihf": ["@napi-rs/snappy-linux-arm-gnueabihf@7.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-v1uzm8+6uYjasBPcFkv90VLZ+WhLzr/tnfkZ/iD9mHYiULqkqpRuC8zvc3FZaJy5wLQE9zTDkTJN1IvUcZ+Vcg=="],
"@napi-rs/snappy-linux-arm64-gnu": ["@napi-rs/snappy-linux-arm64-gnu@7.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-LrEMa5pBScs4GXWOn6ZYXfQ72IzoolZw5txqUHVGs8eK4g1HR9HTHhb2oY5ySNaKakG5sOgMsb1rwaEnjhChmQ=="],
"@napi-rs/snappy-linux-arm64-musl": ["@napi-rs/snappy-linux-arm64-musl@7.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-3orWZo9hUpGQcB+3aTLW7UFDqNCQfbr0+MvV67x8nMNYj5eAeUtMmUE/HxLznHO4eZ1qSqiTwLbVx05/Socdlw=="],
"@napi-rs/snappy-linux-x64-gnu": ["@napi-rs/snappy-linux-x64-gnu@7.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-jZt8Jit/HHDcavt80zxEkDpH+R1Ic0ssiVCoueASzMXa7vwPJeF4ZxZyqUw4qeSy7n8UUExomu8G8ZbP6VKhgw=="],
"@napi-rs/snappy-linux-x64-musl": ["@napi-rs/snappy-linux-x64-musl@7.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Dh96IXgcZrV39a+Tej/owcd9vr5ihiZ3KRix11rr1v0MWtVb61+H1GXXlz6+Zcx9y8jM1NmOuiIuJwkV4vZ4WA=="],
"@napi-rs/snappy-win32-arm64-msvc": ["@napi-rs/snappy-win32-arm64-msvc@7.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-9No0b3xGbHSWv2wtLEn3MO76Yopn1U2TdemZpCaEgOGccz1V+a/1d16Piz3ofSmnA13HGFz3h9NwZH9EOaIgYA=="],
"@napi-rs/snappy-win32-ia32-msvc": ["@napi-rs/snappy-win32-ia32-msvc@7.2.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-QiGe+0G86J74Qz1JcHtBwM3OYdTni1hX1PFyLRo3HhQUSpmi13Bzc1En7APn+6Pvo7gkrcy81dObGLDSxFAkQQ=="],
"@napi-rs/snappy-win32-x64-msvc": ["@napi-rs/snappy-win32-x64-msvc@7.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-a43cyx1nK0daw6BZxVcvDEXxKMFLSBSDTAhsFD0VqSKcC7MGUBMaqyoWUcMiI7LBSz4bxUmxDWKfCYzpEmeb3w=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
@ -683,6 +729,26 @@
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
"@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
"@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="],
"@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="],
"@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="],
"@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
"@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="],
"@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
"@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.2", "", { "os": "android", "cpu": "arm" }, "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.2", "", { "os": "android", "cpu": "arm64" }, "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw=="],
@ -761,6 +827,8 @@
"@stock-bot/http-client": ["@stock-bot/http-client@workspace:libs/http-client"],
"@stock-bot/logger": ["@stock-bot/logger@workspace:libs/logger"],
"@stock-bot/market-data-gateway": ["@stock-bot/market-data-gateway@workspace:apps/core-services/market-data-gateway"],
"@stock-bot/types": ["@stock-bot/types@workspace:libs/types"],
@ -867,6 +935,8 @@
"@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="],
"@types/triple-beam": ["@types/triple-beam@1.3.5", "", {}, "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="],
"@types/uuid": ["@types/uuid@9.0.8", "", {}, "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA=="],
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
@ -927,6 +997,10 @@
"array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="],
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
"async-exit-hook": ["async-exit-hook@2.0.1", "", {}, "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
@ -969,6 +1043,8 @@
"bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="],
"btoa": ["btoa@1.2.1", "", { "bin": { "btoa": "bin/btoa.js" } }, "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g=="],
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
@ -1021,12 +1097,18 @@
"collect-v8-coverage": ["collect-v8-coverage@1.0.2", "", {}, "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q=="],
"color": ["color@3.2.1", "", { "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
"colorspace": ["colorspace@1.1.4", "", { "dependencies": { "color": "^3.1.3", "text-hex": "1.0.x" } }, "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"compressible": ["compressible@2.0.18", "", { "dependencies": { "mime-db": ">= 1.43.0 < 2" } }, "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg=="],
@ -1123,6 +1205,8 @@
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"enabled": ["enabled@2.0.0", "", {}, "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="],
"encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
"encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="],
@ -1221,10 +1305,14 @@
"feature-store": ["feature-store@workspace:apps/data-services/feature-store"],
"fecha": ["fecha@4.2.3", "", {}, "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="],
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
"file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="],
"file-stream-rotator": ["file-stream-rotator@0.6.1", "", { "dependencies": { "moment": "^2.29.1" } }, "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"finalhandler": ["finalhandler@1.1.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" } }, "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA=="],
@ -1235,6 +1323,8 @@
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"fn.name": ["fn.name@1.1.0", "", {}, "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="],
"follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
@ -1487,6 +1577,8 @@
"kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
"kuler": ["kuler@2.0.0", "", {}, "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="],
"leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
@ -1535,6 +1627,10 @@
"log4js": ["log4js@6.9.1", "", { "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", "flatted": "^3.2.7", "rfdc": "^1.3.0", "streamroller": "^3.1.5" } }, "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g=="],
"logform": ["logform@2.7.0", "", { "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ=="],
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"luxon": ["luxon@3.5.0", "", {}, "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ=="],
@ -1587,6 +1683,8 @@
"mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
"moment": ["moment@2.30.1", "", {}, "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="],
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@ -1651,6 +1749,8 @@
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
@ -1661,6 +1761,8 @@
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"one-time": ["one-time@1.0.0", "", { "dependencies": { "fn.name": "1.x.x" } }, "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g=="],
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
@ -1743,6 +1845,8 @@
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
"protobufjs": ["protobufjs@7.5.3", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
@ -1765,7 +1869,7 @@
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
"readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
@ -1841,6 +1945,8 @@
"sigstore": ["sigstore@3.1.0", "", { "dependencies": { "@sigstore/bundle": "^3.1.0", "@sigstore/core": "^2.0.0", "@sigstore/protobuf-specs": "^0.4.0", "@sigstore/sign": "^3.1.0", "@sigstore/tuf": "^3.1.0", "@sigstore/verify": "^2.1.0" } }, "sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q=="],
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
@ -1849,6 +1955,8 @@
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
"snappy": ["snappy@7.2.2", "", { "optionalDependencies": { "@napi-rs/snappy-android-arm-eabi": "7.2.2", "@napi-rs/snappy-android-arm64": "7.2.2", "@napi-rs/snappy-darwin-arm64": "7.2.2", "@napi-rs/snappy-darwin-x64": "7.2.2", "@napi-rs/snappy-freebsd-x64": "7.2.2", "@napi-rs/snappy-linux-arm-gnueabihf": "7.2.2", "@napi-rs/snappy-linux-arm64-gnu": "7.2.2", "@napi-rs/snappy-linux-arm64-musl": "7.2.2", "@napi-rs/snappy-linux-x64-gnu": "7.2.2", "@napi-rs/snappy-linux-x64-musl": "7.2.2", "@napi-rs/snappy-win32-arm64-msvc": "7.2.2", "@napi-rs/snappy-win32-ia32-msvc": "7.2.2", "@napi-rs/snappy-win32-x64-msvc": "7.2.2" } }, "sha512-iADMq1kY0v3vJmGTuKcFWSXt15qYUz7wFkArOrsSg0IFfI3nJqIJvK2/ZbEIndg7erIJLtAVX2nSOqPz7DcwbA=="],
"socket.io": ["socket.io@4.8.1", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg=="],
"socket.io-adapter": ["socket.io-adapter@2.5.5", "", { "dependencies": { "debug": "~4.3.4", "ws": "~8.17.1" } }, "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg=="],
@ -1881,6 +1989,8 @@
"ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="],
"stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="],
"stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
"standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
@ -1923,6 +2033,8 @@
"test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="],
"text-hex": ["text-hex@1.0.0", "", {}, "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="],
"text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="],
"thread-stream": ["thread-stream@2.7.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw=="],
@ -1939,6 +2051,8 @@
"trading-dashboard": ["trading-dashboard@workspace:apps/interface-services/trading-dashboard"],
"triple-beam": ["triple-beam@1.4.1", "", {}, "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg=="],
"ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
@ -1985,6 +2099,10 @@
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"url-polyfill": ["url-polyfill@1.1.13", "", {}, "sha512-tXzkojrv2SujumYthZ/WjF7jaSfNhSXlYMpE5AYdL2I3D7DCeo+mch8KtW2rUuKjDg+3VXODXHVgipt8yGY/eQ=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
"uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
@ -2013,6 +2131,14 @@
"which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="],
"winston": ["winston@3.17.0", "", { "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.9.0" } }, "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw=="],
"winston-daily-rotate-file": ["winston-daily-rotate-file@4.7.1", "", { "dependencies": { "file-stream-rotator": "^0.6.1", "object-hash": "^2.0.1", "triple-beam": "^1.3.0", "winston-transport": "^4.4.0" }, "peerDependencies": { "winston": "^3" } }, "sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA=="],
"winston-loki": ["winston-loki@6.1.3", "", { "dependencies": { "async-exit-hook": "2.0.1", "btoa": "^1.2.1", "protobufjs": "^7.2.4", "url-polyfill": "^1.1.12", "winston-transport": "^4.3.0" }, "optionalDependencies": { "snappy": "^7.2.2" } }, "sha512-DjWtJ230xHyYQWr9mZJa93yhwHttn3JEtSYWP8vXZWJOahiQheUhf+88dSIidbGXB3u0oLweV6G1vkL/ouT62Q=="],
"winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"wrap-ansi": ["wrap-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q=="],
@ -2131,6 +2257,8 @@
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"color/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"connect/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
@ -2183,6 +2311,8 @@
"jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
"karma/@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="],
"karma/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"karma/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="],
@ -2229,10 +2359,14 @@
"path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"pino-abstract-transport/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
"pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
"simple-swizzle/is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
"slice-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
"slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
@ -2325,6 +2459,8 @@
"cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
"compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"connect/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],

342
libs/logger/README.md Normal file
View file

@ -0,0 +1,342 @@
# @stock-bot/logger
Enhanced logging library with Loki integration for the Stock Bot platform.
## Features
- 🎯 **Multiple Log Levels**: debug, info, warn, error, http
- 🌐 **Loki Integration**: Centralized logging with Grafana visualization
- 📁 **File Logging**: Daily rotating log files with compression
- 🎨 **Console Logging**: Colored, formatted console output
- 📊 **Structured Logging**: JSON-formatted logs with metadata
- ⚡ **Performance Optimized**: Batching and async logging
- 🔐 **Security**: Automatic sensitive data masking
- 🎭 **Express Middleware**: Request/response logging
- 📈 **Business Events**: Specialized logging for trading operations
## Installation
```bash
cd libs/logger
npm install
```
## Basic Usage
### Simple Logging
```typescript
import { getLogger } from '@stock-bot/logger';
const logger = getLogger('my-service');
logger.info('Service started');
logger.warn('This is a warning');
logger.error('An error occurred', new Error('Something went wrong'));
```
### With Context
```typescript
import { Logger } from '@stock-bot/logger';
const logger = new Logger('trading-service', {
userId: '12345',
sessionId: 'abc-def-ghi'
});
logger.info('Trade executed', {
symbol: 'AAPL',
quantity: 100,
price: 150.25
});
```
### Performance Logging
```typescript
import { getLogger, createTimer } from '@stock-bot/logger';
const logger = getLogger('data-processor');
const timer = createTimer('data-processing');
// ... do some work ...
const timing = timer.end();
logger.performance('Data processing completed', timing);
```
### Business Events
```typescript
import { getLogger, createBusinessEvent } from '@stock-bot/logger';
const logger = getLogger('order-service');
logger.business('Order placed', createBusinessEvent(
'order',
'place',
{
entity: 'order-123',
result: 'success',
symbol: 'TSLA',
amount: 50000
}
));
```
### Security Events
```typescript
import { getLogger, createSecurityEvent } from '@stock-bot/logger';
const logger = getLogger('auth-service');
logger.security('Failed login attempt', createSecurityEvent(
'authentication',
{
user: 'john@example.com',
result: 'failure',
ip: '192.168.1.100',
severity: 'medium'
}
));
```
## Express Middleware
### Basic Request Logging
```typescript
import express from 'express';
import { loggingMiddleware } from '@stock-bot/logger';
const app = express();
app.use(loggingMiddleware({
serviceName: 'api-gateway',
skipPaths: ['/health', '/metrics']
}));
```
### Error Logging
```typescript
import { errorLoggingMiddleware, getLogger } from '@stock-bot/logger';
const logger = getLogger('api-gateway');
// Add after your routes but before error handlers
app.use(errorLoggingMiddleware(logger));
```
### Request-scoped Logger
```typescript
import { createRequestLogger, getLogger } from '@stock-bot/logger';
const baseLogger = getLogger('api-gateway');
app.use((req, res, next) => {
req.logger = createRequestLogger(req, baseLogger);
next();
});
app.get('/api/data', (req, res) => {
req.logger.info('Processing data request');
// ... handle request ...
});
```
## Configuration
The logger uses configuration from `@stock-bot/config`. Key environment variables:
```bash
# Logging
LOG_LEVEL=info
LOG_CONSOLE=true
LOG_FILE=true
LOG_FILE_PATH=./logs
# Loki
LOKI_HOST=localhost
LOKI_PORT=3100
LOKI_BATCH_SIZE=1024
LOKI_FLUSH_INTERVAL_MS=5000
```
## Advanced Usage
### Child Loggers
```typescript
const parentLogger = getLogger('trading-service');
const orderLogger = parentLogger.child({
module: 'order-processing',
orderId: '12345'
});
orderLogger.info('Order validated'); // Will include parent context
```
### Custom Transports
```typescript
import { createLogger } from '@stock-bot/logger';
const logger = createLogger('custom-service', {
level: 'debug',
enableLoki: true,
enableFile: false,
enableConsole: true
});
```
### Sensitive Data Masking
```typescript
import { sanitizeMetadata, maskSensitiveData } from '@stock-bot/logger';
const unsafeData = {
username: 'john',
password: 'secret123',
apiKey: 'abc123def456'
};
const safeData = sanitizeMetadata(unsafeData);
// { username: 'john', password: '[REDACTED]', apiKey: '[REDACTED]' }
const message = maskSensitiveData('User API key: abc123def456');
// 'User API key: [API_KEY]'
```
### Log Throttling
```typescript
import { LogThrottle } from '@stock-bot/logger';
const throttle = new LogThrottle(10, 60000); // 10 logs per minute
if (throttle.shouldLog('error-key')) {
logger.error('This error will be throttled');
}
```
## Viewing Logs
### Grafana Dashboard
1. Start the monitoring stack: `docker-compose up grafana loki`
2. Open Grafana at http://localhost:3000
3. Use the "Stock Bot Logs" dashboard
4. Query logs with LogQL: `{service="your-service"}`
### Log Files
When file logging is enabled, logs are written to:
- `./logs/{service-name}-YYYY-MM-DD.log` - All logs
- `./logs/{service-name}-error-YYYY-MM-DD.log` - Error logs only
## Best Practices
1. **Use appropriate log levels**:
- `debug`: Detailed development information
- `info`: General operational messages
- `warn`: Potential issues
- `error`: Actual errors requiring attention
2. **Include context**: Always provide relevant metadata
```typescript
logger.info('Trade executed', { symbol, quantity, price, orderId });
```
3. **Use structured logging**: Avoid string concatenation
```typescript
// Good
logger.info('User logged in', { userId, ip, userAgent });
// Avoid
logger.info(`User ${userId} logged in from ${ip}`);
```
4. **Handle sensitive data**: Use sanitization utilities
```typescript
const safeMetadata = sanitizeMetadata(requestData);
logger.info('API request', safeMetadata);
```
5. **Use correlation IDs**: Track requests across services
```typescript
const logger = getLogger('service').child({
correlationId: req.headers['x-correlation-id']
});
```
## Integration with Services
To use in your service:
1. Add dependency to your service's `package.json`:
```json
{
"dependencies": {
"@stock-bot/logger": "workspace:*"
}
}
```
2. Update your service's `tsconfig.json` references:
```json
{
"references": [
{ "path": "../../../libs/logger" }
]
}
```
3. Import and use:
```typescript
import { getLogger } from '@stock-bot/logger';
const logger = getLogger('my-service');
```
## Performance Considerations
- Logs are batched and sent asynchronously to Loki
- File logging uses daily rotation to prevent large files
- Console logging can be disabled in production
- Use log throttling for high-frequency events
- Sensitive data is automatically masked
## Troubleshooting
### Logs not appearing in Loki
1. Check Loki connection:
```bash
curl http://localhost:3100/ready
```
2. Verify environment variables:
```bash
echo $LOKI_HOST $LOKI_PORT
```
3. Check container logs:
```bash
docker logs stock-bot-loki
```
### High memory usage
- Reduce `LOKI_BATCH_SIZE` if batching too many logs
- Decrease `LOKI_FLUSH_INTERVAL_MS` to flush more frequently
- Disable file logging if not needed
### Missing logs
- Check log level configuration
- Verify service name matches expectations
- Ensure proper error handling around logger calls

26
libs/logger/package.json Normal file
View file

@ -0,0 +1,26 @@
{
"name": "@stock-bot/logger",
"version": "1.0.0",
"description": "Enhanced logging library with Loki integration for stock-bot services",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"clean": "rm -rf dist",
"test": "jest"
},
"dependencies": {
"@stock-bot/types": "workspace:*",
"@stock-bot/config": "workspace:*",
"winston": "^3.11.0",
"winston-loki": "^6.0.8",
"winston-daily-rotate-file": "^4.7.1"
},
"devDependencies": {
"@types/jest": "^29.5.2",
"@types/node": "^20.5.0",
"jest": "^29.5.0",
"typescript": "^5.4.5"
}
}

43
libs/logger/src/index.ts Normal file
View file

@ -0,0 +1,43 @@
/**
* @stock-bot/logger - Enhanced logging library with Loki integration
*
* Main exports for the logger library
*/
// Core logger classes and functions
export { Logger, createLogger, getLogger, shutdownLoggers } from './logger';
// Utility functions
export {
createTimer,
formatError,
sanitizeMetadata,
generateCorrelationId,
extractHttpMetadata,
createBusinessEvent,
createSecurityEvent,
maskSensitiveData,
calculateLogSize,
LogThrottle
} from './utils';
// Express middleware
export {
loggingMiddleware,
errorLoggingMiddleware,
createRequestLogger
} from './middleware';
// Type exports
export type {
LogLevel,
LogContext,
LogMetadata,
LoggerOptions,
LokiTransportOptions,
PerformanceTimer,
LokiLogEntry,
StructuredLog
} from './types';
export type { LoggingMiddlewareOptions } from './middleware';

358
libs/logger/src/logger.ts Normal file
View file

@ -0,0 +1,358 @@
/**
* Enhanced logger with Loki integration for Stock Bot platform
*
* Features:
* - Multiple log levels (debug, info, warn, error)
* - Console and file logging
* - Loki integration for centralized logging
* - Structured logging with metadata
* - Performance optimized with batching
* - Service-specific context
*/
import winston from 'winston';
import LokiTransport from 'winston-loki';
import DailyRotateFile from 'winston-daily-rotate-file';
import { loggingConfig, lokiConfig } from '@stock-bot/config';
import type { LogLevel, LogContext, LogMetadata } from './types';
// Global logger instances cache
const loggerInstances = new Map<string, winston.Logger>();
/**
* Create or retrieve a logger instance for a specific service
*/
export function createLogger(serviceName: string, options?: {
level?: LogLevel;
enableLoki?: boolean;
enableFile?: boolean;
enableConsole?: boolean;
}): winston.Logger {
const key = `${serviceName}-${JSON.stringify(options || {})}`;
if (loggerInstances.has(key)) {
return loggerInstances.get(key)!;
}
const logger = buildLogger(serviceName, options);
loggerInstances.set(key, logger);
return logger;
}
/**
* Build a winston logger with all configured transports
*/
function buildLogger(serviceName: string, options?: {
level?: LogLevel;
enableLoki?: boolean;
enableFile?: boolean;
enableConsole?: boolean;
}): winston.Logger {
const {
level = loggingConfig.LOG_LEVEL as LogLevel,
enableLoki = true,
enableFile = loggingConfig.LOG_FILE,
enableConsole = loggingConfig.LOG_CONSOLE
} = options || {}; // Base logger configuration
const transports: winston.transport[] = [];
// Console transport
if (enableConsole) {
transports.push(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple(),
winston.format.printf(({ timestamp, level, service, message, metadata }) => {
const meta = metadata && Object.keys(metadata).length > 0
? `\n${JSON.stringify(metadata, null, 2)}`
: '';
return `${timestamp} [${level}] [${service}] ${message}${meta}`;
})
)
}));
}
// File transport with daily rotation
if (enableFile) {
// General log file
transports.push(new DailyRotateFile({
filename: `${loggingConfig.LOG_FILE_PATH}/${serviceName}-%DATE%.log`,
datePattern: loggingConfig.LOG_FILE_DATE_PATTERN,
zippedArchive: true,
maxSize: loggingConfig.LOG_FILE_MAX_SIZE,
maxFiles: loggingConfig.LOG_FILE_MAX_FILES,
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
)
}));
// Separate error log file
if (loggingConfig.LOG_ERROR_FILE) {
transports.push(new DailyRotateFile({
level: 'error',
filename: `${loggingConfig.LOG_FILE_PATH}/${serviceName}-error-%DATE%.log`,
datePattern: loggingConfig.LOG_FILE_DATE_PATTERN,
zippedArchive: true,
maxSize: loggingConfig.LOG_FILE_MAX_SIZE,
maxFiles: loggingConfig.LOG_FILE_MAX_FILES,
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
)
}));
}
}
// Loki transport for centralized logging
if (enableLoki && lokiConfig.LOKI_HOST) {
try {
const lokiTransport = new LokiTransport({
host: lokiConfig.LOKI_URL || `http://${lokiConfig.LOKI_HOST}:${lokiConfig.LOKI_PORT}`,
labels: {
service: serviceName,
environment: lokiConfig.LOKI_ENVIRONMENT_LABEL,
...(lokiConfig.LOKI_DEFAULT_LABELS ? JSON.parse(lokiConfig.LOKI_DEFAULT_LABELS) : {})
},
json: true,
batching: true,
interval: lokiConfig.LOKI_FLUSH_INTERVAL_MS,
timeout: lokiConfig.LOKI_PUSH_TIMEOUT,
basicAuth: lokiConfig.LOKI_USERNAME && lokiConfig.LOKI_PASSWORD
? `${lokiConfig.LOKI_USERNAME}:${lokiConfig.LOKI_PASSWORD}`
: undefined,
onConnectionError: (err) => {
console.error('Loki connection error:', err);
}
});
transports.push(lokiTransport);
} catch (error) {
console.warn('Failed to initialize Loki transport:', error);
}
}
const loggerConfig: winston.LoggerOptions = {
level,
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
winston.format.errors({ stack: true }),
winston.format.metadata({
fillExcept: ['message', 'level', 'timestamp', 'service']
}),
winston.format.json()
),
defaultMeta: {
service: serviceName,
environment: loggingConfig.LOG_ENVIRONMENT,
version: loggingConfig.LOG_SERVICE_VERSION
},
transports
};
return winston.createLogger(loggerConfig);
}
/**
* Enhanced Logger class with convenience methods
*/
export class Logger {
private winston: winston.Logger;
private serviceName: string;
private context: LogContext;
constructor(serviceName: string, context: LogContext = {}, options?: {
level?: LogLevel;
enableLoki?: boolean;
enableFile?: boolean;
enableConsole?: boolean;
}) {
this.serviceName = serviceName;
this.context = context;
this.winston = createLogger(serviceName, options);
}
/**
* Debug level logging
*/
debug(message: string, metadata?: LogMetadata): void {
this.log('debug', message, metadata);
}
/**
* Info level logging
*/
info(message: string, metadata?: LogMetadata): void {
this.log('info', message, metadata);
}
/**
* Warning level logging
*/
warn(message: string, metadata?: LogMetadata): void {
this.log('warn', message, metadata);
}
/**
* Error level logging
*/
error(message: string, error?: Error | any, metadata?: LogMetadata): void {
const logData: LogMetadata = { ...metadata };
if (error) {
if (error instanceof Error) {
logData.error = {
name: error.name,
message: error.message,
stack: loggingConfig.LOG_ERROR_STACK ? error.stack : undefined
};
} else {
logData.error = error;
}
}
this.log('error', message, logData);
}
/**
* HTTP request logging
*/
http(message: string, requestData?: {
method?: string;
url?: string;
statusCode?: number;
responseTime?: number;
userAgent?: string;
ip?: string;
}): void {
if (!loggingConfig.LOG_HTTP_REQUESTS) return;
this.log('http', message, {
request: requestData,
type: 'http_request'
});
}
/**
* Performance/timing logging
*/
performance(message: string, timing: {
operation: string;
duration: number;
startTime?: number;
endTime?: number;
}): void {
if (!loggingConfig.LOG_PERFORMANCE) return;
this.log('info', message, {
performance: timing,
type: 'performance'
});
}
/**
* Business event logging
*/
business(message: string, event: {
type: string;
entity?: string;
action?: string;
result?: 'success' | 'failure' | 'partial';
amount?: number;
symbol?: string;
}): void {
this.log('info', message, {
business: event,
type: 'business_event'
});
}
/**
* Security event logging
*/
security(message: string, event: {
type: 'authentication' | 'authorization' | 'access' | 'vulnerability';
user?: string;
resource?: string;
action?: string;
result?: 'success' | 'failure';
ip?: string;
severity?: 'low' | 'medium' | 'high' | 'critical';
}): void {
this.log('warn', message, {
security: event,
type: 'security_event'
});
}
/**
* Create a child logger with additional context
*/
child(context: LogContext): Logger {
return new Logger(this.serviceName, { ...this.context, ...context });
}
/**
* Add persistent context to this logger
*/
addContext(context: LogContext): void {
this.context = { ...this.context, ...context };
}
/**
* Internal logging method
*/
private log(level: LogLevel, message: string, metadata?: LogMetadata): void {
const logData = {
...this.context,
...metadata,
timestamp: new Date().toISOString()
};
this.winston.log(level, message, logData);
}
/**
* Get the underlying winston logger
*/
getWinstonLogger(): winston.Logger {
return this.winston;
}
/**
* Gracefully close all transports
*/
async close(): Promise<void> {
return new Promise((resolve) => {
this.winston.end(() => {
resolve();
});
});
}
}
/**
* Create a default logger instance for a service
*/
export function getLogger(serviceName: string, context?: LogContext): Logger {
return new Logger(serviceName, context);
}
/**
* Shutdown all logger instances gracefully
*/
export async function shutdownLoggers(): Promise<void> {
const closePromises = Array.from(loggerInstances.values()).map(logger =>
new Promise<void>((resolve) => {
logger.end(() => {
resolve();
});
})
);
await Promise.all(closePromises);
loggerInstances.clear();
}
// Export types for convenience
export type { LogLevel, LogContext, LogMetadata } from './types';

View file

@ -0,0 +1,200 @@
/**
* Hono middleware for request logging
*/
import type { Context, Next } from 'hono';
import { Logger } from './logger';
import { generateCorrelationId, createTimer } from './utils';
export interface LoggingMiddlewareOptions {
logger?: Logger;
serviceName?: string;
skipPaths?: string[];
skipSuccessfulRequests?: boolean;
logRequestBody?: boolean;
logResponseBody?: boolean;
maxBodySize?: number;
}
/**
* Hono middleware for HTTP request/response logging
*/
export function loggingMiddleware(options: LoggingMiddlewareOptions = {}) {
const {
serviceName = 'unknown-service',
skipPaths = ['/health', '/metrics', '/favicon.ico'],
skipSuccessfulRequests = false,
logRequestBody = false,
logResponseBody = false,
maxBodySize = 1024
} = options;
// Create logger if not provided
const logger = options.logger || new Logger(serviceName);
return async (c: Context, next: Next) => {
const url = new URL(c.req.url);
const path = url.pathname;
// Skip certain paths
if (skipPaths.some(skipPath => path.startsWith(skipPath))) {
return next();
} // Generate correlation ID
const correlationId = generateCorrelationId();
// Store correlation ID in context for later use
c.set('correlationId', correlationId);
// Set correlation ID as response header
c.header('x-correlation-id', correlationId);
// Start timer
const timer = createTimer(`${c.req.method} ${path}`); // Extract request metadata
const requestMetadata: any = {
method: c.req.method,
url: c.req.url,
path: path,
userAgent: c.req.header('user-agent'),
contentType: c.req.header('content-type'),
contentLength: c.req.header('content-length'),
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
};
// Add request body if enabled
if (logRequestBody) {
try {
const body = await c.req.text();
if (body) {
requestMetadata.body = body.length > maxBodySize
? body.substring(0, maxBodySize) + '...[truncated]'
: body;
}
} catch (error) {
// Body might not be available or already consumed
}
} // Log request start
logger.http('HTTP Request started', {
method: c.req.method,
url: c.req.url,
userAgent: c.req.header('user-agent'),
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
});
// Process request
await next();
// Calculate response time
const timing = timer.end();
// Get response information
const response = c.res;
const status = response.status;
// Determine log level based on status code
const isError = status >= 400;
const isSuccess = status >= 200 && status < 300;
// Skip successful requests if configured
if (skipSuccessfulRequests && isSuccess) {
return;
}
const responseMetadata: any = {
correlationId,
request: requestMetadata,
response: {
statusCode: status,
statusText: response.statusText,
contentLength: response.headers.get('content-length'),
contentType: response.headers.get('content-type')
},
performance: timing
};
// Add response body if enabled
if (logResponseBody) {
try {
const responseBody = await response.clone().text();
if (responseBody) {
responseMetadata.response.body = responseBody.length > maxBodySize
? responseBody.substring(0, maxBodySize) + '...[truncated]'
: responseBody;
}
} catch (error) {
// Response body might not be available
}
} // Log based on status code
if (isError) {
logger.error('HTTP Request failed', undefined, {
correlationId,
request: requestMetadata,
response: {
statusCode: status,
statusText: response.statusText,
contentLength: response.headers.get('content-length'),
contentType: response.headers.get('content-type')
},
performance: timing
});
} else {
logger.http('HTTP Request completed', {
method: c.req.method,
url: c.req.url,
statusCode: status,
responseTime: timing.duration,
userAgent: c.req.header('user-agent'),
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
});
}
};
}
/**
* Error logging middleware for Hono
*/
export function errorLoggingMiddleware(logger?: Logger) {
return async (c: Context, next: Next) => {
const errorLogger = logger || new Logger('error-handler');
try {
await next();
} catch (err) {
const correlationId = c.get('correlationId') || c.req.header('x-correlation-id');
const url = new URL(c.req.url);
const requestMetadata = {
method: c.req.method,
url: c.req.url,
path: url.pathname,
userAgent: c.req.header('user-agent'),
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
};
errorLogger.error('Unhandled HTTP error', err instanceof Error ? err : new Error(String(err)), {
correlationId,
request: requestMetadata,
response: {
statusCode: c.res.status
}
});
// Re-throw the error so Hono can handle it
throw err;
}
};
}
/**
* Create a child logger with request context for Hono
*/
export function createRequestLogger(c: Context, baseLogger: Logger): Logger {
const correlationId = c.get('correlationId') || c.req.header('x-correlation-id');
const url = new URL(c.req.url);
return baseLogger.child({
correlationId,
requestId: correlationId,
method: c.req.method,
path: url.pathname,
userAgent: c.req.header('user-agent'),
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
});
}

119
libs/logger/src/types.ts Normal file
View file

@ -0,0 +1,119 @@
/**
* Type definitions for the logger library
*/
// Standard log levels
export type LogLevel = 'error' | 'warn' | 'info' | 'http' | 'verbose' | 'debug' | 'silly';
// Context that persists across log calls
export interface LogContext {
[key: string]: any;
requestId?: string;
sessionId?: string;
userId?: string;
traceId?: string;
spanId?: string;
correlationId?: string;
}
// Metadata for individual log entries
export interface LogMetadata {
[key: string]: any;
correlationId?: string;
error?: {
name: string;
message: string;
stack?: string;
} | any;request?: {
method?: string;
url?: string;
path?: string;
statusCode?: number;
responseTime?: number;
userAgent?: string;
ip?: string;
body?: string;
headers?: Record<string, string>;
contentType?: string;
contentLength?: string;
};
performance?: {
operation: string;
duration: number;
startTime?: number;
endTime?: number;
};
business?: {
type: string;
entity?: string;
action?: string;
result?: 'success' | 'failure' | 'partial';
amount?: number;
symbol?: string;
};
security?: {
type: 'authentication' | 'authorization' | 'access' | 'vulnerability';
user?: string;
resource?: string;
action?: string;
result?: 'success' | 'failure';
ip?: string;
severity?: 'low' | 'medium' | 'high' | 'critical';
};
type?: 'http_request' | 'performance' | 'business_event' | 'security_event' | 'system' | 'custom';
}
// Logger configuration options
export interface LoggerOptions {
level?: LogLevel;
enableLoki?: boolean;
enableFile?: boolean;
enableConsole?: boolean;
}
// Loki-specific configuration
export interface LokiTransportOptions {
host: string;
port?: number;
url?: string;
username?: string;
password?: string;
tenantId?: string;
labels?: Record<string, string>;
batchSize?: number;
interval?: number;
timeout?: number;
json?: boolean;
batching?: boolean;
basicAuth?: string;
}
// Performance timer utility type
export interface PerformanceTimer {
operation: string;
startTime: number;
end(): { operation: string; duration: number; startTime: number; endTime: number };
}
// Log entry structure for Loki
export interface LokiLogEntry {
timestamp: string;
level: LogLevel;
message: string;
service: string;
environment?: string;
version?: string;
metadata?: LogMetadata;
}
// Structured log format
export interface StructuredLog {
timestamp: string;
level: LogLevel;
service: string;
message: string;
context?: LogContext;
metadata?: LogMetadata;
environment?: string;
version?: string;
}

230
libs/logger/src/utils.ts Normal file
View file

@ -0,0 +1,230 @@
/**
* Utility functions for logging operations
*/
import type { PerformanceTimer, LogMetadata } from './types';
/**
* Create a performance timer to measure operation duration
*/
export function createTimer(operation: string): PerformanceTimer {
const startTime = Date.now();
return {
operation,
startTime,
end() {
const endTime = Date.now();
return {
operation,
duration: endTime - startTime,
startTime,
endTime
};
}
};
}
/**
* Format error for logging
*/
export function formatError(error: Error | any): LogMetadata['error'] {
if (error instanceof Error) {
return {
name: error.name,
message: error.message,
stack: error.stack
};
}
if (typeof error === 'object' && error !== null) {
return {
name: error.constructor?.name || 'UnknownError',
message: error.message || error.toString(),
...error
};
}
return {
name: 'UnknownError',
message: String(error)
};
}
/**
* Sanitize sensitive data from log metadata
*/
export function sanitizeMetadata(metadata: LogMetadata): LogMetadata {
const sensitiveKeys = [
'password', 'token', 'secret', 'key', 'apiKey', 'api_key',
'authorization', 'auth', 'credential', 'credentials'
];
function sanitizeValue(value: any): any {
if (typeof value === 'string') {
return '[REDACTED]';
}
if (Array.isArray(value)) {
return value.map(sanitizeValue);
}
if (typeof value === 'object' && value !== null) {
const sanitized: any = {};
for (const [key, val] of Object.entries(value)) {
const lowerKey = key.toLowerCase();
if (sensitiveKeys.some(sensitive => lowerKey.includes(sensitive))) {
sanitized[key] = '[REDACTED]';
} else {
sanitized[key] = sanitizeValue(val);
}
}
return sanitized;
}
return value;
}
const sanitized: LogMetadata = {};
for (const [key, value] of Object.entries(metadata)) {
const lowerKey = key.toLowerCase();
if (sensitiveKeys.some(sensitive => lowerKey.includes(sensitive))) {
sanitized[key] = '[REDACTED]';
} else {
sanitized[key] = sanitizeValue(value);
}
}
return sanitized;
}
/**
* Generate a correlation ID for request tracking
*/
export function generateCorrelationId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
/**
* Extract relevant HTTP request information for logging
*/
export function extractHttpMetadata(req: any): LogMetadata['request'] {
if (!req) return undefined;
return {
method: req.method,
url: req.url || req.originalUrl,
userAgent: req.get?.('User-Agent') || req.headers?.['user-agent'],
ip: req.ip || req.connection?.remoteAddress || req.headers?.['x-forwarded-for']
};
}
/**
* Create standardized business event metadata
*/
export function createBusinessEvent(
type: string,
action: string,
options?: {
entity?: string;
result?: 'success' | 'failure' | 'partial';
amount?: number;
symbol?: string;
[key: string]: any;
}
): LogMetadata {
return {
business: {
type,
action,
...options
},
type: 'business_event'
};
}
/**
* Create standardized security event metadata
*/
export function createSecurityEvent(
type: 'authentication' | 'authorization' | 'access' | 'vulnerability',
options?: {
user?: string;
resource?: string;
action?: string;
result?: 'success' | 'failure';
ip?: string;
severity?: 'low' | 'medium' | 'high' | 'critical';
[key: string]: any;
}
): LogMetadata {
return {
security: {
type,
...options
},
type: 'security_event'
};
}
/**
* Mask sensitive data in strings
*/
export function maskSensitiveData(str: string): string {
// Credit card numbers
str = str.replace(/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, '****-****-****-****');
// Email addresses
str = str.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, '***@***.***');
// API keys (common patterns)
str = str.replace(/\b[A-Za-z0-9]{32,}\b/g, '[API_KEY]');
// JWT tokens
str = str.replace(/eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*/g, '[JWT_TOKEN]');
return str;
}
/**
* Calculate log entry size for batching
*/
export function calculateLogSize(entry: any): number {
return JSON.stringify(entry).length;
}
/**
* Throttle logging to prevent spam
*/
export class LogThrottle {
private counters = new Map<string, { count: number; lastReset: number }>();
private readonly maxLogs: number;
private readonly windowMs: number;
constructor(maxLogs = 100, windowMs = 60000) {
this.maxLogs = maxLogs;
this.windowMs = windowMs;
}
shouldLog(key: string): boolean {
const now = Date.now();
const counter = this.counters.get(key);
if (!counter || now - counter.lastReset > this.windowMs) {
this.counters.set(key, { count: 1, lastReset: now });
return true;
}
if (counter.count >= this.maxLogs) {
return false;
}
counter.count++;
return true;
}
reset(key?: string): void {
if (key) {
this.counters.delete(key);
} else {
this.counters.clear();
}
}
}

16
libs/logger/tsconfig.json Normal file
View file

@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true
},
"include": [
"src/**/*"
],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"],
"references": [
{ "path": "../config" },
{ "path": "../types" }
]
}

View file

@ -39,12 +39,12 @@
"exclude": [
"node_modules",
"dist"
],
"references": [
], "references": [
{ "path": "./libs/api-client" },
{ "path": "./libs/config" },
{ "path": "./libs/event-bus" },
{ "path": "./libs/http-client" },
{ "path": "./libs/logger" },
{ "path": "./libs/types" },
{ "path": "./libs/utils" },
]