A hierarchical, human-readable logging system for V that turns scattered log lines into visual trees making it easier for both humans and LLMs to debug code with richer context.
The Problem: Traditional structured loggers (like JSON logging) dump everything in dense, hard-to-scan blobs:
{"timestamp":"2026-03-06T12:34:56","level":"INFO","service":"api","method":"GET","path":"/api/users","status":200,"user":{"id":123,"name":"John","email":"john@example.com"},"duration_ms":45}
The Solution: Tree Logger renders structured data as visual trees with field prioritization — important data expanded, noise collapsed:
[2026-03-06 12:34:56] INFO [api] GET /api/users 200 in 45ms
├─ user: John (id: 123)
│ ├─ id: 123
│ └─ name: John
└─ [3 fields hidden, use --verbose]
- evlog - The philosophy of wide events and contextual logging
- logging sucks - Why traditional logging makes debugging painful
- Tree Format - Logs render as visual trees instead of dense JSON
- Field Prioritization - Headers/important fields expanded, boring/internal fields collapsed
- Three Display Modes -
compact,normal,verbosefor different contexts - Correlation Tracking - Built-in UUID-based request tracing across distributed systems
- Data Sanitization - Automatic masking of API keys, passwords, and PII
v install github.com/Macho0x/tree-logger/Or copy all .v files into your project.
import logger
import x.json2
fn main() {
// Initialize
logger.init_logger(logger.LoggerConfig{
env: logger.EnvironmentContext{service: 'my-app'}
mode: .normal
})
// Simple logging
logger.info('startup', 'Server started')
// Structured logging
logger.log.info_data({
'method': json2.Any('GET')
'path': json2.Any('/api/users')
'status': json2.Any(200)
})
}// Initialize with configuration
logger.init_logger(logger.LoggerConfig{
env: logger.EnvironmentContext{service: 'my-app'}
mode: .normal // .compact, .normal, .verbose
})
// Or load from TOML
logger.init_from_toml('setup.toml')!
// Runtime changes
logger.set_log_mode(.verbose)
logger.set_log_level(.debug)// Simple logging with automatic file:line
logger.info('tag', 'message')
logger.warn('tag', 'message')
logger.log_error('tag', 'message')
logger.log_debug('tag', 'message')
// Structured logging
logger.log.info_data({'key': json2.Any('value')})
// Scoped logging (build events incrementally)
mut req_log := logger.new_request_logger({
'method': json2.Any('USER_SIGNUP')
})
req_log.set({'email': json2.Any('user@example.com')})
req_log.info('Sending welcome email', none)
req_log.emit(none) // Final outputBefore (JSON):
{"timestamp":"2026-03-06T12:34:56","level":"INFO","service":"api","method":"GET","path":"/api/users/123","status":200,"user":{"id":123,"name":"Alice Johnson","email":"alice@example.com","role":"admin"},"timing":{"total_ms":245,"db_ms":45,"cache_hit":false},"request_id":"req_abc123"}After (Tree Logger - Normal Mode):
[2026-03-06 12:34:56] INFO [api] GET /api/users/123 200 in 245ms
├─ user: Alice Johnson (admin)
│ ├─ id: 123
│ ├─ name: Alice Johnson
│ └─ role: admin
├─ timing: {3 fields}
└─ request_id: req_abc123
Compact Mode:
[2026-03-06 12:34:56] INFO [api] GET /api/users/123 200 in 245ms
└─ user: Alice Johnson (admin)
Verbose Mode:
[2026-03-06 12:34:56] INFO [api] GET /api/users/123 200 in 245ms
├─ method: GET
├─ path: /api/users/123
├─ status: 200
├─ user: Alice Johnson (admin)
│ ├─ id: 123
│ ├─ name: Alice Johnson
│ ├─ email: alice@example.com
│ └─ role: admin
├─ timing: {3 fields}
│ ├─ total_ms: 245
│ ├─ db_ms: 45
│ └─ cache_hit: false
├─ request_id: req_abc123
└─ duration_ms: 245
Before (Scattered Lines):
2026-03-06 12:34:56 ERROR Connection timeout after 30s
2026-03-06 12:34:56 INFO Table: user_sessions
2026-03-06 12:34:56 INFO Attempt: 3 of 5
2026-03-06 12:34:56 INFO Query: SELECT * FROM user_sessions WHERE user_id = 456
After (Tree Logger):
[2026-03-06 12:34:56] ERROR [api] DB_QUERY_FAILED
├─ operation: cleanup_expired_sessions
├─ query: {1 fields}
│ └─ table: user_sessions
├─ error: Connection timeout after 30s
│ ├─ table: user_sessions
│ └─ attempt: 3 of 5
└─ duration: 30.15s
Before (Raw JSON with Secrets):
{"timestamp":"2026-03-06T12:34:56","level":"INFO","method":"CHECKOUT_COMPLETE","api_key":"AK1234567890ABCDEFGH","password":"super_secret_pass","user":{"id":789,"email":"customer@example.com"},"payment":{"method":"credit_card","last4":"4242","amount":99.99},"items":[{"product":"Wireless Headphones","price":79.99},{"product":"USB Cable","price":19.99}],"total":99.99}After (Tree Logger with Sanitization):
[2026-03-06 12:34:56] INFO [shop] CHECKOUT_COMPLETE
├─ api_key: AK1********FGH
├─ password: ********
├─ user: customer@example.com
│ ├─ id: 789
│ └─ email: customer@example.com
├─ payment: {2 fields}
│ ├─ method: credit_card
│ └─ last4: ****4242
├─ items: [2 items]
│ ├─ [0] Wireless Headphones - $79.99
│ └─ [1] USB Cable - $19.99
└─ total: $99.99
Notice: API keys, passwords, and credit card numbers are automatically masked.
| Feature | Input | Output |
|---|---|---|
| Color Coding | Log level INFO |
[2026-03-06 12:34:56] INFO api Request completed |
| Sanitizer | password: "secret123" |
password: ******** |
| Sanitizer | api_key: "AK1234567890" |
api_key: AK1********90 |
| Sanitizer | email: "user@example.com" |
email: use********com |
| Field Priority | Important field | user: Alice (expanded) |
| Field Priority | Boring field | meta: {5 fields hidden} |
| Display Modes | compact mode |
One-line summary only |
| Display Modes | normal mode |
Important fields + collapsed extras |
| Display Modes | verbose mode |
All fields fully expanded |
| Correlation ID | New request | correlation_id: abc-123-xyz |
| Scoped Logging | Add context | Build up event incrementally |
This logger prioritizes readability and developer experience over raw performance. Open to PRs to improve raw performance.
Create setup.toml:
[logger]
enabled = true
level = "info"
mode = "normal"
[logger.environment]
service = "my-app"
environment = "development"
[logger.sanitizer]
enabled = true
api_key_strategy = "partial"
password_strategy = "full"Load it:
logger.init_from_toml('setup.toml')!MIT License
