hazo_logs
v2.1.1
Published
Logger for hazo packages - Winston wrapper with singleton pattern
Maintainers
Readme
hazo_logs
A Winston-based logging library for Node.js/Next.js applications with a built-in log viewer UI.
Log Level Convention
hazo_logs uses a 5-level system (from design spec D-015). Each level has a defined purpose — use the narrowest one that fits.
| Level | When to use |
|-------|-------------|
| TRACE | High-frequency operational noise: polling ticks, cache hits, ping responses. Off in production. |
| DEBUG | Operational detail useful when actively debugging a problem. Off in production. |
| INFO | State changes and user-facing events (login, job started, order placed). Default level in dev. |
| WARN | Recoverable problems: rate-limited, retrying, degraded-mode fallback. Default level in production. |
| ERROR | Unrecovered failures that require attention: unhandled exception, data loss, external service down. |
Default levels
| Environment | Default level |
|-------------|--------------|
| development / test (NODE_ENV) | info |
| production / staging (HAZO_ENV or NODE_ENV) | warn |
The resolver checks HAZO_ENV first, then NODE_ENV, then falls back to development.
Per-namespace overrides (INI config)
Fine-tune the level for a specific package or sub-namespace in config/hazo_logs_config.ini:
[log.overrides]
; Package-level — matches hazo_chat and any hazo_chat/* sub-namespace
hazo_chat = warn
; Sub-namespace — more specific, takes precedence over the package-level override
hazo_chat/poll = error
; Prefix match — matches any hazo_files/* sub-namespace
hazo_files/ = debugPrecedence (highest wins): exact match > prefix match > package-level > global default.
You can also use environment variables to override INI config at runtime:
HAZO_LOGS_LOG_OVERRIDES_hazo_files=debug node server.jsSmart logging helpers
Use these instead of raw log.info loops to reduce noise without losing signal:
log.heartbeat(name, { status, ...fields }, opts?)
Emits only on state change. Silent on repeated same-status calls. Use for "is it still working?" checks on polled resources (DB connectivity, external API, queue depth).
// Emits once when connected, once on disconnect, once when it reconnects
log.heartbeat('db.primary', { status: connected ? 'ok' : 'down', latencyMs });log.sampled(level, msg, fields?, { sample })
Emits with the given probability. Default sample rate is 0.1 (10%). Use for high-frequency paths where you want occasional visibility without flooding logs.
// Logs roughly 1 in 10 cache hits
log.sampled('debug', 'cache.hit', { key }, { sample: 0.1 });log.dedupe(level, msg, fields?, { window, key? })
Collapses identical-keyed messages within a time window. The first occurrence emits immediately; at window end, if more than one occurred, emits a summary "msg (×N over Xs)". Use when the same warning fires rapidly (e.g. validation errors on bulk import).
// First occurrence emits; subsequent in the same minute are collapsed
log.dedupe('warn', 'validation.failed', { field }, { window: '1m' });Features
- Structured Logging: Winston wrapper with singleton pattern for consistent logging across your app
- Package Tagging: Automatically tag logs by package/module name
- Daily Rotation: Automatic log file rotation with configurable retention
- Session Tracking: Track logs across async operations with sessionId and reference
- Fatal Level:
logger.fatal()for process-critical events, aboveerrorin severity - Context Fields: Attach
scope(dotted operation path) anduserIdto all logs via ALS context - Error Handling Surface:
withErrorHandler,installGlobalErrorHandlers,registerAlertTrigger,<ErrorBoundary />,useErrorReporter— structured error capture with correlationId tracking - Built-in UI: React-based log viewer with filtering (including
correlationIddeep-link), sorting, and pagination - Zero Config: Works out of the box with sensible defaults
- Minimal Integration: Add log viewer to your app with just 2 files (~6 lines of code)
Quick Start
Installation
npm install hazo_logsFor the UI components, also install peer dependencies:
npm install hazo_logs hazo_core next react react-dom hazo_uiBasic Usage (Logging Only)
1. Create a logger in your code:
import { createLogger } from 'hazo_logs';
const logger = createLogger('my-package');
logger.info('Application started');
logger.warn('This is a warning', { userId: 123 });
logger.error('Something went wrong', { error: 'details' });
logger.debug('Debug information');2. (Optional) Configure logging:
Create a config/ directory and add hazo_logs_config.ini:
mkdir -p config
cp node_modules/hazo_logs/config/hazo_logs_config.example.ini config/hazo_logs_config.iniOr create config/hazo_logs_config.ini manually:
[hazo_logs]
log_directory = ./logs
log_level = info
enable_console = true
enable_file = true
max_file_size = 20m
max_files = 14dThat's it! Logs will be written to ./logs/hazo-YYYY-MM-DD.log files.
Add Log Viewer UI (Optional)
Add a log viewer to your Next.js app with just 2 files:
1. Create API route (app/api/logs/route.ts):
import { createLogApiHandler } from 'hazo_logs/ui/server';
const handler = createLogApiHandler();
// GET for log viewer, POST for client-side logging
export const { GET, POST } = handler;2. Create UI page (app/logs/page.tsx):
'use client';
import { LogViewerPage } from 'hazo_logs/ui';
export default function LogsPage() {
return <LogViewerPage apiBasePath="/api/logs" title="System Logs" />;
}3. Configure Tailwind CSS (tailwind.config.ts):
import type { Config } from 'tailwindcss';
import { tailwindSafelist, tailwindContentPath } from 'hazo_logs/tailwind';
const config: Config = {
content: [
'./src/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
tailwindContentPath, // Add hazo_logs components
],
safelist: tailwindSafelist, // Prevent purging of dynamic classes
// ... rest of your config
};
export default config;Visit /logs in your app to view logs!
Client-Side Logging (Browser)
For logging from client components (browser), use the client logger.
1. Configure global defaults (recommended):
Set up global configuration once at app initialization. This ensures all client loggers (including those from dependency packages) use the correct API endpoint:
// app/providers.tsx or lib/hazo-init.ts
'use client';
import { configureClientLogger } from 'hazo_logs/ui';
// Configure once at app startup
configureClientLogger({
apiBasePath: '/api/logs', // Required: your log API endpoint
minLevel: 'info', // Optional: minimum log level
});2. Create loggers in your components:
'use client';
import { createClientLogger } from 'hazo_logs/ui';
// No need to specify apiBasePath - inherits from global config
const logger = createClientLogger({
packageName: 'my-app-client',
});
logger.info('User clicked button', { buttonId: 'submit' });
logger.error('Failed to load data', { error: err.message });Why use global configuration?
- Dependency packages (like
hazo_collab_forms) automatically use your configured endpoint - Avoids 404 errors from loggers using the wrong default path
- Single place to configure all client logging settings
Important Notes
Server/Client Import Paths
hazo_logs provides different import paths for different environments:
| Import Path | Environment | Use Case |
|-------------|-------------|----------|
| hazo_logs | Universal | Basic logging (console on client, file on server) |
| hazo_logs/server | Server-only | Full logging with context, sessions, log reader |
| hazo_logs/ui | Client | React components, createClientLogger() |
| hazo_logs/ui/server | Server-only | API handlers for log viewer |
// Universal - works everywhere (recommended for libraries)
import { createLogger } from 'hazo_logs';
// Server-only - full capabilities (file logging, context, sessions)
import { createLogger, runWithLogContext } from 'hazo_logs/server';
// Client components - sends logs to server API
import { createClientLogger } from 'hazo_logs/ui';
// API routes - create log viewer endpoints
import { createLogApiHandler } from 'hazo_logs/ui/server';Note: The hazo_logs/server and hazo_logs/ui/server imports use the server-only package and will throw an error if accidentally imported in client bundles. This prevents Next.js build errors from Node.js APIs (fs, async_hooks) being bundled for the browser.
Next.js 16+ Turbopack Compatibility: As of v1.0.10, hazo_logs uses conditional exports in package.json to automatically serve a client-safe bundle to browser environments. When bundlers (Next.js Turbopack, Webpack, etc.) resolve hazo_logs, they automatically receive:
- Browser/Client: A lightweight console-only logger with zero Node.js dependencies
- Node.js/Server: The full winston-based logger with file transports
This eliminates "Can't resolve 'fs'" errors completely without requiring any special import paths for basic logging.
Tailwind CSS Setup Required
The log viewer UI uses dynamic Tailwind classes that would be purged during build.
Tailwind v4 Setup
Add the @source directive to your globals.css to enable Tailwind to scan hazo_logs classes:
@import "tailwindcss";
/* Required: Enable Tailwind to scan hazo_logs package classes */
@source "../node_modules/hazo_logs/dist";Why this is needed: Tailwind v4's JIT compiler doesn't scan node_modules/ by default. Without this directive, hover states and interactive styles will be invisible.
Tailwind v3 Setup
Configure both the content path and safelist:
import { tailwindSafelist, tailwindContentPath } from 'hazo_logs/tailwind';
export default {
content: [
// your paths...
tailwindContentPath,
],
safelist: tailwindSafelist,
};Alternatively, use the preset:
import { hazoLogsPreset } from 'hazo_logs/tailwind';
export default {
presets: [hazoLogsPreset],
content: [
// your paths...
'./node_modules/hazo_logs/dist/**/*.{js,jsx}',
],
};Advanced Usage
Session and Reference Tracking
Track logs across async operations (server-only feature):
import { createLogger, runWithLogContext } from 'hazo_logs/server';
const logger = createLogger('auth');
async function handle_request(req) {
await runWithLogContext(
{
sessionId: req.sessionId,
reference: `user-${req.userId}`,
depth: 0,
scope: 'auth.login', // Fine-grained operation scope (dotted notation)
userId: req.userId, // User ID propagated to all log entries
},
async () => {
logger.info('Request started'); // Automatically includes all context fields
await process_request(req);
logger.info('Request completed');
}
);
}Available context fields: sessionId, reference, depth, scope, userId. All fields present in the ALS context are automatically written to every LogEntry produced within the callback.
### Embedded Log Viewer
Use the log viewer as a sidebar component:
```typescript
import { LogViewerPage } from 'hazo_logs/ui';
function AdminPanel() {
return (
<div className="flex">
<Sidebar />
<div className="flex-1">
<LogViewerPage
apiBasePath="/api/logs"
title="Recent Logs"
embedded={true}
showHeader={true}
className="h-screen"
/>
</div>
</div>
);
}hazo_debug Integration
Pipe all log entries to hazo_debug's Debug tab using the on_log callback:
'use client';
import { createClientLogger } from 'hazo_logs/ui';
import { use_debug_log } from 'hazo_debug/client';
// In a React component:
const { log_debug } = use_debug_log('MyApp');
const logger = createClientLogger({
packageName: 'hazo_files',
on_log: log_debug, // Pipes all logs to hazo_debug's Debug tab
});
logger.info('Upload started', { file: 'report.pdf' });
// → Appears in both server logs AND hazo_debug's Debug tabThe on_log callback also works with the basic ConsoleLogger:
import { ConsoleLogger } from 'hazo_logs';
const logger = new ConsoleLogger('my-package', 'debug', log_debug);Child loggers inherit on_log from their parent unless overridden.
hazo_debug Server Dual-Write Endpoint
hazo_debug has a dual-write feature that posts debug console entries to a server endpoint for persistence. Set up the endpoint:
1. Create API route (app/api/hazo_logs/client-log/route.ts):
import { createClientLogHandler } from 'hazo_logs/ui/server';
const handler = createClientLogHandler();
export const { GET, POST } = handler;2. POST entries (done automatically by hazo_debug when hazo_logs_endpoint is configured):
{
"level": "debug",
"source": "ComponentName",
"message": "Something happened",
"data": { "key": "value" },
"timestamp": 1743303600000,
"origin": "hazo_debug_console"
}3. Query entries (for Claude/LLMs to read recent debug logs):
GET /api/hazo_logs/client-log?limit=50&level=error&since=1743303600000Returns entries with origin: 'hazo_debug_console' in reverse chronological order.
Custom Authentication
Protect your log viewer with authentication:
import { createLogApiHandler, withLogAuth } from 'hazo_logs/ui/server';
const handler = createLogApiHandler();
const authHandler = withLogAuth(handler, async (request) => {
const session = await getSession(request);
return session?.user?.role === 'admin';
});
export const { GET } = authHandler;Individual Components
Import components separately for custom layouts:
import {
LogTable,
LogTimeline,
LogPagination,
LogLevelBadge,
} from 'hazo_logs/ui';
// Build your own custom log viewer
function CustomLogViewer() {
const [logs, setLogs] = useState([]);
return (
<div>
<LogTable logs={logs} isLoading={false} />
<LogPagination
currentPage={1}
totalPages={10}
pageSize={50}
total={500}
onPageChange={setPage}
onPageSizeChange={setPageSize}
/>
</div>
);
}Configuration Reference
Create a config/ directory in your project root and add hazo_logs_config.ini:
mkdir -p config
cp node_modules/hazo_logs/config/hazo_logs_config.example.ini config/hazo_logs_config.iniConfiguration options:
[hazo_logs]
# Core Logging Settings
log_directory = ./logs # Where to write log files
log_level = info # Minimum level: error, warn, info, debug
enable_console = true # Log to console
enable_file = true # Log to files with rotation
max_file_size = 20m # Max size per file (supports k, m, g)
max_files = 14d # Retention period (e.g., 14d = 14 days)
date_pattern = YYYY-MM-DD # Date format for log filenames
# Log Viewer UI Settings
enable_log_viewer = true # Enable the API endpoints
log_viewer_page_size = 50 # Results per page
log_viewer_max_results = 1000 # Max entries to load for filteringIf no config file is found, sensible defaults are used.
API Reference
Core Exports (hazo_logs)
createLogger(packageName: string): Logger
Create a package-specific logger.
const logger = createLogger('my-package');
logger.info('Hello world');Logger Interface
interface Logger {
fatal(message: string, data?: Record<string, unknown>): void;
error(message: string, data?: Record<string, unknown>): void;
warn(message: string, data?: Record<string, unknown>): void;
info(message: string, data?: Record<string, unknown>): void;
debug(message: string, data?: Record<string, unknown>): void;
}runWithLogContext(context: LogContext, fn: () => Promise<T>): Promise<T>
Run code with log context (sessionId, reference, depth).
readLogs(options): Promise<LogQueryResult>
Read logs from files with filtering and pagination (server-side only).
Options include level, package, executionId, sessionId, reference, origin, search (top-level fields), plus two nested-data filters added in 2.1.0:
correlationId?: string[]— matcheslog.data.correlationId(SDK-enriched on every collect run entry)runId?: string[]— matcheslog.data.runId
getUniqueCorrelationIds(logDirectory?, date?): Promise<string[]>
Return all distinct correlationId values found in log.data for the given date. Use with ?action=correlationIds in the API handler or import directly from hazo_logs/server.
UI Exports (hazo_logs/ui)
configureClientLogger(config: ClientLoggerGlobalConfig): void
Configure global defaults for all client loggers. Call once at app initialization.
configureClientLogger({
apiBasePath: '/api/logs', // Required
minLevel: 'info', // Optional
consoleOutput: true, // Optional
batchMode: false, // Optional
batchInterval: 5000, // Optional (ms)
});getClientLoggerConfig(): ClientLoggerGlobalConfig | undefined
Get current global configuration (returns undefined if not configured).
isClientLoggerConfigured(): boolean
Check if global configuration has been set.
createClientLogger(config?: ClientLoggerConfig): ClientLogger
Create a client-side logger. Inherits from global config if set.
const logger = createClientLogger({
packageName: 'my-component', // Tag for this logger
sessionId: 'sess_123', // Optional session tracking
reference: 'user_456', // Optional reference tracking
});ErrorBoundary
React class component that catches render errors, generates a correlationId, and logs to the server via the client logger pipeline. The fallback render prop receives { error, correlationId, retry }.
import { ErrorBoundary } from 'hazo_logs/ui';
<ErrorBoundary
fallback={({ error, correlationId, retry }) => (
<div>
<p>Something went wrong. Reference: {correlationId}</p>
<button onClick={retry}>Retry</button>
</div>
)}
>
<MyComponent />
</ErrorBoundary>Props: fallback (required), onError?: (error, correlationId) => void, apiBasePath?: string, packageName?: string.
React 19 + Next.js 16 note: Wrap state changes that may cause throws inside startTransition() to prevent the dev overlay from covering the error fallback. React 19 dev mode re-reports caught errors globally; transitions suppress this.
useErrorReporter(options?)
React hook for manual error reporting. Each method returns a correlationId string. Auto-attaches route_path (window.location.pathname) and user_agent to every entry.
import { useErrorReporter } from 'hazo_logs/ui';
function MyComponent() {
const { reportError } = useErrorReporter({ userId: session?.user?.id });
const handle_action = async () => {
try {
await risky_operation();
} catch (err) {
const id = reportError(err, { component: 'MyComponent' });
show_toast(`Something went wrong. Reference: ${id}`);
}
};
}Methods: reportError(error, context?), reportWarning(message, context?), reportInfo(message, context?). Pass options.userId to attach user_id to all entries — no hazo_auth import required.
LogViewerPage
Main log viewer component.
Props:
apiBasePath?: string- API endpoint path (default:/api/logs)title?: string- Page title (default:Log Viewer)className?: string- Additional CSS classesembedded?: boolean- Embedded mode (default:false)showHeader?: boolean- Show header (default:true)
Individual Components
LogTable- Table view of logsLogTimeline- Timeline view with groupingLogPagination- Pagination controlsLogLevelBadge- Badge for log levels
Server Exports (hazo_logs/ui/server)
createLogApiHandler(config?): LogApiHandler
Create Next.js API route handler for the log viewer.
Options:
logDirectory?: string- Override default log directory
Returns:
{
GET: (request: Request) => Promise<Response>, // Log viewer queries
POST: (request: Request) => Promise<Response>, // Client-side log ingestion
}createClientLogHandler(config?): LogApiHandler
Create Next.js API route handler for hazo_debug_console dual-write entries.
Options:
logDirectory?: string- Override default log directory
Returns:
{
GET: (request: Request) => Promise<Response>, // Query debug console entries
POST: (request: Request) => Promise<Response>, // Receive debug console entries
}withLogAuth(handler, authCheck): LogApiHandler
Wrap handler with authentication.
const authHandler = withLogAuth(handler, async (request) => {
// Return true to allow access, false to deny
return await isAdmin(request);
});Error Handling Surface (hazo_logs/server)
withErrorHandler(handler)
Wraps a Next.js App Router handler with structured error handling. Generates a correlationId (UUID), sets X-Correlation-Id on error responses, catches HandledError duck-typed throws returning structured JSON with the correct HTTP status, and catches generic Error logging at fatal and returning 500 (internal message never reaches the client).
import { withErrorHandler } from 'hazo_logs/server';
export const GET = withErrorHandler(async (req: Request) => {
// Throw HandledError for structured 4xx/5xx responses:
throw { code: 'not_found', statusCode: 404, userMessage: 'Resource not found' };
// Throw Error for 500 — message never reaches client:
throw new Error('db connection failed');
});Error response shape:
{ "error": "Resource not found", "code": "not_found", "correlationId": "uuid" }Level routing: 5xx throws → error, 4xx throws → warn, generic Error → fatal.
installGlobalErrorHandlers(options?)
Installs process.on('unhandledRejection') and process.on('uncaughtException'). Both log at fatal with a correlationId, call optional onFatal, then wait 500ms and call process.exit(1). Call once at process startup (e.g. instrumentation.ts).
import { installGlobalErrorHandlers } from 'hazo_logs/server';
installGlobalErrorHandlers({
onFatal: async (err) => {
await alertOpsTeam(err);
},
exitOnUncaught: true, // default
});Options: onFatal?: (err: Error) => void | Promise<void>, exitOnUncaught?: boolean.
registerAlertTrigger(config)
Sliding-window alert trigger via hazo_notify (optional peer dep). Hooks into HazoLogger.addLogHook and fires when a match threshold is crossed within a time window. Returns an unregister function.
import { registerAlertTrigger } from 'hazo_logs/server';
const unregister = registerAlertTrigger({
id: 'payment_errors',
match: { level: 'error', scopePrefix: 'payment.' },
threshold: { count: 3, windowMs: 60_000 },
notify: {
kind: 'ops.payment_error',
scopeId: 'my_app',
recipientUserIds: ['ops-user-uuid'],
surfaces: ['email'],
},
});
// Call unregister() to stop listeningmatch supports: level, scopePrefix, message_contains. Set threshold.count to 0 to fire on every match. recipientUserIds can be a static array or (entry) => string[].
Live tail (v1.1+)
tailLogs watches the active rotating log file and emits new entries as they are written. Filters mirror readLogs. Use it directly for in-process consumers:
import { tailLogs } from "hazo_logs/server";
const ctrl = new AbortController();
for await (const entry of tailLogs({ reference: ["job-123"], signal: ctrl.signal })) {
console.log(entry.message);
}
// later: ctrl.abort();For SSE-over-HTTP (admin UIs, dashboards), use createLogStreamApiHandler:
import { createLogStreamApiHandler } from "hazo_logs/ui/server";
// Next.js app router
export const GET = createLogStreamApiHandler({ reference: ["job-123"] });Clients consume via the standard EventSource API. The stream closes when the client disconnects.
Log File Format
Logs are stored as newline-delimited JSON:
{"timestamp":"2025-12-18T10:30:45.123Z","level":"info","package":"auth","message":"User logged in","filename":"auth.ts","line":42,"executionId":"2025-12-18-10:30:45-1234","sessionId":"sess_abc123","reference":"user-456","data":{"userId":456}}Fields:
timestamp- ISO 8601 timestamplevel- Log level (fatal, error, warn, info, debug)package- Package name from createLoggermessage- Log messagefilename- Source file nameline- Line number in source fileexecutionId- Unique ID per server startsessionId- Optional session ID from contextreference- Optional reference from contextdepth- Optional call depth from contextscope- Optional operation scope from context (dotted notation, e.g."payment.checkout.refund")userId- Optional user ID from ALS contextdata- Optional additional data
UI Screenshots
Table View
Filter and sort logs by level, package, session, execution ID, or search text.
Timeline View
Visualize logs grouped by package or session with hierarchical depth display.
Examples
See the test-app/ directory for a complete working example.
Troubleshooting
Config file not found
If you see [HazoLog] Config file 'config/hazo_logs_config.ini' not found, the library will still work with defaults. The warning shows the paths that were searched. To configure custom settings:
mkdir -p config
cp node_modules/hazo_logs/config/hazo_logs_config.example.ini config/hazo_logs_config.iniLogs not appearing in UI
- Check that you're looking at the correct date (UI defaults to today)
- Verify logs are being written to the
log_directoryconfigured - Check browser console for API errors
Styles broken in log viewer
- Ensure
tailwindContentPathis in your Tailwindcontentarray - Ensure
tailwindSafelistis in your Tailwindsafelistarray - Rebuild your project after config changes (
npm run build)
Import errors in client components
Error: Module not found: Can't resolve 'async_hooks'
Error: Module not found: Can't resolve 'fs'This error occurred in earlier versions when server-only code was imported in client components. As of v1.0.10, this is fully fixed - the library now uses conditional exports in package.json, so bundlers automatically receive a client-safe bundle with zero Node.js dependencies.
If you still see this error:
- Upgrade hazo_logs: Run
npm update hazo_logsto get v1.0.10 or later - Clear build cache: Run
rm -rf .nextand rebuild - For logging in client components: The base
hazo_logsimport now works automatically (returns console logger on client) - For client logging that posts to server: Use
createClientLoggerfromhazo_logs/ui - For full server capabilities: Import from
hazo_logs/serverfor file logging, sessions, and context
hazo_ui missing errors
The log viewer UI requires hazo_ui package. Install it:
npm install hazo_uiPOST /api/logs 404 errors
If you see repeated POST /api/logs 404 errors, client loggers are using the default endpoint which doesn't match your API route location.
Solution: Configure the global client logger at app startup:
// app/providers.tsx or lib/hazo-init.ts
'use client';
import { configureClientLogger } from 'hazo_logs/ui';
configureClientLogger({
apiBasePath: '/api/hazo_logs/logs', // Match your actual route
});This ensures all client loggers (including those from dependency packages) use the correct endpoint.
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
# Clone the repo
git clone https://github.com/pub12/hazo_logs.git
cd hazo_logs
# Install dependencies
npm install
# Run tests
npm test
# Build
npm run build
# Test with example app
npm run dev:test-appLicense
MIT - See LICENSE file for details
Author
Pubs Abayasiri
Links
Related Packages
- hazo_ui - UI component library (required for log viewer)
Support
For issues and questions:
- Open an issue on GitHub
- Check existing issues for solutions
