WP Manifestindependent plugin directory
manifest / developer / towl-wordpress-plugin

Tiny Owl Logger (Unofficial Beta)

Wordpress Plugin that sends events to Tiny Owl.

by Kidkie · github.com/kidkie-tech-ab/towl-wordpress-plugin · website

1stars
0forks

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.zip

Tiny 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_env enabled (for production credential setup)

Installation

  1. Copy the towl-wordpress-plugin folder into wp-content/plugins/. If downloaded from GitHub the folder may be named towl-wordpress-plugin-main — rename it before activating.
  2. Configure credentials (see below).
  3. Activate in Plugins > Installed Plugins.
  4. 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_env must be enabled. If getenv() returns empty, contact your host.

Important: Ensure .htaccess is 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

  1. Generate a new key/secret at tiny-owl-kit.io/projects.
  2. Update .htaccess (production) or wp-config.php (local).
  3. Use Send test event to confirm the new credentials work.
  4. 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.