Tiny Owl Logger (Unofficial Beta)
Wordpress Plugin that sends events to Tiny Owl.
by Kidkie · github.com/kidkie-tech-ab/towl-wordpress-plugin · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/kidkie-tech-ab/towl-wordpress-plugin/archive/refs/heads/main.zipTiny Owl Logger
Unofficial beta plugin. Independent, community-built. Not developed or endorsed by Tiny Owl.
Captures PHP errors, uncaught exceptions, and fatal errors and forwards them to the Tiny Owl observability service. No external SDK — credentials are kept server-side and never touch the database or source code.
How it works
flowchart TD
subgraph Browser["Browser (Frontend)"]
JS["JS error / console.error()"]
FETCH["fetch() → REST endpoint\nPOST /wp-json/towl/v1/log\nwith X-Towl-Nonce header"]
JS --> FETCH
end
subgraph WordPress["WordPress (Backend)"]
subgraph AutoCapture["Automatic capture"]
EH["set_error_handler()\nE_WARNING · E_NOTICE · etc."]
EX["set_exception_handler()\nUncaught exceptions"]
SD["register_shutdown_function()\nFatal errors"]
end
subgraph Manual["Manual logging"]
HELPER["Plugin-local helper\ne.g. myplugin_log()"]
GUARD["function_exists('tiny_owl_log')\nguard check"]
TOWL["tiny_owl_log(message, severity, context)"]
HELPER --> GUARD --> TOWL
end
REST["REST route handler\ntowl_rest_log()\nrate limit · context allowlist"]
FETCH --> REST
SEVERITY["Severity filter\ntowl_min_severity option\ninfo / warning / error"]
EH --> SEVERITY
EX --> SEVERITY
SD --> SEVERITY
TOWL --> SEVERITY
REST --> SEVERITY
subgraph APIClient["API Client"]
SIGN["HMAC-SHA256 signature\nPROJECT_SECRET never sent"]
BUILD["Build payload\napiKey · message · severity · context"]
POST["wp_remote_post()\nnon-blocking · 5 s timeout"]
LOG["Append to send log\nwp_options · last 50 entries"]
SIGN --> BUILD --> POST --> LOG
end
SEVERITY --> APIClient
end
subgraph TinyOwl["Tiny Owl Service (tiny-owl-kit.io)"]
VERIFY["Verify HMAC signature\n+ timestamp + nonce"]
INGEST["Ingest event"]
DASH["Dashboard / alerts"]
VERIFY --> INGEST --> DASH
end
POST -->|"HTTPS\nx-signature · x-timestamp · x-nonce\nbody: apiKey + payload"| VERIFY
subgraph Credentials["Credentials (never in DB or code)"]
ENV[".htaccess SetEnv\nor wp-config.php (local only)"]
CONST["wp-config.php constants\nTOWL_API_KEY\nTOWL_PROJECT_SECRET\nTOWL_ENDPOINT"]
ENV --> CONST
end
CONST -.->|"read at runtime"| APIClient
Requirements
- WordPress 6.4+ (tested up to 6.9.4)
- PHP 8.0+
- Apache with
mod_envenabled (for production credential setup)
Installation
- Copy the
towl-wordpress-pluginfolder intowp-content/plugins/. If downloaded from GitHub the folder may be namedtowl-wordpress-plugin-main— rename it before activating. - Configure credentials (see below).
- Activate in Plugins > Installed Plugins.
- Go to Settings > Tiny Owl Logger and use Send test event to verify credentials.
Credential setup
Requires three constants in wp-config.php. Secrets must never be hardcoded in tracked files — read them from environment variables on production.
Production — Apache + .htaccess
SetEnv TOWL_API_KEY "your-actual-key-here"
SetEnv TOWL_PROJECT_SECRET "your-actual-secret-here"
define( 'TOWL_API_KEY', getenv('TOWL_API_KEY') );
define( 'TOWL_PROJECT_SECRET', getenv('TOWL_PROJECT_SECRET') );
define( 'TOWL_ENDPOINT', 'https://be.tiny-owl-kit.io/api/ingest' );
Note:
mod_envmust be enabled. Ifgetenv()returns empty, contact your host.
Important: Ensure
.htaccessis gitignored. If currently tracked:git rm --cached .htaccess
Local development
Add to wp-config.php:
define( 'TOWL_API_KEY', 'your-actual-key-here' );
define( 'TOWL_PROJECT_SECRET', 'your-actual-secret-here' );
define( 'TOWL_ENDPOINT', 'https://be.tiny-owl-kit.io/api/ingest' );
Manual logging
Backend (PHP)
tiny_owl_log( string $message, string $severity = 'info', array $context = [] ): bool|WP_Error
| Parameter | Type | Description |
|---|---|---|
$message |
string |
Human-readable description |
$severity |
string |
'info' (default), 'warning', or 'error' |
$context |
array |
Key/value pairs for diagnostics |
Returns true on success, WP_Error if disabled or credentials missing. Manual calls bypass the minimum severity filter.
Frontend (JS)
Frontend errors are proxied through a REST endpoint so credentials never reach the browser. window.towlFrontend is output in <head> when the plugin is active.
fetch(window.towlFrontend.restUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Towl-Nonce': window.towlFrontend.nonce,
},
body: JSON.stringify({
message: 'Checkout JS error',
severity: 'error',
context: {
component: 'CheckoutForm',
action: 'submitOrder',
code: error.code,
stack: error.stack?.slice(0, 500),
},
}),
});
Allowed frontend context keys (others stripped server-side): userId, url, component, action, code, line, column, stack, product_id, product_sku, pim_api_product_id, pim_code, pim_status, is_published.
Rate limit: 20 requests per minute per IP.
Guarding calls in other plugins
tiny_owl_log() is defined by this plugin. Calling it directly from another plugin will throw a fatal error if Tiny Owl Logger is deactivated. Always guard it.
Recommended — one wrapper per plugin (guard lives in one place):
function myplugin_log( string $message, string $severity = 'error', array $context = [] ): void {
if ( function_exists( 'tiny_owl_log' ) ) {
tiny_owl_log( $message, $severity, $context );
}
}
Alternative — inline guard (for one-off call sites):
if ( function_exists( 'tiny_owl_log' ) ) {
tiny_owl_log( 'Payment failed', 'error', [ 'order_id' => $order_id ] );
}
Logging patterns for AI diagnostics
Structured context is what allows an AI assistant to diagnose an error without re-reading the code. Log at the exact point of failure and include why it failed, not just that it failed.
API / HTTP failure:
if ( is_wp_error( $response ) ) {
myplugin_log( 'My API: request failed', 'error', [
'resource_id' => $id,
'error_code' => $response->get_error_code(),
'error_msg' => $response->get_error_message(),
'endpoint' => $endpoint,
] );
return false;
}
Non-200 response:
if ( $code !== 200 ) {
myplugin_log( 'My API: unexpected HTTP status', 'error', [
'resource_id' => $id,
'http_status' => $code,
'response_body' => substr( wp_remote_retrieve_body( $response ), 0, 300 ),
'endpoint' => $endpoint,
] );
return false;
}
Missing configuration:
if ( empty( $token ) ) {
myplugin_log( 'My API: bearer token not configured', 'error', [
'resource_id' => $id,
] );
return false;
}
Data / validation mismatch:
myplugin_log( 'My API: ID mismatch in response', 'warning', [
'expected_id' => $expected,
'returned_id' => $returned,
'endpoint' => $endpoint,
] );
Recommended context keys — use these names consistently across plugins:
| Key | When to use |
|---|---|
resource_id |
Primary ID being looked up (size_id, product_id, etc.) |
http_status |
HTTP response code |
endpoint |
URL that was called |
error_code |
WP_Error code or exception class |
error_msg |
Human-readable failure reason |
response_body |
First 300 chars of an unexpected response |
user_id |
When failure is user-specific |
order_id |
Order/checkout context |
Settings
Located at Settings > Tiny Owl Logger.
| Setting | Description |
|---|---|
| Enable error tracking | Master on/off switch |
| Minimum severity | info (all), warning (default), or error only |
| Environment | Label sent with every event, e.g. production, staging |
Credentials are verified on this page — actual values are never displayed.
Rotating credentials
- Generate a new key/secret at tiny-owl-kit.io/projects.
- Update
.htaccess(production) orwp-config.php(local). - Use Send test event to confirm the new credentials work.
- Delete the old key from the dashboard immediately.
AI agent prompt
Paste the block below directly into Claude Code (or any AI agent) at the start of a conversation. Select from the first line to the last.
You are working in a WordPress codebase that uses the Tiny Owl Logger plugin
(towl-wordpress-plugin) for observability. Follow all rules below before
writing or suggesting any logging code.
## What Tiny Owl Logger is
A WordPress plugin that forwards PHP errors, uncaught exceptions, fatal errors,
and custom log events to the Tiny Owl observability service
(https://tiny-owl-kit.io). The core function is:
tiny_owl_log( string $message, string $severity = 'info', array $context = [] ): bool|WP_Error
Severity values: 'info', 'warning', 'error'.
The context array is the primary machine-readable surface for diagnostics.
## Rules you must follow
1. NEVER call tiny_owl_log() directly from a plugin or theme that does not own
the function. Always wrap it in a guard or a plugin-local helper. A missing
guard causes a fatal "Call to undefined function" if Tiny Owl Logger is
deactivated.
Correct — plugin-local helper (preferred):
function myplugin_log( string $message, string $severity = 'error', array $context = [] ): void {
if ( function_exists( 'tiny_owl_log' ) ) {
tiny_owl_log( $message, $severity, $context );
}
}
Correct — inline guard (one-off):
if ( function_exists( 'tiny_owl_log' ) ) {
tiny_owl_log( 'Something failed', 'error', [ 'id' => $id ] );
}
Wrong — bare call:
tiny_owl_log( 'Something failed', 'error', [ 'id' => $id ] ); // FATAL if plugin inactive
2. NEVER add logging to the towl-wordpress-plugin itself using tiny_owl_log().
That plugin defines the function — it must not call itself.
3. NEVER log sensitive data: passwords, tokens, full query strings, POST body
values, or personally identifiable information. Truncate response bodies to
300 characters max.
4. NEVER replace existing error_log() calls wholesale. Add Tiny Owl calls
alongside them unless the user explicitly asks to remove the originals.
5. NEVER install, activate, or configure the plugin autonomously. Walk the user
through each step and wait for confirmation before continuing.
6. NEVER modify wp-config.php or .htaccess without explicit user confirmation.
## Installation guidance
If the plugin is not installed, walk the user through these steps one at a time:
Step 1 — Copy towl-wordpress-plugin/ into wp-content/plugins/.
Rename from towl-wordpress-plugin-main if downloaded from GitHub.
Step 2 — Add credentials to wp-config.php.
Local (safe to hardcode):
define( 'TOWL_API_KEY', 'your-key-here' );
define( 'TOWL_PROJECT_SECRET', 'your-secret-here' );
define( 'TOWL_ENDPOINT', 'https://be.tiny-owl-kit.io/api/ingest' );
Production (read from environment):
.htaccess:
SetEnv TOWL_API_KEY "your-key-here"
SetEnv TOWL_PROJECT_SECRET "your-secret-here"
wp-config.php:
define( 'TOWL_API_KEY', getenv('TOWL_API_KEY') );
define( 'TOWL_PROJECT_SECRET', getenv('TOWL_PROJECT_SECRET') );
define( 'TOWL_ENDPOINT', 'https://be.tiny-owl-kit.io/api/ingest' );
Confirm .htaccess is in .gitignore before proceeding.
Step 3 — Activate in WP Admin > Plugins > Installed Plugins.
Step 4 — Go to Settings > Tiny Owl Logger and use "Send test event".
## Backend logging patterns
Log at the point of failure. Include why it failed, not just that it failed.
API / HTTP failure:
if ( is_wp_error( $response ) ) {
myplugin_log( 'My API: request failed', 'error', [
'resource_id' => $id,
'error_code' => $response->get_error_code(),
'error_msg' => $response->get_error_message(),
'endpoint' => $endpoint,
] );
return false;
}
Non-200 response:
if ( $code !== 200 ) {
myplugin_log( 'My API: unexpected HTTP status', 'error', [
'resource_id' => $id,
'http_status' => $code,
'response_body' => substr( wp_remote_retrieve_body( $response ), 0, 300 ),
'endpoint' => $endpoint,
] );
return false;
}
Missing config:
if ( empty( $token ) ) {
myplugin_log( 'My API: token not configured', 'error', [
'resource_id' => $id,
] );
return false;
}
Data mismatch:
myplugin_log( 'My API: ID mismatch', 'warning', [
'expected_id' => $expected,
'returned_id' => $returned,
'endpoint' => $endpoint,
] );
Recommended context keys (use consistently across plugins):
resource_id — primary ID being looked up
http_status — HTTP response code
endpoint — URL that was called
error_code — WP_Error code or exception class
error_msg — human-readable failure reason
response_body — first 300 chars of unexpected response
user_id — when failure is user-specific
order_id — order/checkout context
## Frontend logging
Errors are proxied through a REST endpoint — credentials never reach the browser.
window.towlFrontend is output in <head> when the plugin is active.
fetch(window.towlFrontend.restUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Towl-Nonce': window.towlFrontend.nonce,
},
body: JSON.stringify({
message: 'Checkout JS error',
severity: 'error',
context: {
component: 'CheckoutForm',
action: 'submitOrder',
code: error.code,
stack: error.stack?.slice(0, 500),
},
}),
});
Allowed frontend context keys (others stripped server-side):
userId, url, component, action, code, line, column, stack,
product_id, product_sku, pim_api_product_id, pim_code, pim_status, is_published.
Rate limit: 20 requests per minute per IP. Do not log in loops.
## Audit workflow — ask before acting
Ask the user these two questions before writing any code:
Q1: "Should I scan the codebase for error_log(), silent return false, and
console.error() calls that have no Tiny Owl coverage, and show you a list?"
Q2: "Should I go ahead and instrument those locations, or do you want to review
the list first?"
When scanning, look for:
- error_log() with no accompanying plugin helper call
- Silent return false / null after a failed condition
- catch blocks that only call error_log or do nothing
- console.error() / console.warn() with no towlFrontend fetch
- wp_remote_get / wp_remote_post with no failure logging
When adding logging:
- Add a plugin-local helper if one does not exist
- Place the log call at the exact failure point, not in a caller
- Keep existing error_log() calls unless the user asks to remove them
Disclaimer
This plugin is unofficial and not affiliated with or supported by Tiny Owl. Use at your own risk.