Logger¶
HIX includes a thread-safe centralized logger that records server events
in a file with automatic rotation. It's used both by the server itself
(startup, routes, errors, sessions, etc.) and your application
via the macros l(), lw(), le(), lf(), and ld().
Along with the general log (hix.log), HIX maintains in parallel an
Apache-style CLF access log (access.log) with one line per HTTP request served.
When do you need it?¶
- To diagnose server behavior in production without stopping it.
- To keep an auditable record of the HTTP requests received
(
access.log). - To have your controllers and middlewares leave structured traces with severity levels.
Setup in hix.json¶
Section paths¶
Define the common directory where all logs are written:
Section log - general log¶
level: debug | info | warn | error | fatal.
console = true also prints to console.
max_size_mb: MB for rotation when this size is exceeded.
max_files = 0: unlimited backups.
{
"log": {
"file": "hix.log",
"level": "info",
"console": true,
"max_size_mb": 10,
"max_files": 0
}
}
Section access_log - request log¶
The access log is generated automatically from
THixRequest:Respond() - you don't need to call it manually.
💡
errors.logis reserved for future structured HTTP error records; today all errors go to the general log with levelERRORorFATAL.
Levels¶
| Macro | Constant | Level | When to use |
|---|---|---|---|
ld() |
HIX_LOG_DEBUG |
1 | Internal details, development only |
l() |
HIX_LOG_INFO |
2 | Normal operational information |
lw() |
HIX_LOG_WARN |
3 | Something recoverable that deserves watching |
le() |
HIX_LOG_ERROR |
4 | Error affecting the request or subsystem |
lf() |
HIX_LOG_FATAL |
5 | Serious error that can crash the server |
Only messages equal to or higher than the configured level
are written. level=info filters out ld(); level=warn filters out ld() and
l(); etc.
Usage from code¶
Any .prg that wants to log must include the macros header:
#include "hix_logger.ch"
FUNCTION MyController()
l( "Access to user panel" )
IF ! _CheckPermissions()
lw( "Unauthorized access attempt from " + UIP() )
RETURN USendError( 403 )
ENDIF
le( "Something went wrong with the database" )
RETURN NIL
Output format¶
[2026-06-27 09:14:32.123] [INFO ] [router] Route /users/42 -> users.show
[2026-06-27 09:14:32.456] [WARN ] [auth ] Login failed: carles
[2026-06-27 09:14:32.789] [ERROR] [db ] Cannot open customers.dbf
Each line contains:
- Timestamp with milliseconds.
- Severity level.
- Sending module (defined by
#define HIX_LOG_MODULEat the start of the .prg). - Free message.
📚 The module is declared like this in each framework
.prgfile (and you can do the same in yours):
Automatic rotation¶
When hix.log reaches max_size_mb:
- The current file is renamed to
hix_YYYYMMDDHHMMSS_NNNNNN.log. - A new empty
hix.logis created. - If
max_files > 0, the oldest backups are deleted to keep onlymax_fileshistorical files.
Rotation is seamless: no logs are lost, write handlers are synchronized via mutex.
.logs/
├── hix.log <- active
├── hix_20260620120134000000.log <- rotated
├── hix_20260622150812000000.log
└── access.log <- active (no automatic rotation)
⚠️
access.logdoes not rotate automatically. If you need it with high traffic, configurelogrotate(Linux) or a scheduled script (Windows).
Access log: CLF format¶
access.log follows the Common Log Format
standard from Apache:
192.168.1.100 - - [27/Jun/2026:09:14:32 +0000] "GET /users/42 HTTP/1.1" 200
192.168.1.100 - - [27/Jun/2026:09:14:35 +0000] "POST /login HTTP/1.1" 302
10.0.0.5 - - [27/Jun/2026:09:14:40 +0000] "GET /admin HTTP/1.1" 403
Compatible with standard tools (awstats, goaccess, lnav...).
Per-module tracing¶
ld() and l() accept an additional sending module filter. This lets you
activate detail only from one subsystem without flooding the log:
HIX_TraceSet( "router", .T. ) // activate DEBUG/INFO from router
HIX_TraceSet( "session", .F. ) // silence DEBUG/INFO from session
HIX_TraceAll( .T. ) // activate all
The levels WARN, ERROR, and FATAL always pass regardless of the
trace filter. This is the key difference from simple level:
level= global threshold by severity.HIX_TraceSet= fine-grained filter by module, only for lower levels.
🔧 Useful for diagnosing a production issue without lowering the
levelsetting todebug(which would generate huge logs).
Manual initialization¶
In standalone applications that start THixServer without going through
hix.json, you must initialize the logger yourself:
HIX_LoggerInit( "logs/app.log", HIX_LOG_DEBUG, .T., 10485760, 5 )
// ^cFile ^level ^console ^maxsize ^maxfiles
In the normal flow with hix.json, THixServer:New() takes care of it.
Common errors¶
| Symptom | Cause | Fix |
|---|---|---|
| The log is empty | Level above the emitted messages | Lower level to debug or info |
| Appears in console but not in file | paths.log points to a directory without write permission |
Create/allow the directory |
access.log is not written |
access_log.enabled = false |
Set enabled = true |
| Old logs are not deleted | max_files = 0 |
Define a limit (max_files = 10) |
Missing l() in a .prg |
You forgot #include "hix_logger.ch" |
Add it in header |