added logger
This commit is contained in:
parent
dd27f3bf2c
commit
58ae897e90
13 changed files with 1493 additions and 12 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
140
bun.lock
|
|
@ -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
342
libs/logger/README.md
Normal 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
26
libs/logger/package.json
Normal 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
43
libs/logger/src/index.ts
Normal 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
358
libs/logger/src/logger.ts
Normal 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';
|
||||
200
libs/logger/src/middleware.ts
Normal file
200
libs/logger/src/middleware.ts
Normal 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
119
libs/logger/src/types.ts
Normal 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
230
libs/logger/src/utils.ts
Normal 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
16
libs/logger/tsconfig.json
Normal 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" }
|
||||
]
|
||||
}
|
||||
|
|
@ -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" },
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue