diff --git a/package-lock.json b/package-lock.json index 2e30b41..6543582 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,8 @@ "fs": "^0.0.1-security", "multer": "^2.1.1", "p-queue": "^9.2.0", + "pino": "^10.3.1", + "pino-http": "^11.0.0", "unzipper": "^0.12.3" }, "devDependencies": { @@ -856,12 +858,50 @@ "js-tokens": "^10.0.0" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.2.tgz", + "integrity": "sha512-dKmJxJsGItLmc5CYZKuEjuG6GnBs6PG4gohMhyFOWKaNQoYCuRZJDECaBlHmcG0lv2wc2E0uU8lESmBEumC3DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/axios": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", @@ -983,6 +1023,13 @@ "integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==", "license": "ISC" }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1087,6 +1134,16 @@ "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.", "license": "ISC" }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1143,6 +1200,27 @@ "wrappy": "1" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1568,6 +1646,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2114,6 +2201,305 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2192,6 +2578,16 @@ "url": "https://opencollective.com/express" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2317,6 +2713,26 @@ ], "license": "MIT" }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -2453,6 +2869,22 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2497,6 +2929,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2605,6 +3043,15 @@ ], "license": "MIT" }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", diff --git a/package.json b/package.json index f9b70a0..25a7964 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,8 @@ "fs": "^0.0.1-security", "multer": "^2.1.1", "p-queue": "^9.2.0", + "pino": "^10.3.1", + "pino-http": "^11.0.0", "unzipper": "^0.12.3" }, "devDependencies": { diff --git a/src/app.ts b/src/app.ts index 9e43bef..36f0353 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,8 +1,13 @@ import express from "express"; +import pinoHttp from "pino-http"; import { deployRouter } from "./routes/deploy.js"; import { invokeRouter } from "./routes/invoke.js"; +import { httpLoggerOptions } from "./utils/logger.js"; export const app = express(); + +// @ts-expect-error +app.use(pinoHttp(httpLoggerOptions)); app.use(express.json()); app.use("/deploy", deployRouter); app.use("/f", invokeRouter); diff --git a/src/deploy/firecracker.ts b/src/deploy/firecracker.ts index a952906..8970495 100644 --- a/src/deploy/firecracker.ts +++ b/src/deploy/firecracker.ts @@ -2,14 +2,36 @@ import { spawn } from "child_process"; import fs from "fs"; import axios from "axios"; import path from "path"; +import { firecrackerLogger } from "../utils/logger.js"; export async function startFirecrackerProcess(apiSock: string) { + firecrackerLogger.debug({ apiSock }, "spawning firecracker process"); + const fc = spawn("firecracker", ["--api-sock", apiSock]); - fc.on("error", console.error); - fc.stderr.on("data", (d) => console.error(d.toString())); + fc.on("error", (err) => { + firecrackerLogger.error( + { err, apiSock }, + "firecracker process error", + ); + }); + + fc.stderr.on("data", (d) => { + firecrackerLogger.warn( + { apiSock, stderr: d.toString().trim() }, + "firecracker stderr output", + ); + }); + + fc.on("exit", (code, signal) => { + firecrackerLogger.info( + { apiSock, exitCode: code, signal }, + "firecracker process exited", + ); + }); await waitForFile(apiSock, 5000); + firecrackerLogger.debug({ apiSock }, "firecracker API socket ready"); return fc; } @@ -21,6 +43,10 @@ export async function waitForFile(path: any, timeout = 5000) { if (fs.existsSync(path)) return; if (Date.now() - start > timeout) { + firecrackerLogger.error( + { path, timeoutMs: timeout }, + "timeout waiting for file", + ); throw new Error("timeout waiting for socket"); } @@ -43,15 +69,19 @@ export async function configureVM( functionId: string, image: string, ) { + firecrackerLogger.debug({ functionId }, "configuring VM"); + await client.put("/machine-config", { vcpu_count: 1, mem_size_mib: 128, }); + firecrackerLogger.debug({ functionId, vcpu: 1, memMib: 128 }, "machine config set"); await client.put("/boot-source", { kernel_image_path: path.resolve("vmlinux"), boot_args: "console=ttyS0 reboot=k panic=1 pci=off init=/init -- /start.sh", }); + firecrackerLogger.debug({ functionId }, "boot source configured"); await client.put("/drives/rootfs", { drive_id: "rootfs", @@ -59,12 +89,16 @@ export async function configureVM( is_root_device: true, is_read_only: false, }); + firecrackerLogger.debug({ functionId, image }, "rootfs drive attached"); + const guestCid = Math.floor(Math.random() * 10000) + 3; + const vsockPath = `/tmp/vsock-${functionId}.sock`; await client.put("/vsock", { vsock_id: "vsock0", - guest_cid: Math.floor(Math.random() * 10000) + 3, - uds_path: `/tmp/vsock-${functionId}.sock`, + guest_cid: guestCid, + uds_path: vsockPath, }); + firecrackerLogger.debug({ functionId, guestCid, vsockPath }, "vsock configured"); await client.put("/logger", { log_path: `firecracker.log`, @@ -75,6 +109,7 @@ export async function configureVM( await client.put("/actions", { action_type: "InstanceStart", }); + firecrackerLogger.info({ functionId }, "VM instance started"); } export function waitForVMReady(fc: any) { @@ -82,6 +117,7 @@ export function waitForVMReady(fc: any) { let buffer = ""; const timeout = setTimeout(() => { + firecrackerLogger.error("VM startup timeout — READY signal not received within 50s"); reject(new Error("VM startup timeout")); }, 50000); @@ -90,6 +126,7 @@ export function waitForVMReady(fc: any) { if (buffer.includes("READY")) { clearTimeout(timeout); + firecrackerLogger.debug("VM READY signal received"); setTimeout(resolve, 200); } }); @@ -97,19 +134,41 @@ export function waitForVMReady(fc: any) { } export async function snapshotVM(client: any, functionId: string) { + firecrackerLogger.debug({ functionId }, "pausing VM for snapshot"); await client.patch("/vm", { state: "Paused" }); + const snapshotPath = path.resolve(`snapshot/snapshot-${functionId}`); + const memPath = path.resolve(`mem/mem-${functionId}`); + await client.put("/snapshot/create", { snapshot_type: "Full", - snapshot_path: path.resolve(`snapshot/snapshot-${functionId}`), - mem_file_path: path.resolve(`mem/mem-${functionId}`), + snapshot_path: snapshotPath, + mem_file_path: memPath, }); + + firecrackerLogger.info( + { functionId, snapshotPath, memPath }, + "VM snapshot created", + ); } export async function cleanupResources(paths: any) { - await Promise.allSettled([ + firecrackerLogger.debug( + { outputDir: paths.outputDir, apiSock: paths.apiSock, vsock: paths.vsock }, + "cleaning up deployment resources", + ); + + const results = await Promise.allSettled([ fs.promises.rm(paths.outputDir, { recursive: true, force: true }), fs.promises.rm(paths.apiSock), fs.promises.rm(paths.vsock), ]); + + const failed = results.filter((r) => r.status === "rejected"); + if (failed.length > 0) { + firecrackerLogger.warn( + { failedCount: failed.length, errors: failed.map((r: any) => r.reason?.message) }, + "some cleanup operations failed (non-critical)", + ); + } } diff --git a/src/deploy/pipeline.ts b/src/deploy/pipeline.ts index 7c23837..5cbcbcc 100644 --- a/src/deploy/pipeline.ts +++ b/src/deploy/pipeline.ts @@ -10,51 +10,114 @@ import { import fs from "fs"; import crypto from "crypto"; import { getPaths } from "../utils/path.js"; +import { pipelineLogger } from "../utils/logger.js"; export async function deployFunction(zipPath: string) { const functionId = crypto.randomBytes(8).toString("hex"); const paths = getPaths(functionId); let fc: ReturnType extends Promise ? T : never; + pipelineLogger.info( + { functionId, zipPath }, + "starting deployment pipeline", + ); + try { + // ── Stage 1: Extract zip ────────────────────────────────────── const t0 = performance.now(); await extractZip(zipPath, paths.outputDir); - console.log("extract:", performance.now() - t0); + const extractDuration = performance.now() - t0; + pipelineLogger.info( + { functionId, stage: "extract", durationMs: extractDuration }, + "zip extraction completed", + ); await fs.promises.unlink(zipPath); + // ── Stage 2: Prepare rootfs ─────────────────────────────────── const t1 = performance.now(); const image = await prepareRootfs(functionId); - console.log("rootfs:", performance.now() - t1); + const rootfsDuration = performance.now() - t1; + pipelineLogger.info( + { functionId, stage: "rootfs", durationMs: rootfsDuration, image }, + "rootfs preparation completed", + ); + // ── Stage 3: Spawn Firecracker ──────────────────────────────── const t2 = performance.now(); fc = await startFirecrackerProcess(paths.apiSock); - console.log("fc spawn:", performance.now() - t2); + const spawnDuration = performance.now() - t2; + pipelineLogger.info( + { functionId, stage: "fc-spawn", durationMs: spawnDuration }, + "firecracker process spawned", + ); + // ── Stage 4: Configure VM ───────────────────────────────────── const t3 = performance.now(); const readyPromise = waitForVMReady(fc); const client = createFcCient(paths.apiSock); const t4 = performance.now(); await configureVM(client, functionId, image); - console.log("configure Vm: ", performance.now() - t4); + const configureDuration = performance.now() - t4; + pipelineLogger.info( + { functionId, stage: "configure-vm", durationMs: configureDuration }, + "VM configured", + ); + // ── Stage 5: Wait for VM ready ──────────────────────────────── await readyPromise; - console.log("wait for vmReady: ", performance.now() - t3); + const readyDuration = performance.now() - t3; + pipelineLogger.info( + { functionId, stage: "vm-ready", durationMs: readyDuration }, + "VM reported READY", + ); + // ── Stage 6: Snapshot ───────────────────────────────────────── const t5 = performance.now(); await snapshotVM(client, functionId); - console.log("snapshot time: ", performance.now() - t5); + const snapshotDuration = performance.now() - t5; + pipelineLogger.info( + { functionId, stage: "snapshot", durationMs: snapshotDuration }, + "VM snapshot created", + ); + + const totalDuration = performance.now() - t0; + pipelineLogger.info( + { + functionId, + stage: "complete", + totalDurationMs: totalDuration, + stages: { + extractMs: extractDuration, + rootfsMs: rootfsDuration, + spawnMs: spawnDuration, + configureMs: configureDuration, + readyMs: readyDuration, + snapshotMs: snapshotDuration, + }, + }, + "deployment pipeline completed successfully", + ); return { functionId, url: `http://localhost:3000/f/${functionId}`, }; + } catch (err) { + pipelineLogger.error( + { functionId, err }, + "deployment pipeline failed", + ); + throw err; } finally { // Always kill the FC process — whether deploy succeeded or failed try { fc!?.kill("SIGKILL"); } catch { } const t6 = performance.now(); await cleanupResources(paths); - console.log("cleanupResources: ", performance.now() - t6); + pipelineLogger.debug( + { functionId, stage: "cleanup", durationMs: performance.now() - t6 }, + "post-deploy cleanup completed", + ); } } diff --git a/src/deploy/rootfs.ts b/src/deploy/rootfs.ts index 3714641..b7de429 100644 --- a/src/deploy/rootfs.ts +++ b/src/deploy/rootfs.ts @@ -1,36 +1,49 @@ import extract from "extract-zip"; import { exec as execCb, spawn } from "child_process"; import { promisify } from "util"; +import { rootfsLogger } from "../utils/logger.js"; const exec = promisify(execCb); export async function extractZip(zip: string, outputDir: string) { + rootfsLogger.debug({ zip, outputDir }, "extracting zip archive"); + await extract(zip, { dir: outputDir, onEntry: (entry) => { if (entry.fileName.includes("..")) { + rootfsLogger.error( + { fileName: entry.fileName }, + "path traversal detected in zip — aborting", + ); throw new Error("Invalid zip content"); } }, }); + + rootfsLogger.debug({ zip, outputDir }, "zip extraction completed"); } export async function prepareRootfs(functionId: string) { const baseImage = "rootfs.ext4"; const image = `rootfs/rootfs-${functionId}.ext4`; + rootfsLogger.debug({ functionId, baseImage, image }, "copying base rootfs image"); await exec(`cp --reflink=auto ${baseImage} ${image}`); const mountDir = `/mnt/rootfs-${functionId}`; const extracted = `extracted/${functionId}`; await exec(`sudo mkdir -p ${mountDir}`); + rootfsLogger.debug({ functionId, mountDir, image }, "mounting rootfs image"); await exec(`sudo mount -o loop ${image} ${mountDir}`); + rootfsLogger.debug({ functionId, from: extracted, to: `${mountDir}/app/` }, "copying user code into rootfs"); await exec(`sudo cp -r ${extracted}/. ${mountDir}/app/`); await exec(`sudo umount ${mountDir}`); await exec(`sudo rm -rf ${mountDir}`); + rootfsLogger.debug({ functionId, image }, "rootfs prepared and unmounted"); return image; } diff --git a/src/routes/deploy.ts b/src/routes/deploy.ts index bcce0c6..9b36620 100644 --- a/src/routes/deploy.ts +++ b/src/routes/deploy.ts @@ -3,23 +3,41 @@ import crypto from "crypto"; import { upload } from "../deploy/upload.js"; import { jobs, deployQueue } from "../deploy/queue.js"; import { deployFunction } from "../deploy/pipeline.js"; +import { deployLogger } from "../utils/logger.js"; export const deployRouter = Router(); deployRouter.post("/", upload.single("code"), async (req, res) => { if (!req.file?.path) { + deployLogger.warn("deploy request rejected: no file uploaded"); return res.status(400).json({ error: "No file uploaded" }); } if (deployQueue.size > 50) { + deployLogger.warn( + { queueSize: deployQueue.size }, + "deploy request rejected: queue full", + ); return res.status(429).json({ error: "Too many jobs" }); } const jobId = crypto.randomBytes(8).toString("hex"); jobs.set(jobId, { state: "pending" }); + deployLogger.info( + { + jobId, + fileName: req.file.originalname, + fileSize: req.file.size, + queueSize: deployQueue.size + 1, + }, + "deployment job enqueued", + ); + deployQueue.add(async () => { jobs.set(jobId, { state: "running" }); + deployLogger.info({ jobId }, "deployment job started"); + try { const result = await deployFunction(req.file!.path); jobs.set(jobId, { @@ -27,8 +45,16 @@ deployRouter.post("/", upload.single("code"), async (req, res) => { functionId: result.functionId, url: result.url, }); + deployLogger.info( + { jobId, functionId: result.functionId, url: result.url }, + "deployment job completed successfully", + ); } catch (err: any) { jobs.set(jobId, { state: "error", message: err.message }); + deployLogger.error( + { jobId, err }, + "deployment job failed", + ); } }); @@ -40,6 +66,10 @@ deployRouter.post("/", upload.single("code"), async (req, res) => { deployRouter.get("/status/:jobId", (req, res) => { const job = jobs.get(req.params.jobId); - if (!job) return res.status(404).json({ error: "Unknown job" }); + if (!job) { + deployLogger.debug({ jobId: req.params.jobId }, "status lookup: unknown job"); + return res.status(404).json({ error: "Unknown job" }); + } + deployLogger.debug({ jobId: req.params.jobId, state: job.state }, "status lookup"); res.json(job); }); diff --git a/src/routes/invoke.ts b/src/routes/invoke.ts index 00b80c0..6cf437a 100644 --- a/src/routes/invoke.ts +++ b/src/routes/invoke.ts @@ -1,21 +1,38 @@ import { Router } from "express"; import { enqueueRequest } from "../runtime/scheduler.js"; +import { runtimeLogger } from "../utils/logger.js"; export const invokeRouter = Router(); invokeRouter.use("/:functionId", async (req, res) => { + const { functionId } = req.params; + const subPath = req.path || "/"; + + runtimeLogger.info( + { functionId, method: req.method, subPath }, + "function invocation received", + ); + try { await new Promise((resolve, reject) => { - enqueueRequest(req.params.functionId, { + enqueueRequest(functionId, { req, res, - subPath: req.path || "/", + subPath, resolve, reject, }); }); - } catch (e) { - console.error(e); + + runtimeLogger.info( + { functionId, method: req.method, subPath, statusCode: res.statusCode }, + "function invocation completed", + ); + } catch (e: any) { + runtimeLogger.error( + { functionId, method: req.method, subPath, err: e }, + "function invocation failed", + ); if (!res.headersSent) { res.status(500).json({ diff --git a/src/runtime/cleanup.ts b/src/runtime/cleanup.ts index c1f475c..58bda59 100644 --- a/src/runtime/cleanup.ts +++ b/src/runtime/cleanup.ts @@ -1,4 +1,5 @@ import fs from "fs"; +import { cleanupLogger } from "../utils/logger.js"; import type { RuntimeFunction, Vm } from "../types/types.js"; @@ -6,20 +7,28 @@ export async function cleanupVm(fn: RuntimeFunction, vm: Vm) { if (vm.cleaned) return; vm.cleaned = true; + cleanupLogger.info({ functionId: fn.functionId, vmId: vm.id }, "cleaning up VM"); try { vm.firecrackerProcess.kill(); + cleanupLogger.debug({ vmId: vm.id }, "firecracker process killed"); } catch {} try { if (fs.existsSync(vm.apiSock)) { fs.unlinkSync(vm.apiSock); + cleanupLogger.debug({ path: vm.apiSock }, "API socket removed"); } if (fs.existsSync(vm.vsock)) { fs.unlinkSync(vm.vsock); + cleanupLogger.debug({ path: vm.vsock }, "vsock removed"); } } catch {} fn.vms = fn.vms.filter((v) => v !== vm); + cleanupLogger.info( + { functionId: fn.functionId, vmId: vm.id, remainingVms: fn.vms.length }, + "VM cleanup completed", + ); } diff --git a/src/runtime/protocol.ts b/src/runtime/protocol.ts index 1bc690c..8a59f00 100644 --- a/src/runtime/protocol.ts +++ b/src/runtime/protocol.ts @@ -1,4 +1,5 @@ import type { Socket } from "net"; +import { protocolLogger } from "../utils/logger.js"; export function buildPayload(req: any, subPath: string): string { return ( @@ -30,6 +31,7 @@ export function readVsockResponse( }; const timer = setTimeout(() => { + protocolLogger.error({ timeoutMs: timeout }, "function execution timeout"); socket.destroy(); reject(new Error("Function timeout")); }, timeout); @@ -52,11 +54,17 @@ export function readVsockResponse( if (msg.type === "response" || msg.type === "error") { clearTimeout(timer); cleanup(); + if (msg.type === "error") { + protocolLogger.warn( + { errorData: msg.data, errorMsg: msg.error }, + "VM returned error response", + ); + } resolve(msg); return; } } catch { - console.error("invalid json:", line); + protocolLogger.error({ rawLine: line }, "invalid JSON received from VM"); } } }; @@ -64,12 +72,14 @@ export function readVsockResponse( onError = (err) => { clearTimeout(timer); cleanup(); + protocolLogger.error({ err }, "vsock read error"); reject(err); }; onEnd = () => { clearTimeout(timer); cleanup(); + protocolLogger.error("vsock connection closed before response received"); reject(new Error("Connection closed before response")); }; diff --git a/src/runtime/scheduler.ts b/src/runtime/scheduler.ts index 4deb3f2..917a513 100644 --- a/src/runtime/scheduler.ts +++ b/src/runtime/scheduler.ts @@ -1,6 +1,7 @@ import { runtimeStore } from "./store.js"; import { createVm } from "./vm-manager.js"; import { sendRequest } from "./transport.js"; +import { schedulerLogger } from "../utils/logger.js"; import type { RequestTask, RuntimeFunction } from "../types/types.js"; @@ -12,25 +13,25 @@ export async function enqueueRequest(functionId: string, task: RequestTask) { if (!fn) { fn = { functionId, - queue: [], - vms: [], - processing: false, }; - runtimeStore.functions.set(functionId, fn); + schedulerLogger.info({ functionId }, "new runtime function registered"); } fn.queue.push(task); + schedulerLogger.debug( + { functionId, queueDepth: fn.queue.length }, + "request enqueued", + ); processQueue(fn); } async function processQueue(fn: RuntimeFunction) { if (fn.processing) return; - fn.processing = true; try { @@ -38,22 +39,38 @@ async function processQueue(fn: RuntimeFunction) { let vm = fn.vms.find((v) => v.state === "ready"); if (!vm && fn.vms.length < MAX_VMS) { + schedulerLogger.info( + { functionId: fn.functionId, currentVms: fn.vms.length }, + "creating new VM", + ); vm = await createVm(fn.functionId, fn); } - if (!vm) return; + if (!vm) { + schedulerLogger.warn( + { functionId: fn.functionId, vmCount: fn.vms.length }, + "no VM available, max reached", + ); + return; + } const task = fn.queue.shift(); - if (!task) return; vm.state = "busy"; + schedulerLogger.debug( + { functionId: fn.functionId, vmId: vm.id, subPath: task.subPath }, + "dispatching request to VM", + ); try { await sendRequest(task.subPath, task.req, task.res, vm); - task.resolve(); } catch (err) { + schedulerLogger.error( + { functionId: fn.functionId, vmId: vm.id, err }, + "request handling failed", + ); task.reject(err); } finally { vm.state = "ready"; diff --git a/src/runtime/transport.ts b/src/runtime/transport.ts index 1376f2f..b04b803 100644 --- a/src/runtime/transport.ts +++ b/src/runtime/transport.ts @@ -1,11 +1,14 @@ import net, { Socket } from "net"; import { buildPayload, readVsockResponse } from "./protocol.js"; +import { transportLogger } from "../utils/logger.js"; import type { Vm } from "../types/types.js"; export async function connectVsock( path: string, timeout = 5000, ): Promise { + transportLogger.debug({ path, timeoutMs: timeout }, "connecting to vsock"); + return new Promise((resolve, reject) => { const start = Date.now(); @@ -13,6 +16,10 @@ export async function connectVsock( const socket = net.createConnection({ path }); socket.once("connect", () => { + transportLogger.debug( + { path, elapsedMs: Date.now() - start }, + "vsock connected", + ); resolve(socket); }); @@ -20,6 +27,7 @@ export async function connectVsock( socket.destroy(); if (Date.now() - start > timeout) { + transportLogger.error({ path, timeoutMs: timeout }, "vsock connection timeout"); return reject(new Error("Vsock timeout")); } @@ -36,8 +44,8 @@ export async function getVmSocket(vm: Vm) { return vm.socket; } + transportLogger.debug({ vmId: vm.id, vsock: vm.vsock }, "establishing new VM socket"); vm.socket = await connectVsock(vm.vsock); - vm.socket.write("CONNECT 5000\n"); return vm.socket; @@ -45,9 +53,13 @@ export async function getVmSocket(vm: Vm) { export async function sendRequest(subPath: string, req: any, res: any, vm: Vm) { const socket = await getVmSocket(vm); - socket.write(buildPayload(req, subPath)); + transportLogger.debug( + { vmId: vm.id, method: req.method, subPath }, + "request payload sent to VM", + ); + const msg = await readVsockResponse(socket, 10000); if (msg.type === "response") { @@ -55,14 +67,21 @@ export async function sendRequest(subPath: string, req: any, res: any, vm: Vm) { try { const body = JSON.parse(msg.data.body); - res.status(statusCode).json(body); } catch { res.status(statusCode).send(msg.data.body ?? ""); } + + transportLogger.debug( + { vmId: vm.id, statusCode }, + "response forwarded to client", + ); } else { - res - .status(msg.data?.statusCode || 500) - .json(msg.data ?? { error: msg.error }); + const statusCode = msg.data?.statusCode || 500; + transportLogger.error( + { vmId: vm.id, statusCode, error: msg.error }, + "VM returned error response", + ); + res.status(statusCode).json(msg.data ?? { error: msg.error }); } } diff --git a/src/runtime/vm-manager.ts b/src/runtime/vm-manager.ts index cd61575..88e3da8 100644 --- a/src/runtime/vm-manager.ts +++ b/src/runtime/vm-manager.ts @@ -3,6 +3,7 @@ import net from "net"; import axios from "axios"; import path from "path"; import crypto from "crypto"; +import { vmManagerLogger } from "../utils/logger.js"; import type { RuntimeFunction, Vm } from "../types/types.js"; @@ -13,8 +14,22 @@ export async function createVm( const instanceId = crypto.randomBytes(4).toString("hex"); const apiSock = `/tmp/firecracker-${functionId}-${instanceId}.socket`; const vsock = `/tmp/vsock-${functionId}-${instanceId}.sock`; + + vmManagerLogger.info( + { functionId, instanceId, apiSock, vsock }, + "creating new VM instance", + ); + const fc = spawn("firecracker", ["--api-sock", apiSock]); + fc.on("error", (err) => { + vmManagerLogger.error({ instanceId, err }, "firecracker process error"); + }); + + fc.on("exit", (code, signal) => { + vmManagerLogger.info({ instanceId, exitCode: code, signal }, "firecracker process exited"); + }); + await waitForFirecrackerApiSocket(apiSock); const client = createFcClient(apiSock); @@ -30,6 +45,10 @@ export async function createVm( }; fn.vms.push(vm); + vmManagerLogger.info( + { functionId, instanceId, totalVms: fn.vms.length }, + "VM instance created and ready", + ); return vm; } @@ -45,6 +64,10 @@ export async function waitForFirecrackerApiSocket( client.once("connect", () => { client.destroy(); + vmManagerLogger.debug( + { path, elapsedMs: Date.now() - start }, + "API socket connected", + ); resolve(); }); @@ -52,6 +75,7 @@ export async function waitForFirecrackerApiSocket( client.destroy(); if (Date.now() - start > timeout) { + vmManagerLogger.error({ path, timeoutMs: timeout }, "API socket connection timeout"); return reject(new Error("socket timeout")); } setTimeout(tryConnect, 50); @@ -77,6 +101,8 @@ export async function restoreVm( functionId: string, vsock: string, ) { + vmManagerLogger.debug({ functionId, vsock }, "restoring VM from snapshot"); + await client.put("/snapshot/load", { snapshot_path: path.resolve(`snapshot/snapshot-${functionId}`), mem_backend: { @@ -89,4 +115,6 @@ export async function restoreVm( uds_path: vsock, }, }); + + vmManagerLogger.debug({ functionId }, "VM restored from snapshot"); } diff --git a/src/server.ts b/src/server.ts index 3b7b468..89747ca 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,5 +1,8 @@ import { app } from "./app.js"; +import { logger } from "./utils/logger.js"; -app.listen(3000, () => { - console.log("listening on http://localhost:3000"); +const PORT = process.env.PORT || 3000; + +app.listen(PORT, () => { + logger.info({ port: PORT }, `server listening on http://localhost:${PORT}`); }); diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..6ddf478 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,80 @@ +import { pino } from "pino"; +import type { Options } from "pino-http"; +import type { LoggerOptions } from "pino"; + +const isProd = process.env.NODE_ENV === "production"; + +const baseOptions: LoggerOptions = { + level: process.env.LOG_LEVEL || "info", + redact: { + paths: [ + "req.headers.authorization", + "req.headers.cookie", + "password", + "token", + ], + censor: "[REDACTED]", + }, + timestamp: pino.stdTimeFunctions.isoTime, +}; + +if (!isProd) { + baseOptions.transport = { + target: "pino-pretty", + options: { + colorize: true, + translateTime: "SYS:HH:MM:ss.l", + ignore: "pid,hostname", + }, + }; +} + +export const logger = pino(baseOptions); + +export const deployLogger = logger.child({ module: "deploy" }); +export const pipelineLogger = logger.child({ module: "pipeline" }); +export const firecrackerLogger = logger.child({ module: "firecracker" }); +export const rootfsLogger = logger.child({ module: "rootfs" }); +export const queueLogger = logger.child({ module: "queue" }); +export const runtimeLogger = logger.child({ module: "runtime" }); +export const schedulerLogger = logger.child({ module: "scheduler" }); +export const vmManagerLogger = logger.child({ module: "vm-manager" }); +export const transportLogger = logger.child({ module: "transport" }); +export const protocolLogger = logger.child({ module: "protocol" }); +export const cleanupLogger = logger.child({ module: "cleanup" }); + +export const httpLoggerOptions: Options = { + logger: logger.child({ module: "http" }), + autoLogging: { + ignore: (req) => req.url === "/health", + }, + customLogLevel: (_req, res, err) => { + if (res.statusCode >= 500 || err) return "error"; + if (res.statusCode >= 400) return "warn"; + return "info"; + }, + customSuccessMessage: (req, res) => { + return `${req.method} ${req.url} completed with ${res.statusCode}`; + }, + customErrorMessage: (req, _res, err) => { + return `${req.method} ${req.url} errored: ${err.message}`; + }, + customReceivedMessage: (req) => { + return `${req.method} ${req.url} received`; + }, + serializers: { + req(req) { + return { + method: req.method, + url: req.url, + remoteAddress: req.remoteAddress, + contentType: req.headers?.["content-type"], + }; + }, + res(res) { + return { + statusCode: res.statusCode, + }; + }, + }, +};