Examples
Real-world examples and patterns for using Satori.
Table of Contents
- Basic Logging
- React Integration
- Express.js Middleware
- State Management
- Error Tracking
- User Session Tracking
- Performance Monitoring
- Debug Mode
Basic Logging
Simple Application Logging
import { createSatori } from '@nisoku/satori-log';
const satori = createSatori({ logLevel: 'info' });
const logger = satori.createLogger('app');
function initializeApp() {
logger.info('Application starting');
try {
loadConfig();
logger.info('Configuration loaded');
connectDatabase();
logger.info('Database connected');
startServer();
logger.info('Server started', {
state: { port: 3000 }
});
} catch (error) {
logger.error('Failed to initialize', {
state: { error: error.message }
});
process.exit(1);
}
}
Structured Logging with Tags
const authLogger = satori.createLogger('auth');
function login(username: string) {
authLogger.info('Login attempt', {
tags: ['login', 'security'],
state: { username }
});
if (validateCredentials(username)) {
authLogger.info('Login successful', {
tags: ['login', 'security', 'success'],
state: { username }
});
return true;
}
authLogger.warn('Login failed', {
tags: ['login', 'security', 'failure'],
state: { username }
});
return false;
}
React Integration
Custom Hook for Logging
// useSatoriLogger.ts
import { useEffect, useRef, useMemo } from 'react';
import { createSatori, SatoriLogger } from '@nisoku/satori-log';
// Create a single Satori instance
const satori = createSatori({
logLevel: 'debug',
persistence: { enabled: true, adapter: 'localStorage' }
});
export function useSatoriLogger(scope: string): SatoriLogger {
const loggerRef = useRef<SatoriLogger>();
if (!loggerRef.current) {
loggerRef.current = satori.createLogger(scope);
}
return loggerRef.current;
}
// Track component lifecycle
export function useComponentLogger(componentName: string) {
const logger = useSatoriLogger(`ui.${componentName}`);
useEffect(() => {
logger.debug('Component mounted');
return () => logger.debug('Component unmounted');
}, []);
return logger;
}
Usage in Components
// UserProfile.tsx
import { useComponentLogger } from './useSatoriLogger';
function UserProfile({ userId }) {
const logger = useComponentLogger('UserProfile');
const handleEdit = () => {
logger.info('Edit profile clicked', {
tags: ['user-action'],
state: { userId }
});
// ...
};
return (
<div>
<button onClick={handleEdit}>Edit Profile</button>
</div>
);
}
Watching React State
import { useEffect, useState } from 'react';
import { useSatoriLogger } from './useSatoriLogger';
function ShoppingCart() {
const [items, setItems] = useState([]);
const logger = useSatoriLogger('cart');
// Watch cart changes
useEffect(() => {
const handle = logger.watch(
() => items.length,
'cartItemCount'
);
return () => handle.stop();
}, [items]);
// Watch for empty cart condition
useEffect(() => {
const handle = logger.when(
() => items.length === 0,
'cartEmpty',
() => {
logger.info('Cart is now empty', { tags: ['cart', 'state'] });
}
);
return () => handle.stop();
}, [items]);
return (/* ... */);
}
Express.js Middleware
Request Logging Middleware
import express from 'express';
import { createSatori } from '@nisoku/satori-log';
const satori = createSatori({
rateLimit: { enabled: true, maxEventsPerSecond: 1000 }
});
const logger = satori.createLogger('http');
const requestLogger = (req, res, next) => {
const startTime = Date.now();
const requestId = generateRequestId();
logger.info(`${req.method} ${req.path}`, {
tags: ['request', 'incoming'],
state: {
requestId,
method: req.method,
path: req.path,
query: req.query,
ip: req.ip
}
});
// Capture the request ID for causal linking
req.satoriRequestId = logger.lastEventId;
// Log response
res.on('finish', () => {
const duration = Date.now() - startTime;
const level = res.statusCode >= 400 ? 'error' : 'info';
logger.log(level, `${req.method} ${req.path} ${res.statusCode}`, {
tags: ['request', 'response'],
cause: req.satoriRequestId,
state: {
requestId,
statusCode: res.statusCode,
duration
}
});
});
next();
};
const app = express();
app.use(requestLogger);
Error Handling Middleware
const errorLogger = satori.createLogger('error');
const errorHandler = (err, req, res, next) => {
errorLogger.error(err.message, {
tags: ['error', 'unhandled'],
cause: req.satoriRequestId,
state: {
stack: err.stack,
path: req.path,
method: req.method
}
});
res.status(500).json({ error: 'Internal Server Error' });
};
app.use(errorHandler);
State Management
Redux Integration
// satoriMiddleware.ts
import { createSatori } from '@nisoku/satori-log';
const satori = createSatori();
const logger = satori.createLogger('redux');
export const satoriMiddleware = store => next => action => {
const prevState = store.getState();
logger.debug('Action dispatched', {
tags: ['redux', 'action'],
state: { type: action.type, payload: action.payload }
});
const result = next(action);
const nextState = store.getState();
// Log significant state changes
if (JSON.stringify(prevState) !== JSON.stringify(nextState)) {
logger.info('State changed', {
tags: ['redux', 'state-change'],
cause: logger.lastEventId,
state: {
action: action.type,
// Include only changed parts of state
changes: computeStateDiff(prevState, nextState)
}
});
}
return result;
};
Zustand Integration
import create from 'zustand';
import { createSatori } from '@nisoku/satori-log';
const satori = createSatori();
const logger = satori.createLogger('store');
// Logging middleware for Zustand
const logMiddleware = (config) => (set, get, api) => {
return config(
(args) => {
const prevState = get();
set(args);
const nextState = get();
logger.debug('Store updated', {
tags: ['zustand', 'state-change'],
state: { prevState, nextState }
});
},
get,
api
);
};
const useStore = create(
logMiddleware((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 }))
}))
);
Error Tracking
Global Error Handler
const errorLogger = satori.createLogger('errors');
// Browser
window.onerror = (message, source, line, column, error) => {
errorLogger.error(String(message), {
tags: ['error', 'uncaught', 'window'],
state: {
source,
line,
column,
stack: error?.stack
}
});
};
window.onunhandledrejection = (event) => {
errorLogger.error('Unhandled Promise rejection', {
tags: ['error', 'promise', 'unhandled'],
state: {
reason: event.reason?.message || String(event.reason),
stack: event.reason?.stack
}
});
};
// Node.js
process.on('uncaughtException', (error) => {
errorLogger.error(error.message, {
tags: ['error', 'uncaught', 'fatal'],
state: { stack: error.stack }
});
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
errorLogger.error('Unhandled Promise rejection', {
tags: ['error', 'promise', 'unhandled'],
state: { reason: String(reason) }
});
});
Error Boundary (React)
import { Component, ErrorInfo } from 'react';
import { createSatori } from '@nisoku/satori-log';
const satori = createSatori();
const errorLogger = satori.createLogger('react.error');
class SatoriErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
errorLogger.error(error.message, {
tags: ['error', 'react', 'boundary'],
state: {
stack: error.stack,
componentStack: errorInfo.componentStack
}
});
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
User Session Tracking
Session Logger
const sessionLogger = satori.createLogger('session');
class SessionTracker {
private sessionId: string;
private userId?: string;
startSession(userId?: string) {
this.sessionId = generateSessionId();
this.userId = userId;
sessionLogger.info('Session started', {
tags: ['session', 'start'],
state: {
sessionId: this.sessionId,
userId,
userAgent: navigator.userAgent,
screenSize: `${window.innerWidth}x${window.innerHeight}`
}
});
// Track visibility changes
document.addEventListener('visibilitychange', this.onVisibilityChange);
window.addEventListener('beforeunload', this.onUnload);
}
private onVisibilityChange = () => {
sessionLogger.info(`Page ${document.hidden ? 'hidden' : 'visible'}`, {
tags: ['session', 'visibility'],
state: { sessionId: this.sessionId }
});
};
private onUnload = () => {
sessionLogger.info('Session ending', {
tags: ['session', 'end'],
state: { sessionId: this.sessionId }
});
};
trackPageView(path: string) {
sessionLogger.info('Page view', {
tags: ['session', 'navigation'],
state: {
sessionId: this.sessionId,
path,
referrer: document.referrer
}
});
}
trackUserAction(action: string, details?: object) {
sessionLogger.info(action, {
tags: ['session', 'user-action'],
state: {
sessionId: this.sessionId,
...details
}
});
}
}
const tracker = new SessionTracker();
tracker.startSession();
Performance Monitoring
Performance Logger
const perfLogger = satori.createLogger('performance');
class PerformanceMonitor {
private marks: Map<string, number> = new Map();
mark(name: string) {
this.marks.set(name, performance.now());
}
measure(name: string, startMark: string) {
const startTime = this.marks.get(startMark);
if (!startTime) return;
const duration = performance.now() - startTime;
perfLogger.info(`${name}: ${duration.toFixed(2)}ms`, {
tags: ['performance', 'timing'],
state: {
name,
startMark,
duration
}
});
return duration;
}
measureAsync<T>(name: string, fn: () => Promise<T>): Promise<T> {
const mark = `${name}_${Date.now()}`;
this.mark(mark);
return fn().then(result => {
this.measure(name, mark);
return result;
}).catch(error => {
this.measure(`${name}_error`, mark);
throw error;
});
}
}
const perf = new PerformanceMonitor();
// Usage
perf.mark('fetch_start');
const data = await fetchData();
perf.measure('api_fetch', 'fetch_start');
// Or with async wrapper
const result = await perf.measureAsync('api_call', () => fetchData());
Resource Timing
// Track slow resources
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 1000) { // Slow resource (>1s)
perfLogger.warn('Slow resource', {
tags: ['performance', 'slow', 'resource'],
state: {
name: entry.name,
type: entry.entryType,
duration: entry.duration,
size: entry.transferSize
}
});
}
}
});
observer.observe({ entryTypes: ['resource'] });
Debug Mode
Conditional Debugging
const DEBUG = localStorage.getItem('satori_debug') === 'true';
const satori = createSatori({
logLevel: DEBUG ? 'debug' : 'info',
persistence: {
enabled: DEBUG,
adapter: 'console'
}
});
// Enable debug mode from console:
// localStorage.setItem('satori_debug', 'true')
// location.reload()
Debug Utilities
// Add to window for console access
if (typeof window !== 'undefined') {
(window as any).satoriDebug = {
satori,
getEvents: () => satori.getEvents(),
getMetrics: () => satori.getMetrics(),
filterByLevel: (level: string) =>
satori.getEvents().filter(e => e.level === level),
filterByScope: (scope: string) =>
satori.getEvents().filter(e => e.scope.startsWith(scope)),
exportEvents: () => {
const blob = new Blob(
[JSON.stringify(satori.getEvents(), null, 2)],
{ type: 'application/json' }
);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `satori-events-${Date.now()}.json`;
a.click();
}
};
console.log('Satori debug utilities available at window.satoriDebug');
}
Query Events from Console
// In browser console:
satoriDebug.getEvents()
satoriDebug.filterByLevel('error')
satoriDebug.filterByScope('auth')
satoriDebug.getMetrics()
satoriDebug.exportEvents()