Skip to content

Releases

1.5.0

Choose a tag to compare

@damith-dasasian damith-dasasian released this 14 Oct 15:13

Added

  • fsl logs --all reads every line of the service. By default a cloud query now reads only the entries fsl wrote (see Changed); --all leaves that out, for a failure where fsl wrote nothing: out of memory, a crash at startup. It cannot be combined with --local.
  • fsl logs and fsl logs attachments sign in with a key file. Name it in GOOGLE_APPLICATION_CREDENTIALS (a .env.local is loaded). fsl hands it to its own gcloud process as CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE and leaves the person's gcloud login alone. A machine that already sets GOOGLE_APPLICATION_CREDENTIALS for another purpose now has fsl logs use that key. With a key file named, nothing falls back to the personal login; a permission failure names the variable and the role (roles/logging.viewer, or read access to the bucket for attachments), and a sign-in failure with no key file names the key-file way.
  • fsl doctor reports where it found the backend. --json has one new field, setup.backendFrom: "flag", "firebase.json", "search" or null. The Setup line adds "(found by search)" to a backend that the search found, so a person can see the deduction and correct it with --backend.
  • enableActions() records marked user actions. From @dasasian/firebase-structured-logger/client/actions, an element marked data-fsl-action="apply_discount" becomes an action breadcrumb on click, submit (<form>) or change (<select>, <input>, <textarea>), with no code per handler, so it also tells repeats apart by path.

