React Integration
Create a shared client instance and use it across your app.
// src/gunsole.ts — singleton clientimport { createGunsoleClient } from "@gunsole/web";
export const gunsole = createGunsoleClient({ projectId: "my-react-app", mode: "local", env: "development", appName: "React Vite App", appVersion: "1.0.0", defaultTags: { framework: "react", bundler: "vite" },});Import gunsole wherever you need it:
import { gunsole } from "../gunsole";import { useEffect, useState } from "react";
function UserList() { const [users, setUsers] = useState([]);
useEffect(() => { fetch("/api/users") .then((res) => res.json()) .then((data) => { setUsers(data); gunsole.info({ bucket: "ui", message: `Loaded ${data.length} users`, tags: { component: "UserList", action: "fetch" }, }); }) .catch((err) => { gunsole.error({ bucket: "ui", message: "Failed to load users", context: { error: err.message }, tags: { component: "UserList", action: "fetch" }, }); }); }, []);
return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;}Error boundary
Section titled “Error boundary”Log fatal errors caught by React error boundaries:
import { gunsole } from "./gunsole";
class GunsoleErrorBoundary extends React.Component< { children: React.ReactNode }, { hasError: boolean }> { state = { hasError: false };
static getDerivedStateFromError() { return { hasError: true }; }
componentDidCatch(error: Error, info: React.ErrorInfo) { gunsole.fatal({ bucket: "fatal", message: error.message, context: { name: error.name, stack: error.stack, componentStack: info.componentStack, }, }); }
render() { if (this.state.hasError) return <div>Something went wrong.</div>; return this.props.children; }}
createRoot(document.getElementById("root")!).render( <GunsoleErrorBoundary> <App /> </GunsoleErrorBoundary>);Lifecycle logging and user tracking
Section titled “Lifecycle logging and user tracking”useEffect(() => { gunsole.info({ bucket: "app_lifecycle", message: "App mounted", context: { framework: "react", router: "tanstack" }, });}, []);
API calls with tracing
Section titled “API calls with tracing”const fetchPokemon = async () => { const traceId = `trace-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; const startTime = performance.now();
gunsole.info({ bucket: "api_request", message: `Fetching Pokemon: ${pokemonName}`, context: { pokemon: pokemonName }, tags: { api: "pokeapi", action: "fetch_start" }, traceId, });
try { const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonName}`); const fetchTime = performance.now() - startTime;
if (!response.ok) throw new Error(`Pokemon not found: ${pokemonName}`); const data = await response.json(); const totalTime = performance.now() - startTime;
gunsole.info({ bucket: "api_request", message: `Pokemon fetched successfully: ${data.name}`, context: { pokemon: data.name, pokemonId: data.id, fetchTimeMs: Math.round(fetchTime), totalTimeMs: Math.round(totalTime), }, tags: { api: "pokeapi", action: "fetch_success", status: "200" }, traceId, }); } catch (err) { const totalTime = performance.now() - startTime; gunsole.error({ bucket: "api_request", message: `Failed to fetch Pokemon: ${pokemonName}`, context: { pokemon: pokemonName, error: err instanceof Error ? err.message : "Unknown error", totalTimeMs: Math.round(totalTime), }, tags: { api: "pokeapi", action: "fetch_error" }, traceId, }); }};Web Vitals
Section titled “Web Vitals”Track Core Web Vitals with gunsole:
import { onCLS, onFCP, onINP, onLCP, onTTFB } from "web-vitals";import { gunsole } from "./gunsole";
useEffect(() => { const report = (metric: { name: string; value: number; rating: string }) => { gunsole.info({ bucket: "web_vitals", message: `${metric.name}: ${metric.value.toFixed(2)}`, context: { value: metric.value, rating: metric.rating }, tags: { metric: metric.name, rating: metric.rating }, }); }; onCLS(report); onFCP(report); onINP(report); onLCP(report); onTTFB(report);}, []);Manual flush
Section titled “Manual flush”await gunsole.flush();