The browser: a custom event
6d. The custom event
Section titled “6d. The custom event”Any event.name that is not one of the six in the contract is a custom event. ouo11y shows it on the session’s timeline and never counts it in health. addEvent() from events.js makes one:
addEvent("cart.added", { "product.id": 1 });Replace frontend/src/App.jsx with the final version. Compared with step 3, the API URL comes from telemetry/config.js (the same value the fetch instrumentation matches), and Add records a custom cart.added event:
frontend/src/App.jsx
import { useEffect, useState } from "react";import { API, addEvent } from "./telemetry";
export default function App() { const [products, setProducts] = useState([]); const [cart, setCart] = useState([]); const [message, setMessage] = useState("");
useEffect(() => { fetch(`${API}/api/products`).then((r) => r.json()).then(setProducts); }, []);
function addToCart(p) { setCart([...cart, p]); // A custom event: a log record with event.name "cart.added" in Loki. addEvent("cart.added", { "product.id": p.id, "product.name": p.name }); }
async function checkout() { const res = await fetch(`${API}/api/checkout`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ items: cart.map((p) => p.id) }), }); const data = await res.json(); setMessage(`Order #${data.orderId} placed`); setCart([]); }
async function callBrokenApi() { // A 500: recorded as http.request.failed in the browser and as an error span in the API. const res = await fetch(`${API}/api/fail`); setMessage(`API answered ${res.status}`); }
function crash() { // An uncaught error: an exception event, and the session is marked crashed. throw new Error("Button exploded"); }
return ( <main style={{ fontFamily: "sans-serif", maxWidth: 480, margin: "40px auto" }}> <h1>Tiny Shop</h1> <ul> {products.map((p) => ( <li key={p.id}> {p.name} (${p.price}) <button onClick={() => addToCart(p)}>Add</button> </li> ))} </ul> <p>Cart: {cart.length} item(s)</p> <button onClick={checkout} disabled={!cart.length}>Checkout</button>{" "} <button onClick={callBrokenApi}>Call broken API</button>{" "} <button onClick={crash}>Throw error</button> <p>{message}</p> </main> );}That is the last file. Your frontend/src/telemetry/ folder now holds the six files of the finished quickstart, about 400 lines in all.
What you should see
Section titled “What you should see”Vite updates the page on its own. Click Add on a product. In Grafana, Explore, Loki, within a few seconds:
{service_name="tiny-shop-otel-frontend", event_name="cart.added"}One line per click, cart.added {"product.id":1,"product.name":"Coffee"}, with the session’s session_id.
Next: see the full picture.