Changed

  • Every deprecated name has its own Mistakes line in CAPABILITIES.md, with its replacement, and a test fails when one has none. ClientLogRequest had no line. Six renamed options shared one line in one section, and the lines for bc.nav and setScreen named no replacement. Each name now has a line in the section of the code that takes it, with the replacement the run-time warning names, so /fsl-review finds it where it reads. The test takes the names from the list the deprecation tests use.
  • The backend writes the local log file in development on any server, and never in a test run. An app that passes logLocalDir to the /functions initLogger and runs off Cloud Functions, started with tsx watch or node on a developer's machine, gets dev.jsonl there, and fsl logs --local reads it. The file is written when K_SERVICE is unset and NODE_ENV is neither production nor test: Jest and Vitest set NODE_ENV=test, so a test run writes no file and makes no folder. A test runner that sets no NODE_ENV (Mocha, node --test) needs NODE_ENV=test in the test script. What the process prints does not change. A live server off Google Cloud that never sets NODE_ENV=production writes the file too, bounded by the record and rotation limits. Nothing changes for an app on Cloud Functions or in the emulator.
  • fsl logs reads the entries fsl wrote. The Cloud Logging filter asks for entries that carry the appId label, for the query, schema and --repeats, so a service's own request lines no longer fill the 5000-entry scan. Pass --all to read every line. A cloud query that finds nothing says on stderr that --all also reads lines fsl did not write.
  • A word fsl logs does not know is an error. fsl logs --local schema used to run a plain query and print "0 entries matched"; it now exits 1 and shows fsl logs schema --local. Any other stray word exits 1 and names the two sub-commands. schema and attachments refuse a stray word too.
  • A key file that is not there is an error in fsl upload-sourcemaps. FIREBASE_SERVICE_ACCOUNT_PATH naming a missing file used to upload with the personal application-default login without a word. It now exits 1, names the variable and the path, and sends nothing. GOOGLE_APPLICATION_CREDENTIALS is read the same way.
  • fsl doctor finds the backend by flag, then the functions key of firebase.json, then a search. The search looks at the project root and each folder one level down whose package.json lists this package, drops a folder that holds a web build, and answers only when the web build is known and one folder is left. With none left the backend is not checked; with several, or with one and no known web build, doctor reports could-not-check naming the folders and the flags to pass. --backend and --dist now work next to a firebase.json. A project whose firebase.json has a functions key gets the same report as before, plus backendFrom.
  • /fsl-review and /fsl-logs run the fsl of the installed package by its path. npx fsl in a folder with no install reaches for another author's package named fsl. /fsl-review looks one level down when the project root has no install.
  • createClientLogFunction() and createClientLogHandler() take no argument when every default fits. The config was a required parameter, so the shortest correct call was createClientLogFunction({}). A call with a config works as before.
  • The docs moved to https://fsl.dasasian.com/. The README is now a front page with a quick start and a map of the docs, and the npm homepage points at the site.
  • withLogging logs what the handler throws, then throws it again. After the upgrade an app gets new entries: every throw out of a wrapped handler is logged with the request's functionName, userId and labels, and the caller, the schedule or the task queue gets the same value as before. A handler needs no try/catch only to log; an error already given to logError is not logged twice. A plain Error or an error with a status of 500 or more is an ERROR; an HttpsError with a 4xx code is a WARNING with its code and status and no stack. A plain Error from a callable now shows as two ERROR entries: fsl's, with the labels, and Firebase's own Unhandled error.
  • Repeats are told apart by the user's last actions on the screen. An error's repeat signature now includes the names of the last 3 bc.action breadcrumbs since the last navigation, so the same error reached by two different paths on one screen gets its own full copies and its own summary. An app with no action breadcrumbs gets the signature it always had.
  • /fsl-review proposes by user flow when a capability fits many places, and asks which flows matter. Past about ten places it groups them by what the user is trying to do, gives the count for each flow, names the flow it would do first, and ends with one question. CAPABILITIES.md says which controls to mark first: payment, sign-in and saving work. The review still proposes and stops.
  • /fsl-review lists what the code has before it decides, says what it read, and reads comments before it proposes a removal. The review lists each component file and each block rendered on a condition, then decides for each which capability covers it, or none; the words in "Fits when" are no longer the limit of the search. The proposal says in one line how many files were read and the kinds of place looked for. When the code says why a line is there, the proposal gives both sides and does not call its removal a fix.
  • CAPABILITIES.md knows more places for screens and views. Pages without a URL change fit a Vue v-if / v-else-if / v-else chain and a React tree swapped by early returns on auth or gate state. Views fit role="menu", role="alertdialog", <Teleport, createPortal( and hand-made overlays; the section says to mark dialogs, overlays and the layout mode first, and warns against marking regions that are always visible together.
  • The browser logger adds an error breadcrumb for every error that reaches it. Its name is the entry's errorType and it has no data. It is added whether the rate limiter sends the error, counts it as a duplicate or refuses it, and not again while the newest breadcrumb is an error with the same name. The trail of every later entry, feedback included, now shows the errors that reached the logger before it, with no code in the app; the entry's own trail does not hold its own. A repeat summary, feedback and entries with no error add none. After the upgrade, trails hold error breadcrumbs the app did not add, and an app that added its own bc.handledError( next to logger.error( gets two until it removes its line.

Deprecated

  • FIREBASE_SERVICE_ACCOUNT_PATH. Use GOOGLE_APPLICATION_CREDENTIALS, with the same path. The old name still works, warns once, wins when both are set, and is removed in 2.0.

Removed

  • sendPendingSummaries() is gone from the client logger's type. It was never documented, and a call from an app sent nothing useful. fsl sends repeat summaries itself: once after initLogger, hourly, and when the tab is hidden. No code change is needed in an app.

Fixed

  • The rate limiter limits where browser storage fails. With no sessionStorage or localStorage (a private window, a sandboxed iframe, an app's tests in Node with no DOM), or storage that throws on a read or a write (a full quota), each log started from a fresh budget: the same error twenty times sent twenty entries, where a working browser sends three and counts the rest. From the first read or write that fails, that store's state (the budget and duplicate counts, or the summary queue) now lives in memory for the rest of the page load; a reload loses it. A working storage is used as before, and resetSession() clears the memory state too.
  • A flag's value comes after = or after a space in fsl doctor and fsl upload-sourcemaps. fsl doctor --backend backend used to read the folder as true; --backend, --dist, --functions, --bucket, --prefix and --release now take the next word, and a value flag with no value exits 1 and shows the correct form. --json, --strict, --force, --global and --embed-sourcemaps stay switches and never take the next word.
  • **A cut fsl logs sca...
Read more

1.4.0

Choose a tag to compare

@damith-dasasian damith-dasasian released this 08 Oct 21:13

Tools for agents. fsl logs lets a coding agent read production logs with flags named
after SQL clauses, one JSON entry per line, and an error that names the valid fields when
it guesses one. /fsl-review reads CAPABILITIES.md from the installed package and
proposes how fsl could serve the code in front of it. Errors a framework catches are now
logged where it catches them, so a React boundary or a router no longer hides them. And
withLogging wraps scheduled and task handlers too. A production app ran each release
candidate before this release.

Added

  • withLogging wraps onSchedule and onTaskDispatched as well as onCall: pass ScheduledEvent or Request<Data> as the second type argument. userId comes from auth.uid when the event has one. CAPABILITIES.md no longer lists onRequest(, which withLogging cannot wrap.
  • fsl logs reads production logs through gcloud logging read, or .fsl-logs/ with --local, with flags named after SQL clauses (--where, --select, --group-by, --order-by, --limit, --distinct, --since, --repeats). One JSON entry per line; a field or flag it does not know is an error that names the valid ones.
  • fsl logs schema lists every label in the logs with counts and sample values (none for userId or keys containing email, name, phone), and keeps labels your code writes with --add. fsl logs attachments <logId> downloads an entry's files.
  • The logs folder ignores itself in git. Every folder fsl creates for logs (logLocalDir, .fsl-logs/) gets a .gitignore of * unless one exists, so the README's .gitignore step is gone. fsl logs now says on stderr when 0 entries matched, and fsl logs schema keeps samples for fsl's own functionName.
  • /fsl-logs skill for coding agents, installed by fsl install-skills.
  • /fsl-review [scope] skill and CAPABILITIES.md. The skill reads CAPABILITIES.md from the installed package and the code in the scope (the app, a folder or a file), and proposes how fsl could best serve that code. It proposes and stops. CAPABILITIES.md ships in the package: for each capability, what it gives, the code signals it fits, how to add it, and the mistakes to avoid.
  • fsl install-skills writes fsl-version (the package version) into each skill's frontmatter, and asks before removing an installed logs or query-logs; --force answers yes.
  • fsl doctor finding skill-out-of-date (warning): a skill's fsl-version differs from the fsl running doctor. Skills without fsl-version are skipped.
  • BaseLabels declares every label fsl writes, each with a one-line comment: repeatKey, repeatOf, repeatCount, firstSeen, lastSeen, sentLate, truncated, hasAttachments, logId, functionName, trace, run, slow, step, feedback.
  • Errors a framework catches are logged where it catches them. createRoot(el, { onCaughtError: handleReactError }) and app.config.errorHandler = handleVueError (new) send React and Vue render errors, handleReactError now accepts what React 19 passes, and the React Router and Vue Router adapters log their router's errors on their own (errorType RouteError: a loader or action error or 5xx response as ERROR, a 4xx response as WARNING; a guard that throws or a lazy route that fails to load as ERROR). The function an adapter returns stops its error listener too.
  • /testing capture.settled() waits for every send already started, so a test can read entries after a log call with attachments or after a repeat summary; log methods still return void.

Fixed

  • fsl doctor maps-published is an error only when nothing removes the maps before a deploy: no fsl upload-sourcemaps in a package.json script or hosting predeploy, and no **/*.map in hosting ignore. A plain npm run build no longer fails doctor.
  • fsl --help, -h and help, and --help or -h after any command, print the usage and exit 0 instead of running the command (install-skills --help ran the install). fsl install-skills with no terminal and no --force no longer answers N for you: it asks nothing, installs what needs no answer, names on stderr what it left and the --force that does it, and exits 1.
  • /fsl-review and CAPABILITIES.md no longer flag a data-fsl-view value from a fixed set of code names, no longer raise maxInstances from code alone, and name both versions when a whole-app review finds two installs that differ.

Removed

  • /query-logs skill, which depended on firebase-mcp-server. fsl install-skills offers to remove an installed copy.
  • /logs skill, replaced by /fsl-review: it carried facts in its own copy, which went stale. fsl install-skills offers to remove an installed copy.

v1.3.0

Choose a tag to compare

@damith-dasasian damith-dasasian released this 07 Oct 23:42

Where the user is, told by the router and the page. A router adapter takes route and
screen from React Router or Vue Router's own patterns and names, so a redirect on first
load is one breadcrumb and a :param route is one page. data-fsl-view marks name the
tabs, steps and dialogs on screen, and every entry carries them as a view label. And
/testing lets an app assert what it logs, with the real entries the logger sends.

Added

  • labelsFor and defaultLabelsFor on /client/navigation: one function names a page's route, screen and path, and its answer is used exactly as returned. It replaces routeFor, cleanPath and path: false.
  • navigatedTo(screen, { route?, path? }) for apps whose screens change without the URL changing — the same breadcrumb and labels as automatic navigation.
  • bc.handledError — the clearer name for an error your code handled and did not log.
  • /testing: captureEntries() keeps every entry the logger sends, for your own tests, and resetSession() starts each test with an empty trail and a full budget.
  • fsl doctor warns logs-inside-functions-source when local log files sit inside the Functions source folder.
  • Views: enableViews() from /client/views, and data-fsl-view="name" in your markup. Every browser entry carries a view label naming the marked tabs, steps and dialogs visible when it was written, joined with ›.
  • Router adapters: enableVueRouterNavigation(router) from /client/navigation/vue-router and enableReactRouterNavigation(router) from /client/navigation/react-router. They take route and screen from the router's own patterns and names instead of guessing from the path. One option, adjust, edits the labels. Neither router is a dependency.

Changed

  • One structured nav breadcrumb per page change: name is the screen (or the route), data holds route and path. In 1.1–1.2 the name was the path. With navigation on, bc.nav and setScreen no longer add a second breadcrumb for the same change.
  • With navigation on, the screen label comes from navigation, not setScreen. An app that called both now sees navigation's screen on every entry, and repeat counting groups errors by that screen too.

Fixed

  • React Router: a redirect on first load records one breadcrumb, for the page it ends on. It recorded the starting page too.
  • The README's local log folder is now .fsl-logs/ at the repo root. Inside functions/, every entry restarted the emulator, which rotated the file and deleted entries within seconds.

Deprecated

  • enableNavigation({ routeFor }), ({ cleanPath }), ({ path: false }) → labelsFor; the routeSource label; bc.nav and setScreen → navigation or navigatedTo; bc.error → bc.handledError. Each warns once where code can, and is removed in 2.0 (#56).

v1.2.0

Choose a tag to compare

@damith-dasasian damith-dasasian released this 05 Oct 00:20

Timing traces. Some failures never throw: a loading screen waits on eight pieces, one of
them takes thirty seconds, and the logs say nothing. A trace reports exactly that — a
step or a whole flow that ran past its limit, or never finished — and nothing when it
ran in time. Your code names the steps; the limits live in one setup call.

Added

  • Timing traces — trace(name, fn) / startTrace(name) with .step(), from /client/timing and /functions, and one configureTraces({...}) holding every limit. A run that crosses a limit sends one WARNING, while it is still running, with the steps done and still waiting; a run in time sends nothing. Browser runs that were hidden or paused (a laptop sleeping, a frozen tab) are not judged. Steps are standard performance.measure entries underneath.

Fixed

  • npm publish no longer rewrites bin. It was declared as ./dist/tools/index.js, which npm normalised with a warning on every publish.

v1.1.0

Choose a tag to compare

@damith-dasasian damith-dasasian released this 04 Oct 22:43

Navigation, automatically. One import, and every route change in a single-page app
becomes a breadcrumb, and every entry says which route and which real page it came
from. It is its own entry point, so an app that does not import it ships none of it —
the first of the optional helpers built that way.

Added

  • Navigation, automatically — enableNavigation() from the new /client/navigation entry point records every route change as a breadcrumb and labels each entry with route (the pattern, /orders/:id/items), path (the real path) and routeSource. screen keeps its meaning and falls back to route when unset. An app that does not import it ships none of it; the query string and non-route fragments never leave the browser; routeFor, cleanPath and path: false for apps that need them.

v1.0.0

Choose a tag to compare

@damith-dasasian damith-dasasian released this 28 Sep 06:16

The API is stable. From here, a breaking change means 2.0. Before freezing it, every
public name was reviewed and eight were renamed; the old ones keep working through 1.x
with a one-time warning, so upgrading breaks nothing. New in 1.0 is fsl doctor, which
reads a setup off disk and says what will go wrong before it does. Also here: warnings
can no longer spend the error reserve, one copy of Storage whatever firebase-admin a
project has, TypeScript 6, and Node 22 and 24 tested — Node 20 reached end of life.

Added

  • fsl doctor checks the setup from disk — no network, no credentials — and prints how it will behave plus any findings, as text or --json. It reports only facts it can read from files, never guesses from source code. Errors fail with exit code 1, --strict fails on warnings too, and a check it could not run always fails. --backend/--dist point it at a setup with no firebase.json.

Fixed

  • Warnings could spend the error reserve. The log limit recharges continuously, so its count is almost never a whole number, and the reserve check available <= reserve let a warning through at, say, 1.02 with a reserve of 1 — spending the room kept for the next crash. It now asks whether spending one log would dip into the reserve. Found as a CI-only test flake: on a fast machine the calls share one millisecond and the count stays whole.

Changed

  • Node 20 is no longer tested. It reached end of life on 2026-04-30. engines already required Node 22, so nothing changes for a supported install; CI now runs Node 22 and 24, the current LTS.
  • @google-cloud/storage is accepted as ^7.19.0 || ^8.1.0 (was ^7.0.0). firebase-admin 14.5 brings Storage 8 and 13.x brings 7; with the old range a project on the newest firebase-admin installed two copies. Now npm reuses the one already there. Both majors are exercised by npm run smoke:install, which CI now runs.
  • Built with TypeScript 6 (was 5.9), with module/moduleResolution: node16. The published JavaScript is byte-identical and the .d.ts unchanged; a consumer on TypeScript 5.9, or resolving with node16 or bundler, compiles against it as before.

Deprecated

  • Eight names, renamed before the 1.0 API freeze. minLogLevel → minSeverity (the server already used it, and so does Cloud Logging); bucketName → bucket (every other bucket option already was); rateLimitOptions.sessionLimit → burstLimit, refillPerMinute → rechargeSecondsPerLog, and errorReserve (a share) → reservedForErrors (a count), because the limit now recharges one log at a time rather than lasting a session; type ClientLogRequest → LogRequest; triggerTestLog() → sendTestLog(); the CLI flag fsl upload-sourcemaps --functions → --backend. Every old name still works in 1.x with a one-time console warning, and is removed in 2.0. The README's Reference has the full table.

v0.9.0

Choose a tag to compare

@damith-dasasian damith-dasasian released this 28 Sep 00:18

The logger no longer goes quiet when things get bad. A long session used to spend its
50-log budget by mid-morning and send nothing after, a flood of one error hid everything
behind it, and an entry past 100 KiB arrived as broken text. Now the budget refills and
keeps a fifth for errors, repeats are counted and sent as a summary — on the next visit
if the tab closed first — and a big entry is shortened with its full copy in Storage.
Each was checked live, and the live checks found two bugs the unit tests had passed.

Changed

  • The client's log budget refills, keeps an error reserve, and turns repeats into summaries instead of dropping them (#40). The budget was 50 logs per page load, cleared on beforeunload, with every severity spending from the same pool and every duplicate past 3 copies simply dropped. Now: the budget refills at refillPerMinute (default 1/min) and survives a reload in sessionStorage — beforeunload no longer resets it; the last errorReserve share (default 20%) can only be spent by ERROR and above, by rank (SEVERITY_ORDER), so CRITICAL, ALERT and EMERGENCY — new severities, see below — may spend it too; and after duplicateLimit full copies, further occurrences are counted rather than dropped and sent as one WARNING summary — hourly, when the tab is hidden, and once at startup for anything a previous visit left queued — carrying repeatOf, repeatCount, firstSeen, lastSeen, and appId/releaseId/userId/screen/errorType recovered from the signature itself rather than a second stored copy of them. The browser has no server logId for the first copy, so repeatOf instead points at a client-side labels.repeatKey that every full copy carries. The per-signature map is capped at 200 entries (evicting the oldest with nothing pending first) so a flood of distinct messages cannot grow it without bound. Unsent summaries wait in localStorage (survives the tab closing), capped at maxPendingSummaries, expired after summaryMaxAgeDays, marked sentLate="true" when a later visit sends them, and removed from the queue only once their send has actually succeeded — a failed send (offline, or the tab closing right after) leaves it queued for the next flush or visit to retry, never twice. All seven numbers are configurable via rateLimitOptions. The three console messages a dropped or counted log produces changed to match. See README, "Volume controls".
  • writeLog's production branch honours a client-supplied timestamp, but only on a repeat summary. LogPayload gains an optional timestamp; it is written into the entry only when labels.repeatCount is present and the value is within the last 8 days and no more than 5 minutes in the future — so a summary is timestamped at lastSeen, not at send time, and anything else keeps Cloud Logging's own ingestion time.

Fixed

  • An entry over 100 KiB arrived broken, with no severity or labels. Cloud Functions and Cloud Run both cut a stdout/stderr log line at exactly 102,400 bytes — measured live. writeLog's production branch now measures the finished entry and, past 90 KiB, shortens it (breadcrumb data, then other context, then the tail of a long stack, then long text fields, then oversized label values — severity, labels, trace and serviceContext are never touched) before writing, saves the full original to Cloud Storage as fsl-overflow.json when a bucket is available, and labels the entry truncated="true" (issue #21).

Documentation

  • /query-logs counts repeats. The shipped skill now adds repeatCount from the summaries to the full copies when asked how often something happened, and reads fsl-overflow.json for a shortened entry. The README's Querying table gains the truncated, repeatKey/repeatOf and sentLate filters.
  • The server half has to run on Google Cloud. 0.8.0's README said "any Node server". Entries go to stdout, which reaches Cloud Logging only on Cloud Functions, Cloud Run, GKE, App Engine, or Compute Engine with the Ops Agent — elsewhere they print and go nowhere.

v0.8.0

Choose a tag to compare

@damith-dasasian damith-dasasian released this 24 Sep 20:26

Firebase is optional now, and that is tested rather than claimed. 0.7.0 added
createHttpLogHandler for backends that are not Cloud Functions, but on one — Cloud
Run with no Firebase packages — /functions failed on require, and once loaded it
dropped every trace id. Both are fixed, and the smoke run now includes exactly that
service. Thanks to Vivek Rao, whose adoption found
the first of it.

Fixed

  • /functions could not be loaded without firebase-functions installed, which made createHttpLogHandler (0.7.0) unusable on the backend it was written for. logger.ts imported firebase-functions/logger at module load and logHandler.ts imported onCall the same way, so a Cloud Run service without it failed on require before reaching the HTTP handler. Both are optional peers now, loaded lazily. Where firebase-functions is installed nothing changes — its write() still attaches each trigger's trace id. Where it is not, entries are written as one JSON line each (WARNING and above to stderr, like write()), and the trace comes from the request headers. Reported and fixed by Vivek Rao in #39.
  • /functions could not be loaded without firebase-admin either. sourceMapCache.ts imported firebase-admin/storage at module load, for a bucket only older-release maps and attachments need. Storage now resolves on first use, down a chain: firebase-admin if installed (unchanged), else @google-cloud/storage with the bucket you named, else no Storage — current-release embedded maps still resolve, older releases stay minified, attachments are dropped with the entry kept, and one warning says which. hasAttachments is no longer set on an entry whose attachments had nowhere to go. Found by npm run smoke:install, a new check that installs the packed tarball with no optional peers.
  • Trace ids were never written on Cloud Run. 0.7.0 built projects/<id>/traces/<trace> from GCLOUD_PROJECT or GOOGLE_CLOUD_PROJECT, and Cloud Run sets neither — so behind createHttpLogHandler the trace was silently dropped. On Cloud Run the project now comes from the metadata server, once, the way Google's own Cloud Run sample does. Without a project the bare trace id is written, which is still a valid trace but does not meet Cloud Run's request log in "show entries for this trace"; the smoke run checks that the two do meet.
  • The client's production floor was never applied in a browser. defaultMinLevel guarded on typeof process, which is undefined in every browser even though bundlers fold process.env.NODE_ENV to a literal — so the folded branch was unreachable and every browser build defaulted to DEBUG in production. Seen in a real Vite bundle as typeof process<"u"?"WARNING":"DEBUG". The guard is gone; a runtime with neither the fold nor process throws on the read and is caught.

Documentation

  • Without Firebase. The README says what is true now: the browser half has no Firebase dependency, and the server half runs on Cloud Functions, Cloud Run or any Node server. A new section covers what changes without Firebase — authorize, naming a bucket, and which tools are Cloud Functions only.
  • A Hono adapter next to the Express one. createHttpLogHandler takes Node's request and response shapes, and Hono wraps them.
  • Volume controls: a Vite define: { 'process.env': {} } makes the client's production floor DEBUG. Pass minLogLevel.

v0.7.0 — errors group now

Choose a tag to compare

@damith-dasasian damith-dasasian released this 01 Sep 04:10

Errors group now. Not because this package learned to fingerprint them, but because Google
Cloud already runs an error tracker in your project and it could not see ours.

Added

  • Cloud Error Reporting shape. Production error entries carry stack_trace at the top level and a serviceContext naming your appId and release. Error Reporting reads Cloud Logging and groups by exception type plus the five top-most frames — with occurrence counts, an Open/Acknowledged/Resolved/Muted state, notifications, and an issue-tracker link — none of it ours to build or run, and none of it leaving your project.

    It groups on those top frames, which is why it does nothing for almost every web app: they read app-4f2a.js:1:98432 and change every release. We resolve them before the entry is written, so yours group. Verified live: two errors with different messages from one source location share a group id, attributed to the appId rather than to the function that wrote them, stable across releases. Warnings and feedback are deliberately excluded — an issue is something a person has to resolve, and neither of those is a bug.

  • createHttpLogHandler — receive client logs over plain HTTP, for a backend that is not Cloud Functions. Reads method, headers and a parsed body and writes through statusCode/setHeader/end, so Express, Fastify's compat layer and a raw server all satisfy it with no dependency added. The client was already portable: logFunction is any (payload) => Promise<unknown>, and httpsCallable() merely happens to fit.

    authorize is required and has no default, because the honest default does not exist — a callable gets Firebase's token check for free, an HTTP endpoint gets nothing, and an open one writes to your Cloud Logging bill on anyone's say-so. Opening it deliberately is spelled 'unauthenticated' at the call site. It is a gate, not an identity check: the handler never reads request.auth, and a gate that throws counts as a rejection.

  • Trace ids outside Cloud Functions. firebase-functions attaches logging.googleapis.com/trace from a store it populates inside its own request wrapper, so on Cloud Run nothing was attached and a request's entries did not correlate — silently. The HTTP handler now reads X-Cloud-Trace-Context or W3C traceparent itself. A malformed header yields nothing rather than a guess: Cloud Logging rejects an invalid trace resource name, which costs the whole entry.

  • Configurable Storage buckets and prefixes. createClientLogFunction({ sourceMaps: { bucket, prefix } }) is genuinely per-handler; configureAttachments({ bucket, prefix }) is a separate global call, because the attachment upload happens in writeLog — reached from every log call, including ones inside handlers that never touch createClientLogFunction — so a field on the handler would let a second one silently retarget the first's attachments.

    Every default is the previous behaviour, so nothing changes for a deploy that does not opt in. Worth opting in where user content needs its own region for residency, its own retention policy, or different IAM from your source maps — none of which a prefix can arrange.

  • fsl upload-sourcemaps runs without a bucket. With --embed-sourcemaps alone it embeds the current release and uploads nothing, which is the whole flow for a backend that has no bucket. Only the deployed release can then be symbolicated. Given neither a bucket nor an embed it now refuses outright rather than running, since that would delete the maps and produce nothing.

  • fsl upload-sourcemaps --prefix, the writer half of the configurable Storage prefix.

Changed

Breaking: a production error's stack moved from jsonPayload.error.stack to top-level stack_trace. One copy, not two — the stack is the largest field in an entry capped at 256 KB. Anything reading error.stack off a production error entry reads stack_trace now. The rest of the error object — message, name, cause — is unchanged, and entries below ERROR, or marked as feedback, keep their stack where it was.

createClientLogHandler is no longer Firebase-shaped. It takes { data: LogPayload }, the minimum it reads, and throws ClientLogError rather than HttpsError. createClientLogFunction converts at the Firebase boundary, so a callable client sees exactly the error it saw before; only a caller wrapping the bare handler themselves is affected.

Every Storage path is defined once, in src/shared/paths.ts. Each source-map path previously existed twice — a writer in /tools, a reader in /functions — as independent literals with nothing enforcing that they matched, and when they disagree symbolication does not fail, it returns minified frames. Two things stopped being coincidences: the .map suffix, which the writer and reader agreed on only because a basename happened to end in it, and separators, since a Storage object name built with path.join is a different object on Windows.

A misconfigured source-map lookup says so. When nothing resolves — wrong bucket, wrong prefix, a release never uploaded, a missing fsl step — the function warns once per release naming both paths it tried. A prefix mismatch is only obvious once you can see the two strings.

Fixed

  • fsl never wrote the .release marker, so the release check had never run. The constant was declared, exported and used by nothing; the reader hardcoded its own copy. Every stack was symbolicated with the currently embedded maps whatever release it came from — precisely the failure the marker exists to prevent — and the fallback warning was unreachable code. Narrow in practice, since Vite content-hashes bundle filenames, but real for any build with stable names. The tests missed it because they wrote the marker themselves before exercising the reader.

  • A prefix with a trailing slash addressed a different object. 'fsl/' yielded fsl//r7/app.js.map, which is not fsl/r7/app.js.map — Storage keys are opaque strings and the double slash is not collapsed, so a slash typed out of habit would have split writer from reader silently.

Documentation

The README is reorganised around what you get rather than how the machine is built. How it works now precedes setup, so the Cloud Function is explained before you are asked to deploy one, and its diagram shows both doors into the stream. Setup is two paths, browser errors and Cloud Functions logging — backend setup previously did not exist as a path at all. New sections cover the eleven fields that arrive without being asked for, the five gates that silently drop a log, attachments as the way past the 256 KB entry limit, and querying. What this is not is rewritten, since it claimed there was no grouping.

skills/query-logs had severity=ERROR on its flagship query, which keeps the crash and throws away the sequence that led to it. The sequential read is now the headline example.

Internal

tests/ and smoke/ are typechecked for the first time, under a second config, and the smoke harness no longer uses any. Neither was checked before, which is how a read of entry.metadata.errorGroups — a field the Logging client does not surface — reached a live run and reported, wrongly, that Error Reporting had grouped nothing.

v0.6.0

Choose a tag to compare

@damith-dasasian damith-dasasian released this 24 Aug 00:32

Added

  • sendFeedback(text, extras?) — capture what error tracking structurally cannot see. A button that does nothing, a wrong total, the wrong data rendered: none of them throw, so none are logged. Feedback captures that class, and the breadcrumb trail already in memory turns "the discount didn't apply" from a complaint into a reproduction. Headless — the app owns the UI. Carries the same labels and breadcrumbs as any log, plus an optional screenshot.
  • NOTICE severity — ranks between INFO and WARNING, matching Cloud Logging's own ordering. Feedback from a person outranks routine status without being a warning about system health, and any existing severity >= WARNING alert ignores it with no configuration. This widens the public LogSeverity union, which is breaking for exhaustive switch statements and Record<LogSeverity, T> in consumer code.

Changed

  • The 5-minute breadcrumb cutoff now applies when reading, not only when writing. It previously lapsed exactly when nothing was happening: a user who went idle for ten minutes and then hit an error shipped a trail of ten-minute-old steps presented as the path that led there.
  • Every log now carries the full breadcrumb trail (up to 50 entries), not the newest 20. The trail already retained 50; the send path asked for 20, so 30 were kept in memory that nothing could read. Error and feedback payloads grow by roughly 3 KB, or ~2% of Cloud Logging's 256 KB entry limit.

Removed

Breaking: initRequestLogger is gone. Use withLogging. It bound the request scope with AsyncLocalStorage.enterWith(), which is never unwound, so a request's labels outlived the request — a later handler that did not call it inherited whichever user last touched the warm instance. Deprecated in 0.5.0 with a warning naming the leak. withLogging binds with run(), which restores the previous context when the handler settles, including on a throw.

Breaking: setActiveActivity and BaseLabels.activity are gone. activity was ambient state with no natural end — nothing cleared it automatically, so a forgotten one contaminated the rest of the session, and because it fed the dedup signature a stale value distorted which errors were suppressed. A wrong signal in a filtering rule is worse than a missing one. screen looks similar but is self-correcting: the next setScreen() overwrites it. Nothing set activity in practice, so removing it from the signature changes no behaviour — the segment was always empty. See #29 for restoring path discrimination from breadcrumbs, which are derived rather than manually maintained and therefore cannot go stale.

Fixed

  • An unrecognised severity crashed writeLog and destroyed the entry — both dispatch paths look the value up in a fixed table (CONSOLE_FN in emulator mode, firebase-functions' own CONSOLE_SEVERITY inside ffWrite), and a miss resolves to undefined; calling it throws, Logger.send()'s catch swallows it, and the entry disappears with no diagnostic. It also slipped past the min-severity floor, since SEVERITY_ORDER[unknown] is undefined and undefined > n is false. writeLog is exported, so a caller could reach it with a value read from config, crossing a type boundary, or from plain JavaScript. Unknown severities are now written as ERROR with a warning naming the bad value — an entry arriving loud beats one vanishing quietly.