@dasasian/firebase-structured-logger 0.7.0 → 0.9.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 +183 -17
- package/dist/client/logger.d.ts +16 -0
- package/dist/client/logger.d.ts.map +1 -1
- package/dist/client/logger.js +137 -18
- package/dist/client/logger.js.map +1 -1
- package/dist/client/rateLimiter.d.ts +98 -10
- package/dist/client/rateLimiter.d.ts.map +1 -1
- package/dist/client/rateLimiter.js +282 -18
- package/dist/client/rateLimiter.js.map +1 -1
- package/dist/functions/httpHandler.d.ts.map +1 -1
- package/dist/functions/httpHandler.js +3 -0
- package/dist/functions/httpHandler.js.map +1 -1
- package/dist/functions/logHandler.d.ts +2 -1
- package/dist/functions/logHandler.d.ts.map +1 -1
- package/dist/functions/logHandler.js +16 -3
- package/dist/functions/logHandler.js.map +1 -1
- package/dist/functions/logger.d.ts +13 -0
- package/dist/functions/logger.d.ts.map +1 -1
- package/dist/functions/logger.js +299 -14
- package/dist/functions/logger.js.map +1 -1
- package/dist/functions/sourceMapCache.d.ts +14 -4
- package/dist/functions/sourceMapCache.d.ts.map +1 -1
- package/dist/functions/sourceMapCache.js +95 -6
- package/dist/functions/sourceMapCache.js.map +1 -1
- package/dist/functions/traceContext.d.ts +21 -7
- package/dist/functions/traceContext.d.ts.map +1 -1
- package/dist/functions/traceContext.js +60 -12
- package/dist/functions/traceContext.js.map +1 -1
- package/dist/shared/types.d.ts +7 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/package.json +11 -5
- package/skills/query-logs/SKILL.md +35 -0
package/README.md
CHANGED
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
|
|
16
16
|
Your 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.
|
|
17
17
|
|
|
18
|
-
Ships a
|
|
18
|
+
Ships a browser logger, a server logger, and the `fsl` CLI. Built for Firebase, but
|
|
19
|
+
Firebase is optional: the browser half has no Firebase dependency, and the server half
|
|
20
|
+
runs on Cloud Functions, Cloud Run, or any Node server on Google Cloud, where stdout goes
|
|
21
|
+
to Cloud Logging. What it needs is a Google Cloud project — that is where the logs live.
|
|
19
22
|
|
|
20
23
|
## One query, both halves
|
|
21
24
|
|
|
@@ -62,9 +65,12 @@ npm install @dasasian/firebase-structured-logger
|
|
|
62
65
|
|
|
63
66
|
# Cloud Functions
|
|
64
67
|
cd functions && npm install @dasasian/firebase-structured-logger
|
|
68
|
+
|
|
69
|
+
# Cloud Run, or any Node server on Google Cloud
|
|
70
|
+
npm install @dasasian/firebase-structured-logger
|
|
65
71
|
```
|
|
66
72
|
|
|
67
|
-
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).
|
|
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).
|
|
68
74
|
|
|
69
75
|
## Setup
|
|
70
76
|
|
|
@@ -192,7 +198,7 @@ The entry should carry `userId` without your having written it.
|
|
|
192
198
|
|
|
193
199
|
### If your backend is not Cloud Functions
|
|
194
200
|
|
|
195
|
-
Cloud Run, or any Node server you already run. Two things differ from the browser
|
|
201
|
+
Cloud Run, or any Node server you already run on Google Cloud. Two things differ from the browser
|
|
196
202
|
path above; everything else — breadcrumbs, labels, symbolication, the free fields — is
|
|
197
203
|
identical, and the entries land in the same stream in the same shape.
|
|
198
204
|
|
|
@@ -218,6 +224,37 @@ app.post('/log', createHttpLogHandler({
|
|
|
218
224
|
}))
|
|
219
225
|
```
|
|
220
226
|
|
|
227
|
+
The handler reads Node's request and response shapes. A framework that wraps them, like
|
|
228
|
+
Hono, needs a few lines of adapter:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { Hono } from 'hono'
|
|
232
|
+
import { createHttpLogHandler } from '@dasasian/firebase-structured-logger/functions'
|
|
233
|
+
|
|
234
|
+
const app = new Hono()
|
|
235
|
+
const logHandler = createHttpLogHandler({ authorize })
|
|
236
|
+
|
|
237
|
+
app.on(['POST', 'OPTIONS'], '/log', async (c) => {
|
|
238
|
+
const headers: Record<string, string> = {}
|
|
239
|
+
let status = 200
|
|
240
|
+
let body: string | undefined
|
|
241
|
+
await logHandler(
|
|
242
|
+
{
|
|
243
|
+
method: c.req.method,
|
|
244
|
+
headers: Object.fromEntries(c.req.raw.headers),
|
|
245
|
+
body: await c.req.json().catch(() => null),
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
get statusCode() { return status },
|
|
249
|
+
set statusCode(v: number) { status = v },
|
|
250
|
+
setHeader: (name, value) => { headers[name] = value },
|
|
251
|
+
end: (b) => { body = b },
|
|
252
|
+
},
|
|
253
|
+
)
|
|
254
|
+
return new Response(body ?? null, { status, headers })
|
|
255
|
+
})
|
|
256
|
+
```
|
|
257
|
+
|
|
221
258
|
**Point the client at it.** `logFunction` is any async function, so a `fetch` works:
|
|
222
259
|
|
|
223
260
|
```ts
|
|
@@ -238,6 +275,25 @@ initLogger({
|
|
|
238
275
|
})
|
|
239
276
|
```
|
|
240
277
|
|
|
278
|
+
#### Without Firebase
|
|
279
|
+
|
|
280
|
+
Nothing here needs a Firebase project — only a Google Cloud one. What changes:
|
|
281
|
+
|
|
282
|
+
- **The server has to run on Google Cloud** — Cloud Run, GKE, App Engine, or Compute
|
|
283
|
+
Engine with the Ops Agent. Entries are written to stdout, and only there does stdout
|
|
284
|
+
reach Cloud Logging. On AWS, Vercel or your own box they print and go nowhere.
|
|
285
|
+
- **The browser** needs no `firebase` package. `logFunction` is the `fetch` above; send
|
|
286
|
+
whatever credential your app already uses.
|
|
287
|
+
- **`authorize`** checks your own session instead of a Firebase ID token — for example
|
|
288
|
+
`authorize: (req) => sessions.isValid(req.headers.cookie)`.
|
|
289
|
+
- **Storage** has no Firebase default bucket to fall back to. Name one with `bucketName`
|
|
290
|
+
(it holds source maps and attachments), and give the service's account access to it.
|
|
291
|
+
Or name none: embedded maps still resolve the current release, older releases stay
|
|
292
|
+
minified, and attachments are dropped — the log says so once.
|
|
293
|
+
- **`fsl upload-sourcemaps --bucket`** works with any Cloud Storage bucket.
|
|
294
|
+
- **`withLogging` and `createClientLogFunction`** are Cloud Functions tools. Outside
|
|
295
|
+
them, `logInfo` / `logError` and friends still write the same entries.
|
|
296
|
+
|
|
241
297
|
#### `authorize` is required, and that is deliberate
|
|
242
298
|
|
|
243
299
|
A callable gets Firebase's token check for free. An HTTP endpoint gets nothing, and an
|
|
@@ -262,14 +318,24 @@ A gate that throws counts as a rejection, not an opening.
|
|
|
262
318
|
before the handler. Raise its limit if you send attachments.
|
|
263
319
|
- **CORS** defaults to `*`, matching `cors: true` on the callable. Pass `allowOrigin` to
|
|
264
320
|
name your origin — a browser cannot send cookies to a wildcard.
|
|
265
|
-
- **
|
|
266
|
-
|
|
267
|
-
|
|
321
|
+
- **No `firebase-functions`? Not needed.** The entry point loads without it. Each entry is
|
|
322
|
+
written as one line of JSON, which Cloud Run's log agent parses into the same Cloud
|
|
323
|
+
Logging fields a function's would.
|
|
324
|
+
- **Trace correlation works**, and needs nothing from you on Cloud Run. The handler reads
|
|
325
|
+
`X-Cloud-Trace-Context` or `traceparent` off the request, and asks the metadata server
|
|
326
|
+
for the project id once, so a request's entries group with Cloud Run's own request log
|
|
327
|
+
in Cloud Logging. Somewhere else — GKE, a VM — set `GOOGLE_CLOUD_PROJECT`; without it
|
|
328
|
+
the trace id is still written, but does not join the platform's request log.
|
|
329
|
+
- **No `firebase-admin`? Also optional.** Storage is only needed for older releases'
|
|
330
|
+
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
|
|
332
|
+
`configureAttachments({ bucket })` — and the service's own credentials are used.
|
|
268
333
|
- **No Storage bucket?** You do not need one. `fsl upload-sourcemaps --embed-sourcemaps`
|
|
269
334
|
without `--bucket` embeds the current release's maps into your deploy and uploads
|
|
270
335
|
nothing. The catch: only the **deployed** release can be symbolicated, because older
|
|
271
336
|
ones live in a bucket there isn't one of. Errors from a previous release come back
|
|
272
|
-
minified.
|
|
337
|
+
minified, and attachments are dropped — the entry is still written. With neither
|
|
338
|
+
`firebase-admin` nor a bucket name, the log says so once at the first lookup.
|
|
273
339
|
- **Response codes:** `204` written, `400` malformed payload, `401` gate refused, `405`
|
|
274
340
|
not a POST, `500` something else. The client treats a non-2xx as a throw.
|
|
275
341
|
|
|
@@ -376,6 +442,12 @@ logger.error(err, { orderId })
|
|
|
376
442
|
| resolved file and line | function | your source maps — `Checkout.tsx:42`, not `app-4f2a.js:1:98432` |
|
|
377
443
|
| trace context | function | request correlation in Cloud Logging |
|
|
378
444
|
|
|
445
|
+
**Repeats are counted, not thrown away.** The usual answer to an error that fires 200 times
|
|
446
|
+
is to send the first few and drop the rest. This one sends 3 in full, counts the other 197,
|
|
447
|
+
and tells you when it started, when it stopped, which release and which user. Close the tab
|
|
448
|
+
and the count waits on the device for the next visit. See
|
|
449
|
+
[Repeats are counted, not dropped](#repeats-are-counted-not-dropped).
|
|
450
|
+
|
|
379
451
|
Backend logs get the same treatment: `withLogging` attaches `functionName`, `userId` from
|
|
380
452
|
the verified `request.auth.uid`, and whatever else you bind.
|
|
381
453
|
|
|
@@ -559,15 +631,33 @@ or different IAM from your source maps — none of which can be arranged with a
|
|
|
559
631
|
Nothing expires them. Add a lifecycle rule on `logAttachments/` to delete after N days, or
|
|
560
632
|
they accumulate for the life of the project.
|
|
561
633
|
|
|
634
|
+
## Big entries
|
|
635
|
+
|
|
636
|
+
Cloud Functions and Cloud Run both cut a stdout/stderr log line at exactly **102,400 bytes**
|
|
637
|
+
(100 KiB) — measured live. Past that the entry does not arrive truncated-but-valid: it arrives
|
|
638
|
+
as broken plain text, with no `severity`, no labels, and nothing for Error Reporting to group.
|
|
639
|
+
|
|
640
|
+
The backend logger watches for this. An entry over 90 KiB is shortened before it is written —
|
|
641
|
+
breadcrumb data first, then other context, then the tail of a long stack (the top frames are
|
|
642
|
+
what Error Reporting groups on, so those survive longest), then long text fields, and label
|
|
643
|
+
values only as a last resort. `severity`, labels, the trace and `serviceContext` are never
|
|
644
|
+
touched.
|
|
645
|
+
|
|
646
|
+
The full, unshortened entry is saved to Cloud Storage as `fsl-overflow.json`, at the same
|
|
647
|
+
`logAttachments/{logId}/` path an ordinary attachment would use, whenever a bucket is
|
|
648
|
+
available — see [Attachments](#attachments). A shortened entry carries `labels.truncated="true"`,
|
|
649
|
+
and `labels.hasAttachments="true"` when the full entry was saved. With no Storage configured,
|
|
650
|
+
the entry is still shortened; the original is lost, and the process warns once, not per entry.
|
|
651
|
+
|
|
562
652
|
## Volume controls
|
|
563
653
|
|
|
564
|
-
|
|
565
|
-
|
|
654
|
+
Several gates decide whether a log is written. All have defaults, and the defaults
|
|
655
|
+
hold things back — so this is worth reading before you conclude something is broken.
|
|
566
656
|
|
|
567
657
|
| Gate | Default | Where |
|
|
568
658
|
|---|---|---|
|
|
569
|
-
|
|
|
570
|
-
|
|
|
659
|
+
| Log budget | **50 logs**, refilling **1 per minute**; the last **20%** for errors only | client, per browser tab |
|
|
660
|
+
| Duplicates | **3** full copies of the same error, then counted and sent as a summary | client, per browser tab |
|
|
571
661
|
| Client severity floor | `WARNING` in production, `DEBUG` in dev | client, `minLogLevel` |
|
|
572
662
|
| Server severity floor | `WARNING` in production, `DEBUG` in the emulator | function, `minSeverity` |
|
|
573
663
|
| Function concurrency | `maxInstances: 1` on `createClientLogFunction` | function |
|
|
@@ -578,21 +668,91 @@ initLogger({
|
|
|
578
668
|
releaseId,
|
|
579
669
|
logFunction,
|
|
580
670
|
minLogLevel: 'INFO',
|
|
581
|
-
rateLimitOptions: {
|
|
671
|
+
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
|
|
675
|
+
duplicateLimit: 3, // full copies of one error before counting starts
|
|
676
|
+
summaryIntervalMinutes: 60,
|
|
677
|
+
summaryMaxAgeDays: 7, // how long an unsent summary waits on the device
|
|
678
|
+
maxPendingSummaries: 50,
|
|
679
|
+
},
|
|
582
680
|
})
|
|
583
681
|
```
|
|
584
682
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
`
|
|
683
|
+
The client's production default comes from `process.env.NODE_ENV`, which Vite replaces at
|
|
684
|
+
build time. A `define: { 'process.env': {} }` in `vite.config` — common, to quiet a library
|
|
685
|
+
that expects Node — replaces the whole object instead, `NODE_ENV` reads as undefined, and
|
|
686
|
+
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
|
|
688
|
+
here whose failure mode is a bill rather than a missing log.
|
|
689
|
+
|
|
690
|
+
### The budget refills
|
|
691
|
+
|
|
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`.
|
|
694
|
+
|
|
695
|
+
So a burst at start-up is fine, and a user who works in the app all afternoon is never
|
|
696
|
+
silenced for long. A bug that logs in a loop is still held to about 60 entries an hour per
|
|
697
|
+
user: 1,000 users stuck in such a loop for a working month stays around the 50 GiB of
|
|
698
|
+
Cloud Logging that each project gets free.
|
|
699
|
+
|
|
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.
|
|
702
|
+
|
|
703
|
+
### Repeats are counted, not dropped
|
|
704
|
+
|
|
705
|
+
Two errors are the same when the **message and the screen both match**, so the same error
|
|
706
|
+
on two different screens is kept apart. The first 3 are sent in full, with their stack and
|
|
707
|
+
breadcrumbs. After that the browser only counts them, and sends one summary:
|
|
708
|
+
|
|
709
|
+
```
|
|
710
|
+
WARNING Repeated 197 more times: cart sync failed
|
|
711
|
+
labels.repeatOf="<repeatKey of the first copy>" labels.repeatCount="197"
|
|
712
|
+
labels.firstSeen="…" labels.lastSeen="…"
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
The same count and window are in the entry's body, under `context.repeat`, so a summary is
|
|
716
|
+
filed as JSON like every other entry and a search on `jsonPayload.message` finds it.
|
|
717
|
+
|
|
718
|
+
The browser never learns the server's `logId` for the first copy — `writeLog` assigns it
|
|
719
|
+
after the log has already left the tab. So each full copy instead carries its own
|
|
720
|
+
`labels.repeatKey`, a client-side id, and the summary's `repeatOf` points at that instead:
|
|
721
|
+
query `labels.repeatKey="…" OR labels.repeatOf="…"` to find a full copy and its summary
|
|
722
|
+
together.
|
|
723
|
+
|
|
724
|
+
A summary is sent once an hour, when the tab is hidden, and once at startup for anything
|
|
725
|
+
a previous visit left queued. It is keyed by the error, the `releaseId` and the `userId`,
|
|
726
|
+
so two releases or two people on one computer are never counted together. Its entry is
|
|
727
|
+
timestamped at `lastSeen`, not at the time it was sent.
|
|
728
|
+
|
|
729
|
+
The summary's own labels are `appId`, `releaseId`, `userId`, `screen` and `errorType` — from
|
|
730
|
+
the time the errors happened, not from whoever's tab eventually sends it — plus
|
|
731
|
+
`repeatOf`/`repeatCount`/`firstSeen`/`lastSeen`/`sentLate`. Everything else about the error —
|
|
732
|
+
browser, platform, breadcrumbs, the stack — is not repeated here; it is already on the full
|
|
733
|
+
copies `repeatOf` points back to.
|
|
734
|
+
|
|
735
|
+
Unsent summaries are kept in `localStorage`, so closing the tab does not lose them: the
|
|
736
|
+
next visit sends them, marked `labels.sentLate="true"`. A summary is only removed from that
|
|
737
|
+
queue once it has actually been sent — a failed attempt (offline, or the tab closing right
|
|
738
|
+
after it was queued) leaves it for the next flush or the next visit to retry, never twice.
|
|
739
|
+
At most 50 wait, and any older than 7 days are deleted rather than sent — they are error
|
|
740
|
+
messages, and may hold personal data.
|
|
741
|
+
|
|
742
|
+
A summary is a `WARNING` with no stack, so Cloud Error Reporting sees the 3 full copies and
|
|
743
|
+
not the summary. For the true count, add up `labels.repeatCount` in Cloud Logging.
|
|
744
|
+
|
|
745
|
+
Summaries do not spend the log budget. There is at most one per error, per release, per user,
|
|
746
|
+
per hour.
|
|
588
747
|
|
|
589
748
|
### What a dropped log looks like
|
|
590
749
|
|
|
591
750
|
The two rate limits say so in the browser console:
|
|
592
751
|
|
|
593
752
|
```
|
|
594
|
-
[fsl] Duplicate
|
|
595
|
-
[fsl]
|
|
753
|
+
[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
|
|
596
756
|
```
|
|
597
757
|
|
|
598
758
|
**The severity floors are silent.** Both of them — the client's `minLogLevel` and the
|
|
@@ -627,6 +787,9 @@ Narrow it when you need to:
|
|
|
627
787
|
| `labels.screen="checkout"` | one screen |
|
|
628
788
|
| `labels.feedback="true"` | user-reported issues |
|
|
629
789
|
| `labels.hasAttachments="true"` | entries with files in GCS |
|
|
790
|
+
| `labels.truncated="true"` | entries shortened to fit — the full copy is `fsl-overflow.json` |
|
|
791
|
+
| `labels.repeatKey="<key>" OR labels.repeatOf="<key>"` | one repeating error: its full copies and its summaries |
|
|
792
|
+
| `labels.sentLate="true"` | repeat summaries sent on a later visit |
|
|
630
793
|
|
|
631
794
|
Locally, the emulator's JSONL answers the same questions. Point
|
|
632
795
|
**[firebase-mcp-server](https://github.com/dasasian/firebase-mcp-server)** at either and ask
|
|
@@ -762,6 +925,9 @@ Some of what is already handled here, all of it learned the expensive way:
|
|
|
762
925
|
disagree over a typo.
|
|
763
926
|
- **Checking a rate limit and spending it as two calls double-counts**, quietly making a
|
|
764
927
|
configured budget of 50 a budget of 25.
|
|
928
|
+
- **A fixed budget goes silent in a long session.** 50 logs per page load is spent by
|
|
929
|
+
mid-morning in an app someone keeps open all day, and the crash at lunch is never sent.
|
|
930
|
+
A budget that refills, with a share kept for errors, does not have that shape.
|
|
765
931
|
- **An old stack naming a bundle that still exists** resolves against the current release's
|
|
766
932
|
map, giving line numbers that are confidently wrong — worse than none, because nothing
|
|
767
933
|
signals it.
|
package/dist/client/logger.d.ts
CHANGED
|
@@ -46,6 +46,22 @@ export declare class Logger<AppLabels extends Record<string, string | undefined>
|
|
|
46
46
|
*/
|
|
47
47
|
sendFeedback(text: string, extras?: FeedbackOptions<AppLabels>): void;
|
|
48
48
|
private send;
|
|
49
|
+
private readonly summariesInFlight;
|
|
50
|
+
/** Send whatever repeat summaries are due, from this visit or an earlier one. */
|
|
51
|
+
sendPendingSummaries(): void;
|
|
52
|
+
/**
|
|
53
|
+
* A repeat summary: a WARNING with no stack, timestamped at `lastSeen` (see
|
|
54
|
+
* README, "Repeats are counted, not dropped"). It still respects the
|
|
55
|
+
* severity floor — only the budget and the duplicate gate are skipped, via
|
|
56
|
+
* `send`'s `skipBudget`.
|
|
57
|
+
*
|
|
58
|
+
* It is queued in `localStorage` precisely so a failed send does not lose
|
|
59
|
+
* it — offline, or the tab tearing down right after the `visibilitychange`
|
|
60
|
+
* that triggered this. So it is only acknowledged (removed from the queue)
|
|
61
|
+
* once `send` reports the entry actually reached `logFunction`; otherwise
|
|
62
|
+
* the next flush, or the next visit, finds it still there and retries it.
|
|
63
|
+
*/
|
|
64
|
+
private sendRepeatSummary;
|
|
49
65
|
}
|
|
50
66
|
/**
|
|
51
67
|
* Send user feedback through the configured logger.
|
|
@@ -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;AAWxF,OAAO,
|
|
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
|
@@ -9,9 +9,23 @@ const severity_1 = require("../shared/severity");
|
|
|
9
9
|
const error_1 = require("../shared/error");
|
|
10
10
|
const breadcrumbs_1 = require("./breadcrumbs");
|
|
11
11
|
const rateLimiter_1 = require("./rateLimiter");
|
|
12
|
+
/**
|
|
13
|
+
* No `typeof process` guard, on purpose. Every bundler folds `process.env.NODE_ENV` to a
|
|
14
|
+
* string literal at build time, so the comparison is safe in a browser — but `process`
|
|
15
|
+
* itself does not exist there, and guarding on it meant the folded branch was never
|
|
16
|
+
* reached: every browser build defaulted to DEBUG in production, and the README's
|
|
17
|
+
* "WARNING in production" was true only under Node. Seen in a real Vite bundle as
|
|
18
|
+
* `typeof process<"u"?"WARNING":"DEBUG"`. A runtime with neither the fold nor
|
|
19
|
+
* `process` throws on the read, and that is the case the catch is for.
|
|
20
|
+
*/
|
|
12
21
|
function defaultMinLevel() {
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
try {
|
|
23
|
+
if (process.env.NODE_ENV === 'production')
|
|
24
|
+
return 'WARNING';
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Not Node and not folded: development.
|
|
28
|
+
}
|
|
15
29
|
return 'DEBUG';
|
|
16
30
|
}
|
|
17
31
|
// Order matters — first match wins.
|
|
@@ -122,33 +136,51 @@ class Logger {
|
|
|
122
136
|
// same pipe — it is rare by nature and there is nothing to throttle. The
|
|
123
137
|
// exemption keys on the record being feedback, not on its severity, so a
|
|
124
138
|
// NOTICE emitted for anything else still behaves normally.
|
|
125
|
-
bypassVolumeControls = false
|
|
139
|
+
bypassVolumeControls = false,
|
|
140
|
+
// A repeat summary spends no budget and is never itself a duplicate — but
|
|
141
|
+
// it still respects the severity floor, unlike feedback above.
|
|
142
|
+
skipBudget = false, timestamp) {
|
|
126
143
|
if (!bypassVolumeControls && severity_1.SEVERITY_ORDER[severity] > this.minLevel)
|
|
127
|
-
return;
|
|
144
|
+
return false;
|
|
145
|
+
const allLabels = {
|
|
146
|
+
appId: this.config.appId,
|
|
147
|
+
releaseId: this.config.releaseId,
|
|
148
|
+
screen: (0, breadcrumbs_1.getCurrentScreen)(),
|
|
149
|
+
userId: this.userId,
|
|
150
|
+
platform: PLATFORM,
|
|
151
|
+
browser: BROWSER,
|
|
152
|
+
...this.userLabels,
|
|
153
|
+
...labels,
|
|
154
|
+
};
|
|
128
155
|
// One gate for every severity. Passing a signature opts this log into
|
|
129
156
|
// duplicate suppression; the check and the budget spend are one operation,
|
|
130
157
|
// so a log can neither be counted twice nor checked without being counted.
|
|
131
|
-
|
|
158
|
+
// Once a signature passes `duplicateLimit`, further occurrences are
|
|
159
|
+
// "counted", not refused outright — the rate limiter keeps a running
|
|
160
|
+
// count toward the next repeat summary.
|
|
161
|
+
const decision = bypassVolumeControls || skipBudget
|
|
162
|
+
? { allowed: true }
|
|
163
|
+
: (0, rateLimiter_1.allow)({ severity, signature, labels: allLabels });
|
|
132
164
|
if (!decision.allowed) {
|
|
133
165
|
if (decision.reason === 'duplicate') {
|
|
134
|
-
console.warn(`[fsl] Duplicate
|
|
166
|
+
console.warn(`[fsl] Duplicate counted for the next summary: ${(0, rateLimiter_1.describeSignature)(decision.signature ?? '')}`);
|
|
167
|
+
}
|
|
168
|
+
else if (decision.reason === 'reserve') {
|
|
169
|
+
console.warn('[fsl] Log budget: only errors can use the reserve now');
|
|
135
170
|
}
|
|
136
171
|
else {
|
|
137
|
-
console.warn('[fsl]
|
|
172
|
+
console.warn('[fsl] Log budget empty — next log in about a minute');
|
|
138
173
|
}
|
|
139
|
-
return;
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
// Ties this full copy to the summary its repeats will eventually become.
|
|
177
|
+
// The client has no server logId to put in the summary's `repeatOf` — see
|
|
178
|
+
// README, "Repeats are counted, not dropped" — so this client-side id is
|
|
179
|
+
// the join key instead, carried by every full copy of the signature.
|
|
180
|
+
if ('repeatKey' in decision && decision.repeatKey) {
|
|
181
|
+
allLabels.repeatKey = decision.repeatKey;
|
|
140
182
|
}
|
|
141
183
|
try {
|
|
142
|
-
const allLabels = {
|
|
143
|
-
appId: this.config.appId,
|
|
144
|
-
releaseId: this.config.releaseId,
|
|
145
|
-
screen: (0, breadcrumbs_1.getCurrentScreen)(),
|
|
146
|
-
userId: this.userId,
|
|
147
|
-
platform: PLATFORM,
|
|
148
|
-
browser: BROWSER,
|
|
149
|
-
...this.userLabels,
|
|
150
|
-
...labels,
|
|
151
|
-
};
|
|
152
184
|
// Each attachment is converted in its own try. A Blob or File can fail to
|
|
153
185
|
// read — a user picks a file from <input type="file">, moves or deletes it,
|
|
154
186
|
// then submits, and the browser raises NotReadableError. Previously one
|
|
@@ -186,13 +218,65 @@ class Logger {
|
|
|
186
218
|
error,
|
|
187
219
|
},
|
|
188
220
|
...(base64Attachments ? { attachments: base64Attachments } : {}),
|
|
221
|
+
...(timestamp ? { timestamp } : {}),
|
|
189
222
|
};
|
|
190
223
|
await this.config.logFunction(payload);
|
|
224
|
+
return true;
|
|
191
225
|
}
|
|
192
226
|
catch (err) {
|
|
193
227
|
console.error('[fsl] Failed to send log:', err instanceof Error ? err.message : err);
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Guards against sending the same queued summary twice when a flush is
|
|
232
|
+
// triggered again (interval, visibilitychange) before a previous attempt's
|
|
233
|
+
// `logFunction` call has resolved — `peekPendingSummaries` does not remove
|
|
234
|
+
// anything, so without this a slow or slow-to-fail send could be picked up
|
|
235
|
+
// more than once.
|
|
236
|
+
summariesInFlight = new Set();
|
|
237
|
+
/** Send whatever repeat summaries are due, from this visit or an earlier one. */
|
|
238
|
+
sendPendingSummaries() {
|
|
239
|
+
for (const summary of (0, rateLimiter_1.peekPendingSummaries)()) {
|
|
240
|
+
if (this.summariesInFlight.has(summary.id))
|
|
241
|
+
continue;
|
|
242
|
+
this.summariesInFlight.add(summary.id);
|
|
243
|
+
void this.sendRepeatSummary(summary).finally(() => this.summariesInFlight.delete(summary.id));
|
|
194
244
|
}
|
|
195
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* A repeat summary: a WARNING with no stack, timestamped at `lastSeen` (see
|
|
248
|
+
* README, "Repeats are counted, not dropped"). It still respects the
|
|
249
|
+
* severity floor — only the budget and the duplicate gate are skipped, via
|
|
250
|
+
* `send`'s `skipBudget`.
|
|
251
|
+
*
|
|
252
|
+
* It is queued in `localStorage` precisely so a failed send does not lose
|
|
253
|
+
* it — offline, or the tab tearing down right after the `visibilitychange`
|
|
254
|
+
* that triggered this. So it is only acknowledged (removed from the queue)
|
|
255
|
+
* once `send` reports the entry actually reached `logFunction`; otherwise
|
|
256
|
+
* the next flush, or the next visit, finds it still there and retries it.
|
|
257
|
+
*/
|
|
258
|
+
async sendRepeatSummary(summary) {
|
|
259
|
+
const labels = {
|
|
260
|
+
appId: this.config.appId,
|
|
261
|
+
releaseId: summary.releaseId ?? this.config.releaseId,
|
|
262
|
+
userId: summary.userId,
|
|
263
|
+
screen: summary.screen,
|
|
264
|
+
errorType: summary.errorType,
|
|
265
|
+
repeatOf: summary.repeatOf,
|
|
266
|
+
repeatCount: String(summary.repeatCount),
|
|
267
|
+
firstSeen: summary.firstSeen,
|
|
268
|
+
lastSeen: summary.lastSeen,
|
|
269
|
+
...(summary.sentLate ? { sentLate: 'true' } : {}),
|
|
270
|
+
};
|
|
271
|
+
const sent = await this.send(`Repeated ${summary.repeatCount} more times: ${summary.message}`, 'WARNING', labels,
|
|
272
|
+
// A body besides the message. With only `message` left once the timestamp
|
|
273
|
+
// is promoted, Cloud Logging files the entry as textPayload, and a search on
|
|
274
|
+
// jsonPayload.message — /query-logs, or anyone's saved query — misses every
|
|
275
|
+
// summary. The smoke run found it.
|
|
276
|
+
{ repeat: { count: summary.repeatCount, firstSeen: summary.firstSeen, lastSeen: summary.lastSeen } }, undefined, undefined, undefined, false, true, summary.lastSeen);
|
|
277
|
+
if (sent)
|
|
278
|
+
(0, rateLimiter_1.acknowledgeSummary)(summary.id);
|
|
279
|
+
}
|
|
196
280
|
}
|
|
197
281
|
exports.Logger = Logger;
|
|
198
282
|
/**
|
|
@@ -229,6 +313,13 @@ function triggerTestLog() {
|
|
|
229
313
|
let instance = null;
|
|
230
314
|
function initLogger(config) {
|
|
231
315
|
instance = new Logger(config);
|
|
316
|
+
// A previous visit's queued summaries (README, "the next visit sends
|
|
317
|
+
// them") would otherwise wait for the first `visibilitychange: hidden` or
|
|
318
|
+
// the first 60s interval tick — a short delay so init itself can finish
|
|
319
|
+
// first, not a wait for either of those.
|
|
320
|
+
if (typeof setTimeout !== 'undefined') {
|
|
321
|
+
setTimeout(() => instance?.sendPendingSummaries(), 0);
|
|
322
|
+
}
|
|
232
323
|
return instance;
|
|
233
324
|
}
|
|
234
325
|
function getClientLogger() {
|
|
@@ -236,4 +327,32 @@ function getClientLogger() {
|
|
|
236
327
|
throw new Error('[fsl] initLogger() not called');
|
|
237
328
|
return instance;
|
|
238
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* Repeat summaries are sent hourly and when the tab is hidden (README,
|
|
332
|
+
* "Repeats are counted, not dropped"). Neither is triggered by an ordinary
|
|
333
|
+
* log call — `send` does not check for due summaries itself, since doing so
|
|
334
|
+
* with frozen-for-testing time reliably makes an interval-based check look
|
|
335
|
+
* due immediately (see tests/rateLimiter.ts). So there are exactly three
|
|
336
|
+
* triggers for a flush: `initLogger` (a previous visit's queue, once, above),
|
|
337
|
+
* this `visibilitychange` listener (the tab going hidden), and the interval
|
|
338
|
+
* below (an app that never hides its tab and rarely reloads).
|
|
339
|
+
*
|
|
340
|
+
* Neither exists in the esbuild bundle test's sandbox (no `window`/`document`
|
|
341
|
+
* there — see tests/browserBundle.ts), so both are guarded.
|
|
342
|
+
*/
|
|
343
|
+
if (typeof document !== 'undefined') {
|
|
344
|
+
document.addEventListener('visibilitychange', () => {
|
|
345
|
+
if (document.visibilityState !== 'hidden')
|
|
346
|
+
return;
|
|
347
|
+
(0, rateLimiter_1.flushDueSummaries)(true);
|
|
348
|
+
instance?.sendPendingSummaries();
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
if (typeof window !== 'undefined') {
|
|
352
|
+
const interval = setInterval(() => {
|
|
353
|
+
(0, rateLimiter_1.flushDueSummaries)();
|
|
354
|
+
instance?.sendPendingSummaries();
|
|
355
|
+
}, 60_000);
|
|
356
|
+
interval.unref?.();
|
|
357
|
+
}
|
|
239
358
|
//# sourceMappingURL=logger.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/client/logger.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/client/logger.ts"],"names":[],"mappings":"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"}
|