Search by

watchtower15 / cakephp

watchtower15

The WatchTower15 PHP SDK's CakePHP plugin.

Package info

github.com/Watchtower15/watchtower15-cakephp

Homepage

Type:cakephp-plugin

pkg:composer/watchtower15/cakephp

Statistics

Installs: 10

Dependents: 0

Suggesters: 0

Stars: 0

v1.0.2 2026-11-04 20:51 UTC

This package is auto-updated.

Last update: 2026-11-10 12:11:05 UTC


README

The WatchTower15 PHP SDK's CakePHP plugin. It turns the core SDK (watchtower15/sdk) into CakePHP services: the application's WatchTower15\Client, the metrics aggregator and its flush, a CakePHP log engine, a transport on CakePHP's own HTTP client, and the console commands watchtower15 test and watchtower15 flush. CakePHP ^5.1.10, PHP ^8.3.

Nothing is built, sent or logged when the application boots, and a mistyped credential never breaks the application or your logging.

This repository is a read-only split of the WatchTower15 PHP SDK: pull requests are not accepted. Documentation: https://docs.watchtower15.com. Report a vulnerability to contact@watchtower15.com with the subject "Security".

Support policy. PHP 8.3 and later and CakePHP 5.1 and later, Semantic Versioning, all four packages released together: see the support policy.

Install

composer require watchtower15/cakephp

The plugin installer registers the plugin from its Composer type (cakephp-plugin). Without it, load it in src/Application.php:

public function bootstrap(): void
{
    parent::bootstrap();
    $this->addPlugin(\WatchTower15\Cake\CakePlugin::class);
}

Then check the setup:

bin/cake watchtower15 test

Configuration

Every key is optional. Credentials come from the environment (WT15_API_KEY, WT15_METRICS_TOKEN, WT15_LOGS_TOKEN, and WT15_API_URL, WT15_INGEST_URL) unless Configure's WatchTower15 key names them, which wins. A blank value is read as not set.

