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.
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.
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');
});
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.