Hexa Puffs uses a two-tier logging architecture:
- Console Logger (
Shared/Utils/logger.ts) — Structured text logs written to stderr. Every MCP imports this. Output is redirected to.logfiles bystart-all.sh. - JSONL Audit Loggers — Domain-specific structured logs written directly to
.jsonlfiles. Used by Guardian, Filer, CodeExec, and Thinker for audit trails and tracing.
No external logging library is used — everything is built on Node.js builtins (console.error, fs/promises).
File: Shared/Utils/logger.ts
Stdio MCPs communicate with the Orchestrator over stdout using JSON-RPC. Any stray console.log() output would corrupt the transport. The Logger class uses console.error() for all log levels to keep stdout clean.
import { logger, Logger } from '@mcp/shared/Utils/logger.js';
// Default singleton instance (context: 'mcp')
logger.info('Server started', { port: 8010 });
// Custom instance
const log = new Logger('gmail');
log.warn('Token expiring soon');
// Child loggers — compound context
const child = logger.child('orchestrator'); // context: 'mcp:orchestrator'
const grandchild = child.child('health'); // context: 'mcp:orchestrator:health'
// Dynamic control
log.setLevel('debug');
log.setContext('gmail-v2');[2026-02-11T15:30:45.123Z] [INFO] [gmail] Token refreshed {"expiresIn":3600}
[2026-02-11T15:30:46.001Z] [ERROR] [mcp:orchestrator] MCP crashed {"message":"ECONNREFUSED","name":"Error","stack":"...","code":"ECONNREFUSED"}
- Timestamp: ISO 8601
- Level:
DEBUG,INFO,WARN,ERROR - Context: logger name, colon-separated for children
- Data: optional JSON payload.
Errorobjects are serialized withmessage,name,stack, andcode.
| Level | Priority | Description |
|---|---|---|
debug |
0 | Verbose diagnostic info (tool args, route decisions) |
info |
1 | Normal operations (startup, tool calls, health checks) |
warn |
2 | Degraded state (MCP down, token expiring, blocked tool) |
error |
3 | Failures (crash, unhandled exception, tool error) |
Only messages at or above the configured level are emitted. Default: info.
File: Shared/Logging/jsonl.ts
A reusable typed logger for writing structured audit trails to .jsonl files (one JSON object per line).
import { JsonlLogger } from '@mcp/shared/Logging/jsonl.js';
interface MyEntry extends BaseAuditEntry {
operation: string;
success: boolean;
}
const audit = new JsonlLogger<MyEntry>('/path/to/audit.jsonl');
// Write
await audit.write({ timestamp: new Date().toISOString(), operation: 'read', success: true });
// Read with filtering
const recent = await audit.read({
limit: 50,
sortDescending: true,
filter: (e) => !e.success,
});Features:
- Auto-creates parent directories on first write
- Skips malformed lines on read (graceful recovery)
- Sorts by
timestamp(descending by default) - Default read limit: 100 entries
All logs centralize under ~/.hexa-puffs/logs/. The directory is created by start-all.sh at startup.
| File | Source |
|---|---|
orchestrator.log |
Orchestrator + all stdio MCP child stderr (Guardian, 1Password, Filer, Memorizer, CodeExec, Searcher, Gmail, Telegram, Browser) + Thinker agent stderr |
inngest.log |
Inngest Dev Server |
ollama.log |
Ollama model server |
seed-skills.log |
Cron skill seeding |
All MCPs are stdio — their stderr is piped through the Orchestrator's process and lands in orchestrator.log with their context prefix (e.g. [mcp:filer], [thinker:hexa-puffs]).
| File | Source | Content |
|---|---|---|
~/.hexa-puffs/logs/traces.jsonl |
Thinker | Per-request trace events |
~/.hexa-puffs/logs/fileops-audit.log |
Filer MCP | File operation audit trail |
Guardian/logs/audit.jsonl |
Guardian | Security scan results |
~/.hexa-puffs/codexec/logs/executions-YYYY-MM-DD.jsonl |
CodeExec | Daily execution logs |
~/.hexa-puffs/codexec/logs/session-{id}.jsonl |
CodeExec | Per-session lifecycle |
File: Guardian/src/logging/audit.ts
Logs every security scan to Guardian/logs/audit.jsonl with:
interface AuditEntry {
scan_id: string; // UUID v4
timestamp: string;
source: string; // what triggered the scan
content_hash: string; // SHA-256 truncated to 16 chars (privacy)
content_length: number;
safe: boolean;
confidence: number;
threats: ThreatInfo[]; // { path, type, snippet }
model: string; // which model scanned
latency_ms: number;
}Queryable via the get_scan_log MCP tool (supports threats_only, limit, scan_id filters).
File: Filer-MCP/src/logging/audit.ts
Logs every file operation to ~/.hexa-puffs/logs/fileops-audit.log (configurable via AUDIT_LOG_PATH):
interface AuditEntry {
timestamp: string;
operation: string; // read, write, delete, list, etc.
path: string;
domain: 'workspace' | 'granted';
grant_id: string | null;
agent_id: string;
session_id: string;
success: boolean;
size_bytes?: number;
error?: string;
}Queryable via the get_audit_log MCP tool (supports path_filter, operation_filter, date_from, limit).
Files: CodeExec-MCP/src/logging/writer.ts, CodeExec-MCP/src/logging/types.ts
Two log streams, both JSONL, stored in ~/.hexa-puffs/codexec/logs/ (configurable via CODEXEC_LOG_DIR):
Daily execution logs (executions-YYYY-MM-DD.jsonl):
interface ExecutionLogEntry {
type: 'execution';
execution_id: string;
language: 'python' | 'node' | 'bash';
code: string;
stdout: string;
stderr: string;
exit_code: number | null;
timed_out: boolean;
duration_ms: number;
sandbox_mode: 'subprocess';
working_dir: string;
artifacts: { created: string[]; modified: string[]; deleted: string[] };
executed_at: string;
}Per-session logs (session-{sessionId}.jsonl):
type SessionLogEntry =
| SessionStartLogEntry // PID, language, started_at
| SessionExecLogEntry // code, stdout, stderr, duration_ms
| SessionEndLogEntry // reason (manual|idle_timeout|process_exit), total_duration_ms
| PackageInstallLogEntry // package_name, version, successTelegram MCP overrides console.log = () => {} at startup to suppress GramJS library noise that would otherwise corrupt the MCP JSON-RPC transport. Actual logging goes through the shared Logger (stderr).
1Password, Memorizer, Searcher, Gmail all use the shared Logger class directly with no additional audit logging. Typical pattern:
import { Logger } from '@mcp/shared/Utils/logger.js';
const logger = new Logger('searcher');Files: Thinker/src/tracing/logger.ts, Thinker/src/tracing/types.ts
A specialized request-level tracer that logs every step of a conversation to ~/.hexa-puffs/logs/traces.jsonl (configurable via TRACE_LOG_PATH).
interface TraceEntry {
trace_id: string; // Request ID, propagated via X-Trace-Id header
ts: string; // ISO timestamp
mcp: string; // MCP/service name
event: TraceEvent | string;
data: Record<string, unknown>;
}| Event | Data |
|---|---|
message_received |
chat_id, text (truncated to 100 chars for privacy) |
context_loaded |
facts count, profile presence |
llm_call_start |
provider, model |
llm_call_complete |
provider, model, input_tokens, output_tokens, duration_ms |
tool_call_start |
tool, args |
tool_call_complete |
tool, success, duration_ms |
tool_call_error |
tool, error |
response_sent |
chat_id, response_length |
error |
error, plus any extra details |
complete |
duration_ms, tools_used, total_steps |
Uses a singleton pattern: getTraceLogger(path?) returns a shared instance.
start-all.sh handles all log file creation and process management:
# Create log directory
mkdir -p ~/.hexa-puffs/logs
# HTTP MCPs — stdout+stderr → log file
TRANSPORT=http PORT=$port npm start >> ~/.hexa-puffs/logs/${log_name}.log 2>&1 &
# Orchestrator — same pattern
npm start >> ~/.hexa-puffs/logs/orchestrator.log 2>&1 &
# Inngest Dev Server
npx inngest-cli@latest dev --no-discovery >> ~/.hexa-puffs/logs/inngest.log 2>&1 &
# Ollama
ollama serve >> ~/.hexa-puffs/logs/ollama.log 2>&1 &All processes use >> file 2>&1 (append mode, stderr merged into stdout).
PIDs are tracked in ~/.hexa-puffs/hexa-puffs.pids for shutdown management.
Stdio MCP stderr is captured differently — it's piped through the Orchestrator's StdioClientTransport, which reads child stderr line-by-line and logs each line via logger.child('mcp:${name}').
File: Orchestrator/src/commands/slash-commands.ts
Available via Telegram (or any channel adapter):
| Command | Result |
|---|---|
/logs |
List all log files with sizes and last-modified times |
/logs N |
Show last N warnings/errors across all service logs (default: 15) |
Scans these files for [WARN] and [ERROR] lines:
orchestrator.log, gmail.log, telegram.log, searcher.log, filer.log, memorizer.log, ollama.log, web.log
Runs a health audit that includes a "Recent Logs (WARN/ERROR)" section, pulling the same log parsing logic.
- Guardian:
get_scan_log— queryGuardian/logs/audit.jsonl - Filer:
get_audit_log— query~/.hexa-puffs/logs/fileops-audit.log
# Tail all logs
tail -f ~/.hexa-puffs/logs/*.log
# Tail specific service
tail -f ~/.hexa-puffs/logs/orchestrator.log
# Search for errors
grep '\[ERROR\]' ~/.hexa-puffs/logs/*.log
# Read traces
cat ~/.hexa-puffs/logs/traces.jsonl | jq .
# Read CodeExec execution logs for today
cat ~/.hexa-puffs/codexec/logs/executions-$(date +%Y-%m-%d).jsonl | jq .| Env Variable | Default | Scope | Description |
|---|---|---|---|
LOG_LEVEL |
info |
All MCPs | Console log verbosity (debug, info, warn, error) |
CODEXEC_LOG_DIR |
~/.hexa-puffs/codexec/logs |
CodeExec | Execution/session log directory |
AUDIT_LOG_PATH |
~/.hexa-puffs/logs/fileops-audit.log |
Filer | File operations audit path |
TRACE_LOG_PATH |
~/.hexa-puffs/logs/traces.jsonl |
Thinker | Conversation trace path |
- No external dependencies — Pure Node.js (
console.error,fs/promises,crypto). No winston, pino, or bunyan. - Stdout is sacred — Stdio MCPs must never write to stdout. All logging goes through
console.error(). - JSONL for audit — Structured logs use JSON Lines format for easy
jqqueries, programmatic filtering, and append-only writes. - Hierarchical contexts — Child loggers create compound context strings (
mcp:filer,mcp:orchestrator:health) for easy grep filtering. - No log rotation — Log files grow unbounded. Shell-level
>>append means they survive restarts. Manual cleanup or external rotation is required. - Privacy-conscious — Guardian hashes scanned content (SHA-256, truncated). Thinker truncates message text to 100 chars in traces.
- Centralized directory — All runtime logs under
~/.hexa-puffs/logs/, except Guardian audit (co-located with the MCP) and CodeExec (under~/.hexa-puffs/codexec/logs/).