// config/app_local.php
'WatchTower15' => [
    'metrics' => [
        'cache' => 'redis',                    // a Cache config on the Redis engine, shared by the fleet
        'prefix' => 'shop',                    // required with 'cache': one per application
        'allowed' => ['orders.shipped' => 'count', 'queue.depth' => 'level'],
    ],
],
'Log' => [
    'watchtower15' => [
        'className' => \WatchTower15\Cake\Log\WatchTower15Log::class,
        'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
    ],
],
Key Meaning
apiKey, apiUrl, ingestUrl The API key, and the two base URLs (unset is the SDK's default)
timeout, connectTimeout, maxRetries, appInfo Passed to the application's client
httpClient true (default) sends through CakePHP's HTTP client; false uses the SDK's own cURL transport
metrics.token, metrics.enabled The metrics token, and the switch for the pipe (true by default)
metrics.cache, metrics.prefix A Cache config name for the store, and the application's prefix (below)
metrics.store file:///absolute/folder or memory://; unset is a folder under TMP
metrics.allowed A map of metric key to count or level: nothing else is sent
metrics.allowAny true sends any metric key (not with metrics.cache)
metrics.heartbeat A metric key the flusher adds once a minute
metrics.flushAfterResponse auto, always or never (below)
logs.token The logs token

A value of the wrong type is reported by bin/cake watchtower15 test, naming its key, and the default is used. A key the plugin does not know is reported the same way (a typo), and ignored.

The clients

Client Holds A bad value
WatchTower15\Client The API key, and the other credentials Throws WatchTower15\ConfigurationException to the code that asks for it, and to nothing else
the metrics pipe's client The metrics token alone Never throws: the flush stops with a config count and keeps its buckets
the log engine's client The logs token alone Counted config by the engine, never thrown

WatchTower15\Client, WatchTower15\Metrics\Aggregator, Flusher and BucketStore are in the application's container, so a controller or command asks for them in its constructor.

public function __construct(private readonly \WatchTower15\Client $wt15, private readonly \WatchTower15\Metrics\Aggregator $metrics) {}
// …
$this->metrics->increment('orders.shipped');
$page = $this->wt15->servers->list(['health' => ['critical']]);

The transport

The plugin sends through its own Cake\Http\Client, which never follows redirects and which the HTTP client's HttpClient.beforeSend and HttpClient.afterSend events and Client::addMockResponse() reach as they do for your own calls. Timeouts are set in milliseconds, and a response larger than the SDK's cap is stopped during the transfer.

The metrics store

The store is the SDK's, on one of your Cache configs. CakePHP's cache has no way to list a key, so the plugin reads the keys you allow (metrics.allowed and the heartbeat), and a store that is not a Redis, Memcached or APCu config is refused.

// config/app_local.php
'Cache' => ['redis' => ['className' => 'Redis', 'host' => 'redis', 'prefix' => 'cake_']],
Store Shared by
unset: a file under TMP/watchtower15 PHP-FPM and the CLI on one host
metrics.store file:///absolute/folder one host
metrics.cache on a Redis config a fleet
metrics.cache on a Memcached config a fleet (not tested by the SDK's own suite)
metrics.cache on an APCu config one SAPI only
metrics.store memory:// one process
  • metrics.prefix is required with metrics.cache, one per application: two applications on one cache backend would share buckets, and the first flusher would send the other's keys under its own token. Keys live under wt15_<the config's prefix><metrics.prefix>:, so a clear of your application's cache leaves them alone.
  • The plugin keeps one config per expiry, named wt15_<your config>_<seconds>, each a copy of yours without its groups and without a fallback. They appear in Cache::configured().
  • ⚠️ APCu is per SAPI: the CLI cannot see what PHP-FPM wrote to APCu, so an APCu store is flushed from requests only.
  • memory:// is for console commands and tests only.
  • A store the plugin refuses (an unknown scheme, an engine that cannot increment, a missing prefix) disables the metrics pipe, with its reason, and throws nothing: nothing is buffered, and watchtower15 test and watchtower15 flush print the reason.
  • While Cache::disable() is on, or a Redis server is unreachable, the pipe counts the failure and the request goes on.

Flushing

  • After the response. flushAfterResponse: auto flushes on Server.terminate where the response is already finished (PHP-FPM and FrankenPHP, decided by fastcgi_finish_request() and never by the SAPI's name), for an APCu or memory:// store in every runtime, and on Command.afterExecute of every command but watchtower15 flush. always does so in every runtime; never on neither. One flush a minute runs per shared store, whoever claims it first.

  • Every minute. In the web server's user's crontab on each host:

    * * * * * cd /app && bin/cake watchtower15 flush --if-due
    

    The files a file store writes carry the process's umask: a cron job running as another user than PHP-FPM cannot write FPM's files, so run them all as the same user.

Logs

Configure the engine under Log as above. It sends one push per request, after the response where PHP-FPM allows it, and it never sends the SDK's own warnings back to WatchTower15: the SDK writes those to your other log engines.

Commands

  • bin/cake watchtower15 test checks each configured credential on a client of its own: the API key with me(), the metrics token with the source's listing, the logs token by its format (a log push is a write). It then prints the store and its scope, the allow-list, the flush paths and the log engines. Exit 2 when nothing is configured or a credential or URL is malformed (the checks the others allow still run); else 1 when a check failed or the store is refused; else 0.
  • bin/cake watchtower15 flush sends the closed minute buckets now. It prints "disabled" (exit 0) while metrics are off or have no token, refuses a store it cannot read from the CLI or a refused configuration (exit 2, with no store call), and otherwise flushes and prints the report (exit 1 when the flush stopped on a failure). --if-due takes the same minute claim as the after-response flush and prints "Not due" when another process holds it: this is the cron line's form.

Security

API keys and tokens are personal: keep them in the environment or a secrets store, never in code. The plugin never prints a credential.

MIT licence.