@everystack/logging
v0.2.0
Published
Structured logging, crash reporting, and analytics for Expo apps
Readme
@everystack/logging
Structured logging, crash reporting, and observability for Expo apps. Covers the full stack — server-side log ingestion, client-side event capture, source map symbolication, webhook alerts, and admin dashboard screens.
Install
pnpm add @everystack/logging drizzle-ormEntry Points
| Import | Description |
|--------|-------------|
| @everystack/logging | Server: handler, service, webhooks, RPC helpers |
| @everystack/logging/core | Core: Logger, EventBuffer, Redactor, Sampler, Tracing |
| @everystack/logging/client | Client: providers, crash capture, performance monitoring |
| @everystack/logging/schema | Drizzle tables (events, crashes, issues, breadcrumbs) |
| @everystack/logging/admin | Admin dashboard screens and components |
Core Logger
Structured logger with 5 levels, child loggers, and trace correlation:
import { createLogger } from '@everystack/logging/core';
const logger = createLogger({
level: 'info', // 'debug' | 'info' | 'warn' | 'error' | 'fatal'
source: 'api',
traceId: requestId,
defaultData: { service: 'my-app' },
onEvent: (event) => {
// Send to your log storage, stdout, etc.
console.log(JSON.stringify(event));
},
});
logger.info('Request received', { method: 'GET', path: '/api/users' });
logger.warn('Rate limit approaching', { remaining: 5 });
logger.error('Database query failed', new Error('Connection timeout'), { query: 'SELECT...' });
// Child logger inherits parent config + adds context
const childLogger = logger.child({
source: 'auth',
defaultData: { userId: '123' },
});
childLogger.info('Login successful'); // Includes { service: 'my-app', userId: '123' }Log Event Structure
interface LogEvent {
id: string;
timestamp: number;
level: 'debug' | 'info' | 'warn' | 'error' | 'fatal';
message: string;
data?: Record<string, unknown>;
error?: { name: string; message: string; stack?: string; cause?: ErrorInfo };
source?: string;
traceId?: string;
spanId?: string;
}Server: HTTP Handler
Ingest logs from clients and manage observability data:
import { createLoggingHandler, InMemoryLogStorage } from '@everystack/logging';
const handler = createLoggingHandler({
storage: new InMemoryLogStorage(), // Or a Drizzle-backed store
basePath: '/api/logging',
verifyToken: async (token) => {
const payload = await verifyJWT(token);
return payload ? { userId: payload.sub } : null;
},
webhooks: [
{ url: 'https://hooks.slack.com/...', events: ['error', 'fatal'] },
],
redaction: {
fields: ['password', 'token', 'secret', 'authorization'],
patterns: [/\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g], // Credit cards
},
sampling: {
rates: { debug: 0.1, info: 0.5, warn: 1.0, error: 1.0, fatal: 1.0 },
},
});Server: Logging Service
Programmatic access to log storage:
import { LoggingService, InMemoryLogStorage } from '@everystack/logging';
const service = new LoggingService(new InMemoryLogStorage());
// Query logs
const logs = await service.query({
level: 'error',
source: 'api',
startTime: new Date('2025-01-01'),
limit: 100,
});
// Get aggregations
const aggregation = await service.aggregate({
groupBy: 'level',
startTime: new Date('2025-01-01'),
});Server: Source Map Symbolication
Resolve minified stack traces to original source:
import { parseStackTrace, formatSymbolicatedStack, InMemorySourceMapStore } from '@everystack/logging';
const store = new InMemorySourceMapStore();
await store.put('main.js.map', sourceMapContent);
const frames = parseStackTrace(minifiedStack);
const symbolicated = await store.symbolicate(frames);
const readable = formatSymbolicatedStack(symbolicated);Server: Webhooks
Alert on error events:
import { createWebhookManager } from '@everystack/logging';
const webhooks = createWebhookManager([
{ url: 'https://hooks.slack.com/services/...', events: ['error', 'fatal'] },
{ url: 'https://discord.com/api/webhooks/...', events: ['fatal'] },
]);
// Called automatically by the handler, or manually:
await webhooks.notify(logEvent);Core Utilities
Event Buffer
Batch log events before sending to reduce network requests:
import { createEventBuffer } from '@everystack/logging/core';
const buffer = createEventBuffer({
maxSize: 50,
flushInterval: 5000,
onFlush: async (events) => {
await fetch('/api/logging/batch', {
method: 'POST',
body: JSON.stringify(events),
});
},
});
buffer.add(event);
await buffer.flush(); // Force flushRedactor
Strip sensitive data from log events:
import { createRedactor } from '@everystack/logging/core';
const redactor = createRedactor({
fields: ['password', 'token', 'secret'],
patterns: [/\b\d{16}\b/g], // Credit card numbers
replacement: '[REDACTED]',
});
const safe = redactor.redact(logEvent);Sampler
Control log volume with per-level sampling:
import { createSampler } from '@everystack/logging/core';
const sampler = createSampler({
rates: { debug: 0.01, info: 0.1, warn: 1.0, error: 1.0, fatal: 1.0 },
});
if (sampler.shouldSample(event)) {
// Send event
}Tracing
Request-scoped trace/span propagation:
import { createTraceContext, generateTraceId, generateSpanId } from '@everystack/logging/core';
const traceId = generateTraceId();
const spanId = generateSpanId();
const logger = createLogger({
traceId,
spanId,
onEvent: (event) => { /* event includes traceId, spanId */ },
});Client SDK
React Native providers for automatic event capture:
import {
LoggingProvider,
ErrorBoundary,
CrashCapture,
PerformanceMonitoring,
NetworkMonitoring,
} from '@everystack/logging/client';The client SDK captures:
- Crashes and unhandled exceptions
- Performance metrics (render times, interactions)
- Network request monitoring
- Navigation tracking
- Memory profiling
- Connectivity state changes
- App lifecycle events
Admin Dashboard
Admin screens for observability (requires @everystack/admin):
import { ObservabilityDashboard, IssueDetailScreen } from '@everystack/logging/admin';Server-side RPC Helpers
import { createIssuesRpc, createHealthRpc, createDashboardDataRpc } from '@everystack/logging';
// Wire into your handler's RPC
const handler = createHandler(db, schema, {
rpc: {
issues: createIssuesRpc(loggingService),
health: createHealthRpc(loggingService),
'dashboard-data': createDashboardDataRpc(loggingService),
},
});Schema
Add logging tables to your Drizzle migrations:
import { events, crashes, issues, breadcrumbs } from '@everystack/logging/schema';Peer Dependencies
| Package | Version | Required |
|---------|---------|----------|
| drizzle-orm | >=0.35.0 | For Drizzle storage |
| react | >=18.0.0 | Client SDK |
| react-native | >=18.0.0 | Client SDK |
| @react-navigation/native | >=6.0.0 | Navigation tracking |
| @everystack/admin | >=0.1.0 | Admin screens |
| d3-array, d3-scale, etc. | optional | Admin charts |
Part of everystack — a self-hosted application stack for Expo apps on AWS.
License
MIT
