Send log entries to BunnyLogs from any PHP application using a custom log handler — no dependencies required beyond a standard PHP install.
BunnyLogs accepts log entries as an HTTP POST to your logspace endpoint.
PHP's built-in stream_context_create + file_get_contents
is all you need, or you can use Monolog — the de-facto standard
logging library for PHP — to drop in a handler with almost no code.
The POST accepts three fields: message (required), level
(DEBUG, INFO, WARNING, or ERROR — defaults to INFO), and program
(a label for the source, defaults to php). Use the program
field to distinguish subsystems — e.g. auth, payments,
cron — so you can filter by service in the BunnyLogs dashboard.
Copy this helper into your project. It uses only PHP core functions and works on any host that allows outbound HTTPS.
<?php
function bunnylog(string $message, string $level = 'INFO', string $program = 'php'): void
{
$uuid = '<uuid>'; // replace with your logspace UUID
$url = "https://bunnylogs.com/live/{$uuid}";
$payload = http_build_query([
'message' => $message,
'level' => $level,
'program' => $program,
]);
$ctx = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
'content' => $payload,
'timeout' => 3,
'ignore_errors' => true,
],
]);
@file_get_contents($url, false, $ctx); // fire-and-forget; suppress errors
}
// Usage:
bunnylog('User signed up', 'INFO', 'auth');
bunnylog('Stripe webhook failed', 'ERROR', 'payments');
If your project already uses Monolog (e.g. Laravel, Symfony, or a standalone install), add this handler class and wire it up in a few lines.
Install Monolog (skip if already present):
composer require monolog/monolog
BunnyLogsHandler.php
<?php
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Level;
use Monolog\LogRecord;
class BunnyLogsHandler extends AbstractProcessingHandler
{
private string $url;
public function __construct(string $uuid, int|string|Level $level = Level::Debug, bool $bubble = true)
{
parent::__construct($level, $bubble);
$this->url = "https://bunnylogs.com/live/{$uuid}";
}
protected function write(LogRecord $record): void
{
$levelMap = [
'DEBUG' => 'DEBUG',
'INFO' => 'INFO',
'NOTICE' => 'INFO',
'WARNING' => 'WARNING',
'ERROR' => 'ERROR',
'CRITICAL' => 'ERROR',
'ALERT' => 'ERROR',
'EMERGENCY' => 'ERROR',
];
$payload = http_build_query([
'message' => $record->message,
'level' => $levelMap[$record->level->getName()] ?? 'INFO',
'program' => $record->channel,
]);
$ctx = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
'content' => $payload,
'timeout' => 3,
'ignore_errors' => true,
],
]);
@file_get_contents($this->url, false, $ctx);
}
}
Wire it up:
<?php
require 'vendor/autoload.php';
require 'BunnyLogsHandler.php';
use Monolog\Logger;
$log = new Logger('myapp');
$log->pushHandler(new BunnyLogsHandler('<uuid>'));
$log->info('Application started');
$log->warning('Disk space low', ['free_gb' => 1.2]);
$log->error('Database connection failed');
In Laravel, place BunnyLogsHandler.php in app/Logging/
and add a custom channel to config/logging.php:
'bunnylogs' => [
'driver' => 'monolog',
'handler' => App\Logging\BunnyLogsHandler::class,
'with' => [
'uuid' => env('BUNNYLOGS_UUID'),
],
'level' => env('LOG_LEVEL', 'warning'),
],
Then set LOG_CHANNEL=bunnylogs (or add it to a stack channel) in .env.
To include BunnyLogs alongside your existing channel rather than replacing it, use
a stack channel:
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'bunnylogs'],
],
...
],
Register the handler as a service in config/services.yaml:
App\Logging\BunnyLogsHandler:
arguments:
$uuid: '%env(BUNNYLOGS_UUID)%'
Then reference it in config/packages/monolog.yaml:
monolog:
handlers:
bunnylogs:
type: service
id: App\Logging\BunnyLogsHandler
level: warning
Add the standalone helper to your theme's functions.php or a must-use plugin,
then log anywhere in WordPress:
add_action('wp_login_failed', function (string $username) {
bunnylog("Failed login for {$username}", 'WARNING', 'wordpress');
});
add_action('upgrader_process_complete', function () {
bunnylog('Plugin/theme update completed', 'INFO', 'wordpress');
});
To forward uncaught PHP errors and exceptions to BunnyLogs automatically, register handlers early in your bootstrap file:
<?php
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool {
$level = match (true) {
in_array($errno, [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR]) => 'ERROR',
in_array($errno, [E_WARNING, E_CORE_WARNING, E_COMPILE_WARNING]) => 'WARNING',
default => 'INFO',
};
bunnylog("{$errstr} in {$errfile}:{$errline}", $level, 'php-error');
return false; // let PHP's default handler run too
});
set_exception_handler(function (Throwable $e): void {
bunnylog(
get_class($e) . ': ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine(),
'ERROR',
'php-exception'
);
});
The standalone helper uses a 3-second timeout and suppresses errors with @, which
makes it effectively fire-and-forget for the vast majority of requests. However, on very
high-traffic sites even a 3-second stall on a slow network call can accumulate. Two options
to eliminate the block entirely:
timeout to 0.001
in the stream context. PHP opens the TCP connection, starts the request, then returns
immediately when the timeout expires — the server still receives the full POST because the
kernel handles the rest.
// Zero-timeout fire-and-forget (advanced)
$ctx = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
'content' => $payload,
'timeout' => 0.001,
'ignore_errors' => true,
],
]);
@file_get_contents($url, false, $ctx);
A clean pattern is to use a different logspace UUID per environment. Keep the UUID in an environment variable so you can switch without code changes:
// In .env.local
BUNNYLOGS_UUID=your-dev-uuid
// In .env.production
BUNNYLOGS_UUID=your-prod-uuid
This means your local development logs go to a separate stream, so they don't pollute your production view. You can share the development stream URL with a colleague for pair debugging without granting any access to production logs.
To disable BunnyLogs in automated test runs, simply set BUNNYLOGS_UUID to an
empty string and guard the call:
function bunnylog(string $message, string $level = 'INFO', string $program = 'php'): void
{
$uuid = getenv('BUNNYLOGS_UUID');
if (!$uuid) return;
// ... rest of the function
}
BUNNYLOGS_UUID) rather than hard-coding it.timeout of 3 seconds prevents a slow BunnyLogs network call from blocking your users. For high-traffic sites, consider dispatching the HTTP call in a background job or queue.program field to distinguish between subsystems — e.g. auth, payments, cron — so you can filter by service in the BunnyLogs dashboard.channel name is used automatically as the program field by the handler above, so each new Logger('name') shows up as a distinct source.application/x-www-form-urlencoded, JSON, and multipart — use whichever your HTTP client sends most naturally.