@dasasian/firebase-structured-logger 0.9.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -32
- package/dist/client/breadcrumbs.d.ts +11 -1
- package/dist/client/breadcrumbs.d.ts.map +1 -1
- package/dist/client/breadcrumbs.js +18 -0
- package/dist/client/breadcrumbs.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/logger.d.ts +6 -2
- package/dist/client/logger.d.ts.map +1 -1
- package/dist/client/logger.js +26 -8
- package/dist/client/logger.js.map +1 -1
- package/dist/client/navigation.d.ts +34 -0
- package/dist/client/navigation.d.ts.map +1 -0
- package/dist/client/navigation.js +89 -0
- package/dist/client/navigation.js.map +1 -0
- package/dist/client/rateLimiter.d.ts +12 -5
- package/dist/client/rateLimiter.d.ts.map +1 -1
- package/dist/client/rateLimiter.js +61 -12
- package/dist/client/rateLimiter.js.map +1 -1
- package/dist/functions/httpHandler.d.ts +1 -1
- package/dist/functions/httpHandler.js +1 -1
- package/dist/functions/index.d.ts +1 -1
- package/dist/functions/index.d.ts.map +1 -1
- package/dist/functions/logHandler.d.ts +10 -5
- package/dist/functions/logHandler.d.ts.map +1 -1
- package/dist/functions/logHandler.js +16 -5
- package/dist/functions/logHandler.js.map +1 -1
- package/dist/functions/logger.js +1 -1
- package/dist/functions/logger.js.map +1 -1
- package/dist/functions/sourceMapCache.js +1 -1
- package/dist/functions/sourceMapCache.js.map +1 -1
- package/dist/shared/deprecate.d.ts +4 -0
- package/dist/shared/deprecate.d.ts.map +1 -0
- package/dist/shared/deprecate.js +30 -0
- package/dist/shared/deprecate.js.map +1 -0
- package/dist/shared/nodeModules.d.ts +39 -0
- package/dist/shared/nodeModules.d.ts.map +1 -0
- package/dist/shared/nodeModules.js +120 -0
- package/dist/shared/nodeModules.js.map +1 -0
- package/dist/shared/types.d.ts +17 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/versionRange.d.ts +17 -0
- package/dist/shared/versionRange.d.ts.map +1 -0
- package/dist/shared/versionRange.js +48 -0
- package/dist/shared/versionRange.js.map +1 -0
- package/dist/tools/doctor.d.ts +54 -0
- package/dist/tools/doctor.d.ts.map +1 -0
- package/dist/tools/doctor.js +381 -0
- package/dist/tools/doctor.js.map +1 -0
- package/dist/tools/index.js +34 -5
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/uploadSourceMaps.d.ts +7 -0
- package/dist/tools/uploadSourceMaps.d.ts.map +1 -1
- package/dist/tools/uploadSourceMaps.js.map +1 -1
- package/package.json +18 -11
- package/skills/logs/SKILL.md +10 -0
- package/skills/query-logs/SKILL.md +18 -1
package/README.md
CHANGED
|
@@ -70,7 +70,19 @@ cd functions && npm install @dasasian/firebase-structured-logger
|
|
|
70
70
|
npm install @dasasian/firebase-structured-logger
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
Ships
|
|
73
|
+
Ships 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).
|
|
74
|
+
|
|
75
|
+
Installing next to **firebase-admin 13** in one command can leave two copies of
|
|
76
|
+
`@google-cloud/storage` (8 for this package, 7 for firebase-admin). Both work; run
|
|
77
|
+
`npm dedupe` once and npm keeps the one they share. firebase-admin 14.5 and later use
|
|
78
|
+
the same Storage as this package, so there is nothing to do.
|
|
79
|
+
|
|
80
|
+
### Upgrading from 0.x
|
|
81
|
+
|
|
82
|
+
Nothing breaks. Eight names were renamed for the 1.0 API — `minLogLevel` became
|
|
83
|
+
`minSeverity`, `bucketName` became `bucket`, and so on — and each old name still works in
|
|
84
|
+
1.x, with a one-time console warning naming its replacement. They are removed in 2.0.
|
|
85
|
+
The full list is in [Renamed in 1.0](#client). Run `npx fsl doctor` after upgrading.
|
|
74
86
|
|
|
75
87
|
## Setup
|
|
76
88
|
|
|
@@ -113,7 +125,7 @@ import { initLogger, createClientLogFunction } from '@dasasian/firebase-structur
|
|
|
113
125
|
initLogger({ appId: 'my-app' })
|
|
114
126
|
|
|
115
127
|
export const logFrontendEvent = createClientLogFunction({
|
|
116
|
-
|
|
128
|
+
bucket: 'my-app.firebasestorage.app', // holds source maps AND attachments
|
|
117
129
|
})
|
|
118
130
|
```
|
|
119
131
|
|
|
@@ -126,13 +138,13 @@ gs://my-app.firebasestorage.app/logAttachments/{logId}/{name}
|
|
|
126
138
|
|
|
127
139
|
Both the bucket and the prefix can be changed per half — see [Source maps](#source-maps)
|
|
128
140
|
for `sourceMaps: { bucket, prefix }`, and [Attachments](#attachments) for
|
|
129
|
-
`configureAttachments({ bucket, prefix })`. Omit `
|
|
141
|
+
`configureAttachments({ bucket, prefix })`. Omit `bucket` entirely and both fall back
|
|
130
142
|
to your project's default bucket.
|
|
131
143
|
|
|
132
144
|
**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`:
|
|
133
145
|
|
|
134
146
|
```json
|
|
135
|
-
"deploy": "export VITE_RELEASE_ID=$(git rev-parse --short HEAD) && npm run build && npx fsl upload-sourcemaps --
|
|
147
|
+
"deploy": "export VITE_RELEASE_ID=$(git rev-parse --short HEAD) && npm run build && npx fsl upload-sourcemaps --backend=./functions --embed-sourcemaps && firebase deploy"
|
|
136
148
|
```
|
|
137
149
|
|
|
138
150
|
`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.
|
|
@@ -142,9 +154,9 @@ to your project's default bucket.
|
|
|
142
154
|
**4. Verify it works** — prove the round trip before you trust it:
|
|
143
155
|
|
|
144
156
|
```ts
|
|
145
|
-
import {
|
|
157
|
+
import { sendTestLog } from '@dasasian/firebase-structured-logger/client'
|
|
146
158
|
|
|
147
|
-
|
|
159
|
+
sendTestLog() // wire to a dev-only button; sends one error, one warning, one info
|
|
148
160
|
```
|
|
149
161
|
|
|
150
162
|
Look for `labels.errorType="fsl-verify"` — in Cloud Logging once deployed, or in `dev.jsonl`
|
|
@@ -215,7 +227,7 @@ const app = express()
|
|
|
215
227
|
app.use(express.json({ limit: '10mb' })) // attachments ride in the body
|
|
216
228
|
|
|
217
229
|
app.post('/log', createHttpLogHandler({
|
|
218
|
-
|
|
230
|
+
bucket: 'my-app.firebasestorage.app', // holds source maps AND attachments
|
|
219
231
|
authorize: async (req) => {
|
|
220
232
|
const header = String(req.headers.authorization ?? '')
|
|
221
233
|
if (!header.startsWith('Bearer ')) return false
|
|
@@ -286,7 +298,7 @@ Nothing here needs a Firebase project — only a Google Cloud one. What changes:
|
|
|
286
298
|
whatever credential your app already uses.
|
|
287
299
|
- **`authorize`** checks your own session instead of a Firebase ID token — for example
|
|
288
300
|
`authorize: (req) => sessions.isValid(req.headers.cookie)`.
|
|
289
|
-
- **Storage** has no Firebase default bucket to fall back to. Name one with `
|
|
301
|
+
- **Storage** has no Firebase default bucket to fall back to. Name one with `bucket`
|
|
290
302
|
(it holds source maps and attachments), and give the service's account access to it.
|
|
291
303
|
Or name none: embedded maps still resolve the current release, older releases stay
|
|
292
304
|
minified, and attachments are dropped — the log says so once.
|
|
@@ -328,7 +340,7 @@ A gate that throws counts as a rejection, not an opening.
|
|
|
328
340
|
the trace id is still written, but does not join the platform's request log.
|
|
329
341
|
- **No `firebase-admin`? Also optional.** Storage is only needed for older releases'
|
|
330
342
|
source maps and for attachments. With `firebase-admin` installed, its Storage and
|
|
331
|
-
default bucket are used. Without it, name the bucket — `
|
|
343
|
+
default bucket are used. Without it, name the bucket — `bucket` on the handler, or
|
|
332
344
|
`configureAttachments({ bucket })` — and the service's own credentials are used.
|
|
333
345
|
- **No Storage bucket?** You do not need one. `fsl upload-sourcemaps --embed-sourcemaps`
|
|
334
346
|
without `--bucket` embeds the current release's maps into your deploy and uploads
|
|
@@ -339,6 +351,75 @@ A gate that throws counts as a rejection, not an opening.
|
|
|
339
351
|
- **Response codes:** `204` written, `400` malformed payload, `401` gate refused, `405`
|
|
340
352
|
not a POST, `500` something else. The client treats a non-2xx as a throw.
|
|
341
353
|
|
|
354
|
+
## Check your setup
|
|
355
|
+
|
|
356
|
+
Most of what can go wrong here goes wrong silently: logs that never arrive, stacks that
|
|
357
|
+
never resolve, source code published next to the app. `fsl doctor` reads the project from
|
|
358
|
+
disk and says what it finds. It needs no network and no credentials, and it only reports
|
|
359
|
+
facts it can read from a file — it never guesses from your source code, so a finding is
|
|
360
|
+
never a false alarm.
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
npx fsl doctor # Firebase: reads firebase.json
|
|
364
|
+
npx fsl doctor --backend=./server --dist=./dist # anything else: say where things are
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
It prints a summary of how your setup will behave, then any findings:
|
|
368
|
+
|
|
369
|
+
```
|
|
370
|
+
Setup: Firebase — functions in ./functions, web build in ./dist
|
|
371
|
+
|
|
372
|
+
Logging firebase-functions write()
|
|
373
|
+
Trace ids from each Cloud Functions trigger
|
|
374
|
+
Storage firebase-admin, default bucket
|
|
375
|
+
Callable createClientLogFunction available
|
|
376
|
+
|
|
377
|
+
⚠ duplicate-storage 2 copies of @google-cloud/storage (8.2.0 at the top level, 7.22.0 inside firebase-admin)
|
|
378
|
+
Fix: npm dedupe
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
| Id | Level | Means |
|
|
382
|
+
|---|---|---|
|
|
383
|
+
| `maps-published` | error | `.map` files are in the folder hosting serves — your source code is public |
|
|
384
|
+
| `node-version` | error | the backend's Node is below 22 |
|
|
385
|
+
| `callable-without-firebase-functions` | error | `firebase.json` has functions, but `firebase-functions` is not installed there |
|
|
386
|
+
| `could-not-check` | error | doctor could not read something it needed — including "run `npm install` first" |
|
|
387
|
+
| `duplicate-storage` | warning | two copies of `@google-cloud/storage`; fix with `npm dedupe` |
|
|
388
|
+
| `unsupported-peer` | warning | an installed peer (`firebase-admin`, `firebase-functions`, `firebase`) is outside the supported range |
|
|
389
|
+
| `embedded-maps-without-release` | warning | maps are embedded without a `.release` marker, so an older release can resolve against the wrong map |
|
|
390
|
+
|
|
391
|
+
**Exit code:** `0` when there are no errors, `1` when there is one. `--strict` also fails on
|
|
392
|
+
warnings, for teams that want a spotless setup; without it, a deliberate choice like
|
|
393
|
+
running with no Storage does not break CI. A check doctor could not run is always an
|
|
394
|
+
error, never a pass.
|
|
395
|
+
|
|
396
|
+
**`--json`** prints the same result for scripts and agents:
|
|
397
|
+
|
|
398
|
+
```json
|
|
399
|
+
{
|
|
400
|
+
"setup": { "kind": "firebase", "backend": "./functions", "dist": "./dist",
|
|
401
|
+
"logging": "firebase-functions", "trace": "trigger", "storage": "firebase-admin",
|
|
402
|
+
"callable": true },
|
|
403
|
+
"findings": [
|
|
404
|
+
{ "id": "duplicate-storage", "level": "warning",
|
|
405
|
+
"message": "2 copies of @google-cloud/storage (8.2.0 at the top level, 7.22.0 inside firebase-admin)",
|
|
406
|
+
"fix": "npm dedupe" }
|
|
407
|
+
],
|
|
408
|
+
"exitCode": 0
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
`setup.kind` is `firebase`, `node`, `browser-only` or `backend-only`; `logging` is
|
|
413
|
+
`firebase-functions`, `stdout` or `none`; `trace` is `trigger`, `header` or `none`; and
|
|
414
|
+
`storage` is `firebase-admin`, `google-cloud-storage` (which still needs a bucket named in
|
|
415
|
+
your code — doctor cannot see it) or `none`. The ids and fields are part of the 1.0 API:
|
|
416
|
+
scripts may depend on them.
|
|
417
|
+
|
|
418
|
+
What doctor does not check: anything set in your own code — the release id you pass to
|
|
419
|
+
`initLogger`, a bucket name passed to a handler. Its Storage line says so — "needs a bucket named in code (not checked)" — rather than
|
|
420
|
+
guessing. [`fsl verify`](https://github.com/dasasian/firebase-structured-logger/issues/25),
|
|
421
|
+
planned after 1.0, will check those by sending a real log.
|
|
422
|
+
|
|
342
423
|
## Local development
|
|
343
424
|
|
|
344
425
|
The Functions emulator writes the same entries to a local JSONL file instead of Cloud
|
|
@@ -432,6 +513,7 @@ logger.error(err, { orderId })
|
|
|
432
513
|
|---|---|---|
|
|
433
514
|
| `appId`, `releaseId` | client | your `initLogger` config |
|
|
434
515
|
| `screen` | client | tracked as the user moves |
|
|
516
|
+
| `route`, `path`, `routeSource` | client | with `enableNavigation()` — the route pattern, the real path, and where the pattern came from |
|
|
435
517
|
| `userId` | client | `setUser`, held for the session |
|
|
436
518
|
| `platform` | client | user agent — `ios` / `android` / `macos` / `web` |
|
|
437
519
|
| `browser` | client | user agent |
|
|
@@ -548,6 +630,51 @@ current screen, so `labels.screen` stays correct without a second call.
|
|
|
548
630
|
> Record the step, not the data. Breadcrumb `data` is written to your logs verbatim — keep
|
|
549
631
|
> PII, tokens and card numbers out of it, the same as you would for any label.
|
|
550
632
|
|
|
633
|
+
### Navigation, automatically
|
|
634
|
+
|
|
635
|
+
```ts
|
|
636
|
+
import { enableNavigation } from '@dasasian/firebase-structured-logger/client/navigation'
|
|
637
|
+
|
|
638
|
+
enableNavigation()
|
|
639
|
+
|
|
640
|
+
// or name routes the way your router does
|
|
641
|
+
enableNavigation({
|
|
642
|
+
routeFor: (path) => router.match(path)?.name, // undefined falls back to the id rule
|
|
643
|
+
})
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
Its own entry point, so an app that never imports it ships none of it, whatever its
|
|
647
|
+
bundler — and the import says plainly that it wraps `history.pushState` and
|
|
648
|
+
`replaceState`, the only way to notice a single-page app changing route. Call it once,
|
|
649
|
+
before or after `initLogger`. It records the current page at once and every route change
|
|
650
|
+
after it, back and forward included, as a `nav` breadcrumb, and every entry carries three
|
|
651
|
+
labels:
|
|
652
|
+
|
|
653
|
+
| Label | Example | Means |
|
|
654
|
+
|---|---|---|
|
|
655
|
+
| `route` | `/orders/:id/items` | the pattern — for grouping and counting |
|
|
656
|
+
| `path` | `/orders/1042/items` | the real path — for "what went wrong for order 1042?" |
|
|
657
|
+
| `routeSource` | `router` or `pattern` | whether `route` came from your `routeFor`, or from the id rule |
|
|
658
|
+
|
|
659
|
+
`screen` keeps its meaning — the name you give with `bc.nav` or `setScreen` — and falls
|
|
660
|
+
back to `route` when you never set one, so an app that does nothing still gets a useful
|
|
661
|
+
screen.
|
|
662
|
+
|
|
663
|
+
**The id rule**, when there is no `routeFor` or it returns `undefined`: a path segment that
|
|
664
|
+
is all digits, a UUID, a long hex string or a ULID becomes `:id`. Anything else is kept as
|
|
665
|
+
written, so a slug like `/blog/my-post` stays as it is — no rule tells a slug from a page
|
|
666
|
+
name reliably, which is what `routeFor` is for.
|
|
667
|
+
|
|
668
|
+
**What never leaves the browser:** the query string, always — it is where tokens and
|
|
669
|
+
emails usually ride. The fragment too, unless it is a route: `#/orders/1042` is read as the
|
|
670
|
+
path, `#section-3` or `#access_token=…` is dropped. If your paths themselves can hold
|
|
671
|
+
personal data (`/users/jane@example.com`), clean them or switch `path` off:
|
|
672
|
+
|
|
673
|
+
```ts
|
|
674
|
+
enableNavigation({ cleanPath: (path) => path.replace(/[^/]+@[^/]+/g, ':email') })
|
|
675
|
+
enableNavigation({ path: false })
|
|
676
|
+
```
|
|
677
|
+
|
|
551
678
|
## User feedback
|
|
552
679
|
|
|
553
680
|
```ts
|
|
@@ -609,7 +736,7 @@ labels.hasAttachments="true" # entries that have files
|
|
|
609
736
|
labels.logId="01J..." # the entry whose files you are looking for
|
|
610
737
|
```
|
|
611
738
|
|
|
612
|
-
By default they share the bucket passed to `createClientLogFunction({
|
|
739
|
+
By default they share the bucket passed to `createClientLogFunction({ bucket })` — the
|
|
613
740
|
same one the source maps live in, falling back to the project's default bucket.
|
|
614
741
|
|
|
615
742
|
Send them somewhere else with `configureAttachments`, in your functions entry point:
|
|
@@ -656,9 +783,9 @@ hold things back — so this is worth reading before you conclude something is b
|
|
|
656
783
|
|
|
657
784
|
| Gate | Default | Where |
|
|
658
785
|
|---|---|---|
|
|
659
|
-
| Log
|
|
786
|
+
| 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 |
|
|
660
787
|
| Duplicates | **3** full copies of the same error, then counted and sent as a summary | client, per browser tab |
|
|
661
|
-
| Client severity floor | `WARNING` in production, `DEBUG` in dev | client, `
|
|
788
|
+
| Client severity floor | `WARNING` in production, `DEBUG` in dev | client, `minSeverity` |
|
|
662
789
|
| Server severity floor | `WARNING` in production, `DEBUG` in the emulator | function, `minSeverity` |
|
|
663
790
|
| Function concurrency | `maxInstances: 1` on `createClientLogFunction` | function |
|
|
664
791
|
|
|
@@ -667,11 +794,11 @@ initLogger({
|
|
|
667
794
|
appId: 'my-app',
|
|
668
795
|
releaseId,
|
|
669
796
|
logFunction,
|
|
670
|
-
|
|
797
|
+
minSeverity: 'INFO',
|
|
671
798
|
rateLimitOptions: {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
799
|
+
burstLimit: 50, // how many logs can go at once
|
|
800
|
+
rechargeSecondsPerLog: 60, // after a burst, one log recharges every 60 seconds
|
|
801
|
+
reservedForErrors: 10, // of the burst, how many only ERROR and above may use
|
|
675
802
|
duplicateLimit: 3, // full copies of one error before counting starts
|
|
676
803
|
summaryIntervalMinutes: 60,
|
|
677
804
|
summaryMaxAgeDays: 7, // how long an unsent summary waits on the device
|
|
@@ -684,21 +811,24 @@ The client's production default comes from `process.env.NODE_ENV`, which Vite re
|
|
|
684
811
|
build time. A `define: { 'process.env': {} }` in `vite.config` — common, to quiet a library
|
|
685
812
|
that expects Node — replaces the whole object instead, `NODE_ENV` reads as undefined, and
|
|
686
813
|
the floor is silently `DEBUG` in production. If your config has that line, pass
|
|
687
|
-
`
|
|
814
|
+
`minSeverity` explicitly. Stating it is the safe habit either way: it is the one default
|
|
688
815
|
here whose failure mode is a bill rather than a missing log.
|
|
689
816
|
|
|
690
|
-
###
|
|
817
|
+
### Bursts recharge
|
|
691
818
|
|
|
692
|
-
Each browser tab
|
|
693
|
-
|
|
819
|
+
Each browser tab can send a burst of up to `burstLimit` logs (50). After that, one log
|
|
820
|
+
recharges every `rechargeSecondsPerLog` seconds (60) — one at a time, so a full burst takes
|
|
821
|
+
50 minutes to come back. A reload does not reset it: the tab keeps its count in
|
|
822
|
+
`sessionStorage`.
|
|
694
823
|
|
|
695
824
|
So a burst at start-up is fine, and a user who works in the app all afternoon is never
|
|
696
825
|
silenced for long. A bug that logs in a loop is still held to about 60 entries an hour per
|
|
697
826
|
user: 1,000 users stuck in such a loop for a working month stays around the 50 GiB of
|
|
698
827
|
Cloud Logging that each project gets free.
|
|
699
828
|
|
|
700
|
-
The last
|
|
701
|
-
`ERROR` and above can
|
|
829
|
+
The last `reservedForErrors` logs (10) are kept for errors: warnings can use the burst down
|
|
830
|
+
to 10, and only `ERROR` and above can use the rest. A value over half of `burstLimit` is
|
|
831
|
+
capped at half, with one warning, so warnings always have room. A noisy warning cannot use up the room a crash needs.
|
|
702
832
|
|
|
703
833
|
### Repeats are counted, not dropped
|
|
704
834
|
|
|
@@ -742,7 +872,7 @@ messages, and may hold personal data.
|
|
|
742
872
|
A summary is a `WARNING` with no stack, so Cloud Error Reporting sees the 3 full copies and
|
|
743
873
|
not the summary. For the true count, add up `labels.repeatCount` in Cloud Logging.
|
|
744
874
|
|
|
745
|
-
Summaries do not
|
|
875
|
+
Summaries do not count against the burst. There is at most one per error, per release, per user,
|
|
746
876
|
per hour.
|
|
747
877
|
|
|
748
878
|
### What a dropped log looks like
|
|
@@ -751,11 +881,11 @@ The two rate limits say so in the browser console:
|
|
|
751
881
|
|
|
752
882
|
```
|
|
753
883
|
[fsl] Duplicate counted for the next summary: TypeError: cannot read 'id' | checkout
|
|
754
|
-
[fsl] Log
|
|
755
|
-
[fsl] Log
|
|
884
|
+
[fsl] Log limit reached — recharging, next log in about a minute
|
|
885
|
+
[fsl] Log limit: only errors can use the reserved logs now
|
|
756
886
|
```
|
|
757
887
|
|
|
758
|
-
**The severity floors are silent.** Both of them — the client's `
|
|
888
|
+
**The severity floors are silent.** Both of them — the client's `minSeverity` and the
|
|
759
889
|
function's `minSeverity` — simply return, with nothing written and nothing logged about it.
|
|
760
890
|
|
|
761
891
|
So if an entry never arrived and there is no `[fsl]` warning in the console, it was a floor,
|
|
@@ -764,7 +894,7 @@ not a limit. In production both default to `WARNING`, which drops `DEBUG`, `INFO
|
|
|
764
894
|
`INFO` you expected to see has two places it can vanish.
|
|
765
895
|
|
|
766
896
|
`maxInstances: 1` is a deliberate cost guard on what is usually the busiest function in the
|
|
767
|
-
system. Raise it (`createClientLogFunction({
|
|
897
|
+
system. Raise it (`createClientLogFunction({ bucket, maxInstances: 5 })`) if you are
|
|
768
898
|
dropping client logs under load — and watch your Cloud Logging bill when you do.
|
|
769
899
|
|
|
770
900
|
Feedback is exempt from every one of these. See [User feedback](#user-feedback).
|
|
@@ -785,6 +915,8 @@ Narrow it when you need to:
|
|
|
785
915
|
| `labels.functionName:*` | server entries only |
|
|
786
916
|
| `labels.releaseId="<sha>"` | one build |
|
|
787
917
|
| `labels.screen="checkout"` | one screen |
|
|
918
|
+
| `labels.route="/orders/:id/items"` | one route, every id (with `enableNavigation`) |
|
|
919
|
+
| `labels.path="/orders/1042/items"` | one real page — that order, that user (with `enableNavigation`) |
|
|
788
920
|
| `labels.feedback="true"` | user-reported issues |
|
|
789
921
|
| `labels.hasAttachments="true"` | entries with files in GCS |
|
|
790
922
|
| `labels.truncated="true"` | entries shortened to fit — the full copy is `fsl-overflow.json` |
|
|
@@ -819,10 +951,32 @@ logger.setUser(uid, extraLabels?)
|
|
|
819
951
|
logger.clearUser()
|
|
820
952
|
logger.setScreen(screen)
|
|
821
953
|
logger.addBreadcrumb(type, name, data?)
|
|
954
|
+
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
Optional helpers are separate entry points, so an app ships only what it imports:
|
|
958
|
+
|
|
959
|
+
```ts
|
|
960
|
+
import { enableNavigation } from '@dasasian/firebase-structured-logger/client/navigation'
|
|
961
|
+
enableNavigation({ routeFor?, cleanPath?, path? }) // see "Navigation, automatically"
|
|
822
962
|
```
|
|
823
963
|
|
|
824
964
|
Also exported: `initLogger`, `getClientLogger`, `setupGlobalErrorHandler`, `handleReactError`,
|
|
825
|
-
`sendFeedback`, `
|
|
965
|
+
`sendFeedback`, `sendTestLog`, `addBreadcrumb`, `bc`.
|
|
966
|
+
|
|
967
|
+
**Renamed in 1.0.** The old names still work in 1.x, each with a one-time console warning,
|
|
968
|
+
and are removed in 2.0:
|
|
969
|
+
|
|
970
|
+
| Old | New |
|
|
971
|
+
|---|---|
|
|
972
|
+
| `minLogLevel` (client `initLogger`) | `minSeverity` |
|
|
973
|
+
| `bucketName` (`createClientLogHandler`, `createClientLogFunction`, `createHttpLogHandler`) | `bucket` |
|
|
974
|
+
| `rateLimitOptions.sessionLimit` | `rateLimitOptions.burstLimit` |
|
|
975
|
+
| `rateLimitOptions.refillPerMinute` | `rateLimitOptions.rechargeSecondsPerLog` (seconds, not a rate: `60 / refillPerMinute`) |
|
|
976
|
+
| `rateLimitOptions.errorReserve` (a share) | `rateLimitOptions.reservedForErrors` (a count: `errorReserve × burstLimit`) |
|
|
977
|
+
| type `ClientLogRequest` | type `LogRequest` |
|
|
978
|
+
| `triggerTestLog()` | `sendTestLog()` |
|
|
979
|
+
| `fsl upload-sourcemaps --functions=<dir>` | `--backend=<dir>` (it is not only for Cloud Functions) |
|
|
826
980
|
|
|
827
981
|
`Logger` is exported as a **type only** — the client logger is a session singleton, so
|
|
828
982
|
annotate with `Logger<MyAppLabels>` and construct with `initLogger()`. A second instance
|
|
@@ -841,7 +995,7 @@ logError / logWarn / logInfo / logDebug (message, labels?, context?, attachments
|
|
|
841
995
|
configureAttachments({ bucket?, prefix? }) // once, at module load — see Attachments
|
|
842
996
|
|
|
843
997
|
// Receiving client logs. All three take the same source-map config:
|
|
844
|
-
// {
|
|
998
|
+
// { bucket?, sourceMaps?: { bucket?, prefix? } }
|
|
845
999
|
createClientLogFunction({ …, cors?, maxInstances? }) // a ready-to-export callable
|
|
846
1000
|
createHttpLogHandler({ …, authorize, allowOrigin? }) // an (req, res) handler for Express etc.
|
|
847
1001
|
createClientLogHandler({ … }) // the bare handler, wrap it yourself
|
|
@@ -858,13 +1012,18 @@ bare handler only if you are wrapping it in something else yourself.
|
|
|
858
1012
|
|
|
859
1013
|
```bash
|
|
860
1014
|
# Upload source maps to Cloud Storage and strip local .map files (run in deploy)
|
|
861
|
-
npx fsl upload-sourcemaps --
|
|
1015
|
+
npx fsl upload-sourcemaps --backend=./functions --embed-sourcemaps
|
|
862
1016
|
|
|
863
1017
|
# Same, to a bucket and prefix of your choosing — tell the reader the same values
|
|
864
|
-
npx fsl upload-sourcemaps --
|
|
1018
|
+
npx fsl upload-sourcemaps --backend=./functions --embed-sourcemaps --bucket=my-maps --prefix=fsl-maps
|
|
865
1019
|
|
|
866
1020
|
# No bucket at all: embed the current release, upload nothing
|
|
867
|
-
npx fsl upload-sourcemaps --
|
|
1021
|
+
npx fsl upload-sourcemaps --backend=./backend --embed-sourcemaps
|
|
1022
|
+
|
|
1023
|
+
# Check the setup from disk — no network, no credentials (see "Check your setup")
|
|
1024
|
+
npx fsl doctor
|
|
1025
|
+
npx fsl doctor --backend=./server --dist=./dist # no firebase.json to read
|
|
1026
|
+
npx fsl doctor --strict --json # CI: fail on warnings too, machine-readable
|
|
868
1027
|
|
|
869
1028
|
# Install the Claude Code skills into the current project (or --global, --force)
|
|
870
1029
|
npx fsl install-skills
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BreadcrumbEntry } from '../shared/types';
|
|
1
|
+
import type { BreadcrumbEntry, NavigationLabels } from '../shared/types';
|
|
2
2
|
/**
|
|
3
3
|
* How many breadcrumbs are retained — and therefore how many are sent.
|
|
4
4
|
*
|
|
@@ -15,6 +15,16 @@ import type { BreadcrumbEntry } from '../shared/types';
|
|
|
15
15
|
export declare const MAX_BREADCRUMBS = 50;
|
|
16
16
|
export declare function setCurrentScreen(screen: string): void;
|
|
17
17
|
export declare function getCurrentScreen(): string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Called by `enableNavigation()` (`client/navigation.ts`) on the current page and on
|
|
20
|
+
* every route change after it — never by the core itself. Mirrors `setCurrentScreen`:
|
|
21
|
+
* the one-way setter a helper hands data through, rather than the core reaching out to
|
|
22
|
+
* the helper. Adds a `nav` breadcrumb named after the real path, or the route pattern
|
|
23
|
+
* when `path` was omitted (`enableNavigation({ path: false })`).
|
|
24
|
+
*/
|
|
25
|
+
export declare function setCurrentRoute(labels: NavigationLabels): void;
|
|
26
|
+
/** The current page's navigation labels, or `undefined` when `enableNavigation()` was never called. */
|
|
27
|
+
export declare function getCurrentRoute(): NavigationLabels | undefined;
|
|
18
28
|
export declare function addBreadcrumb(type: BreadcrumbEntry['type'], name: string, data?: Record<string, unknown>): void;
|
|
19
29
|
export declare function getLastBreadcrumbs(count: number): BreadcrumbEntry[];
|
|
20
30
|
export declare function clearBreadcrumbs(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumbs.d.ts","sourceRoot":"","sources":["../../src/client/breadcrumbs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"breadcrumbs.d.ts","sourceRoot":"","sources":["../../src/client/breadcrumbs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAExE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,KAAK,CAAA;AAMjC,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAGrD;AAED,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,SAAS,CAErD;AAID;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAG9D;AAED,uGAAuG;AACvG,wBAAgB,eAAe,IAAI,gBAAgB,GAAG,SAAS,CAE9D;AAoBD,wBAAgB,aAAa,CAC3B,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,IAAI,CAQN;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,EAAE,CAKnE;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC;AAED,eAAO,MAAM,EAAE;mBACE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;kBACtC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;kBACpC,MAAM;kBACR,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CACtD,CAAA"}
|
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.bc = exports.MAX_BREADCRUMBS = void 0;
|
|
4
4
|
exports.setCurrentScreen = setCurrentScreen;
|
|
5
5
|
exports.getCurrentScreen = getCurrentScreen;
|
|
6
|
+
exports.setCurrentRoute = setCurrentRoute;
|
|
7
|
+
exports.getCurrentRoute = getCurrentRoute;
|
|
6
8
|
exports.addBreadcrumb = addBreadcrumb;
|
|
7
9
|
exports.getLastBreadcrumbs = getLastBreadcrumbs;
|
|
8
10
|
exports.clearBreadcrumbs = clearBreadcrumbs;
|
|
@@ -30,6 +32,22 @@ function setCurrentScreen(screen) {
|
|
|
30
32
|
function getCurrentScreen() {
|
|
31
33
|
return currentScreen;
|
|
32
34
|
}
|
|
35
|
+
let currentRoute;
|
|
36
|
+
/**
|
|
37
|
+
* Called by `enableNavigation()` (`client/navigation.ts`) on the current page and on
|
|
38
|
+
* every route change after it — never by the core itself. Mirrors `setCurrentScreen`:
|
|
39
|
+
* the one-way setter a helper hands data through, rather than the core reaching out to
|
|
40
|
+
* the helper. Adds a `nav` breadcrumb named after the real path, or the route pattern
|
|
41
|
+
* when `path` was omitted (`enableNavigation({ path: false })`).
|
|
42
|
+
*/
|
|
43
|
+
function setCurrentRoute(labels) {
|
|
44
|
+
currentRoute = labels;
|
|
45
|
+
addBreadcrumb('nav', labels.path ?? labels.route, labels.path !== undefined ? { route: labels.route } : undefined);
|
|
46
|
+
}
|
|
47
|
+
/** The current page's navigation labels, or `undefined` when `enableNavigation()` was never called. */
|
|
48
|
+
function getCurrentRoute() {
|
|
49
|
+
return currentRoute;
|
|
50
|
+
}
|
|
33
51
|
/**
|
|
34
52
|
* Drop anything past the age cutoff.
|
|
35
53
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../../src/client/breadcrumbs.ts"],"names":[],"mappings":";;;AAqBA,4CAGC;AAED,4CAEC;AAoBD,sCAYC;AAED,gDAKC;AAED,4CAGC;
|
|
1
|
+
{"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../../src/client/breadcrumbs.ts"],"names":[],"mappings":"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"}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { initLogger, getClientLogger, sendFeedback, triggerTestLog } from './logger';
|
|
1
|
+
export { initLogger, getClientLogger, sendFeedback, sendTestLog, triggerTestLog } from './logger';
|
|
2
2
|
export type { Logger, InitLoggerConfig, FeedbackOptions, RateLimitConfig } from './logger';
|
|
3
3
|
export { setupGlobalErrorHandler, handleReactError } from './errorHandler';
|
|
4
4
|
export { addBreadcrumb, bc } from './breadcrumbs';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAMjG,YAAY,EAAE,MAAM,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1F,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAC1E,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AACjD,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA"}
|
package/dist/client/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.bc = exports.addBreadcrumb = exports.handleReactError = exports.setupGlobalErrorHandler = exports.triggerTestLog = exports.sendFeedback = exports.getClientLogger = exports.initLogger = void 0;
|
|
3
|
+
exports.bc = exports.addBreadcrumb = exports.handleReactError = exports.setupGlobalErrorHandler = exports.triggerTestLog = exports.sendTestLog = exports.sendFeedback = exports.getClientLogger = exports.initLogger = void 0;
|
|
4
4
|
var logger_1 = require("./logger");
|
|
5
5
|
Object.defineProperty(exports, "initLogger", { enumerable: true, get: function () { return logger_1.initLogger; } });
|
|
6
6
|
Object.defineProperty(exports, "getClientLogger", { enumerable: true, get: function () { return logger_1.getClientLogger; } });
|
|
7
7
|
Object.defineProperty(exports, "sendFeedback", { enumerable: true, get: function () { return logger_1.sendFeedback; } });
|
|
8
|
+
Object.defineProperty(exports, "sendTestLog", { enumerable: true, get: function () { return logger_1.sendTestLog; } });
|
|
8
9
|
Object.defineProperty(exports, "triggerTestLog", { enumerable: true, get: function () { return logger_1.triggerTestLog; } });
|
|
9
10
|
var errorHandler_1 = require("./errorHandler");
|
|
10
11
|
Object.defineProperty(exports, "setupGlobalErrorHandler", { enumerable: true, get: function () { return errorHandler_1.setupGlobalErrorHandler; } });
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":";;;AAAA,mCAAiG;AAAxF,oGAAA,UAAU,OAAA;AAAE,yGAAA,eAAe,OAAA;AAAE,sGAAA,YAAY,OAAA;AAAE,qGAAA,WAAW,OAAA;AAAE,wGAAA,cAAc,OAAA;AAO/E,+CAA0E;AAAjE,uHAAA,uBAAuB,OAAA;AAAE,gHAAA,gBAAgB,OAAA;AAClD,6CAAiD;AAAxC,4GAAA,aAAa,OAAA;AAAE,iGAAA,EAAE,OAAA"}
|
package/dist/client/logger.d.ts
CHANGED
|
@@ -12,8 +12,10 @@ export interface InitLoggerConfig<AppLabels extends Record<string, string | unde
|
|
|
12
12
|
appId: string;
|
|
13
13
|
releaseId: string;
|
|
14
14
|
logFunction: LogCallable;
|
|
15
|
-
|
|
15
|
+
minSeverity?: LogSeverity;
|
|
16
16
|
rateLimitOptions?: RateLimitConfig;
|
|
17
|
+
/** @deprecated Use `minSeverity`. */
|
|
18
|
+
minLogLevel?: LogSeverity;
|
|
17
19
|
}
|
|
18
20
|
export declare class Logger<AppLabels extends Record<string, string | undefined> = Record<string, string | undefined>> {
|
|
19
21
|
private readonly config;
|
|
@@ -71,7 +73,7 @@ export declare class Logger<AppLabels extends Record<string, string | undefined>
|
|
|
71
73
|
*/
|
|
72
74
|
export declare function sendFeedback<AppLabels extends Record<string, string | undefined> = Record<string, string | undefined>>(text: string, extras?: FeedbackOptions<AppLabels>): void;
|
|
73
75
|
/**
|
|
74
|
-
*
|
|
76
|
+
* Send a test log entry to verify the logging pipeline is working end-to-end.
|
|
75
77
|
* Logs at all severities with errorType: 'fsl-verify'. Safe to call in dev only.
|
|
76
78
|
*
|
|
77
79
|
* After clicking, check:
|
|
@@ -79,6 +81,8 @@ export declare function sendFeedback<AppLabels extends Record<string, string | u
|
|
|
79
81
|
* 2. Stack trace is symbolicated (points to source file, not minified bundle)
|
|
80
82
|
* 3. MCP query: source: local, where: [{ field: "labels.errorType", operator: "==", value: "fsl-verify" }]
|
|
81
83
|
*/
|
|
84
|
+
export declare function sendTestLog(): void;
|
|
85
|
+
/** @deprecated Use `sendTestLog()`. */
|
|
82
86
|
export declare function triggerTestLog(): void;
|
|
83
87
|
export declare function initLogger<AppLabels extends Record<string, string | undefined> = Record<string, string | undefined>>(config: InitLoggerConfig<AppLabels>): Logger<AppLabels>;
|
|
84
88
|
export declare function getClientLogger<AppLabels extends Record<string, string | undefined> = Record<string, string | undefined>>(): Logger<AppLabels>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/client/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAgB,UAAU,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/client/logger.ts"],"names":[],"mappings":"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"}
|
package/dist/client/logger.js
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Logger = void 0;
|
|
4
4
|
exports.sendFeedback = sendFeedback;
|
|
5
|
+
exports.sendTestLog = sendTestLog;
|
|
5
6
|
exports.triggerTestLog = triggerTestLog;
|
|
6
7
|
exports.initLogger = initLogger;
|
|
7
8
|
exports.getClientLogger = getClientLogger;
|
|
8
9
|
const severity_1 = require("../shared/severity");
|
|
9
10
|
const error_1 = require("../shared/error");
|
|
11
|
+
const deprecate_1 = require("../shared/deprecate");
|
|
10
12
|
const breadcrumbs_1 = require("./breadcrumbs");
|
|
11
13
|
const rateLimiter_1 = require("./rateLimiter");
|
|
12
14
|
/**
|
|
@@ -75,7 +77,9 @@ class Logger {
|
|
|
75
77
|
userLabels = {};
|
|
76
78
|
constructor(config) {
|
|
77
79
|
this.config = config;
|
|
78
|
-
|
|
80
|
+
if (config.minLogLevel !== undefined)
|
|
81
|
+
(0, deprecate_1.warnDeprecated)('minLogLevel', 'minSeverity');
|
|
82
|
+
this.minLevel = severity_1.SEVERITY_ORDER[config.minSeverity ?? config.minLogLevel ?? defaultMinLevel()];
|
|
79
83
|
if (config.rateLimitOptions) {
|
|
80
84
|
(0, rateLimiter_1.configureRateLimiter)(config.rateLimitOptions);
|
|
81
85
|
}
|
|
@@ -142,10 +146,19 @@ class Logger {
|
|
|
142
146
|
skipBudget = false, timestamp) {
|
|
143
147
|
if (!bypassVolumeControls && severity_1.SEVERITY_ORDER[severity] > this.minLevel)
|
|
144
148
|
return false;
|
|
149
|
+
// Set only by enableNavigation() (@dasasian/firebase-structured-logger/client/navigation),
|
|
150
|
+
// through the setter it calls in breadcrumbs.ts — undefined for an app that never
|
|
151
|
+
// imports that entry point.
|
|
152
|
+
const nav = (0, breadcrumbs_1.getCurrentRoute)();
|
|
145
153
|
const allLabels = {
|
|
146
154
|
appId: this.config.appId,
|
|
147
155
|
releaseId: this.config.releaseId,
|
|
148
|
-
|
|
156
|
+
// Falls back to the route pattern so an app that never calls setScreen/bc.nav
|
|
157
|
+
// still gets a useful screen label once navigation is enabled.
|
|
158
|
+
screen: (0, breadcrumbs_1.getCurrentScreen)() ?? nav?.route,
|
|
159
|
+
route: nav?.route,
|
|
160
|
+
path: nav?.path,
|
|
161
|
+
routeSource: nav?.routeSource,
|
|
149
162
|
userId: this.userId,
|
|
150
163
|
platform: PLATFORM,
|
|
151
164
|
browser: BROWSER,
|
|
@@ -166,10 +179,10 @@ class Logger {
|
|
|
166
179
|
console.warn(`[fsl] Duplicate counted for the next summary: ${(0, rateLimiter_1.describeSignature)(decision.signature ?? '')}`);
|
|
167
180
|
}
|
|
168
181
|
else if (decision.reason === 'reserve') {
|
|
169
|
-
console.warn('[fsl] Log
|
|
182
|
+
console.warn('[fsl] Log limit: only errors can use the reserved logs now');
|
|
170
183
|
}
|
|
171
184
|
else {
|
|
172
|
-
console.warn('[fsl] Log
|
|
185
|
+
console.warn('[fsl] Log limit reached — recharging, next log in about a minute');
|
|
173
186
|
}
|
|
174
187
|
return false;
|
|
175
188
|
}
|
|
@@ -289,7 +302,7 @@ function sendFeedback(text, extras) {
|
|
|
289
302
|
getClientLogger().sendFeedback(text, extras);
|
|
290
303
|
}
|
|
291
304
|
/**
|
|
292
|
-
*
|
|
305
|
+
* Send a test log entry to verify the logging pipeline is working end-to-end.
|
|
293
306
|
* Logs at all severities with errorType: 'fsl-verify'. Safe to call in dev only.
|
|
294
307
|
*
|
|
295
308
|
* After clicking, check:
|
|
@@ -297,8 +310,8 @@ function sendFeedback(text, extras) {
|
|
|
297
310
|
* 2. Stack trace is symbolicated (points to source file, not minified bundle)
|
|
298
311
|
* 3. MCP query: source: local, where: [{ field: "labels.errorType", operator: "==", value: "fsl-verify" }]
|
|
299
312
|
*/
|
|
300
|
-
function
|
|
301
|
-
console.info('[fsl]
|
|
313
|
+
function sendTestLog() {
|
|
314
|
+
console.info('[fsl] sendTestLog called');
|
|
302
315
|
const logger = getClientLogger();
|
|
303
316
|
const testError = new Error('[fsl-verify] Test error — logging pipeline check');
|
|
304
317
|
console.info('[fsl] sending error log...');
|
|
@@ -307,7 +320,12 @@ function triggerTestLog() {
|
|
|
307
320
|
logger.warning('[fsl-verify] Test warning', { errorType: 'fsl-verify' });
|
|
308
321
|
console.info('[fsl] sending info log...');
|
|
309
322
|
logger.info('[fsl-verify] Test info', { errorType: 'fsl-verify' });
|
|
310
|
-
console.info('[fsl]
|
|
323
|
+
console.info('[fsl] sendTestLog scheduled — sends are fire-and-forget; check dev.jsonl in ~1-2s');
|
|
324
|
+
}
|
|
325
|
+
/** @deprecated Use `sendTestLog()`. */
|
|
326
|
+
function triggerTestLog() {
|
|
327
|
+
(0, deprecate_1.warnDeprecated)('triggerTestLog', 'sendTestLog');
|
|
328
|
+
sendTestLog();
|
|
311
329
|
}
|
|
312
330
|
// Module-level singleton
|
|
313
331
|
let instance = null;
|