Skip to content

Logging

What is this?

ZubZet provides a built-in logging system powered by Monolog. It allows you to record application events, errors, and diagnostic information either to a database table or to a stream (file or stderr). The framework automatically logs certain system events such as logins, page views, and REST errors.

Loggers are created lazily on first use and cached for the lifetime of the request, so there is no overhead for channels that are never written to.

Disabling Logging

Logging is enabled by default. To disable it, set the following in z_config/z_settings.ini:

logger_enabled = false

When disabled, all logger() calls still work but write to a NullHandler — log messages are silently discarded without any errors.

Configuration

All logger settings are configured in z_config/z_settings.ini.

Key Type Default Description
logger_enabled boolean true Enables or disables the logging system entirely
logger_type string database The logger backend to use: database or stream
logger_level string notice Minimum log level to record (see levels below)
logger_stream_url string php://stderr Stream target — only used when logger_type = stream

Log Levels

Log levels follow the Monolog/PSR-3 standard. Set logger_level to a level name — only records at or above that level are stored.

logger_level value Description
debug Detailed diagnostic information
info Normal operational events
notice Significant but expected events
warning Exceptional but non-critical events
error Runtime errors
critical Critical conditions
alert Action must be taken immediately
emergency System is unusable

Writing Log Entries

Use the global logger() helper function anywhere in your application.

// Log to the app channel
logger()->info("User submitted a contact form", ["email" => $email]);

// Log to a named channel
logger("payments")->warning("Payment gateway timeout", ["orderId" => $id]);

logger() without arguments defaults to the "app" channel. Any string can be passed to create a named channel — each channel is cached after the first call.

Available Methods

logger()->debug("message", $context);
logger()->info("message", $context);
logger()->notice("message", $context);
logger()->warning("message", $context);
logger()->error("message", $context);
logger()->critical("message", $context);
logger()->alert("message", $context);
logger()->emergency("message", $context);

The second parameter $context is an optional associative array with additional data to attach to the log record.

Registering a Custom Logger

If you need full control over a logger — custom handlers, formatters, or processors — you can build a Monolog Logger instance manually and register it under a channel name using LoggerFactory::register(). Once registered, it is returned by logger() like any other channel.

use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use ZubZet\Framework\Logger\LoggerFactory;

$customLogger = new Logger("audit");
$customLogger->pushHandler(new RotatingFileHandler("/var/log/audit.log", 30));

LoggerFactory::register("audit", $customLogger);
// Anywhere in your application
logger("audit")->info("User exported data", ["userId" => $id]);

If a logger with that name was already created by the framework, register() overwrites it in the cache.

Logger Types

Database Logger

Stores log records in the z_interaction_log database table. This is the default logger type.

logger_type = database

Each log entry is stored as a row with the following columns:

Column Type Description
id INT Auto-incrementing primary key
text MEDIUMTEXT The plain log message
value MEDIUMTEXT Full log record as a JSON string (see below)
userId INT ID of the currently logged-in user (nullable)
userId_exec INT ID of the executing user when using "login as" (nullable)
created TIMESTAMP Timestamp of the log entry

If the database connection is not available at the time of logging, the entry is silently skipped.

value Column Structure

The value column contains a JSON-encoded object with the full log record and environment context:

{
    "message": "User logged in",
    "context": { "userId": 42 },
    "level": 200,
    "level_name": "INFO",
    "channel": "zubzet",
    "datetime": "2026-04-08T12:00:00+00:00",
    "extra": {},
    "environment": {
        "userId": 42,
        "execUserId": 42,
        "source": "web"
    }
}

The environment.source field is either "web" or "cli" depending on how the request was triggered.

Value Normalization

Before JSON encoding, context values are automatically normalized to ensure safe serialization:

PHP type Stored as
string, int, float, bool, null Passed through unchanged
array Recursively normalized
DateTimeInterface ISO-8601 string (e.g. 2026-04-08T12:00:00+00:00)
JsonSerializable Result of jsonSerialize(), then normalized
Object with __toString String representation
Other objects Fully qualified class name
Resource resource(stream) style string

If JSON encoding fails despite normalization, a fallback record containing only the message and the encoding error is stored instead.


Stream Logger

Writes log records as JSON to a file or PHP stream, one record per line.

logger_type = stream
logger_stream_url = php://stderr

logger_stream_url accepts any valid PHP stream URL or file path:

logger_stream_url = php://stderr
logger_stream_url = php://stdout
logger_stream_url = /var/log/app.log
logger_stream_url = z_config/app.log

Each log entry is a single JSON object followed by a newline character. The environment context is appended automatically before formatting:

{"message":"User logged in","context":{"userId":42},"level":200,"level_name":"INFO","channel":"zubzet","datetime":"2026-04-08 12:00:00","extra":{},"environment":{"userId":42,"execUserId":42,"source":"web"}}

Example

# z_config/z_settings.ini
logger_type = database
logger_level = info
public function action_order(Request $req, Response $res) {
    $orderId = model("order")->createOrder($req->getPost("items"));

    logger()->info("Order placed", ["orderId" => $orderId]);

    return $res->success(["orderId" => $orderId]);
}