@ai-digital-factory/logging-react
v0.1.5
Published
React Provider and hook for same-origin browser records
Downloads
391
Readme
@ai-digital-factory/logging-react
React 18+ LogProvider and useLog() hook for sending explicit Browser records to your app’s same-origin Express ingest route (mounted by @ai-digital-factory/logging).
This package does not talk to your log provider from the browser. It batches JSON and POSTs to a path on the current page origin (default /__lms/browser-records). Your Express server forwards accepted records through the same LogManager as server logs (origin: "browser").
Audience: application developers and coding agents integrating logging into an existing React app.
Prerequisites
| Requirement | Notes |
| --- | --- |
| React 18+ | Peer dependency (react, react-dom). |
| Node 22+ on the server | Install @ai-digital-factory/logging and register logs.expressMiddleware() before your routes. |
| Same origin in the browser | The ingest POST must hit the same host/port the user sees (dev proxy or production reverse proxy). |
| No browser credentials | LOG_INGESTION_URL and LOG_CREDENTIAL stay on the server only. |
Runnable reference: apps/example-react in this repository.
For the full step-by-step contract (including agent checklists), see the React integration guide.
Install
pnpm add @ai-digital-factory/logging-react @ai-digital-factory/loggingUse npm or yarn if that is what the app already uses. Install the Node SDK on the server package in a monorepo, and the React package on the client package.
Architecture (one glance)
Browser (React) Your Express app Log destination
───────────────── ───────────────── ───────────────
useLog() → batch
│
POST /__lms/browser-records → expressMiddleware()
(same origin, no token) → LogManager → LOG_INGESTION_URLIntegrate into an existing React app
1. Server (Express)
If the app already has Express (or you add a small API layer), register the SDK middleware once at process startup:
import express from "express";
import { createLogManager } from "@ai-digital-factory/logging";
const logs = createLogManager({
service: "my-frontend-app",
environment: process.env.NODE_ENV ?? "development",
connection: {
ingestionUrl: process.env.LOG_INGESTION_URL!,
credential: process.env.LOG_CREDENTIAL!,
},
redact: ["eventMeta.accessToken"], // extend for your meta shape
});
const app = express();
app.use(logs.expressMiddleware()); // before your routes
// ... your API and static file routes ...Load LOG_INGESTION_URL and LOG_CREDENTIAL from environment or secrets (never from the React bundle).
Details: @ai-digital-factory/logging README.
2. Client — wrap the tree once
Place one LogProvider as high as practical (typically next to your existing root providers).
Vite / CRA (main.tsx / index.tsx):
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { LogProvider } from "@ai-digital-factory/logging-react";
import { App } from "./App";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<LogProvider>
<App />
</LogProvider>
</StrictMode>,
);Next.js App Router: use a client component wrapper (e.g. app/providers.tsx with "use client") and render <LogProvider>{children}</LogProvider> inside it; import that wrapper from app/layout.tsx.
React Router / Redux / other providers: nest LogProvider outside route-specific trees so a single Session covers the mounted app (see Session).
You do not need to change every file at once. Add the Provider first, then call useLog() only in components where you want Browser records.
3. Client — log explicit events
import { useLog } from "@ai-digital-factory/logging-react";
export function CheckoutButton() {
const log = useLog();
return (
<button
type="button"
onClick={() => log("click", "checkout", { step: "payment" })}
>
Pay
</button>
);
}API: log(eventType, eventValue, eventMeta?)
eventTypeandeventValueare short strings you choose (e.g."page view","home").eventMetais optional structured data you supply. There is no fixed schema.- Hooks must run under
LogProvider; otherwiseuseLog()throws.
4. Same origin in development (Vite)
If the UI dev server (e.g. port 5173) is separate from Express (e.g. 3001), proxy the ingest and API paths to Express. Example from this repo’s example app:
// vite.config.ts
import { defineConfig, loadEnv } from "vite";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
const backend = `http://127.0.0.1:${env.PORT ?? "3001"}`;
return {
server: {
proxy: {
"/__lms": backend,
"/api": backend,
},
},
};
});Open the Vite URL in the browser during development. Browser records go to Vite → proxy → Express → provider.
Webpack dev server: configure an equivalent devServer.proxy for /__lms (and your API prefix).
5. Same origin in production
The browser must still POST to the same origin users visit. Typical patterns:
- Express serves the built SPA and API on one host/port, or
- A reverse proxy routes
/__lmsand/apito the Node process that runsexpressMiddleware().
Do not point the React app at the raw LOG_INGESTION_URL from the client.
Custom ingest path
Default path: /__lms/browser-records (DEFAULT_BROWSER_INGEST_PATH).
If you set browserIngestPath on createLogManager, pass the same path to the Provider:
<LogProvider ingestPath="/telemetry/browser">Update dev proxies and production routing to match.
Session and SSR
- Each
LogProvidermount creates a random in-memory Session ID sent with every record in that mount. - Logging starts after the Provider hydrates in the browser. SSR does not emit Browser records.
- Session is not joined to Express
req.logrequest IDs (by design).
Privacy and behavior
Do log (examples): control names, feature flags, coarse outcomes (success / fail), non-sensitive status codes.
Do not log: passwords, tokens, credentials, full URLs, query strings, headers, request/response bodies, or text typed by users. SDK redact paths apply to forwarded records but do not scan arbitrary strings—avoid putting secrets in eventMeta.
Not automatic: DOM events, router transitions, forms, fetch, or XHR. You must call useLog() explicitly.
Delivery: Batched, same-origin, best-effort (bounded buffer; failures are dropped silently on the client batcher). Not an audit trail.
Stored shape: Browser records are written at info with origin: "browser"; server logs use origin: "server".
Agent checklist
Use this when adding logging to an existing codebase:
- Confirm the app is React 18+ and has (or will have) Express with
@ai-digital-factory/loggingmiddleware registered before routes. - Confirm no
LOG_INGESTION_URL/LOG_CREDENTIALin client env or bundledVITE_*/NEXT_PUBLIC_*variables. - Add
@ai-digital-factory/logging-reactto the client package only. - Wrap the existing root with
<LogProvider>(one mount per browser tab experience). - Add
useLog()at intentional UX points; keepeventType/eventValuestable for querying. - Configure dev proxy and production routing so
/__lms/*(or customingestPath) reaches Express on the user-visible origin. - Extend server
redactfor any sensitiveeventMetakeys you allow. - Verify in the log UI: new records on the stream from
LOG_INGESTION_URL,origin: "browser",servicefromcreateLogManager.
Do not import @log-management/*, Pino, or provider SDKs in application code.
Troubleshooting
| Symptom | Likely cause |
| --- | --- |
| useLog must be used within a LogProvider | Component is outside LogProvider, or duplicate React roots without the Provider. |
| Records appear on the wrong stream | Browser traffic hit a different Express app (common: dev proxy still pointing at port 3000 / example-server). Confirm startup log ingest URL on your server process. |
| No records in the UI | Middleware not registered, proxy missing /__lms, or server env vars unset. Check network tab for POST to ingest path (should be same origin, 2xx from Express). |
| example_react stream empty but example_server has browser-shaped messages | Vite proxied to the wrong backend; fix PORT / proxy target and restart dev. |
API reference
export const DEFAULT_BROWSER_INGEST_PATH = "/__lms/browser-records";
export type EventMeta = Readonly<Record<string, unknown>>;
export type LogBrowserEvent = (
eventType: string,
eventValue: string,
eventMeta?: EventMeta,
) => void;
export function LogProvider(props: {
children: React.ReactNode;
ingestPath?: string;
}): JSX.Element;
export function useLog(): LogBrowserEvent;