@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.
Files changed (59) hide show
  1. package/README.md +191 -32
  2. package/dist/client/breadcrumbs.d.ts +11 -1
  3. package/dist/client/breadcrumbs.d.ts.map +1 -1
  4. package/dist/client/breadcrumbs.js +18 -0
  5. package/dist/client/breadcrumbs.js.map +1 -1
  6. package/dist/client/index.d.ts +1 -1
  7. package/dist/client/index.d.ts.map +1 -1
  8. package/dist/client/index.js +2 -1
  9. package/dist/client/index.js.map +1 -1
  10. package/dist/client/logger.d.ts +6 -2
  11. package/dist/client/logger.d.ts.map +1 -1
  12. package/dist/client/logger.js +26 -8
  13. package/dist/client/logger.js.map +1 -1
  14. package/dist/client/navigation.d.ts +34 -0
  15. package/dist/client/navigation.d.ts.map +1 -0
  16. package/dist/client/navigation.js +89 -0
  17. package/dist/client/navigation.js.map +1 -0
  18. package/dist/client/rateLimiter.d.ts +12 -5
  19. package/dist/client/rateLimiter.d.ts.map +1 -1
  20. package/dist/client/rateLimiter.js +61 -12
  21. package/dist/client/rateLimiter.js.map +1 -1
  22. package/dist/functions/httpHandler.d.ts +1 -1
  23. package/dist/functions/httpHandler.js +1 -1
  24. package/dist/functions/index.d.ts +1 -1
  25. package/dist/functions/index.d.ts.map +1 -1
  26. package/dist/functions/logHandler.d.ts +10 -5
  27. package/dist/functions/logHandler.d.ts.map +1 -1
  28. package/dist/functions/logHandler.js +16 -5
  29. package/dist/functions/logHandler.js.map +1 -1
  30. package/dist/functions/logger.js +1 -1
  31. package/dist/functions/logger.js.map +1 -1
  32. package/dist/functions/sourceMapCache.js +1 -1
  33. package/dist/functions/sourceMapCache.js.map +1 -1
  34. package/dist/shared/deprecate.d.ts +4 -0
  35. package/dist/shared/deprecate.d.ts.map +1 -0
  36. package/dist/shared/deprecate.js +30 -0
  37. package/dist/shared/deprecate.js.map +1 -0
  38. package/dist/shared/nodeModules.d.ts +39 -0
  39. package/dist/shared/nodeModules.d.ts.map +1 -0
  40. package/dist/shared/nodeModules.js +120 -0
  41. package/dist/shared/nodeModules.js.map +1 -0
  42. package/dist/shared/types.d.ts +17 -0
  43. package/dist/shared/types.d.ts.map +1 -1
  44. package/dist/shared/versionRange.d.ts +17 -0
  45. package/dist/shared/versionRange.d.ts.map +1 -0
  46. package/dist/shared/versionRange.js +48 -0
  47. package/dist/shared/versionRange.js.map +1 -0
  48. package/dist/tools/doctor.d.ts +54 -0
  49. package/dist/tools/doctor.d.ts.map +1 -0
  50. package/dist/tools/doctor.js +381 -0
  51. package/dist/tools/doctor.js.map +1 -0
  52. package/dist/tools/index.js +34 -5
  53. package/dist/tools/index.js.map +1 -1
  54. package/dist/tools/uploadSourceMaps.d.ts +7 -0
  55. package/dist/tools/uploadSourceMaps.d.ts.map +1 -1
  56. package/dist/tools/uploadSourceMaps.js.map +1 -1
  57. package/package.json +18 -11
  58. package/skills/logs/SKILL.md +10 -0
  59. 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 ESM with three entry points — `/client`, `/functions`, `/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).
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
- bucketName: 'my-app.firebasestorage.app', // holds source maps AND attachments
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 `bucketName` entirely and both fall back
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 --functions=./functions --embed-sourcemaps && firebase deploy"
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 { triggerTestLog } from '@dasasian/firebase-structured-logger/client'
157
+ import { sendTestLog } from '@dasasian/firebase-structured-logger/client'
146
158
 
147
- triggerTestLog() // wire to a dev-only button; sends one error, one warning, one info
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
- bucketName: 'my-app.firebasestorage.app', // holds source maps AND attachments
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 `bucketName`
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 — `bucketName` on the handler, or
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({ bucketName })` — the
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 budget | **50 logs**, refilling **1 per minute**; the last **20%** for errors only | client, per browser tab |
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, `minLogLevel` |
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
- minLogLevel: 'INFO',
797
+ minSeverity: 'INFO',
671
798
  rateLimitOptions: {
672
- sessionLimit: 50, // the budget: how many logs can go at once
673
- refillPerMinute: 1, // how fast it comes back
674
- errorReserve: 0.2, // share of the budget only ERROR and above may spend
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
- `minLogLevel` explicitly. Stating it is the safe habit either way: it is the one default
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
- ### The budget refills
817
+ ### Bursts recharge
691
818
 
692
- Each browser tab starts with 50 logs. Every log spends one, and one comes back each minute,
693
- up to 50. A reload does not reset it — the tab keeps its budget in `sessionStorage`.
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 20% of the budget is reserved: warnings can spend it down to 10, and only
701
- `ERROR` and above can spend the rest. A noisy warning cannot use up the room a crash needs.
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 spend the log budget. There is at most one per error, per release, per user,
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 budget empty — next log in about a minute
755
- [fsl] Log budget: only errors can use the reserve now
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 `minLogLevel` and the
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({ bucketName, maxInstances: 5 })`) if you are
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`, `triggerTestLog`, `addBreadcrumb`, `bc`.
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
- // { bucketName?, sourceMaps?: { bucket?, prefix? } }
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 --functions=./functions --embed-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 --functions=./functions --embed-sourcemaps --bucket=my-maps --prefix=fsl-maps
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 --functions=./backend --embed-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;AAEtD;;;;;;;;;;;;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;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"}
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;AAtED;;;;;;;;;;;;GAYG;AACU,QAAA,eAAe,GAAG,EAAE,CAAA;AACjC,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA,CAAC,YAAY;AAE7C,IAAI,aAAiC,CAAA;AACrC,IAAI,WAAW,GAAsB,EAAE,CAAA;AAEvC,SAAgB,gBAAgB,CAAC,MAAc;IAC7C,aAAa,GAAG,MAAM,CAAA;IACtB,aAAa,CAAC,KAAK,EAAE,YAAY,MAAM,EAAE,CAAC,CAAA;AAC5C,CAAC;AAED,SAAgB,gBAAgB;IAC9B,OAAO,aAAa,CAAA;AACtB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,SAAS,CAAC,OAA0B,EAAE,GAAW;IACxD,MAAM,MAAM,GAAG,GAAG,GAAG,UAAU,CAAA;IAC/B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,MAAM;QAAE,OAAO,OAAO,CAAA;IACzE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,SAAS,GAAG,MAAM,CAAC,CAAA;AACtD,CAAC;AAED,SAAgB,aAAa,CAC3B,IAA6B,EAC7B,IAAY,EACZ,IAA8B;IAE9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,WAAW,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IACtD,WAAW,GAAG,SAAS,CAAC,WAAW,EAAE,GAAG,CAAC,CAAA;IAEzC,IAAI,WAAW,CAAC,MAAM,GAAG,uBAAe,EAAE,CAAC;QACzC,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,GAAG,uBAAe,CAAC,CAAA;IACvE,CAAC;AACH,CAAC;AAED,SAAgB,kBAAkB,CAAC,KAAa;IAC9C,2EAA2E;IAC3E,8BAA8B;IAC9B,WAAW,GAAG,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;IAChD,OAAO,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAA;AACnE,CAAC;AAED,SAAgB,gBAAgB;IAC9B,WAAW,GAAG,EAAE,CAAA;IAChB,aAAa,GAAG,SAAS,CAAA;AAC3B,CAAC;AAEY,QAAA,EAAE,GAAG;IAChB,MAAM,EAAE,CAAC,IAAY,EAAE,IAA8B,EAAE,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC;IAC7F,KAAK,EAAG,CAAC,IAAY,EAAE,IAA8B,EAAE,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC;IAC5F,GAAG,EAAK,CAAC,MAAc,EAAE,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC;IACpD,KAAK,EAAG,CAAC,IAAY,EAAE,IAA8B,EAAE,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC;CAC7F,CAAA"}
1
+ {"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../../src/client/breadcrumbs.ts"],"names":[],"mappings":"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"}
@@ -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;AAMpF,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"}
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"}
@@ -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; } });
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":";;;AAAA,mCAAoF;AAA3E,oGAAA,UAAU,OAAA;AAAE,yGAAA,eAAe,OAAA;AAAE,sGAAA,YAAY,OAAA;AAAE,wGAAA,cAAc,OAAA;AAOlE,+CAA0E;AAAjE,uHAAA,uBAAuB,OAAA;AAAE,gHAAA,gBAAgB,OAAA;AAClD,6CAAiD;AAAxC,4GAAA,aAAa,OAAA;AAAE,iGAAA,EAAE,OAAA"}
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"}
@@ -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
- minLogLevel?: LogSeverity;
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
- * Trigger a test log entry to verify the logging pipeline is working end-to-end.
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":"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"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/client/logger.ts"],"names":[],"mappings":"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"}
@@ -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
- this.minLevel = severity_1.SEVERITY_ORDER[config.minLogLevel ?? defaultMinLevel()];
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
- screen: (0, breadcrumbs_1.getCurrentScreen)(),
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 budget: only errors can use the reserve now');
182
+ console.warn('[fsl] Log limit: only errors can use the reserved logs now');
170
183
  }
171
184
  else {
172
- console.warn('[fsl] Log budget empty — next log in about a minute');
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
- * Trigger a test log entry to verify the logging pipeline is working end-to-end.
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 triggerTestLog() {
301
- console.info('[fsl] triggerTestLog called');
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] triggerTestLog scheduled — sends are fire-and-forget; check dev.jsonl in ~1-2s');
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;