Appearance
Logging
Since essence v0.9.0, all TBE packages log through a single PSR-3 helper, tbeLog(), with a shared level policy and automatic webhook context. This page defines the convention every package (and host app) follows.
Level Policy
| Level | Meaning | Examples |
|---|---|---|
info | Business event — something that is supposed to happen, happened | invoice paid, wallet credited, service provisioned, campaign completed |
warning | Expected user friction or misconfiguration — no stack trace needed | validation failure, stale callback button, gateway rejected a request, unregistered state answer |
error | Genuine bug — always with ['exception' => $e] | unhandled exception, failed Telegram API init, failed webhook setup |
debug | High-volume diagnostics, off in production | per-message campaign send failures, backup-restore file progress |
With production at LOG_LEVEL=info, the error count becomes a real alarm signal: errors are bugs, warnings are friction, info is the business ledger.
tbeLog()
php
tbeLog('billing')->info('Invoice paid', [
'invoice_id' => $invoice->getKey(),
'price' => $invoice->price,
]);
tbeLog('essence')->error('Failed to create bot: ' . $e->getMessage(), ['exception' => $e]);The helper returns a TbeLogger (PSR-3 LoggerInterface). Every entry automatically gains:
package— the tag you pass totbeLog()(omit for none; each package uses its own name:essence,billing,user-wallet,gateway-card,gateway-zibal,affiliates,announcements,settings,user-management, host apps use their app name, e.g.vshop)bot_id,user_id(Telegram peer id),username,chat_id,update_id,update_type,state— pulled from the currentwHook()context when a webhook is active; silently omitted otherwise
Subject and placeholders
Since essence v0.15.0, a log line should say whose it is and what happened without being expanded. Two things make that the default:
- Subject prefix. The message is prefixed with
bot#<bot_id> user#<user_id> @<username> |, from whichever of those keys the entry has. - Placeholders.
{key}in the message is filled from the context, PSR-3 style, by the logger itself (the host channel'sreplace_placeholdersdoesn't matter). Unknown keys and non-scalar values are left as-is.
php
tbeLog('billing')->info('Invoice #{invoice_id} paid: {price}', [
'invoice_id' => $invoice->getKey(),
'price' => $invoice->price,
]);
// bot#3 user#12345 @alice | Invoice #12 paid: 250000 {"invoice_id":12,"price":250000,...}Keep the message short and human (the ids and the outcome); everything else goes in the context for the log viewer to expand. Domain ids are <model>_id (invoice_id, service_id, order_id).
Outside a webhook: for()
A queued job, the scheduler or a gateway callback has no webhook user, so there is nothing to build the subject from. Bind the entry to the user it is about:
php
tbeLog('quota')->for($service->botUser)->info('Quota {status}: service #{service_id} ...', [...]);for() takes the bot user's bot_id, user_id and username, overriding the webhook's. Anything that isn't a BotUser (a missing relation) binds nothing, since logging never throws.
Audit entries: audit()
What an admin or the owner did (role and suspension changes, settings, accepting or rejecting a manual payment) is an audit entry:
php
tbeLog('user-management')->audit('Changed role of user#6001 @alice: member -> admin', [...]);It is written at info, flagged audit: true, to logging.audit_channel, or to the package's channel when that is unset. The subject is the webhook user, i.e. the admin who acted; name the target in the message.
Logging never throws: any failure inside the logger is swallowed so it can never break bot flow.
Rules
- One entry per event. Never split one failure across multiple log calls.
- Identifiers, not payloads. Log
invoice_id,chat_id, amounts — never full update JSON or message text. The full update already goes to the bug-report Telegram chat as a document. - Traces only for bugs. Expected exceptions log at
warningwith no trace; the catch-all path logserrorwith['exception' => $e](Laravel's formatter appends the stack trace). - Sensitive values are redacted. e.g.
settingslogs[redacted]forSENSITIVE-type setting values.
Configuration
php
// config/tbe-essence.php
'logging' => [
// Log channel for all TBE packages; null uses the app's default channel.
'channel' => env('TBE_LOG_CHANNEL'),
// Per-package overrides, keyed by the tbeLog() tag.
'channels' => [
'billing' => 'payments',
'gateway-card' => 'payments',
],
// Where tbeLog()->audit() entries go; null uses the package's channel.
'audit_channel' => env('TBE_LOG_AUDIT_CHANNEL'),
// Also push debug/bug reports to the bug_report Telegram chat.
'telegram_notify' => env('TBE_LOG_TELEGRAM_NOTIFY', true),
],channel: null(default) — everything goes to the app's default channel; existing bots need no changes.channels— send one package's entries somewhere else, e.g. all money movement to apaymentsfile kept longer than bot traffic.audit_channel— the admin audit trail.telegram_notify— controls whetherdebugMessage(),mixedDebugMessage()andexceptionReport()also send to thebug_report.telegram_chat_idchat.
Host App Setup (Reference: vshop)
vshop separates bot traffic from app/framework logs with two rotated daily channels:
php
// config/logging.php
'telegram' => [
'driver' => 'daily',
'path' => storage_path('logs/telegram.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],dotenv
LOG_CHANNEL=stack
LOG_STACK=daily # app log, rotated, 14 days
LOG_LEVEL=info # debug only while actively diagnosing
TBE_LOG_CHANNEL=telegram # bot traffic → telegram-YYYY-MM-DD.logBoth files show up in Log Viewer automatically; filter bot entries by the package context key.
Admin Alerts
Since essence v0.15.0. Some problems only the bot's owner can fix: a setting that stopped working, a permission the bot lost, a payment gateway refusing the merchant id. A log line nobody reads doesn't fix them, so tell the owner and admins directly:
php
adminAlert(
'settings.channel_lock.check_failed', // what it is about
fn () => __('tbe-settings::...alerts.check_failed', [...]), // rendered in the bot's locale
$bot, // optional: the webhook's bot when omitted
$throttle, // optional: seconds, default admin_alerts.throttle (6h)
);- Goes to the bot owner plus every admin (
power >= ADMIN) who is reachable and not suspended, once each, prefixed with ⚠️. - Throttled per bot and key: a condition detected on every update reaches each admin once per window. Put an id in the key (
shop.provisioning_failed.{order}) when every occurrence matters. - Works outside a webhook (jobs, the scheduler, gateway callbacks) by building the bot's own API client.
- Logs one
warningper alert sent, with the text; a delivery failure to one admin doesn't stop the others. - Not for bugs: those still go to the developer through
exceptionReport().
php
// config/tbe-essence.php
'admin_alerts' => [
'throttle' => env('TBE_ADMIN_ALERT_THROTTLE', 21600),
],Bug Reports via Telegram
exceptionReport($e) (used by the catch-all in ExceptionHandler) does two things:
- Logs one
errorentry with the exception. - If
bug_report.telegram_chat_idis set andtelegram_notifyis on, sends a summary message plus.traceand.updatedocuments to that chat.
debugMessage() / mixedDebugMessage() log a single warning and (optionally) mirror it to the same chat.