{"_id":"@dasasian/firebase-structured-logger","_rev":"15-75b5580b19ae5bce7419bfaa8d1b0d91","name":"@dasasian/firebase-structured-logger","dist-tags":{"latest":"1.0.0"},"versions":{"0.1.0":{"name":"@dasasian/firebase-structured-logger","version":"0.1.0","keywords":["firebase","logging","structured-logging","cloud-logging","firebase-functions","source-maps","symbolication","observability","typescript"],"author":{"name":"Dasasian"},"license":"MIT","_id":"@dasasian/firebase-structured-logger@0.1.0","maintainers":[{"name":"damith-dasasian","email":"damith@dasasian.com"}],"homepage":"https://github.com/dasasian/firebase-structured-logger#readme","bugs":{"url":"https://github.com/dasasian/firebase-structured-logger/issues"},"bin":{"fsl":"dist/tools/index.js"},"dist":{"shasum":"3a4872999ec3087cdc67ae14054fc627f13aa293","tarball":"https://registry.npmjs.org/@dasasian/firebase-structured-logger/-/firebase-structured-logger-0.1.0.tgz","fileCount":69,"integrity":"sha512-QoF37W0yRtdo31Xvm6VRIB+mcHRJ+O08rVwME9i94L0+1Mum8s4/58EbXMt30RO5BrJ0xf8dWFrjFUi95ksNsQ==","signatures":[{"sig":"MEUCIGvyjAVK7bxAy9BqJ1Wkm/o5z+0xO6cCGqI3XlDHbu+eAiEAybaQS54s9bFdfI0JU9nJELNvALm+hdqSHKblC1xsEBk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":154164},"engines":{"node":">=18"},"exports":{"./tools":{"types":"./dist/tools/index.d.ts","default":"./dist/tools/index.js"},"./client":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./functions":{"types":"./dist/functions/index.d.ts","default":"./dist/functions/index.js"}},"gitHead":"0b8bd85d2f529a98b9429b15de41e0a9f8c95b62","scripts":{"test":"tsx tests/logger.ts && FUNCTIONS_EMULATOR=true tsx tests/handler.ts && FUNCTIONS_EMULATOR=true tsx tests/symbolication.ts","build":"tsc","clean":"rm -rf dist","typecheck":"tsc --noEmit","build:watch":"tsc --watch","prepublishOnly":"npm run build"},"_npmUser":{"name":"damith-dasasian","email":"damith@dasasian.com"},"repository":{"url":"git+https://github.com/dasasian/firebase-structured-logger.git","type":"git"},"_npmVersion":"11.3.0","description":"Structured logging for Firebase apps — client, functions, and tools","directories":{},"_nodeVersion":"20.18.3","dependencies":{"ulid":"^2.3.0","@google-cloud/storage":"^7.0.0","@jridgewell/trace-mapping":"^0.3.31"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"tools":["dist/tools/index.d.ts"],"client":["dist/client/index.d.ts"],"functions":["dist/functions/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","firebase":"^12.0.0","typescript":"~5.9.3","@types/node":"^22.0.0","firebase-admin":"^13.0.0","firebase-functions":"^7.0.0"},"peerDependencies":{"firebase":">=10.0.0","firebase-admin":">=12.0.0","firebase-functions":">=6.0.0"},"peerDependenciesMeta":{"firebase":{"optional":true},"firebase-admin":{"optional":true},"firebase-functions":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/firebase-structured-logger_0.1.0_1786852576568_0.9494974763499078","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@dasasian/firebase-structured-logger","version":"0.1.1","keywords":["firebase","logging","structured-logging","cloud-logging","firebase-functions","source-maps","symbolication","observability","typescript"],"author":{"name":"Dasasian"},"license":"MIT","_id":"@dasasian/firebase-structured-logger@0.1.1","maintainers":[{"name":"damith-dasasian","email":"damith@dasasian.com"}],"homepage":"https://dasasian.com/firebase-structured-logger","bugs":{"url":"https://github.com/dasasian/firebase-structured-logger/issues"},"bin":{"fsl":"dist/tools/index.js"},"dist":{"shasum":"2fb276d3a12e5c3386bcacf85b39e8fd27046de7","tarball":"https://registry.npmjs.org/@dasasian/firebase-structured-logger/-/firebase-structured-logger-0.1.1.tgz","fileCount":77,"integrity":"sha512-sugg6Y4/w0FfO8javi1IjuQKdkr72i8Dda5la5mZBgRnbQwJI5A3l+FtMcSG3s3ZPjwefaIjMAYNF6xb77GhrA==","signatures":[{"sig":"MEQCIBlS8pgKxJx5dBpV3iekmTUWvuD4DUd76mhfthaJS6dZAiB5alk8PcOnbee3YUDZ5IltnbdmbVfaBqJF6J/Z3qu5ug==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":157749},"engines":{"node":">=18"},"exports":{"./tools":{"types":"./dist/tools/index.d.ts","default":"./dist/tools/index.js"},"./client":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./functions":{"types":"./dist/functions/index.d.ts","default":"./dist/functions/index.js"}},"gitHead":"b30fe48c20e323893019f42834588dbc14b37bc7","scripts":{"test":"tsx tests/logger.ts && tsx tests/breadcrumbs.ts && tsx tests/rateLimiter.ts && tsx tests/errorHandler.ts && FUNCTIONS_EMULATOR=true tsx tests/errorPayload.ts && FUNCTIONS_EMULATOR=true tsx tests/requestLogger.ts && FUNCTIONS_EMULATOR=true tsx tests/handler.ts && FUNCTIONS_EMULATOR=true tsx tests/symbolication.ts","build":"tsc","clean":"rm -rf dist","typecheck":"tsc --noEmit","build:watch":"tsc --watch","prepublishOnly":"npm run build"},"_npmUser":{"name":"damith-dasasian","email":"damith@dasasian.com"},"repository":{"url":"git+https://github.com/dasasian/firebase-structured-logger.git","type":"git"},"_npmVersion":"11.3.0","description":"Structured logging for Firebase apps — client, functions, and tools","directories":{},"_nodeVersion":"20.18.3","dependencies":{"ulid":"^2.3.0","@google-cloud/storage":"^7.0.0","@jridgewell/trace-mapping":"^0.3.31"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"tools":["dist/tools/index.d.ts"],"client":["dist/client/index.d.ts"],"functions":["dist/functions/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","firebase":"^12.0.0","typescript":"~5.9.3","@types/node":"^22.0.0","firebase-admin":"^13.0.0","firebase-functions":"^7.0.0"},"peerDependencies":{"firebase":">=10.0.0","firebase-admin":">=12.0.0","firebase-functions":">=6.0.0"},"peerDependenciesMeta":{"firebase":{"optional":true},"firebase-admin":{"optional":true},"firebase-functions":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/firebase-structured-logger_0.1.1_1786896274282_0.6133084049881696","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@dasasian/firebase-structured-logger","version":"0.2.0","keywords":["firebase","logging","structured-logging","cloud-logging","firebase-functions","source-maps","symbolication","observability","typescript"],"author":{"name":"Dasasian"},"license":"MIT","_id":"@dasasian/firebase-structured-logger@0.2.0","maintainers":[{"name":"damith-dasasian","email":"damith@dasasian.com"}],"homepage":"https://dasasian.com/firebase-structured-logger","bugs":{"url":"https://github.com/dasasian/firebase-structured-logger/issues"},"bin":{"fsl":"dist/tools/index.js"},"dist":{"shasum":"e39e9bebfed81f6d22a5c95c6992a064fc65db66","tarball":"https://registry.npmjs.org/@dasasian/firebase-structured-logger/-/firebase-structured-logger-0.2.0.tgz","fileCount":77,"integrity":"sha512-oWvYMVD+vi51uPbwu7B9ymzUDvKyZr1lc8IMYWMGZOssk/SBBbz1Nxl/TG9meG6sUKEYs9irHnnSTaqj6J5Tog==","signatures":[{"sig":"MEUCIQDS+W9jIKg/GY1jCGsphrgsk4O0JT9+4kDIAwDbDPVHUAIgHhxD2Vo5OcuTAgSER4H8pwL5i8ShQsXhqh0+FuOPypI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":160126},"engines":{"node":">=18"},"exports":{"./tools":{"types":"./dist/tools/index.d.ts","default":"./dist/tools/index.js"},"./client":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./functions":{"types":"./dist/functions/index.d.ts","default":"./dist/functions/index.js"}},"gitHead":"730d8c8d791ffcaeb39c8ddf06808944704e35ee","scripts":{"test":"tsx tests/logger.ts && tsx tests/breadcrumbs.ts && tsx tests/rateLimiter.ts && tsx tests/errorHandler.ts && FUNCTIONS_EMULATOR=true tsx tests/errorPayload.ts && FUNCTIONS_EMULATOR=true tsx tests/requestLogger.ts && FUNCTIONS_EMULATOR=true tsx tests/handler.ts && FUNCTIONS_EMULATOR=true tsx tests/symbolication.ts && FUNCTIONS_EMULATOR=true tsx tests/publicApi.ts && FUNCTIONS_EMULATOR=true tsx tests/configureTwice.ts","build":"tsc","clean":"rm -rf dist","typecheck":"tsc --noEmit","build:watch":"tsc --watch","prepublishOnly":"npm run build"},"_npmUser":{"name":"damith-dasasian","email":"damith@dasasian.com"},"repository":{"url":"git+https://github.com/dasasian/firebase-structured-logger.git","type":"git"},"_npmVersion":"11.3.0","description":"Structured logging for Firebase apps — client, functions, and tools","directories":{},"_nodeVersion":"20.18.3","dependencies":{"ulid":"^3.0.2","@google-cloud/storage":"^7.0.0","@jridgewell/trace-mapping":"^0.3.31"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"tools":["dist/tools/index.d.ts"],"client":["dist/client/index.d.ts"],"functions":["dist/functions/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","firebase":"^12.0.0","typescript":"~5.9.3","@types/node":"^26.2.0","firebase-admin":"^14.2.0","firebase-functions":"^7.0.0"},"peerDependencies":{"firebase":">=10.0.0","firebase-admin":">=12.0.0","firebase-functions":">=6.0.0"},"peerDependenciesMeta":{"firebase":{"optional":true},"firebase-admin":{"optional":true},"firebase-functions":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/firebase-structured-logger_0.2.0_1787438156270_0.5148026855836993","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@dasasian/firebase-structured-logger","version":"0.3.0","keywords":["firebase","logging","structured-logging","cloud-logging","firebase-functions","source-maps","symbolication","observability","typescript"],"author":{"name":"Dasasian"},"license":"MIT","_id":"@dasasian/firebase-structured-logger@0.3.0","maintainers":[{"name":"damith-dasasian","email":"damith@dasasian.com"}],"homepage":"https://dasasian.com/firebase-structured-logger","bugs":{"url":"https://github.com/dasasian/firebase-structured-logger/issues"},"bin":{"fsl":"dist/tools/index.js"},"dist":{"shasum":"60122c7bfcc87bdf9a29472dc585b86bfc922419","tarball":"https://registry.npmjs.org/@dasasian/firebase-structured-logger/-/firebase-structured-logger-0.3.0.tgz","fileCount":77,"integrity":"sha512-hb8wq3kgbTlh9vjbEe/nawkUZp8ZO+so8zwXQBf9OuholNxVNFx4tpuEe+4ZnLvzDnbZ5gOC1ej4zsZz/7SVfw==","signatures":[{"sig":"MEUCIDCvcVqN+5fCk+jgtPFlYCEVrlz+abUJricU49o8CeKqAiEAp6Y0oV55XeP1vnWeddQfKSNhM/JhdOYsCynfGR92kMk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":162586},"engines":{"node":">=22"},"exports":{"./tools":{"types":"./dist/tools/index.d.ts","default":"./dist/tools/index.js"},"./client":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./functions":{"types":"./dist/functions/index.d.ts","default":"./dist/functions/index.js"}},"gitHead":"bba3f7609ab72ef5d93ec35d7dc0cfa9d8b39978","scripts":{"test":"tsx tests/logger.ts && tsx tests/breadcrumbs.ts && tsx tests/rateLimiter.ts && tsx tests/errorHandler.ts && tsx tests/attachments.ts && FUNCTIONS_EMULATOR=true tsx tests/errorPayload.ts && FUNCTIONS_EMULATOR=true tsx tests/requestLogger.ts && FUNCTIONS_EMULATOR=true tsx tests/handler.ts && FUNCTIONS_EMULATOR=true tsx tests/symbolication.ts && FUNCTIONS_EMULATOR=true tsx tests/publicApi.ts && FUNCTIONS_EMULATOR=true tsx tests/configureTwice.ts && tsx tests/productionOutput.ts && tsx tests/handlerSymbolication.ts","build":"tsc","clean":"rm -rf dist","typecheck":"tsc --noEmit","build:watch":"tsc --watch","prepublishOnly":"npm run build"},"_npmUser":{"name":"damith-dasasian","email":"damith@dasasian.com"},"repository":{"url":"git+https://github.com/dasasian/firebase-structured-logger.git","type":"git"},"_npmVersion":"11.3.0","description":"Structured logging for Firebase apps — client, functions, and tools","directories":{},"_nodeVersion":"20.18.3","dependencies":{"ulid":"^3.0.2","@google-cloud/storage":"^7.0.0","@jridgewell/trace-mapping":"^0.3.31"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"tools":["dist/tools/index.d.ts"],"client":["dist/client/index.d.ts"],"functions":["dist/functions/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","jsdom":"^27.0.1","firebase":"^12.0.0","typescript":"~5.9.3","@types/node":"^26.2.0","@types/jsdom":"^30.0.0","firebase-admin":"^14.2.0","firebase-functions":"^7.0.0"},"peerDependencies":{"firebase":">=10.0.0","firebase-admin":">=12.0.0","firebase-functions":">=6.0.0"},"peerDependenciesMeta":{"firebase":{"optional":true},"firebase-admin":{"optional":true},"firebase-functions":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/firebase-structured-logger_0.3.0_1787458037863_0.8680380508045704","host":"s3://npm-registry-packages-npm-production"}},"0.4.0-beta.0":{"name":"@dasasian/firebase-structured-logger","version":"0.4.0-beta.0","keywords":["firebase","logging","structured-logging","cloud-logging","firebase-functions","source-maps","symbolication","observability","typescript"],"author":{"name":"Dasasian"},"license":"MIT","_id":"@dasasian/firebase-structured-logger@0.4.0-beta.0","maintainers":[{"name":"damith-dasasian","email":"damith@dasasian.com"}],"homepage":"https://dasasian.com/firebase-structured-logger","bugs":{"url":"https://github.com/dasasian/firebase-structured-logger/issues"},"bin":{"fsl":"dist/tools/index.js"},"dist":{"shasum":"2c9563921755a508785cac71e23b2d4261cb97ef","tarball":"https://registry.npmjs.org/@dasasian/firebase-structured-logger/-/firebase-structured-logger-0.4.0-beta.0.tgz","fileCount":77,"integrity":"sha512-2hbNIbSd73hY8YsmI0K9Vk8uLt9T0r5Jxx9FleCv1F+xKoc6kj6Ep1fZdxtYUh9c7eE2SEj2dsti3rJamyBjhA==","signatures":[{"sig":"MEUCIHRcmm6f1ap0xIxjnEtsJXwWzx6Z0zeUE6ExqLR/c57mAiEAmK98+XZI5wrdNff+RYpW3JDNcY5Z3/mWipJEbJpAr5E=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":163476},"engines":{"node":">=22"},"exports":{"./tools":{"types":"./dist/tools/index.d.ts","default":"./dist/tools/index.js"},"./client":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./functions":{"types":"./dist/functions/index.d.ts","default":"./dist/functions/index.js"}},"gitHead":"bba3f7609ab72ef5d93ec35d7dc0cfa9d8b39978","scripts":{"test":"tsx tests/logger.ts && tsx tests/breadcrumbs.ts && tsx tests/rateLimiter.ts && tsx tests/errorHandler.ts && tsx tests/attachments.ts && FUNCTIONS_EMULATOR=true tsx tests/errorPayload.ts && FUNCTIONS_EMULATOR=true tsx tests/requestLogger.ts && FUNCTIONS_EMULATOR=true tsx tests/handler.ts && FUNCTIONS_EMULATOR=true tsx tests/symbolication.ts && FUNCTIONS_EMULATOR=true tsx tests/publicApi.ts && FUNCTIONS_EMULATOR=true tsx tests/configureTwice.ts && tsx tests/productionOutput.ts && tsx tests/handlerSymbolication.ts","build":"tsc","clean":"rm -rf dist","smoke":"tsx smoke/run.ts","typecheck":"tsc --noEmit","build:watch":"tsc --watch","smoke:deploy":"cd smoke/functions && npm install && npm run build && cd .. && firebase deploy --only functions --project $(grep '^FSL_SMOKE_PROJECT=' .env.local | cut -d= -f2)","prepublishOnly":"npm run build"},"_npmUser":{"name":"damith-dasasian","email":"damith@dasasian.com"},"repository":{"url":"git+https://github.com/dasasian/firebase-structured-logger.git","type":"git"},"_npmVersion":"11.3.0","description":"Structured logging for Firebase apps — client, functions, and tools","directories":{},"_nodeVersion":"20.18.3","dependencies":{"ulid":"^3.0.2","@google-cloud/storage":"^7.0.0","@jridgewell/trace-mapping":"^0.3.31"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"tools":["dist/tools/index.d.ts"],"client":["dist/client/index.d.ts"],"functions":["dist/functions/index.d.ts"]}},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"tsx":"^4.19.0","jsdom":"^27.0.1","firebase":"^12.0.0","typescript":"~5.9.3","@types/node":"^26.2.0","@types/jsdom":"^30.0.0","firebase-admin":"^14.2.0","firebase-functions":"^7.0.0","@google-cloud/logging":"^11.4.0"},"peerDependencies":{"firebase":">=10.0.0","firebase-admin":">=12.0.0","firebase-functions":">=6.0.0"},"peerDependenciesMeta":{"firebase":{"optional":true},"firebase-admin":{"optional":true},"firebase-functions":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/firebase-structured-logger_0.4.0-beta.0_1787499828348_0.20252626766196635","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@dasasian/firebase-structured-logger","version":"0.4.0","keywords":["firebase","logging","structured-logging","cloud-logging","firebase-functions","source-maps","symbolication","observability","typescript"],"author":{"name":"Dasasian"},"license":"MIT","_id":"@dasasian/firebase-structured-logger@0.4.0","maintainers":[{"name":"damith-dasasian","email":"damith@dasasian.com"}],"homepage":"https://dasasian.com/firebase-structured-logger","bugs":{"url":"https://github.com/dasasian/firebase-structured-logger/issues"},"bin":{"fsl":"dist/tools/index.js"},"dist":{"shasum":"e248f6f7052eb087ea349b244d1afd47562ad64a","tarball":"https://registry.npmjs.org/@dasasian/firebase-structured-logger/-/firebase-structured-logger-0.4.0.tgz","fileCount":73,"integrity":"sha512-TE3XMjZXp1TMLxB3+J90xY7/KpmquSneOnoul0vT6ptQjN0A6R1/PAr3YIKpbLtoOXCDqlXFiv7FAlxrEskOVw==","signatures":[{"sig":"MEUCIAKyn0qmYajmhFH4XueQ64eYnaEls7J1zEnCZ5JbhZIAAiEAmAEYDkvKDAkZba6movDndxQorpCGtNAT2TanVhxUl1U=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":154013},"engines":{"node":">=22"},"exports":{"./tools":{"types":"./dist/tools/index.d.ts","default":"./dist/tools/index.js"},"./client":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./functions":{"types":"./dist/functions/index.d.ts","default":"./dist/functions/index.js"}},"gitHead":"a670fd965ef92ec23062ae1bdcb3aa18b381bd29","scripts":{"test":"tsx tests/logger.ts && tsx tests/breadcrumbs.ts && tsx tests/rateLimiter.ts && tsx tests/errorHandler.ts && tsx tests/attachments.ts && tsx tests/sourceMapStripping.ts && FUNCTIONS_EMULATOR=true tsx tests/errorPayload.ts && FUNCTIONS_EMULATOR=true tsx tests/requestLogger.ts && FUNCTIONS_EMULATOR=true tsx tests/handler.ts && FUNCTIONS_EMULATOR=true tsx tests/symbolication.ts && FUNCTIONS_EMULATOR=true tsx tests/publicApi.ts && FUNCTIONS_EMULATOR=true tsx tests/configureTwice.ts && tsx tests/productionOutput.ts && tsx tests/handlerSymbolication.ts","build":"tsc","clean":"rm -rf dist","smoke":"tsx smoke/run.ts","typecheck":"tsc --noEmit","build:watch":"tsc --watch","smoke:deploy":"cd smoke/functions && npm install && npm run build && cd .. && firebase deploy --only functions --project $(grep '^FSL_SMOKE_PROJECT=' .env.local | cut -d= -f2)","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"damith-dasasian","email":"damith@dasasian.com"},"repository":{"url":"git+https://github.com/dasasian/firebase-structured-logger.git","type":"git"},"_npmVersion":"11.3.0","description":"Structured logging for Firebase apps — client, functions, and tools","directories":{},"_nodeVersion":"20.18.3","dependencies":{"ulid":"^3.0.2","@google-cloud/storage":"^7.0.0","@jridgewell/trace-mapping":"^0.3.31"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"tools":["dist/tools/index.d.ts"],"client":["dist/client/index.d.ts"],"functions":["dist/functions/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","jsdom":"^27.0.1","firebase":"^12.0.0","typescript":"~5.9.3","@types/node":"^26.2.0","@types/jsdom":"^30.0.0","firebase-admin":"^14.2.0","firebase-functions":"^7.0.0","@google-cloud/logging":"^11.4.0"},"peerDependencies":{"firebase":">=10.0.0","firebase-admin":">=12.0.0","firebase-functions":">=6.0.0"},"peerDependenciesMeta":{"firebase":{"optional":true},"firebase-admin":{"optional":true},"firebase-functions":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/firebase-structured-logger_0.4.0_1787502909620_0.5537716831214388","host":"s3://npm-registry-packages-npm-production"}},"0.5.0-beta.0":{"name":"@dasasian/firebase-structured-logger","version":"0.5.0-beta.0","keywords":["firebase","logging","structured-logging","cloud-logging","firebase-functions","source-maps","symbolication","observability","typescript"],"author":{"name":"Dasasian"},"license":"MIT","_id":"@dasasian/firebase-structured-logger@0.5.0-beta.0","maintainers":[{"name":"damith-dasasian","email":"damith@dasasian.com"}],"homepage":"https://dasasian.com/firebase-structured-logger","bugs":{"url":"https://github.com/dasasian/firebase-structured-logger/issues"},"bin":{"fsl":"dist/tools/index.js"},"dist":{"shasum":"2e9fb4b7b8c21b154dccd312ca5bf1e33f92a85d","tarball":"https://registry.npmjs.org/@dasasian/firebase-structured-logger/-/firebase-structured-logger-0.5.0-beta.0.tgz","fileCount":73,"integrity":"sha512-gEao96wH4xkwAFARS7bTJXqg4B+8dR7iAmlt7wTmXaV1y/w7eaASMoP4vHUQD/shcFQbzoXwChro/RTI6afIbw==","signatures":[{"sig":"MEUCIBd8SGkn3OxDk2jeP/0uUxbLlDlPvfuIJ8Ah7U37iYgwAiEA9fqFahEqHtBsLhq3iHqzAQ5nj1bSYDubegV6XHDAp50=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":174992},"engines":{"node":">=22"},"exports":{"./tools":{"types":"./dist/tools/index.d.ts","default":"./dist/tools/index.js"},"./client":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./functions":{"types":"./dist/functions/index.d.ts","default":"./dist/functions/index.js"}},"gitHead":"0633e3b9e842df7426029ec766b113e65f78ffe8","scripts":{"test":"tsx tests/logger.ts && tsx tests/breadcrumbs.ts && tsx tests/rateLimiter.ts && tsx tests/errorHandler.ts && tsx tests/attachments.ts && tsx tests/sourceMapStripping.ts && FUNCTIONS_EMULATOR=true tsx tests/errorPayload.ts && FUNCTIONS_EMULATOR=true tsx tests/requestLogger.ts && FUNCTIONS_EMULATOR=true tsx tests/handler.ts && FUNCTIONS_EMULATOR=true tsx tests/symbolication.ts && FUNCTIONS_EMULATOR=true tsx tests/publicApi.ts && FUNCTIONS_EMULATOR=true tsx tests/configureTwice.ts && tsx tests/productionOutput.ts && tsx tests/handlerSymbolication.ts && tsx tests/releaseResolution.ts && tsx tests/sourceMapCacheBounds.ts","build":"tsc","clean":"rm -rf dist","smoke":"tsx smoke/run.ts","typecheck":"tsc --noEmit","build:watch":"tsc --watch","smoke:deploy":"cd smoke/functions && npm install && npm run build && cd .. && firebase deploy --only functions --project $(grep '^FSL_SMOKE_PROJECT=' .env.local | cut -d= -f2)","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"damith-dasasian","email":"damith@dasasian.com"},"repository":{"url":"git+https://github.com/dasasian/firebase-structured-logger.git","type":"git"},"_npmVersion":"11.3.0","description":"Structured logging for Firebase apps — client, functions, and tools","directories":{},"_nodeVersion":"20.18.3","dependencies":{"ulid":"^3.0.2","@google-cloud/storage":"^7.0.0","@jridgewell/trace-mapping":"^0.3.31"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"tools":["dist/tools/index.d.ts"],"client":["dist/client/index.d.ts"],"functions":["dist/functions/index.d.ts"]}},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"tsx":"^4.19.0","jsdom":"^27.0.1","firebase":"^12.0.0","typescript":"~5.9.3","@types/node":"^26.2.0","@types/jsdom":"^30.0.0","firebase-admin":"^14.2.0","firebase-functions":"^7.0.0","@google-cloud/logging":"^11.4.0"},"peerDependencies":{"firebase":">=10.0.0","firebase-admin":">=12.0.0","firebase-functions":">=6.0.0"},"peerDependenciesMeta":{"firebase":{"optional":true},"firebase-admin":{"optional":true},"firebase-functions":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/firebase-structured-logger_0.5.0-beta.0_1787512688599_0.6130747458987862","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@dasasian/firebase-structured-logger","version":"0.5.0","keywords":["firebase","logging","structured-logging","cloud-logging","firebase-functions","source-maps","symbolication","observability","typescript"],"author":{"name":"Dasasian"},"license":"MIT","_id":"@dasasian/firebase-structured-logger@0.5.0","maintainers":[{"name":"damith-dasasian","email":"damith@dasasian.com"}],"homepage":"https://dasasian.com/firebase-structured-logger","bugs":{"url":"https://github.com/dasasian/firebase-structured-logger/issues"},"bin":{"fsl":"dist/tools/index.js"},"dist":{"shasum":"450139e8a0b7675fe61cb69aaa2e09d73b9551e9","tarball":"https://registry.npmjs.org/@dasasian/firebase-structured-logger/-/firebase-structured-logger-0.5.0.tgz","fileCount":73,"integrity":"sha512-5UIZY0bxs7F5xP9ohLOfIRR8XLl4lvzc3OpeEdHzzsguktw8friewzK/3xknQE2lrtvi94BjnJenzPPYYn0AyA==","signatures":[{"sig":"MEQCID0s0IBqi+xGDAUGBIXFIh/j9xRqLDrtmer4MSJEoHxMAiAg/+0tfw0D0pCTxjh84x8ze4AUU0pjalNtYFr41OR6rg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":174985},"engines":{"node":">=22"},"exports":{"./tools":{"types":"./dist/tools/index.d.ts","default":"./dist/tools/index.js"},"./client":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./functions":{"types":"./dist/functions/index.d.ts","default":"./dist/functions/index.js"}},"gitHead":"9228550e09b218ae518c255cab413947dddffe40","scripts":{"test":"tsx tests/logger.ts && tsx tests/breadcrumbs.ts && tsx tests/rateLimiter.ts && tsx tests/errorHandler.ts && tsx tests/attachments.ts && tsx tests/sourceMapStripping.ts && FUNCTIONS_EMULATOR=true tsx tests/errorPayload.ts && FUNCTIONS_EMULATOR=true tsx tests/requestLogger.ts && FUNCTIONS_EMULATOR=true tsx tests/handler.ts && FUNCTIONS_EMULATOR=true tsx tests/symbolication.ts && FUNCTIONS_EMULATOR=true tsx tests/publicApi.ts && FUNCTIONS_EMULATOR=true tsx tests/configureTwice.ts && tsx tests/productionOutput.ts && tsx tests/handlerSymbolication.ts && tsx tests/releaseResolution.ts && tsx tests/sourceMapCacheBounds.ts","build":"tsc","clean":"rm -rf dist","smoke":"tsx smoke/run.ts","typecheck":"tsc --noEmit","build:watch":"tsc --watch","smoke:deploy":"cd smoke/functions && npm install && npm run build && cd .. && firebase deploy --only functions --project $(grep '^FSL_SMOKE_PROJECT=' .env.local | cut -d= -f2)","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"damith-dasasian","email":"damith@dasasian.com"},"repository":{"url":"git+https://github.com/dasasian/firebase-structured-logger.git","type":"git"},"_npmVersion":"11.3.0","description":"Structured logging for Firebase apps — client, functions, and tools","directories":{},"_nodeVersion":"20.18.3","dependencies":{"ulid":"^3.0.2","@google-cloud/storage":"^7.0.0","@jridgewell/trace-mapping":"^0.3.31"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"tools":["dist/tools/index.d.ts"],"client":["dist/client/index.d.ts"],"functions":["dist/functions/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","jsdom":"^27.0.1","firebase":"^12.0.0","typescript":"~5.9.3","@types/node":"^26.2.0","@types/jsdom":"^30.0.0","firebase-admin":"^14.2.0","firebase-functions":"^7.0.0","@google-cloud/logging":"^11.4.0"},"peerDependencies":{"firebase":">=10.0.0","firebase-admin":">=12.0.0","firebase-functions":">=6.0.0"},"peerDependenciesMeta":{"firebase":{"optional":true},"firebase-admin":{"optional":true},"firebase-functions":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/firebase-structured-logger_0.5.0_1787513477247_0.08495398592203762","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@dasasian/firebase-structured-logger","version":"0.6.0","keywords":["firebase","logging","structured-logging","cloud-logging","firebase-functions","source-maps","symbolication","observability","typescript"],"author":{"name":"Dasasian"},"license":"MIT","_id":"@dasasian/firebase-structured-logger@0.6.0","maintainers":[{"name":"damith-dasasian","email":"damith@dasasian.com"}],"homepage":"https://dasasian.com/firebase-structured-logger","bugs":{"url":"https://github.com/dasasian/firebase-structured-logger/issues"},"bin":{"fsl":"dist/tools/index.js"},"dist":{"shasum":"28d00a9844aa363acf8c0e3ba08bdbb4cce3eca6","tarball":"https://registry.npmjs.org/@dasasian/firebase-structured-logger/-/firebase-structured-logger-0.6.0.tgz","fileCount":73,"integrity":"sha512-e5PiE8GmRFO/0Ct4FoPxbOai42p3jEiWKcyRiJGt+fZrhaDvsyXwGgSIMw1CGBYQTk+G8iPS6Jl5UaIV9G3AqQ==","signatures":[{"sig":"MEYCIQCvrwzEFi3oCUNmL7M7WvpQ38EMzQ5Bex66swVffTxBSwIhANPheurmL6TteeaZseT3aWxDFdujsU6FWkvm9t+6vD4D","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":188863},"engines":{"node":">=22"},"exports":{"./tools":{"types":"./dist/tools/index.d.ts","default":"./dist/tools/index.js"},"./client":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./functions":{"types":"./dist/functions/index.d.ts","default":"./dist/functions/index.js"}},"gitHead":"a10d523df3c96a64355a8a85d3ed07250b11f6ae","scripts":{"test":"tsx tests/logger.ts && tsx tests/breadcrumbs.ts && tsx tests/rateLimiter.ts && tsx tests/errorHandler.ts && tsx tests/attachments.ts && tsx tests/feedback.ts && tsx tests/sourceMapStripping.ts && FUNCTIONS_EMULATOR=true tsx tests/errorPayload.ts && FUNCTIONS_EMULATOR=true tsx tests/requestLogger.ts && FUNCTIONS_EMULATOR=true tsx tests/handler.ts && FUNCTIONS_EMULATOR=true tsx tests/symbolication.ts && FUNCTIONS_EMULATOR=true tsx tests/publicApi.ts && FUNCTIONS_EMULATOR=true tsx tests/configureTwice.ts && tsx tests/productionOutput.ts && tsx tests/handlerSymbolication.ts && tsx tests/releaseResolution.ts && tsx tests/sourceMapCacheBounds.ts","build":"tsc","clean":"rm -rf dist","smoke":"tsx smoke/run.ts","typecheck":"tsc --noEmit","build:watch":"tsc --watch","smoke:deploy":"cd smoke/functions && npm install && npm run build && cd .. && firebase deploy --only functions --project $(grep '^FSL_SMOKE_PROJECT=' .env.local | cut -d= -f2)","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"damith-dasasian","email":"damith@dasasian.com"},"repository":{"url":"git+https://github.com/dasasian/firebase-structured-logger.git","type":"git"},"_npmVersion":"10.9.3","description":"Structured logging for Firebase apps — client, functions, and tools","directories":{},"_nodeVersion":"22.20.0","dependencies":{"ulid":"^3.0.2","@google-cloud/storage":"^7.0.0","@jridgewell/trace-mapping":"^0.3.31"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"tools":["dist/tools/index.d.ts"],"client":["dist/client/index.d.ts"],"functions":["dist/functions/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","jsdom":"^27.0.1","firebase":"^12.0.0","typescript":"~5.9.3","@types/node":"^26.2.0","@types/jsdom":"^30.0.0","firebase-admin":"^14.2.0","firebase-functions":"^7.0.0","@google-cloud/logging":"^11.4.0"},"peerDependencies":{"firebase":">=10.0.0","firebase-admin":">=12.0.0","firebase-functions":">=6.0.0"},"peerDependenciesMeta":{"firebase":{"optional":true},"firebase-admin":{"optional":true},"firebase-functions":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/firebase-structured-logger_0.6.0_1787531507185_0.3205791513718881","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"@dasasian/firebase-structured-logger","version":"0.7.0","keywords":["firebase","logging","structured-logging","cloud-logging","firebase-functions","source-maps","symbolication","observability","typescript"],"author":{"name":"Dasasian"},"license":"MIT","_id":"@dasasian/firebase-structured-logger@0.7.0","maintainers":[{"name":"damith-dasasian","email":"damith@dasasian.com"}],"homepage":"https://dasasian.com/firebase-structured-logger","bugs":{"url":"https://github.com/dasasian/firebase-structured-logger/issues"},"bin":{"fsl":"dist/tools/index.js"},"dist":{"shasum":"514c75a395bdd64c2f7e41fe201857f0645e75f7","tarball":"https://registry.npmjs.org/@dasasian/firebase-structured-logger/-/firebase-structured-logger-0.7.0.tgz","fileCount":85,"integrity":"sha512-c5I8VOZPNxoGJnANPwMn1Trg+F7zN5W4pNH57DVxcLGIrDZb8oyC4jcVEdVBeblJi1UHd4/D7Bhr41VxuSJR+w==","signatures":[{"sig":"MEYCIQDtJGLERLoh+yW5PbQ0MlDGiVbjbE+VPdkgLRdK4yxiiAIhANVbo7pQJOsM90p5bIB2fEEL1yYjr7VwUVCq99N1T7fC","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":264450},"engines":{"node":">=22"},"exports":{"./tools":{"types":"./dist/tools/index.d.ts","default":"./dist/tools/index.js"},"./client":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./functions":{"types":"./dist/functions/index.d.ts","default":"./dist/functions/index.js"}},"gitHead":"dd44b55b5a929ea528053c106d8e08eb97394a87","scripts":{"test":"tsx tests/logger.ts && tsx tests/breadcrumbs.ts && tsx tests/rateLimiter.ts && tsx tests/errorHandler.ts && tsx tests/attachments.ts && tsx tests/feedback.ts && tsx tests/paths.ts && tsx tests/sourceMapStripping.ts && FUNCTIONS_EMULATOR=true tsx tests/errorPayload.ts && FUNCTIONS_EMULATOR=true tsx tests/requestLogger.ts && FUNCTIONS_EMULATOR=true tsx tests/handler.ts && FUNCTIONS_EMULATOR=true tsx tests/httpHandler.ts && FUNCTIONS_EMULATOR=true tsx tests/symbolication.ts && FUNCTIONS_EMULATOR=true tsx tests/publicApi.ts && FUNCTIONS_EMULATOR=true tsx tests/configureTwice.ts && tsx tests/productionOutput.ts && tsx tests/handlerSymbolication.ts && tsx tests/releaseResolution.ts && tsx tests/sourceMapCacheBounds.ts","build":"tsc","clean":"rm -rf dist","smoke":"tsx smoke/run.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.check.json","build:watch":"tsc --watch","smoke:deploy":"cd smoke/functions && npm install && npm run build && cd .. && firebase deploy --only functions --project $(grep '^FSL_SMOKE_PROJECT=' .env.local | cut -d= -f2)","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"damith-dasasian","email":"damith@dasasian.com"},"repository":{"url":"git+https://github.com/dasasian/firebase-structured-logger.git","type":"git"},"_npmVersion":"10.9.3","description":"Structured logging for Firebase apps — client, functions, and tools","directories":{},"_nodeVersion":"22.20.0","dependencies":{"ulid":"^3.0.2","@google-cloud/storage":"^7.0.0","@jridgewell/trace-mapping":"^0.3.31"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"tools":["dist/tools/index.d.ts"],"client":["dist/client/index.d.ts"],"functions":["dist/functions/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","jsdom":"^27.0.1","firebase":"^12.0.0","typescript":"~5.9.3","@types/node":"^26.2.0","@types/jsdom":"^30.0.0","firebase-admin":"^14.2.0","firebase-functions":"^7.0.0","@google-cloud/logging":"^11.4.0"},"peerDependencies":{"firebase":">=10.0.0","firebase-admin":">=12.0.0","firebase-functions":">=6.0.0"},"peerDependenciesMeta":{"firebase":{"optional":true},"firebase-admin":{"optional":true},"firebase-functions":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/firebase-structured-logger_0.7.0_1788235773737_0.8718440865839172","host":"s3://npm-registry-packages-npm-production"}},"0.8.0":{"name":"@dasasian/firebase-structured-logger","version":"0.8.0","keywords":["firebase","logging","structured-logging","cloud-logging","firebase-functions","source-maps","symbolication","observability","typescript","google-cloud","cloud-run","error-reporting"],"author":{"name":"Dasasian"},"license":"MIT","_id":"@dasasian/firebase-structured-logger@0.8.0","maintainers":[{"name":"damith-dasasian","email":"damith@dasasian.com"}],"homepage":"https://dasasian.com/firebase-structured-logger","bugs":{"url":"https://github.com/dasasian/firebase-structured-logger/issues"},"bin":{"fsl":"dist/tools/index.js"},"dist":{"shasum":"fbab37cb1f3c903bc3897b52f248d28ee43bdad2","tarball":"https://registry.npmjs.org/@dasasian/firebase-structured-logger/-/firebase-structured-logger-0.8.0.tgz","fileCount":85,"integrity":"sha512-BBMnrFPtU7rwrlEV08ZL3wnhzZ4Wcu8+cgZ5JVFupdMJy1/BSEPz/QtpKKvhxboqq30NJ/JJa7d5ATTMRq+6+Q==","signatures":[{"sig":"MEQCIATGC8wGnWP4z1f/T+7oq6u8LY54c97hgA4u4LcECEstAiAv1na8S/q8WtNfaYG7eguAr2QFFbTPqA6zk0+YFONKPQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQCO41wo9HEkLV8Vg1JPXghmgBVSB0z+5s9cfSShLDQNGwIgeEkpJ8eZJORSJ30U8R3i7Y8IspKfrcep5nF03460Fio=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":285204},"engines":{"node":">=22"},"exports":{"./tools":{"types":"./dist/tools/index.d.ts","default":"./dist/tools/index.js"},"./client":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./functions":{"types":"./dist/functions/index.d.ts","default":"./dist/functions/index.js"}},"gitHead":"8205544f75accbe202d9730718c5a8e11f41790b","scripts":{"test":"tsx tests/logger.ts && tsx tests/browserBundle.ts && tsx tests/breadcrumbs.ts && tsx tests/rateLimiter.ts && tsx tests/errorHandler.ts && tsx tests/attachments.ts && tsx tests/feedback.ts && tsx tests/paths.ts && tsx tests/loadsWithoutOptionalPeers.ts && tsx tests/sourceMapStripping.ts && FUNCTIONS_EMULATOR=true tsx tests/errorPayload.ts && FUNCTIONS_EMULATOR=true tsx tests/requestLogger.ts && FUNCTIONS_EMULATOR=true tsx tests/handler.ts && FUNCTIONS_EMULATOR=true tsx tests/httpHandler.ts && FUNCTIONS_EMULATOR=true tsx tests/symbolication.ts && FUNCTIONS_EMULATOR=true tsx tests/publicApi.ts && FUNCTIONS_EMULATOR=true tsx tests/configureTwice.ts && tsx tests/productionOutput.ts && tsx tests/storageChain.ts && tsx tests/handlerSymbolication.ts && tsx tests/releaseResolution.ts && tsx tests/sourceMapCacheBounds.ts","build":"tsc","clean":"rm -rf dist","smoke":"tsx smoke/run.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.check.json","build:watch":"tsc --watch","smoke:deploy":"cd smoke/functions && npm install && npm run build && cd .. && firebase deploy --only functions --project $(grep '^FSL_SMOKE_PROJECT=' .env.local | cut -d= -f2)","smoke:install":"tsx smoke/install.ts","prepublishOnly":"npm run clean && npm run build","smoke:deploy:run":"bash smoke/cloudrun/deploy.sh"},"_npmUser":{"name":"damith-dasasian","email":"damith@dasasian.com"},"repository":{"url":"git+https://github.com/dasasian/firebase-structured-logger.git","type":"git"},"_npmVersion":"10.9.3","description":"Structured logging for Firebase and Google Cloud web apps — browser, backend, and tools","directories":{},"_nodeVersion":"22.20.0","dependencies":{"ulid":"^3.0.2","@google-cloud/storage":"^7.0.0","@jridgewell/trace-mapping":"^0.3.31"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"tools":["dist/tools/index.d.ts"],"client":["dist/client/index.d.ts"],"functions":["dist/functions/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","jsdom":"^27.0.1","esbuild":"^0.28.2","firebase":"^12.0.0","typescript":"~5.9.3","@types/node":"^26.2.0","@types/jsdom":"^30.0.0","firebase-admin":"^14.2.0","firebase-functions":"^7.0.0","@google-cloud/logging":"^11.4.0"},"peerDependencies":{"firebase":">=10.0.0","firebase-admin":">=12.0.0","firebase-functions":">=6.0.0"},"peerDependenciesMeta":{"firebase":{"optional":true},"firebase-admin":{"optional":true},"firebase-functions":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/firebase-structured-logger_0.8.0_1790281662756_0.2891618737274939","host":"s3://npm-registry-packages-npm-production"}},"0.9.0":{"name":"@dasasian/firebase-structured-logger","version":"0.9.0","keywords":["firebase","logging","structured-logging","cloud-logging","firebase-functions","source-maps","symbolication","observability","typescript","google-cloud","cloud-run","error-reporting"],"author":{"name":"Dasasian"},"license":"MIT","_id":"@dasasian/firebase-structured-logger@0.9.0","maintainers":[{"name":"damith-dasasian","email":"damith@dasasian.com"}],"homepage":"https://dasasian.com/firebase-structured-logger","bugs":{"url":"https://github.com/dasasian/firebase-structured-logger/issues"},"bin":{"fsl":"dist/tools/index.js"},"dist":{"shasum":"e1043c8e5bc512ada4f978a96d350402cab46231","tarball":"https://registry.npmjs.org/@dasasian/firebase-structured-logger/-/firebase-structured-logger-0.9.0.tgz","fileCount":85,"integrity":"sha512-giNvEx2QddPvQqJc9XQ+5wK5/+2oFPMFo8l/A9i4WQuU1EP+Ckr+R9QWZbXm0qzw3PQK4P8pjQkVQu80y1lA3w==","signatures":[{"sig":"MEUCIQCG0vdD1iAr5f00kIcvtYlV8VBRDF54JPGozZBglCXXZAIgS+HSNOZUZhK1gsI7uaP3p+eJFDd1qkxYo8ArO+dTVMw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIGjNBF+rgrRjFehsXjQe5/A0pampStN+NapQgQSqUNSuAiBi/MXoIOOpIDf+sKCKbCRjFcFh5j+ywkcxV+rRjM68bA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":343299},"engines":{"node":">=22"},"exports":{"./tools":{"types":"./dist/tools/index.d.ts","default":"./dist/tools/index.js"},"./client":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./functions":{"types":"./dist/functions/index.d.ts","default":"./dist/functions/index.js"}},"gitHead":"65666b019330eab3b7b2899eb45ac8861552c08e","scripts":{"test":"tsx tests/logger.ts && tsx tests/browserBundle.ts && tsx tests/breadcrumbs.ts && tsx tests/rateLimiter.ts && tsx tests/errorHandler.ts && tsx tests/attachments.ts && tsx tests/feedback.ts && tsx tests/paths.ts && tsx tests/loadsWithoutOptionalPeers.ts && tsx tests/sourceMapStripping.ts && FUNCTIONS_EMULATOR=true tsx tests/errorPayload.ts && FUNCTIONS_EMULATOR=true tsx tests/requestLogger.ts && FUNCTIONS_EMULATOR=true tsx tests/handler.ts && FUNCTIONS_EMULATOR=true tsx tests/httpHandler.ts && FUNCTIONS_EMULATOR=true tsx tests/symbolication.ts && FUNCTIONS_EMULATOR=true tsx tests/publicApi.ts && FUNCTIONS_EMULATOR=true tsx tests/configureTwice.ts && tsx tests/productionOutput.ts && tsx tests/storageChain.ts && tsx tests/entrySize.ts && tsx tests/handlerSymbolication.ts && tsx tests/releaseResolution.ts && tsx tests/sourceMapCacheBounds.ts","build":"tsc","clean":"rm -rf dist","smoke":"tsx smoke/run.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.check.json","build:watch":"tsc --watch","smoke:deploy":"cd smoke/functions && npm install && npm run build && cd .. && firebase deploy --only functions --project $(grep '^FSL_SMOKE_PROJECT=' .env.local | cut -d= -f2)","smoke:install":"tsx smoke/install.ts","prepublishOnly":"npm run clean && npm run build","smoke:deploy:run":"bash smoke/cloudrun/deploy.sh"},"_npmUser":{"name":"damith-dasasian","email":"damith@dasasian.com"},"repository":{"url":"git+https://github.com/dasasian/firebase-structured-logger.git","type":"git"},"_npmVersion":"10.9.3","description":"Structured logging for Firebase and Google Cloud web apps — browser, backend, and tools","directories":{},"_nodeVersion":"22.20.0","dependencies":{"ulid":"^3.0.2","@google-cloud/storage":"^7.0.0","@jridgewell/trace-mapping":"^0.3.31"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"tools":["dist/tools/index.d.ts"],"client":["dist/client/index.d.ts"],"functions":["dist/functions/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","jsdom":"^27.0.1","esbuild":"^0.28.2","firebase":"^12.0.0","typescript":"~5.9.3","@types/node":"^26.2.0","@types/jsdom":"^30.0.0","firebase-admin":"^14.2.0","firebase-functions":"^7.0.0","@google-cloud/logging":"^11.4.0"},"peerDependencies":{"firebase":">=10.0.0","firebase-admin":">=12.0.0","firebase-functions":">=6.0.0"},"peerDependenciesMeta":{"firebase":{"optional":true},"firebase-admin":{"optional":true},"firebase-functions":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/firebase-structured-logger_0.9.0_1790554786571_0.558785041725151","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"_id":"@dasasian/firebase-structured-logger@1.0.0","bin":{"fsl":"dist/tools/index.js"},"bugs":{"url":"https://github.com/dasasian/firebase-structured-logger/issues"},"dist":{"shasum":"6221d4beb4acee71f52e6aac17d6020d645119ea","tarball":"https://registry.npmjs.org/@dasasian/firebase-structured-logger/-/firebase-structured-logger-1.0.0.tgz","fileCount":101,"integrity":"sha512-lbPPeXbQRKcnCZXdB6lDQbBStuJBLJl/tAdqkWJ01nEhwgvKVyVc50kUl4I0sQpJS86s/gJMuwiPOYTxnLvXpQ==","signatures":[{"sig":"MEQCIFJc16v3POcQxEdrHcA+ZCyPue+Yrb7u9xHYE/Hur3d4AiAUXcCl8Q3xt3gGefSZhmJ5FSR8BRs5/mobzA281+U4+A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBLQJJ8lKvtmcioQpvZ8Ku9IqJEF7j4tUPTAp+8E5uxxAiBBNMJ2q9ra7Z4GBmkbe944o1cymX2pD9eoNXmXrfB+gA=="}],"unpackedSize":409037},"name":"@dasasian/firebase-structured-logger","author":{"name":"Dasasian"},"engines":{"node":">=22"},"exports":{"./tools":{"types":"./dist/tools/index.d.ts","default":"./dist/tools/index.js"},"./client":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./functions":{"types":"./dist/functions/index.d.ts","default":"./dist/functions/index.js"}},"gitHead":"c7c00ed130030b95203b32258a97f24328e465f2","license":"MIT","scripts":{"test":"tsx tests/logger.ts && tsx tests/browserBundle.ts && tsx tests/breadcrumbs.ts && tsx tests/rateLimiter.ts && tsx tests/errorHandler.ts && tsx tests/attachments.ts && tsx tests/feedback.ts && tsx tests/paths.ts && tsx tests/loadsWithoutOptionalPeers.ts && tsx tests/sourceMapStripping.ts && FUNCTIONS_EMULATOR=true tsx tests/errorPayload.ts && FUNCTIONS_EMULATOR=true tsx tests/requestLogger.ts && FUNCTIONS_EMULATOR=true tsx tests/handler.ts && FUNCTIONS_EMULATOR=true tsx tests/httpHandler.ts && FUNCTIONS_EMULATOR=true tsx tests/symbolication.ts && FUNCTIONS_EMULATOR=true tsx tests/publicApi.ts && FUNCTIONS_EMULATOR=true tsx tests/configureTwice.ts && FUNCTIONS_EMULATOR=true tsx tests/deprecatedNames.ts && tsx tests/productionOutput.ts && tsx tests/storageChain.ts && tsx tests/entrySize.ts && tsx tests/handlerSymbolication.ts && tsx tests/releaseResolution.ts && tsx tests/sourceMapCacheBounds.ts && tsx tests/doctor.ts && tsx tests/doctorProcess.ts && tsx tests/uploadSourcemapsBackend.ts","build":"tsc","clean":"rm -rf dist","smoke":"tsx smoke/run.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.check.json","build:watch":"tsc --watch","smoke:deploy":"cd smoke/functions && npm install && npm run build && cd .. && firebase deploy --only functions --project $(grep '^FSL_SMOKE_PROJECT=' .env.local | cut -d= -f2)","smoke:install":"tsx smoke/install.ts","prepublishOnly":"npm run clean && npm run build","smoke:deploy:run":"bash smoke/cloudrun/deploy.sh"},"version":"1.0.0","_npmUser":{"name":"damith-dasasian","email":"damith@dasasian.com"},"homepage":"https://dasasian.com/firebase-structured-logger","keywords":["firebase","logging","structured-logging","cloud-logging","firebase-functions","source-maps","symbolication","observability","typescript","google-cloud","cloud-run","error-reporting"],"repository":{"url":"git+https://github.com/dasasian/firebase-structured-logger.git","type":"git"},"_npmVersion":"10.9.3","description":"Structured logging for Firebase and Google Cloud web apps — browser, backend, and tools","directories":{},"maintainers":[{"name":"damith-dasasian","email":"damith@dasasian.com"}],"_nodeVersion":"22.20.0","dependencies":{"ulid":"^3.0.2","@google-cloud/storage":"^7.19.0 || ^8.1.0","@jridgewell/trace-mapping":"^0.3.31"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"tools":["dist/tools/index.d.ts"],"client":["dist/client/index.d.ts"],"functions":["dist/functions/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.15","jsdom":"^30.1.1","esbuild":"^0.28.2","firebase":"^12.19.0","typescript":"~6.0.3","@types/node":"^26.6.2","@types/jsdom":"^30.0.0","firebase-admin":"^14.5.0","firebase-functions":"^7.4.0","@google-cloud/logging":"^12.1.0"},"peerDependencies":{"firebase":">=10.0.0","firebase-admin":">=12.0.0","firebase-functions":">=6.0.0"},"peerDependenciesMeta":{"firebase":{"optional":true},"firebase-admin":{"optional":true},"firebase-functions":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/firebase-structured-logger_1.0.0_1790576217020_0.8970547789326315"}}},"time":{"created":"2026-08-16T03:56:16.372Z","modified":"2026-09-28T06:16:57.344Z","0.1.0":"2026-08-16T03:56:16.708Z","0.1.1":"2026-08-16T16:04:34.414Z","0.2.0":"2026-08-22T22:35:56.402Z","0.3.0":"2026-08-23T04:07:18.022Z","0.4.0-beta.0":"2026-08-23T15:43:48.481Z","0.4.0":"2026-08-23T16:35:09.770Z","0.5.0-beta.0":"2026-08-23T19:18:08.739Z","0.5.0":"2026-08-23T19:31:17.395Z","0.6.0":"2026-08-24T00:31:47.330Z","0.7.0":"2026-09-01T04:09:33.868Z","0.8.0":"2026-09-24T20:27:42.851Z","0.9.0":"2026-09-28T00:19:46.666Z","1.0.0":"2026-09-28T06:16:57.110Z"},"bugs":{"url":"https://github.com/dasasian/firebase-structured-logger/issues"},"author":{"name":"Dasasian"},"license":"MIT","homepage":"https://dasasian.com/firebase-structured-logger","keywords":["firebase","logging","structured-logging","cloud-logging","firebase-functions","source-maps","symbolication","observability","typescript","google-cloud","cloud-run","error-reporting"],"repository":{"url":"git+https://github.com/dasasian/firebase-structured-logger.git","type":"git"},"description":"Structured logging for Firebase and Google Cloud web apps — browser, backend, and tools","maintainers":[{"name":"damith-dasasian","email":"damith@dasasian.com"}],"readme":"<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/dasasian/firebase-structured-logger/main/assets/hero.svg\" alt=\"firebase-structured-logger — structured logging for Firebase apps\" width=\"900\">\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@dasasian/firebase-structured-logger\"><img alt=\"npm\" src=\"https://img.shields.io/npm/v/@dasasian/firebase-structured-logger?style=flat-square&label=npm&color=235a9b\"></a>\n  <a href=\"https://github.com/dasasian/firebase-structured-logger/actions/workflows/ci.yml\"><img alt=\"CI\" src=\"https://img.shields.io/github/actions/workflow/status/dasasian/firebase-structured-logger/ci.yml?branch=main&style=flat-square&label=CI\"></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://dasasian.com/firebase-structured-logger\"><strong>dasasian.com/firebase-structured-logger</strong></a>\n</p>\n\n# @dasasian/firebase-structured-logger\n\nYour web app crashed at `app-4f2a.js:1:98432`. This tells you it was `Checkout.tsx:42` — in your own Google Cloud project, next to your backend logs. Nothing leaves.\n\nShips a browser logger, a server logger, and the `fsl` CLI. Built for Firebase, but\nFirebase is optional: the browser half has no Firebase dependency, and the server half\nruns on Cloud Functions, Cloud Run, or any Node server on Google Cloud, where stdout goes\nto Cloud Logging. What it needs is a Google Cloud project — that is where the logs live.\n\n## One query, both halves\n\nFrontend and backend write to the same stream in the same shape, so one filter reads the whole story in order:\n\n```\nlabels.userId=\"<uid>\"\n```\n\n```\n10:42:03.114  INFO   screen=checkout      click \"Apply code\"\n10:42:03.118  INFO   screen=checkout      applying discount SAVE20\n10:42:03.402  INFO   fn=applyDiscount     started\n10:42:03.611  ERROR  fn=applyDiscount     coupon lookup failed: timeout\n10:42:03.798  ERROR  screen=checkout      TypeError at Checkout.tsx:42:9\n```\n\nTwo of those lines came from a browser and three from a server. You never had to think about that, and you never had to correlate two systems by timestamp to see it.\n\n**Every label in that query and those lines was attached automatically.** You write the message; the rest rides along. See [What you get for free](#what-you-get-for-free).\n\n## How it works\n\n```\n   Browser                     Your Cloud Functions            Cloud Logging\n   ┌────────────────┐          ┌──────────────────────┐        ┌───────────────┐\n   │ no credentials │─────────▶│ logFrontendEvent()   │───────▶│ your project  │\n   │ no source maps │          │ credentials + maps   │        │ one stream    │\n   └────────────────┘          ├──────────────────────┤        │ one query     │\n                               │ your own functions   │───────▶│               │\n                               │ withLogging()        │        └───────────────┘\n                               └──────────────────────┘\n```\n\n**Why there is a function in the middle.** A browser cannot write to Cloud Logging — it has no credentials, and you would never ship credentials to a browser. So the frontend needs a door, and `logFrontendEvent` is it. Because every frontend error passes through that door anyway, it is also the only place that can hold your source maps: the browser must not have them (`fsl` strips them from `dist/` so they are never published), and Cloud Logging cannot apply them. **Symbolication happens there because there is nowhere else it can happen.**\n\nBoth boxes in the middle are your own Cloud Functions, in the deploy you already run. Nothing new to stand up.\n\n## Install\n\n```bash\n# frontend (project root)\nnpm install @dasasian/firebase-structured-logger\n\n# Cloud Functions\ncd functions && npm install @dasasian/firebase-structured-logger\n\n# Cloud Run, or any Node server on Google Cloud\nnpm install @dasasian/firebase-structured-logger\n```\n\nShips CommonJS with TypeScript types — works from `require` and from `import`, in Node and in any bundler — with three entry points, `/client`, `/functions` and `/tools`, plus the `fsl` CLI. `firebase`, `firebase-admin`, and `firebase-functions` are optional peer dependencies (bring your own versions). None of them is needed to load the package; each only switches on the part that uses it — see [Without Firebase](#without-firebase).\n\nInstalling next to **firebase-admin 13** in one command can leave two copies of\n`@google-cloud/storage` (8 for this package, 7 for firebase-admin). Both work; run\n`npm dedupe` once and npm keeps the one they share. firebase-admin 14.5 and later use\nthe same Storage as this package, so there is nothing to do.\n\n### Upgrading from 0.x\n\nNothing breaks. Eight names were renamed for the 1.0 API — `minLogLevel` became\n`minSeverity`, `bucketName` became `bucket`, and so on — and each old name still works in\n1.x, with a one-time console warning naming its replacement. They are removed in 2.0.\nThe full list is in [Renamed in 1.0](#client). Run `npx fsl doctor` after upgrading.\n\n## Setup\n\nMost projects do both halves. They share one step — `initLogger` inside `functions/` — and\nare otherwise independent.\n\n**Prerequisites**\n\n- A `functions/` directory (`firebase init functions`, TypeScript) referenced by `firebase.json`.\n- For browser errors: **Firebase Storage enabled** (source maps are uploaded there) —\n  Firebase console → **Storage → Get started** — and a frontend build that emits source maps.\n  The `fsl` source-map tooling assumes **Vite**.\n\n### Catching errors from your browser\n\n**1. Initialize the client** — at your app entry (e.g. `src/main.tsx`), before any logging:\n\n```ts\nimport { initLogger, setupGlobalErrorHandler } from '@dasasian/firebase-structured-logger/client'\nimport { httpsCallable } from 'firebase/functions'\nimport { functions } from './config/firebase'\n\nexport const logger = initLogger({\n  appId: 'my-app',\n  releaseId: import.meta.env.VITE_RELEASE_ID ?? 'dev',\n  logFunction: httpsCallable(functions, 'logFrontendEvent'),\n})\n\nsetupGlobalErrorHandler() // capture uncaught errors + unhandled rejections\n```\n\n`logFunction` is just `(payload) => Promise<unknown>`. `httpsCallable()` happens to fit it —\nanything else that fits will work too.\n\n**2. Add the log function** — in `functions/src/index.ts`:\n\n```ts\nimport { initLogger, createClientLogFunction } from '@dasasian/firebase-structured-logger/functions'\n\ninitLogger({ appId: 'my-app' })\n\nexport const logFrontendEvent = createClientLogFunction({\n  bucket: 'my-app.firebasestorage.app', // holds source maps AND attachments\n})\n```\n\nOne bucket serves both, under two default prefixes:\n\n```\ngs://my-app.firebasestorage.app/sourcemaps/{releaseId}/{bundle}.js.map\ngs://my-app.firebasestorage.app/logAttachments/{logId}/{name}\n```\n\nBoth the bucket and the prefix can be changed per half — see [Source maps](#source-maps)\nfor `sourceMaps: { bucket, prefix }`, and [Attachments](#attachments) for\n`configureAttachments({ bucket, prefix })`. Omit `bucket` entirely and both fall back\nto your project's default bucket.\n\n**3. Wire the deploy script** — upload source maps and strip them from the hosting bundle as part of deploy. Merge into your root `package.json` scripts, keeping any existing flags like `--project`:\n\n```json\n\"deploy\": \"export VITE_RELEASE_ID=$(git rev-parse --short HEAD) && npm run build && npx fsl upload-sourcemaps --backend=./functions --embed-sourcemaps && firebase deploy\"\n```\n\n`fsl upload-sourcemaps` reads the bucket from `VITE_FIREBASE_STORAGE_BUCKET` (or `FIREBASE_STORAGE_BUCKET`) after loading `.env.local`. It uploads source maps to Cloud Storage, embeds a copy in `functions/sourcemaps/current/` for fast lookup, and deletes them from `dist/` so they are **not** served to browsers.\n\n> **`VITE_RELEASE_ID`** ties a build to its source maps — the client tags every entry with it, and `upload-sourcemaps` stores maps under the matching path. Use the same value in both places (the deploy script above sets it once from the git SHA). Locally it defaults to `'dev'`, and no maps are uploaded — symbolication isn't needed in development.\n\n**4. Verify it works** — prove the round trip before you trust it:\n\n```ts\nimport { sendTestLog } from '@dasasian/firebase-structured-logger/client'\n\nsendTestLog() // wire to a dev-only button; sends one error, one warning, one info\n```\n\nLook for `labels.errorType=\"fsl-verify\"` — in Cloud Logging once deployed, or in `dev.jsonl`\nif you are running the emulator (see [Local development](#local-development)). Three entries,\nand the error's stack should name a source file rather than a minified bundle. If they are\nnot there, nothing else in this README will work either.\n\nThen log, anywhere in the frontend:\n\n```ts\nlogger.info('checkout started', { orderId })\nlogger.error(err, { screen: 'camera' }, context, { photo: blob }) // attachments optional\n```\n\nDebug logs are suppressed in production automatically.\n\n### Logging from your Cloud Functions\n\nA complete use of this package on its own — no client, no bundles, no source maps.\n\n**1. Initialize the logger** — in `functions/src/index.ts`, at module load:\n\n```ts\nimport { initLogger } from '@dasasian/firebase-structured-logger/functions'\n\ninitLogger({ appId: 'my-app' })\n```\n\nAlready done if you set up the browser half — it is the same call.\n\n**2. Wrap your handlers** — `withLogging` binds the request's labels for the life of the handler:\n\n```ts\nimport { withLogging, logInfo, logError } from '@dasasian/firebase-structured-logger/functions'\n\nexport const checkout = onCall(\n  withLogging({ functionName: 'checkout' }, async (request) => {\n    logInfo('started')          // carries functionName and the caller's userId already\n    ...\n    logError(err, { orderId })  // labels merge with the request's\n  }),\n)\n```\n\n`userId` comes from the verified `request.auth.uid`, so you never pass it. The scope unwinds\nwhen the handler settles — one request's labels can never appear on another's logs, even on a\nwarm instance.\n\n**3. Verify it works** — call the function, then look for `labels.functionName=\"checkout\"`.\nThe entry should carry `userId` without your having written it.\n\n### If your backend is not Cloud Functions\n\nCloud Run, or any Node server you already run on Google Cloud. Two things differ from the browser\npath above; everything else — breadcrumbs, labels, symbolication, the free fields — is\nidentical, and the entries land in the same stream in the same shape.\n\n**Receive the logs over HTTP** instead of exporting a callable:\n\n```ts\nimport express from 'express'\nimport { getAuth } from 'firebase-admin/auth'\nimport { initLogger, createHttpLogHandler } from '@dasasian/firebase-structured-logger/functions'\n\ninitLogger({ appId: 'my-app' })\n\nconst app = express()\napp.use(express.json({ limit: '10mb' }))   // attachments ride in the body\n\napp.post('/log', createHttpLogHandler({\n  bucket: 'my-app.firebasestorage.app', // holds source maps AND attachments\n  authorize: async (req) => {\n    const header = String(req.headers.authorization ?? '')\n    if (!header.startsWith('Bearer ')) return false\n    try { await getAuth().verifyIdToken(header.slice(7)); return true } catch { return false }\n  },\n}))\n```\n\nThe handler reads Node's request and response shapes. A framework that wraps them, like\nHono, needs a few lines of adapter:\n\n```ts\nimport { Hono } from 'hono'\nimport { createHttpLogHandler } from '@dasasian/firebase-structured-logger/functions'\n\nconst app = new Hono()\nconst logHandler = createHttpLogHandler({ authorize })\n\napp.on(['POST', 'OPTIONS'], '/log', async (c) => {\n  const headers: Record<string, string> = {}\n  let status = 200\n  let body: string | undefined\n  await logHandler(\n    {\n      method: c.req.method,\n      headers: Object.fromEntries(c.req.raw.headers),\n      body: await c.req.json().catch(() => null),\n    },\n    {\n      get statusCode() { return status },\n      set statusCode(v: number) { status = v },\n      setHeader: (name, value) => { headers[name] = value },\n      end: (b) => { body = b },\n    },\n  )\n  return new Response(body ?? null, { status, headers })\n})\n```\n\n**Point the client at it.** `logFunction` is any async function, so a `fetch` works:\n\n```ts\ninitLogger({\n  appId: 'my-app',\n  releaseId: import.meta.env.VITE_RELEASE_ID ?? 'dev',\n  logFunction: async (payload) => {\n    const res = await fetch('https://api.example.com/log', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: `Bearer ${await auth.currentUser?.getIdToken()}`,\n      },\n      body: JSON.stringify(payload),\n    })\n    if (!res.ok) throw new Error(`log rejected: ${res.status}`)\n  },\n})\n```\n\n#### Without Firebase\n\nNothing here needs a Firebase project — only a Google Cloud one. What changes:\n\n- **The server has to run on Google Cloud** — Cloud Run, GKE, App Engine, or Compute\n  Engine with the Ops Agent. Entries are written to stdout, and only there does stdout\n  reach Cloud Logging. On AWS, Vercel or your own box they print and go nowhere.\n- **The browser** needs no `firebase` package. `logFunction` is the `fetch` above; send\n  whatever credential your app already uses.\n- **`authorize`** checks your own session instead of a Firebase ID token — for example\n  `authorize: (req) => sessions.isValid(req.headers.cookie)`.\n- **Storage** has no Firebase default bucket to fall back to. Name one with `bucket`\n  (it holds source maps and attachments), and give the service's account access to it.\n  Or name none: embedded maps still resolve the current release, older releases stay\n  minified, and attachments are dropped — the log says so once.\n- **`fsl upload-sourcemaps --bucket`** works with any Cloud Storage bucket.\n- **`withLogging` and `createClientLogFunction`** are Cloud Functions tools. Outside\n  them, `logInfo` / `logError` and friends still write the same entries.\n\n#### `authorize` is required, and that is deliberate\n\nA callable gets Firebase's token check for free. An HTTP endpoint gets nothing, and an\nopen one writes to your Cloud Logging bill on anyone's say-so. There is no honest\ndefault, so there isn't one.\n\nIt is a **gate, not an identity check**. The handler never reads `request.auth` — the\n`userId` on a client entry is self-reported either way. Its job is keeping strangers out.\n\nIf something in front of it already did that work — a VPC, an API gateway, IAM — say so\nat the call site:\n\n```ts\ncreateHttpLogHandler({ authorize: 'unauthenticated' })\n```\n\nA gate that throws counts as a rejection, not an opening.\n\n#### What you need to know\n\n- **Body parsing is yours.** Mount `express.json()` (or your framework's equivalent)\n  before the handler. Raise its limit if you send attachments.\n- **CORS** defaults to `*`, matching `cors: true` on the callable. Pass `allowOrigin` to\n  name your origin — a browser cannot send cookies to a wildcard.\n- **No `firebase-functions`? Not needed.** The entry point loads without it. Each entry is\n  written as one line of JSON, which Cloud Run's log agent parses into the same Cloud\n  Logging fields a function's would.\n- **Trace correlation works**, and needs nothing from you on Cloud Run. The handler reads\n  `X-Cloud-Trace-Context` or `traceparent` off the request, and asks the metadata server\n  for the project id once, so a request's entries group with Cloud Run's own request log\n  in Cloud Logging. Somewhere else — GKE, a VM — set `GOOGLE_CLOUD_PROJECT`; without it\n  the trace id is still written, but does not join the platform's request log.\n- **No `firebase-admin`? Also optional.** Storage is only needed for older releases'\n  source maps and for attachments. With `firebase-admin` installed, its Storage and\n  default bucket are used. Without it, name the bucket — `bucket` on the handler, or\n  `configureAttachments({ bucket })` — and the service's own credentials are used.\n- **No Storage bucket?** You do not need one. `fsl upload-sourcemaps --embed-sourcemaps`\n  without `--bucket` embeds the current release's maps into your deploy and uploads\n  nothing. The catch: only the **deployed** release can be symbolicated, because older\n  ones live in a bucket there isn't one of. Errors from a previous release come back\n  minified, and attachments are dropped — the entry is still written. With neither\n  `firebase-admin` nor a bucket name, the log says so once at the first lookup.\n- **Response codes:** `204` written, `400` malformed payload, `401` gate refused, `405`\n  not a POST, `500` something else. The client treats a non-2xx as a throw.\n\n## Check your setup\n\nMost of what can go wrong here goes wrong silently: logs that never arrive, stacks that\nnever resolve, source code published next to the app. `fsl doctor` reads the project from\ndisk and says what it finds. It needs no network and no credentials, and it only reports\nfacts it can read from a file — it never guesses from your source code, so a finding is\nnever a false alarm.\n\n```bash\nnpx fsl doctor                                     # Firebase: reads firebase.json\nnpx fsl doctor --backend=./server --dist=./dist    # anything else: say where things are\n```\n\nIt prints a summary of how your setup will behave, then any findings:\n\n```\nSetup: Firebase — functions in ./functions, web build in ./dist\n\n  Logging        firebase-functions write()\n  Trace ids      from each Cloud Functions trigger\n  Storage        firebase-admin, default bucket\n  Callable       createClientLogFunction available\n\n  ⚠ duplicate-storage    2 copies of @google-cloud/storage (8.2.0 at the top level, 7.22.0 inside firebase-admin)\n                        Fix: npm dedupe\n```\n\n| Id | Level | Means |\n|---|---|---|\n| `maps-published` | error | `.map` files are in the folder hosting serves — your source code is public |\n| `node-version` | error | the backend's Node is below 22 |\n| `callable-without-firebase-functions` | error | `firebase.json` has functions, but `firebase-functions` is not installed there |\n| `could-not-check` | error | doctor could not read something it needed — including \"run `npm install` first\" |\n| `duplicate-storage` | warning | two copies of `@google-cloud/storage`; fix with `npm dedupe` |\n| `unsupported-peer` | warning | an installed peer (`firebase-admin`, `firebase-functions`, `firebase`) is outside the supported range |\n| `embedded-maps-without-release` | warning | maps are embedded without a `.release` marker, so an older release can resolve against the wrong map |\n\n**Exit code:** `0` when there are no errors, `1` when there is one. `--strict` also fails on\nwarnings, for teams that want a spotless setup; without it, a deliberate choice like\nrunning with no Storage does not break CI. A check doctor could not run is always an\nerror, never a pass.\n\n**`--json`** prints the same result for scripts and agents:\n\n```json\n{\n  \"setup\": { \"kind\": \"firebase\", \"backend\": \"./functions\", \"dist\": \"./dist\",\n             \"logging\": \"firebase-functions\", \"trace\": \"trigger\", \"storage\": \"firebase-admin\",\n             \"callable\": true },\n  \"findings\": [\n    { \"id\": \"duplicate-storage\", \"level\": \"warning\",\n      \"message\": \"2 copies of @google-cloud/storage (8.2.0 at the top level, 7.22.0 inside firebase-admin)\",\n      \"fix\": \"npm dedupe\" }\n  ],\n  \"exitCode\": 0\n}\n```\n\n`setup.kind` is `firebase`, `node`, `browser-only` or `backend-only`; `logging` is\n`firebase-functions`, `stdout` or `none`; `trace` is `trigger`, `header` or `none`; and\n`storage` is `firebase-admin`, `google-cloud-storage` (which still needs a bucket named in\nyour code — doctor cannot see it) or `none`. The ids and fields are part of the 1.0 API:\nscripts may depend on them.\n\nWhat doctor does not check: anything set in your own code — the release id you pass to\n`initLogger`, a bucket name passed to a handler. Its Storage line says so — \"needs a bucket named in code (not checked)\" — rather than\nguessing. [`fsl verify`](https://github.com/dasasian/firebase-structured-logger/issues/25),\nplanned after 1.0, will check those by sending a real log.\n\n## Local development\n\nThe Functions emulator writes the same entries to a local JSONL file instead of Cloud\nLogging, so the whole loop — client, log function, symbolication path, labels — works\nbefore you deploy anything.\n\nAdd a `serve` script to `functions/package.json`:\n\n```json\n\"serve\": \"npm run build && firebase emulators:start --only functions\"\n```\n\nThen tell the logger where to write, in your functions entry point:\n\n```ts\ninitLogger({ appId: 'my-app', logLocalDir: 'logs' })\n```\n\n`logLocalDir` is yours to choose — it is resolved against the emulator's working directory,\nwhich is `functions/`. `functions/logs` is the convention used throughout this README, not a\nrequirement; anywhere writable works, including a path outside the project.\n\nPoint **[firebase-mcp-server](https://github.com/dasasian/firebase-mcp-server)** at that file\nto query your dev logs from Claude exactly as you would query Cloud Logging.\n\n### Keep the logs out of git\n\n```\nfunctions/logs/*.jsonl\n```\n\nTrack the directory, not the files:\n\n```bash\nmkdir -p functions/logs && touch functions/logs/.gitkeep\n```\n\n### Rotation\n\nEntries go to `{logLocalDir}/dev.jsonl`. Each emulator start rotates the current file to\n`dev-{timestamp}.jsonl`, and rotation also happens when the record limit is hit mid-session.\n\n| Config | Default | Description |\n|---|---|---|\n| `logLocalDir` | — | Directory for local log files |\n| `logMaxRecordsPerFile` | 2000 | Records per file before rotation |\n| `logMaxRotatedFiles` | 5 | Rotated files to keep |\n\n## Grouping, without a second product\n\nGoogle Cloud already runs an error tracker in your project. **Error Reporting** watches\nCloud Logging, collapses repeats into issues, and gives you occurrence counts, a\nresolution state — Open, Acknowledged, Resolved, Muted — notifications on new errors, and a\nfield to link your own issue tracker. It costs nothing beyond the logs you are already\nwriting, and it never sees anything outside your project.\n\nIt groups by exception type plus the **five top-most stack frames**. Which is why, for\nalmost every web app, it does nothing at all: those frames read `app-4f2a.js:1:98432`, they\nchange every release, and no two crashes ever look alike.\n\n**We resolve the frames before the entry is written.** So yours read `Checkout.tsx:42`, and\nthey group:\n\n```\nTypeError: cannot read 'id' of undefined     ← \"the discount broke\"\nTypeError: order is not iterable             ← \"checkout is stuck\"\n                                                two reports, two messages,\n                                                one line of code, one issue\n```\n\nErrors at `ERROR` and above carry `stack_trace` and a `serviceContext` naming your `appId`\nand release, which is all Error Reporting needs. Nothing to enable in this package, and\nnothing to configure.\n\nWarnings stay out of it, and so does user feedback — an issue is something a person has to\nresolve, and neither of those is a bug.\n\n> Verified end to end against a real project: two errors with different messages from one\n> source location land in a single group, attributed to the `appId` rather than to the\n> function that wrote them, with the same group id across releases.\n\n## What you get for free\n\nYou write one label. Eleven fields land.\n\n```ts\nlogger.error(err, { orderId })\n```\n\n| Field | Added by | Where it comes from |\n|---|---|---|\n| `appId`, `releaseId` | client | your `initLogger` config |\n| `screen` | client | tracked as the user moves |\n| `userId` | client | `setUser`, held for the session |\n| `platform` | client | user agent — `ios` / `android` / `macos` / `web` |\n| `browser` | client | user agent |\n| `errorType` | client | the Error's own `name` |\n| last 50 breadcrumbs | client | the trail of what the user did |\n| `logId` | function | a ULID, unique per entry — locates attachments in GCS |\n| `hasAttachments` | function | `\"true\"` when files were uploaded alongside |\n| resolved file and line | function | your source maps — `Checkout.tsx:42`, not `app-4f2a.js:1:98432` |\n| trace context | function | request correlation in Cloud Logging |\n\n**Repeats are counted, not thrown away.** The usual answer to an error that fires 200 times\nis to send the first few and drop the rest. This one sends 3 in full, counts the other 197,\nand tells you when it started, when it stopped, which release and which user. Close the tab\nand the count waits on the device for the next visit. See\n[Repeats are counted, not dropped](#repeats-are-counted-not-dropped).\n\nBackend logs get the same treatment: `withLogging` attaches `functionName`, `userId` from\nthe verified `request.auth.uid`, and whatever else you bind.\n\nThat is the \"structured\" in the name. Not that the entry is JSON — that it arrives already\ncarrying who, where, which release, and what led up to it.\n\n**The rule behind it:**\n\n> You never pass context to a log call. You declare it once, and it rides along.\n\nOn the client that scope is the **session**. On the backend it is the **request**. Same idea,\ntwo clocks — and it is why `labels.userId=\"…\"` returns both halves: the client attaches the\nuid from `setUser`, the backend from `request.auth.uid`, same label name, no coordination.\n\nDeclaring context does not replace passing it. There are three scopes, and they merge:\n\n```ts\ninitLogger({ appId: 'my-app' })       // every log, for the life of the app\nlogger.setUser(uid, { orgId })        // every log, until clearUser()\nlogger.error(err, { orderId })        // this log only\n```\n\nInnermost wins — a label passed at the call site overrides the same label from `setUser`.\nThe backend works the same way: `withLogging` binds the request's labels, and each\n`logInfo(message, labels)` can add or override for that one line.\n\n## Adding your own context\n\nDefine your labels once, in a file both the app and `functions/` import:\n\n```ts\n// src/shared/labels.ts\nexport interface MyAppLabels {\n  organizationId?: string\n  itemId?: string\n  // whatever domain entities are relevant\n}\n```\n\nThen use the same type on both sides.\n\n**Client** — scoped to the session:\n\n```ts\nexport const logger = initLogger<MyAppLabels>({ /* … */ })\n\nlogger.setUser(uid, { organizationId })   // on sign in — rides every log until cleared\nlogger.clearUser()                        // on sign out\nlogger.setScreen('checkout')              // on navigation\n```\n\n**Backend** — scoped to the request:\n\n```ts\nimport { withLogging, logInfo } from '@dasasian/firebase-structured-logger/functions'\n\nexport const checkout = onCall(\n  withLogging<MyAppLabels>(\n    (request) => ({ functionName: 'checkout', labels: { organizationId: request.data.orgId } }),\n    async (request) => {\n      logInfo('started')   // carries functionName, userId and organizationId already\n    },\n  ),\n)\n```\n\nThe function form runs per call, so labels can be derived from the request. The static form\nfrom [setup](#logging-from-your-cloud-functions) is the same thing without that.\n\n## Breadcrumbs\n\n```ts\nimport { bc } from '@dasasian/firebase-structured-logger/client'\n\nbc.nav('Checkout')                              // also sets labels.screen\nbc.action('apply_discount', { code: 'SAVE10' })\nbc.state('total_recalculated', { total: 42.00 })\nbc.error('ValidationError', { field: 'price' })\n```\n\nA stack trace tells you where the code broke. It cannot tell you what the person did to\nget there, which is usually the part you need to reproduce it. Breadcrumbs are that trail:\na rolling record of the last steps, attached automatically to every error and every piece\nof feedback, with no correlation work on your side.\n\nDrop a `bc.action` before anything that can fail and a `bc.nav` on every screen change, and\n`total is wrong` arrives as `navigate_Checkout · apply_discount · total_recalculated ·\ntap_place_order`.\n\nThe trail is capped at **50 entries** and **5 minutes** — old enough to cover the steps that\nled here, short enough that it stays the current attempt rather than the whole session, and\nbounded so a long-lived tab cannot grow it without limit. It lives in memory only, so it\nnever touches storage and never leaves the device except attached to a log you send.\n\nBreadcrumbs are session-global by design: one user, one path. `bc.nav()` also sets the\ncurrent screen, so `labels.screen` stays correct without a second call.\n\n> Record the step, not the data. Breadcrumb `data` is written to your logs verbatim — keep\n> PII, tokens and card numbers out of it, the same as you would for any label.\n\n## User feedback\n\n```ts\nimport { sendFeedback } from '@dasasian/firebase-structured-logger/client'\n\nsendFeedback(\"the discount didn't apply\")\nsendFeedback(text, { attachments: { screenshot }, labels: { orderId } })\n```\n\nError tracking only sees things that **throw**. A button that does nothing, a total that\ncomes out wrong, the wrong data rendered — none of them throw, so none are captured, and\nyou hear about them weeks later. `sendFeedback` captures that class, and it is actionable\nbecause the breadcrumb trail is already in memory when the user hits send: *\"the discount\ndidn't apply\"* is a complaint; the same sentence plus `nav→Checkout · apply_discount ·\ntotal_recalculated · tap_place_order` is a reproduction.\n\nIt carries everything a log carries — breadcrumbs, `screen`, `userId`, `releaseId`,\n`platform`, `browser`, and any labels seeded via `setUser`. A screenshot passed as an\nattachment rides the same Cloud Storage path as any other, so it is not bounded by the\nentry size limit — see [Attachments](#attachments).\n\nHeadless: the package renders nothing, so the UI is yours. It returns nothing either —\na reference number is meaningless to a user with no portal to check it against. Say thank\nyou and move on; if the app wants correlation, pass its own id as a label.\n\nEntries are written at **`NOTICE`**, which ranks between `INFO` and `WARNING`: feedback\nfrom a person outranks routine status, and is not a warning about system health. Find it\nwith the severity dropdown, or `labels.feedback=\"true\"`. Any existing `severity >= WARNING`\nalert ignores it with no configuration.\n\nFeedback is exempt from the severity floor and the rate limiter — those are volume controls\nfor events the system emits, and someone hitting send twice is not a duplicate to throttle.\n\n## Attachments\n\n```ts\nlogger.error(err, { orderId }, context, { photo: blob, state: JSON.stringify(cart) })\n```\n\nAny log method takes a final `attachments` argument — `Record<string, Blob | File | string>`\non the client, `Record<string, string | Buffer>` on the backend.\n\n**Attachments are how you send more than a log entry can hold.** A Cloud Logging entry is\ncapped at **256 KB**, and a big payload does not get truncated — the write fails. Attachments\nnever enter the entry: they are uploaded to Cloud Storage and stripped before the entry is\nwritten, so a 5 MB screenshot costs the log line two labels. Use them for anything that would\notherwise blow the cap — screenshots, request bodies, a serialised store, a captured frame.\n\nThey land at:\n\n```\ngs://<bucket>/logAttachments/{logId}/{name}\n```\n\n`logId` is a ULID on the entry itself, so the log line tells you where its files are:\n\n```\nlabels.hasAttachments=\"true\"     # entries that have files\nlabels.logId=\"01J...\"            # the entry whose files you are looking for\n```\n\nBy default they share the bucket passed to `createClientLogFunction({ bucket })` — the\nsame one the source maps live in, falling back to the project's default bucket.\n\nSend them somewhere else with `configureAttachments`, in your functions entry point:\n\n```ts\nimport { configureAttachments } from '@dasasian/firebase-structured-logger/functions'\n\nconfigureAttachments({ bucket: 'my-app-user-content', prefix: 'evidence' })\n```\n\nCall it once, at module load. It is global on purpose and global in the API: the upload\nhappens on every log call, including ones inside your own handlers that never touch\n`createClientLogFunction`, so there is no per-handler setting for it to read. Fields you\nleave out keep today's behaviour, and never calling it changes nothing.\n\nWorth doing when user content needs its own region for residency, its own retention policy,\nor different IAM from your source maps — none of which can be arranged with a prefix.\n\nNothing expires them. Add a lifecycle rule on `logAttachments/` to delete after N days, or\nthey accumulate for the life of the project.\n\n## Big entries\n\nCloud Functions and Cloud Run both cut a stdout/stderr log line at exactly **102,400 bytes**\n(100 KiB) — measured live. Past that the entry does not arrive truncated-but-valid: it arrives\nas broken plain text, with no `severity`, no labels, and nothing for Error Reporting to group.\n\nThe backend logger watches for this. An entry over 90 KiB is shortened before it is written —\nbreadcrumb data first, then other context, then the tail of a long stack (the top frames are\nwhat Error Reporting groups on, so those survive longest), then long text fields, and label\nvalues only as a last resort. `severity`, labels, the trace and `serviceContext` are never\ntouched.\n\nThe full, unshortened entry is saved to Cloud Storage as `fsl-overflow.json`, at the same\n`logAttachments/{logId}/` path an ordinary attachment would use, whenever a bucket is\navailable — see [Attachments](#attachments). A shortened entry carries `labels.truncated=\"true\"`,\nand `labels.hasAttachments=\"true\"` when the full entry was saved. With no Storage configured,\nthe entry is still shortened; the original is lost, and the process warns once, not per entry.\n\n## Volume controls\n\nSeveral gates decide whether a log is written. All have defaults, and the defaults\nhold things back — so this is worth reading before you conclude something is broken.\n\n| Gate | Default | Where |\n|---|---|---|\n| Log limit | bursts of up to **50 logs**, then **one log recharges every 60 s**; the last **10** for errors only | client, per browser tab |\n| Duplicates | **3** full copies of the same error, then counted and sent as a summary | client, per browser tab |\n| Client severity floor | `WARNING` in production, `DEBUG` in dev | client, `minSeverity` |\n| Server severity floor | `WARNING` in production, `DEBUG` in the emulator | function, `minSeverity` |\n| Function concurrency | `maxInstances: 1` on `createClientLogFunction` | function |\n\n```ts\ninitLogger({\n  appId: 'my-app',\n  releaseId,\n  logFunction,\n  minSeverity: 'INFO',\n  rateLimitOptions: {\n    burstLimit: 50,              // how many logs can go at once\n    rechargeSecondsPerLog: 60,   // after a burst, one log recharges every 60 seconds\n    reservedForErrors: 10,       // of the burst, how many only ERROR and above may use\n    duplicateLimit: 3,       // full copies of one error before counting starts\n    summaryIntervalMinutes: 60,\n    summaryMaxAgeDays: 7,    // how long an unsent summary waits on the device\n    maxPendingSummaries: 50,\n  },\n})\n```\n\nThe client's production default comes from `process.env.NODE_ENV`, which Vite replaces at\nbuild time. A `define: { 'process.env': {} }` in `vite.config` — common, to quiet a library\nthat expects Node — replaces the whole object instead, `NODE_ENV` reads as undefined, and\nthe floor is silently `DEBUG` in production. If your config has that line, pass\n`minSeverity` explicitly. Stating it is the safe habit either way: it is the one default\nhere whose failure mode is a bill rather than a missing log.\n\n### Bursts recharge\n\nEach browser tab can send a burst of up to `burstLimit` logs (50). After that, one log\nrecharges every `rechargeSecondsPerLog` seconds (60) — one at a time, so a full burst takes\n50 minutes to come back. A reload does not reset it: the tab keeps its count in\n`sessionStorage`.\n\nSo a burst at start-up is fine, and a user who works in the app all afternoon is never\nsilenced for long. A bug that logs in a loop is still held to about 60 entries an hour per\nuser: 1,000 users stuck in such a loop for a working month stays around the 50 GiB of\nCloud Logging that each project gets free.\n\nThe last `reservedForErrors` logs (10) are kept for errors: warnings can use the burst down\nto 10, and only `ERROR` and above can use the rest. A value over half of `burstLimit` is\ncapped at half, with one warning, so warnings always have room. A noisy warning cannot use up the room a crash needs.\n\n### Repeats are counted, not dropped\n\nTwo errors are the same when the **message and the screen both match**, so the same error\non two different screens is kept apart. The first 3 are sent in full, with their stack and\nbreadcrumbs. After that the browser only counts them, and sends one summary:\n\n```\nWARNING  Repeated 197 more times: cart sync failed\n         labels.repeatOf=\"<repeatKey of the first copy>\"  labels.repeatCount=\"197\"\n         labels.firstSeen=\"…\"  labels.lastSeen=\"…\"\n```\n\nThe same count and window are in the entry's body, under `context.repeat`, so a summary is\nfiled as JSON like every other entry and a search on `jsonPayload.message` finds it.\n\nThe browser never learns the server's `logId` for the first copy — `writeLog` assigns it\nafter the log has already left the tab. So each full copy instead carries its own\n`labels.repeatKey`, a client-side id, and the summary's `repeatOf` points at that instead:\nquery `labels.repeatKey=\"…\" OR labels.repeatOf=\"…\"` to find a full copy and its summary\ntogether.\n\nA summary is sent once an hour, when the tab is hidden, and once at startup for anything\na previous visit left queued. It is keyed by the error, the `releaseId` and the `userId`,\nso two releases or two people on one computer are never counted together. Its entry is\ntimestamped at `lastSeen`, not at the time it was sent.\n\nThe summary's own labels are `appId`, `releaseId`, `userId`, `screen` and `errorType` — from\nthe time the errors happened, not from whoever's tab eventually sends it — plus\n`repeatOf`/`repeatCount`/`firstSeen`/`lastSeen`/`sentLate`. Everything else about the error —\nbrowser, platform, breadcrumbs, the stack — is not repeated here; it is already on the full\ncopies `repeatOf` points back to.\n\nUnsent summaries are kept in `localStorage`, so closing the tab does not lose them: the\nnext visit sends them, marked `labels.sentLate=\"true\"`. A summary is only removed from that\nqueue once it has actually been sent — a failed attempt (offline, or the tab closing right\nafter it was queued) leaves it for the next flush or the next visit to retry, never twice.\nAt most 50 wait, and any older than 7 days are deleted rather than sent — they are error\nmessages, and may hold personal data.\n\nA summary is a `WARNING` with no stack, so Cloud Error Reporting sees the 3 full copies and\nnot the summary. For the true count, add up `labels.repeatCount` in Cloud Logging.\n\nSummaries do not count against the burst. There is at most one per error, per release, per user,\nper hour.\n\n### What a dropped log looks like\n\nThe two rate limits say so in the browser console:\n\n```\n[fsl] Duplicate counted for the next summary: TypeError: cannot read 'id' | checkout\n[fsl] Log limit reached — recharging, next log in about a minute\n[fsl] Log limit: only errors can use the reserved logs now\n```\n\n**The severity floors are silent.** Both of them — the client's `minSeverity` and the\nfunction's `minSeverity` — simply return, with nothing written and nothing logged about it.\n\nSo if an entry never arrived and there is no `[fsl]` warning in the console, it was a floor,\nnot a limit. In production both default to `WARNING`, which drops `DEBUG`, `INFO` and\n`NOTICE` on the way out of the browser *and* again on the way into Cloud Logging — an\n`INFO` you expected to see has two places it can vanish.\n\n`maxInstances: 1` is a deliberate cost guard on what is usually the busiest function in the\nsystem. Raise it (`createClientLogFunction({ bucket, maxInstances: 5 })`) if you are\ndropping client logs under load — and watch your Cloud Logging bill when you do.\n\nFeedback is exempt from every one of these. See [User feedback](#user-feedback).\n\n## Querying\n\nOne filter, both halves, in time order:\n\n```\nlabels.userId=\"<uid>\"\n```\n\nNarrow it when you need to:\n\n| Filter | Returns |\n|---|---|\n| `labels.platform:*` | client entries only |\n| `labels.functionName:*` | server entries only |\n| `labels.releaseId=\"<sha>\"` | one build |\n| `labels.screen=\"checkout\"` | one screen |\n| `labels.feedback=\"true\"` | user-reported issues |\n| `labels.hasAttachments=\"true\"` | entries with files in GCS |\n| `labels.truncated=\"true\"` | entries shortened to fit — the full copy is `fsl-overflow.json` |\n| `labels.repeatKey=\"<key>\" OR labels.repeatOf=\"<key>\"` | one repeating error: its full copies and its summaries |\n| `labels.sentLate=\"true\"` | repeat summaries sent on a later visit |\n\nLocally, the emulator's JSONL answers the same questions. Point\n**[firebase-mcp-server](https://github.com/dasasian/firebase-mcp-server)** at either and ask\nClaude instead — `npx fsl install-skills` installs the two skills below.\n\n| Skill | Description |\n|-------|-------------|\n| `/logs` | Validate logging in a file — error paths, labels, PII, unwrapped handlers, breadcrumbs |\n| `/query-logs` | Query Cloud Logging or local JSONL via [firebase-mcp-server](https://github.com/dasasian/firebase-mcp-server) |\n\n> A stack trace is self-reported by the browser, and so is `userId` on client entries — the\n> uid comes from the client's own labels, not from a verified token. Backend entries are\n> different: `withLogging` reads `request.auth.uid`, which Firebase has verified. Fine for\n> debugging either way; don't build an audit trail on the client half.\n\n## Reference\n\n### Client\n\n```ts\nlogger.error(error, labels?, context?, attachments?)   // attachments: Record<string, Blob | File | string>\nlogger.info(message, labels?, context?, attachments?)\nlogger.warning(message, labels?, context?, attachments?)\nlogger.debug(message, labels?, context?, attachments?) // suppressed in production\n\nlogger.setUser(uid, extraLabels?)\nlogger.clearUser()\nlogger.setScreen(screen)\nlogger.addBreadcrumb(type, name, data?)\n```\n\nAlso exported: `initLogger`, `getClientLogger`, `setupGlobalErrorHandler`, `handleReactError`,\n`sendFeedback`, `sendTestLog`, `addBreadcrumb`, `bc`.\n\n**Renamed in 1.0.** The old names still work in 1.x, each with a one-time console warning,\nand are removed in 2.0:\n\n| Old | New |\n|---|---|\n| `minLogLevel` (client `initLogger`) | `minSeverity` |\n| `bucketName` (`createClientLogHandler`, `createClientLogFunction`, `createHttpLogHandler`) | `bucket` |\n| `rateLimitOptions.sessionLimit` | `rateLimitOptions.burstLimit` |\n| `rateLimitOptions.refillPerMinute` | `rateLimitOptions.rechargeSecondsPerLog` (seconds, not a rate: `60 / refillPerMinute`) |\n| `rateLimitOptions.errorReserve` (a share) | `rateLimitOptions.reservedForErrors` (a count: `errorReserve × burstLimit`) |\n| type `ClientLogRequest` | type `LogRequest` |\n| `triggerTestLog()` | `sendTestLog()` |\n| `fsl upload-sourcemaps --functions=<dir>` | `--backend=<dir>` (it is not only for Cloud Functions) |\n\n`Logger` is exported as a **type only** — the client logger is a session singleton, so\nannotate with `Logger<MyAppLabels>` and construct with `initLogger()`. A second instance\nwould silently share breadcrumbs, screen and the rate-limit budget while looking independent.\n\n\n### Functions\n\n```ts\ninitLogger({ appId, logLocalDir?, minSeverity?, logMaxRecordsPerFile?, logMaxRotatedFiles? })\n\nwithLogging(options | (request) => options, handler)\ngetLogger()                       // the current request's writer, or an anonymous fallback\nlogError / logWarn / logInfo / logDebug (message, labels?, context?, attachments?)\n\nconfigureAttachments({ bucket?, prefix? })    // once, at module load — see Attachments\n\n// Receiving client logs. All three take the same source-map config:\n//   { bucket?, sourceMaps?: { bucket?, prefix? } }\ncreateClientLogFunction({ …, cors?, maxInstances? })   // a ready-to-export callable\ncreateHttpLogHandler({ …, authorize, allowOrigin? })   // an (req, res) handler for Express etc.\ncreateClientLogHandler({ … })                          // the bare handler, wrap it yourself\n```\n\nBackend log methods also accept an optional `attachments` (`Record<string, string | Buffer>`).\n\n`createClientLogHandler` takes `{ data: LogPayload }` — the minimum it reads — and throws\n`ClientLogError` with a `code` of `'invalid-argument'` or `'internal'`. The two wrappers above\ntranslate that: the callable into an `HttpsError`, the HTTP handler into a status code. Use the\nbare handler only if you are wrapping it in something else yourself.\n\n### CLI (`fsl`)\n\n```bash\n# Upload source maps to Cloud Storage and strip local .map files (run in deploy)\nnpx fsl upload-sourcemaps --backend=./functions --embed-sourcemaps\n\n# Same, to a bucket and prefix of your choosing — tell the reader the same values\nnpx fsl upload-sourcemaps --backend=./functions --embed-sourcemaps --bucket=my-maps --prefix=fsl-maps\n\n# No bucket at all: embed the current release, upload nothing\nnpx fsl upload-sourcemaps --backend=./backend --embed-sourcemaps\n\n# Check the setup from disk — no network, no credentials (see \"Check your setup\")\nnpx fsl doctor\nnpx fsl doctor --backend=./server --dist=./dist   # no firebase.json to read\nnpx fsl doctor --strict --json                   # CI: fail on warnings too, machine-readable\n\n# Install the Claude Code skills into the current project (or --global, --force)\nnpx fsl install-skills\n```\n\n### Source maps\n\nFor symbolicated production traces, emit source maps in Vite and upload them at deploy:\n\n```ts\n// vite.config.ts\nbuild: {\n  sourcemap: true,\n  commonjsOptions: {\n    // Rollup's commonjs plugin doesn't cover file: deps by default\n    include: [/firebase-structured-logger/, /node_modules/],\n  },\n}\n```\n\nMaps are stored at `gs://<bucket>/sourcemaps/{releaseId}/{filename}.map` and loaded by whichever log handler you deployed — the callable or the HTTP one — during symbolication.\n\nTo use a different bucket or prefix, tell **both ends** — they are two halves of one contract\nand nothing checks them against each other:\n\n```bash\nnpx fsl upload-sourcemaps --bucket=my-maps --prefix=fsl-maps …\n```\n\n```ts\ncreateClientLogFunction({ sourceMaps: { bucket: 'my-maps', prefix: 'fsl-maps' } })\n```\n\nIf they disagree the maps are simply not found and stacks stay minified — which looks\nidentical to never having uploaded them. The function warns once per release when it\nresolves nothing, naming the exact object it looked for, so the mismatch is visible in your\nlogs rather than inferred.\n\n## This is a hard problem\n\nNot a difficult one — the pieces are all small. A hard one, in that the ways it goes wrong\nare invisible until they aren't, and each is discovered by watching production do something\nstrange rather than by reading a doc.\n\nSome of what is already handled here, all of it learned the expensive way:\n\n- **Entry labels have to be emitted under `logging.googleapis.com/labels`.** Anywhere else\n  and they land inside the payload, so `labels.appId=\"…\"` matches nothing. The logs look\n  perfect and cannot be filtered. Only a deployed run reveals it.\n- **`AsyncLocalStorage.enterWith()` never unwinds.** A request's `userId` outlives the\n  request, and the next handler on a warm instance inherits whichever user came before.\n- **Source maps left in `dist/` are your source code, published.** Uploading them is the\n  easy half; keeping them off the web server is the half people forget.\n- **An unrecognised severity throws inside the write**, the entry is lost with no\n  diagnostic, and it slips past the severity floor on the way there.\n- **A trailing slash on a Storage prefix is a different object.** `fsl//r7/app.js.map` is\n  not `fsl/r7/app.js.map`, and nothing collapses it — so the writer and reader silently\n  disagree over a typo.\n- **Checking a rate limit and spending it as two calls double-counts**, quietly making a\n  configured budget of 50 a budget of 25.\n- **A fixed budget goes silent in a long session.** 50 logs per page load is spent by\n  mid-morning in an app someone keeps open all day, and the crash at lunch is never sent.\n  A budget that refills, with a share kept for errors, does not have that shape.\n- **An old stack naming a bundle that still exists** resolves against the current release's\n  map, giving line numbers that are confidently wrong — worse than none, because nothing\n  signals it.\n- **`@google-cloud/logging` does not surface `errorGroups`.** Read grouping through the\n  client library and you will conclude, wrongly, that nothing grouped.\n\nEvery one of those is fixed here, and each has a test that fails if it comes back. That is\nthe point: you would have found them one at a time, in production, over months.\n\nThe list is not finished. It grows every time this is run against something real, and the\nhonest pitch is not that this package is complete — it plainly isn't — but that someone is\nstill walking into these and fixing them. Code you wrote yourself is frozen the day you\nwrite it.\n\nDecide for yourself whether that is worth a dependency.\n\n## What this is not\n\n**A product of ours.** The grouping above is Google's Error Reporting, running in your\nproject — we make its input legible, we do not build or run it. If it changes, you are\ndownstream of that, the same as you already are for Cloud Logging.\n\n**A triage tool with a console.** There is no assignment, no ownership, no dashboard of\nours. What exists is Google's console, plus whatever queries you write.\n\nThere are hosted error trackers that do all of that, and do it well. The trade is worth\nstating plainly, because it is the whole reason to choose this instead.\n\n**Nothing leaves your project.** Every entry, every breadcrumb, every screenshot stays in\nthe Google Cloud project you already own, under your own IAM and your own retention rules.\nNo third party receives it, no third party stores it, and there is no data-processing\nagreement to negotiate because there is no processor.\n\nThat matters most where it usually matters: **breadcrumbs and attachments carry what a user\nwas actually doing**, and a screenshot of a checkout page is not something everyone is free\nto hand to a vendor. If you are in health, finance, education, or anywhere a contract names\nwhere data may live, that is not a preference — it is the decision.\n\nWhat you give up is a polished product: no vendor UI, no onboarding flow, no support\ncontract, no assignment workflow. What you get is every frontend and backend event in one\nstream you already own, in one query language, grouped by a console that came with the\nproject.\n\n## License\n\nMIT © Dasasian\n","readmeFilename":"README.md"}