fboxrec 0.1.0 → 0.3.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 CHANGED
@@ -32,6 +32,10 @@ Everything is correlated by request via AsyncLocalStorage, timestamped with
32
32
  the monotonic clock, and held in a fixed 64MB ring (configurable) — the
33
33
  recorded window is a consequence of memory and traffic, not a setting.
34
34
 
35
+ `flightbox.stop()` disarms recording and restores the patched modules
36
+ (console, http server/client, fetch, pg) to their originals, so tests and
37
+ clean shutdowns leave no trace; `start()` can be called again afterwards.
38
+
35
39
  ## When something breaks
36
40
 
37
41
  Triggers snapshot the ring into a gzipped `.fbox` incident file:
@@ -653,7 +653,7 @@ var FORMAT_VERSION = 1;
653
653
  var MAX_DECOMPRESSED_BYTES = 1024 * 1024 * 1024;
654
654
 
655
655
  // src/dump/serializer.ts
656
- var FLIGHTBOX_VERSION = "0.1.0";
656
+ var FLIGHTBOX_VERSION = "0.3.0";
657
657
  var DUMP_GZIP_LEVEL = 1;
658
658
  var dumpCounter = 0;
659
659
  function buildIncident(opts) {
@@ -1444,8 +1444,9 @@ function startWatchdog(agent) {
1444
1444
  // src/instrumentations/http-server.ts
1445
1445
  import * as http from "http";
1446
1446
  import { createHash as createHash2, timingSafeEqual } from "crypto";
1447
- var patched = false;
1448
1447
  var agentRef;
1448
+ var active2 = false;
1449
+ var saved = null;
1449
1450
  function tokenMatches(supplied, expected) {
1450
1451
  const a = createHash2("sha256").update(supplied).digest();
1451
1452
  const b = createHash2("sha256").update(expected).digest();
@@ -1487,12 +1488,12 @@ function handleFlightboxEndpoint(agent, req, res) {
1487
1488
  }
1488
1489
  function instrumentHttpServer(agent) {
1489
1490
  agentRef = agent;
1490
- if (patched) return;
1491
- patched = true;
1491
+ active2 = true;
1492
+ if (saved) return;
1492
1493
  const orig = http.Server.prototype.emit;
1493
1494
  const flightboxEmit = function(event) {
1494
1495
  const args = arguments;
1495
- if (event !== "request") {
1496
+ if (!active2 || event !== "request") {
1496
1497
  return orig.apply(this, args);
1497
1498
  }
1498
1499
  const agent2 = agentRef;
@@ -1545,13 +1546,22 @@ function instrumentHttpServer(agent) {
1545
1546
  }
1546
1547
  return requestStorage.run(ctx, () => orig.apply(this, args));
1547
1548
  };
1548
- http.Server.prototype.emit = flightboxEmit;
1549
+ saved = { orig, wrapper: flightboxEmit };
1550
+ http.Server.prototype.emit = saved.wrapper;
1551
+ }
1552
+ function restoreHttpServer() {
1553
+ active2 = false;
1554
+ if (saved && http.Server.prototype.emit === saved.wrapper) {
1555
+ http.Server.prototype.emit = saved.orig;
1556
+ saved = null;
1557
+ }
1549
1558
  }
1550
1559
 
1551
1560
  // src/instrumentations/http-client.ts
1552
1561
  import { createRequire } from "module";
1553
- var patched2 = false;
1554
1562
  var agentRef2;
1563
+ var active3 = false;
1564
+ var liveWraps = /* @__PURE__ */ new Map();
1555
1565
  function extractTarget(args) {
1556
1566
  let method = "GET";
1557
1567
  let host = "";
@@ -1583,10 +1593,12 @@ function extractTarget(args) {
1583
1593
  return { method: String(method).toUpperCase(), host: String(host), path: String(path8) };
1584
1594
  }
1585
1595
  var nodeRequire = createRequire(process.cwd() + "/noop.js");
1586
- function wrapModule(mod) {
1587
- const origRequest = mod.request.bind(mod);
1596
+ function wrapModule(key, mod) {
1597
+ const rawRequest = mod.request;
1598
+ const rawGet = mod.get;
1599
+ const origRequest = rawRequest.bind(mod);
1588
1600
  const tracedRequest = function(...args) {
1589
- if (isShedding()) return origRequest(...args);
1601
+ if (!active3 || isShedding()) return origRequest(...args);
1590
1602
  const agent = agentRef2;
1591
1603
  let spanId = 0n;
1592
1604
  let requestId = 0n;
@@ -1633,18 +1645,27 @@ function wrapModule(mod) {
1633
1645
  }
1634
1646
  return req;
1635
1647
  };
1636
- mod.request = tracedRequest;
1637
- mod.get = function(...args) {
1648
+ const tracedGet = function(...args) {
1638
1649
  const req = tracedRequest(...args);
1639
1650
  req.end();
1640
1651
  return req;
1641
1652
  };
1653
+ mod.request = tracedRequest;
1654
+ mod.get = tracedGet;
1655
+ liveWraps.set(key, () => {
1656
+ let clear = true;
1657
+ if (mod.request === tracedRequest) mod.request = rawRequest;
1658
+ else clear = false;
1659
+ if (mod.get === tracedGet) mod.get = rawGet;
1660
+ else clear = false;
1661
+ return clear;
1662
+ });
1642
1663
  }
1643
1664
  function wrapFetch() {
1644
1665
  const origFetch = globalThis.fetch;
1645
1666
  if (typeof origFetch !== "function") return;
1646
- globalThis.fetch = async function flightboxFetch(input, init) {
1647
- if (isShedding()) return origFetch(input, init);
1667
+ const wrapper = async function flightboxFetch(input, init) {
1668
+ if (!active3 || isShedding()) return origFetch(input, init);
1648
1669
  const agent = agentRef2;
1649
1670
  let spanId = 0n;
1650
1671
  let requestId = 0n;
@@ -1697,22 +1718,41 @@ function wrapFetch() {
1697
1718
  throw err;
1698
1719
  }
1699
1720
  };
1721
+ globalThis.fetch = wrapper;
1722
+ liveWraps.set("fetch", () => {
1723
+ if (globalThis.fetch === wrapper) {
1724
+ globalThis.fetch = origFetch;
1725
+ return true;
1726
+ }
1727
+ return false;
1728
+ });
1700
1729
  }
1701
1730
  function instrumentHttpClient(agent) {
1702
1731
  agentRef2 = agent;
1703
- if (patched2) return;
1704
- patched2 = true;
1705
- try {
1706
- wrapModule(nodeRequire("node:http"));
1707
- } catch {
1732
+ active3 = true;
1733
+ if (!liveWraps.has("http")) {
1734
+ try {
1735
+ wrapModule("http", nodeRequire("node:http"));
1736
+ } catch {
1737
+ }
1708
1738
  }
1709
- try {
1710
- wrapModule(nodeRequire("node:https"));
1711
- } catch {
1739
+ if (!liveWraps.has("https")) {
1740
+ try {
1741
+ wrapModule("https", nodeRequire("node:https"));
1742
+ } catch {
1743
+ }
1712
1744
  }
1713
- try {
1714
- wrapFetch();
1715
- } catch {
1745
+ if (!liveWraps.has("fetch")) {
1746
+ try {
1747
+ wrapFetch();
1748
+ } catch {
1749
+ }
1750
+ }
1751
+ }
1752
+ function restoreHttpClient() {
1753
+ active3 = false;
1754
+ for (const [key, undo] of liveWraps) {
1755
+ if (undo()) liveWraps.delete(key);
1716
1756
  }
1717
1757
  }
1718
1758
 
@@ -1732,17 +1772,21 @@ function paramShapes(values) {
1732
1772
  return values.slice(0, 32).map((v) => v === null ? "null" : Array.isArray(v) ? "array" : typeof v);
1733
1773
  }
1734
1774
  var agentRef3;
1775
+ var active4 = false;
1776
+ var queryPatch = null;
1777
+ var connectPatch = null;
1735
1778
  function instrumentPg(agent) {
1736
1779
  agentRef3 = agent;
1737
1780
  const recorder = {
1738
1781
  record: ((...args) => agentRef3.recorder.record(...args))
1739
1782
  };
1783
+ active4 = true;
1740
1784
  const pg = loadPg();
1741
1785
  if (!pg?.Client?.prototype?.query) return false;
1742
- if (!pg.Client.prototype.query.__flightbox) {
1786
+ if (!queryPatch && !pg.Client.prototype.query.__flightbox) {
1743
1787
  const origQuery = pg.Client.prototype.query;
1744
1788
  const wrappedQuery = function(...args) {
1745
- if (isShedding()) return origQuery.apply(this, args);
1789
+ if (!active4 || isShedding()) return origQuery.apply(this, args);
1746
1790
  let spanId = 0n;
1747
1791
  let requestId = 0n;
1748
1792
  let tStart = 0n;
@@ -1815,13 +1859,14 @@ function instrumentPg(agent) {
1815
1859
  return out;
1816
1860
  };
1817
1861
  wrappedQuery.__flightbox = true;
1862
+ queryPatch = { proto: pg.Client.prototype, orig: origQuery, wrapper: wrappedQuery };
1818
1863
  pg.Client.prototype.query = wrappedQuery;
1819
1864
  }
1820
1865
  const Pool = pg.Pool;
1821
- if (Pool?.prototype?.connect && !Pool.prototype.connect.__flightbox) {
1866
+ if (Pool?.prototype?.connect && !connectPatch && !Pool.prototype.connect.__flightbox) {
1822
1867
  const origConnect = Pool.prototype.connect;
1823
1868
  const wrappedConnect = function(cb) {
1824
- if (isShedding()) return origConnect.call(this, cb);
1869
+ if (!active4 || isShedding()) return origConnect.call(this, cb);
1825
1870
  const tStart = nowMono();
1826
1871
  const requestId = currentRequestId();
1827
1872
  const record = () => {
@@ -1848,33 +1893,58 @@ function instrumentPg(agent) {
1848
1893
  return out;
1849
1894
  };
1850
1895
  wrappedConnect.__flightbox = true;
1896
+ connectPatch = { proto: Pool.prototype, orig: origConnect, wrapper: wrappedConnect };
1851
1897
  Pool.prototype.connect = wrappedConnect;
1852
1898
  }
1853
1899
  return true;
1854
1900
  }
1901
+ function restorePg() {
1902
+ active4 = false;
1903
+ if (queryPatch && queryPatch.proto.query === queryPatch.wrapper) {
1904
+ queryPatch.proto.query = queryPatch.orig;
1905
+ queryPatch = null;
1906
+ }
1907
+ if (connectPatch && connectPatch.proto.connect === connectPatch.wrapper) {
1908
+ connectPatch.proto.connect = connectPatch.orig;
1909
+ connectPatch = null;
1910
+ }
1911
+ }
1855
1912
 
1856
1913
  // src/instrumentations/console.ts
1857
1914
  import { formatWithOptions } from "util";
1858
1915
  var LEVELS = ["log", "info", "warn", "error", "debug"];
1859
1916
  var FORMAT_OPTS = { depth: 2, maxArrayLength: 20, maxStringLength: LIMITS.log };
1860
- var patched3 = false;
1861
1917
  var agentRef4;
1918
+ var active5 = false;
1919
+ var patches = /* @__PURE__ */ new Map();
1862
1920
  function instrumentConsole(agent) {
1863
1921
  agentRef4 = agent;
1864
- if (patched3) return;
1865
- patched3 = true;
1922
+ active5 = true;
1866
1923
  for (const level of LEVELS) {
1867
- const orig = console[level].bind(console);
1868
- console[level] = function flightboxConsole(...args) {
1924
+ if (patches.has(level)) continue;
1925
+ const orig = console[level];
1926
+ const bound = orig.bind(console);
1927
+ const wrapper = function flightboxConsole(...args) {
1869
1928
  try {
1870
- if (!isShedding()) {
1929
+ if (active5 && !isShedding()) {
1871
1930
  const msg = capScrub(formatWithOptions(FORMAT_OPTS, ...args), LIMITS.log);
1872
1931
  agentRef4.recorder.record(8 /* Log */, { level, msg });
1873
1932
  }
1874
1933
  } catch {
1875
1934
  }
1876
- orig(...args);
1935
+ bound(...args);
1877
1936
  };
1937
+ patches.set(level, { orig, wrapper });
1938
+ console[level] = wrapper;
1939
+ }
1940
+ }
1941
+ function restoreConsole() {
1942
+ active5 = false;
1943
+ for (const [level, p] of patches) {
1944
+ if (console[level] === p.wrapper) {
1945
+ console[level] = p.orig;
1946
+ patches.delete(level);
1947
+ }
1878
1948
  }
1879
1949
  }
1880
1950
 
@@ -1896,6 +1966,21 @@ function applyInstrumentations(agent) {
1896
1966
  }
1897
1967
  }
1898
1968
  }
1969
+ var RESTORATIONS = [
1970
+ ["http-server", restoreHttpServer],
1971
+ ["http-client", restoreHttpClient],
1972
+ ["pg", restorePg],
1973
+ ["console", restoreConsole]
1974
+ ];
1975
+ function removeInstrumentations(log) {
1976
+ for (const [name, restore] of RESTORATIONS) {
1977
+ try {
1978
+ restore();
1979
+ } catch (err) {
1980
+ log(`instrumentation ${name} failed to restore: ${err.message}`);
1981
+ }
1982
+ }
1983
+ }
1899
1984
 
1900
1985
  // src/instrumentations/vitals.ts
1901
1986
  import { monitorEventLoopDelay } from "perf_hooks";
@@ -2177,6 +2262,7 @@ function stop() {
2177
2262
  } catch {
2178
2263
  }
2179
2264
  }
2265
+ removeInstrumentations(instance.config.log);
2180
2266
  instance.panicWriter.disarm();
2181
2267
  setShedding(false);
2182
2268
  instance = null;
@@ -2191,4 +2277,4 @@ export {
2191
2277
  stop,
2192
2278
  index_default
2193
2279
  };
2194
- //# sourceMappingURL=chunk-HCJF2ASP.mjs.map
2280
+ //# sourceMappingURL=chunk-4DHWBXXW.mjs.map