Wide-event logging middleware for Hono - structured, high-cardinality, queryable logs inspired by loggingsucks.com.
This monorepo contains the core middleware package and example implementations for different runtimes.
Traditional logging scatters information across multiple lines. Wide-event logging captures everything about a request in one comprehensive structured event:
- One event per request - No more grep-ing through scattered logs
- High cardinality - Include user IDs, request IDs, session IDs for precise filtering
- High dimensionality - 50+ fields with rich context
- Structured & queryable - JSON format for analytics dashboards
The core middleware package implementing wide-event logging for Hono applications.
Published on npm: npm install hono-wide-logger
import { Hono } from "hono";
import { wideLogger } from "hono-wide-logger";
const app = new Hono();
app.use("*", wideLogger());
app.get("/api/users/:id", (c) => {
const logger = c.get("wide-logger");
logger.addContext("user", {
id: c.req.param("id"),
tier: "premium",
});
logger.addContext("business", {
endpoint: "get_user",
});
return c.json({ user: { id: c.req.param("id") } });
});Features:
- ✅ Auto-captures request metadata (method, path, duration, status)
- ✅ Request ID propagation via headers
- ✅ Categorized context (user, business, infra, service)
- ✅ Smart tail sampling (always logs errors + slow requests)
- ✅ Pluggable logger, storage, and sampling
- ✅ Full TypeScript support
Complete example demonstrating all features in a Cloudflare Workers environment.
cd examples/hono-cf
pnpm install
pnpm devTest endpoints:
# Basic usage
curl http://localhost:8787/basic
# Custom storage
curl http://localhost:8787/storage
curl http://localhost:8787/storage/events
# Custom sampling
curl http://localhost:8787/sampling
curl -X POST http://localhost:8787/sampling -H "Content-Type: application/json" -d '{"userId": "123"}'
# All context types
curl http://localhost:8787/all-contexts/all-contexts
# Error handling
curl http://localhost:8787/error/error
curl http://localhost:8787/error/throw-errorExample for Node.js runtime environments.
cd examples/hono-node
pnpm install
pnpm devwide-span-logger/
├── packages/
│ └── wide-logger/ # Core middleware package
│ ├── src/
│ │ ├── middleware.ts # Main implementation
│ │ ├── sampling.ts # Sampling strategies
│ │ └── types.ts # TypeScript definitions
│ ├── tests/ # Test suite
│ ├── package.json
│ └── README.md
│
├── examples/
│ ├── hono-cf/ # Cloudflare Workers example
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── wrangler.jsonc
│ └── hono-node/ # Node.js example
│ ├── src/
│ │ └── index.ts
│ └── package.json
│
├── .opencode/
│ └── plans/ # Implementation plans
│
├── package.json # Root workspace config
├── pnpm-workspace.yaml # pnpm workspace
└── README.md # This file
npm install hono-wide-logger
# or
pnpm add hono-wide-logger
# or
yarn add hono-wide-loggerimport { Hono } from "hono";
import { wideLogger } from "hono-wide-logger";
const app = new Hono();
// Use with default options
app.use("*", wideLogger());
app.get("/", (c) => {
const logger = c.get("wide-logger");
// Add context
logger.addContext("user", { id: "user_123" });
return c.json({ message: "Hello!" });
});const logger = wideLogger({
// Custom logger (default: console)
logger: pino(),
// Storage for persisting events
storage: {
set: async (key, value) => await redis.set(key, JSON.stringify(value)),
get: async (key) => JSON.parse(await redis.get(key)),
delete: async (key) => await redis.del(key),
},
// Custom sampling
sampling: (event) => {
// Always log errors
if (event.status_code >= 500) return true;
// Log 10% of GETs
if (event.method === "GET") return Math.random() < 0.1;
return false;
},
// Sampling configuration
slowThresholdMs: 1000, // Log requests > 1s
sampleRate: 0.05, // 5% of normal traffic
});
app.use("*", logger);A complete wide event contains:
{
// Auto-captured HTTP basics
request_id: "req_8bf7ec2d",
timestamp: "2024-01-15T10:23:45.612Z",
method: "GET",
path: "/api/users/123",
query_params: { filter: "active" },
status_code: 200,
duration_ms: 124,
client_ip: "192.168.1.42",
user_agent: "Mozilla/5.0...",
// User context (you add these)
user: {
id: "user_123",
tier: "premium",
account_age_days: 365
},
// Business context
business: {
endpoint: "get_user",
feature_flags: { new_ui: true }
},
// Infrastructure context
infra: {
region: "us-east-1",
k8s_pod: "api-123"
},
// Service context
service: {
version: "1.2.3",
git_sha: "abc123"
},
// Error details (if any)
error: {
type: "PaymentError",
code: "card_declined",
message: "Card declined by issuer"
}
}# Clone the repository
git clone <repo-url>
cd wide-span-logger
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Run tests
pnpm test# Build specific package
pnpm --filter wide-logger build
# Run tests for specific package
pnpm --filter wide-logger test
# Run Cloudflare Workers example
pnpm --filter hono-cf dev
# Run Node.js example
pnpm --filter hono-node devThe package includes comprehensive tests:
cd packages/wide-logger
pnpm testTest coverage:
- ✅ Basic middleware behavior
- ✅ Request/response capture
- ✅ Context API (user, business, infra, service)
- ✅ Error handling
- ✅ Sampling strategies
- ✅ Storage integration
The middleware uses Hono's createMiddleware for type safety:
export function wideLogger(options?: WideLoggerOptions): MiddlewareHandler {
return createMiddleware<{
Variables: {
"wide-logger": WideLoggerContext;
};
}>(async (c, next) => {
// ... implementation
});
}This gives full TypeScript support when accessing c.get('wide-logger').
Default tail sampling:
- Always keep: Errors (status >= 500)
- Always keep: Slow requests (> threshold)
- Always keep: VIP users (enterprise/premium tier)
- Sample: 5% of remaining traffic
You can provide a custom sampling function or adjust the thresholds.
Before (traditional logging):
2024-01-15 10:23:45 INFO Request started: GET /api/users/123
2024-01-15 10:23:45 DEBUG User authenticated: user_123
2024-01-15 10:23:46 DEBUG Database query: SELECT * FROM users...
2024-01-15 10:23:46 INFO Request completed in 124ms
After (wide event):
{
"timestamp": "2024-01-15T10:23:45.612Z",
"request_id": "req_abc123",
"method": "GET",
"path": "/api/users/123",
"status_code": 200,
"duration_ms": 124,
"user_id": "user_123",
"user_tier": "premium",
"endpoint": "get_user",
"db_queries": 1
}Benefits:
- Query by any field:
user_id = "user_123" - Aggregate easily:
AVG(duration_ms) GROUP BY endpoint - No correlation needed - everything in one place
- Works with modern columnar databases (ClickHouse, BigQuery)
- loggingsucks.com - The philosophy behind wide events
- Hono Documentation
- Stripe's Canonical Log Lines - Similar concept
Contributions are welcome! Please feel free to submit a Pull Request.
MIT
Built with ❤️ for the Hono community.