Build a dinosaur runner game with Deno, pt. 6 This article is the sixth and final part of a series on building a browser-based dinosaur runner game with Deno. It focuses on implementing observability by adding structured JSON logging and custom OpenTelemetry traces to monitor API performance and game events. The guide explains how to use Deno Deploy's built-in dashboards for logs, traces, and metrics without requiring external monitoring tools. Build a dinosaur runner game with Deno, pt. 6 This series of blog posts will guide you through building a simple browser-based dinosaur runner game using Deno. Setup a basic project Game loop, canvas, and controls Obstacles and collision detection Databases and global leaderboards Player profiles and customization - Observability, metrics, and alerting Observability, Metrics, and Alerting In Stage 5, we gave players an identity: a name, a dino colour, a background theme, and a difficulty level, all persisted in PostgreSQL and loaded back on every visit. The game is now feature-complete. But shipping features is only half the story. The other half is understanding what’s happening once real players arrive. Are the API routes fast enough? Is the leaderboard staying healthy? Is there a spike in errors after a deployment? You can’t answer those questions with console.log "Server is running " . In this final stage, we’ll wire up an observability layer that makes every question answerable, leaning on Deno Deploy’s built-in logs, traces, and metrics dashboards so you don’t need to reach for a separate monitoring product to get started. Keep reading and build along or view the entire source here https://github.com/thisisjofrank/game-tutorial . What you’ll build By the end of this post you will have: Structured logs : every HTTP request emits a JSON log line, and key game events score submissions, customization saves emit their own structured events that you can search and filter in the Logs dashboard. Custom traces : key operations score submission, leaderboard fetch, loading player settings each get their own span, so you can see exactly where time is spent inside a request. Both are visible in Deno Deploy’s built-in dashboards alongside the platform’s automatic metrics, no extra infrastructure required. The three pillars of observability Observability is the ability to understand what your system is doing from the outside, by examining the data it produces. That data typically comes in three forms: | Pillar | What it answers | |---|---| Logs | What happened, and when? | Traces | Where did the time go? | Metrics | How is the system trending over time? | Deno Deploy provides a dashboard for all three. Logs and traces support custom instrumentation, you can add your own structured log events and custom spans. The Metrics dashboard shows platform-level data that Deno Deploy captures automatically, which we’ll look at in its own section. Setting up the telemetry module We need one package: the OpenTelemetry API https://www.npmjs.com/package/@opentelemetry/api . On Deno Deploy, the runtime wires up the SDK and exporter for you, no other configuration required. The API package gives us the interfaces we call in our code; the platform handles where the data goes. Add it to deno.json : { "imports": { "@oak/oak": "jsr:@oak/oak@17", "@opentelemetry/api": "npm:@opentelemetry/api@^1.9.0", "npm:pg": "npm:pg@^8.11.0" } } Create src/telemetry.ts to hold the shared tracer instance: js import { SpanStatusCode, trace } from "@opentelemetry/api"; // Shared tracer - used to create custom spans throughout the server export const tracer = trace.getTracer "dino-game", "1.0.0" ; export { SpanStatusCode }; Keeping this in one place means every file uses the same tracer name and version, which groups all your custom spans together in the dashboard. Logs Every console.log call you make is captured by Deno Deploy and shown in the Logs tab of your app’s dashboard. But plain-text logs are hard to filter. The upgrade is to emit structured JSON, a single parseable object per event that you can search by any field. Create a logging middleware in src/middleware/logging.ts : python import type { Context } from "@oak/oak"; export async function loggingMiddleware ctx: Context, next: = Promise