CloudPloy

Application Monitoring and Observability on CloudPloy

Monitoring is how you find out something is broken before your users do. This page covers every layer of observability for PHP, Laravel, and WordPress applications running on CloudPloy - from the built-in container logs and metrics in the dashboard, to integrating external error tracking and APM tools, to setting up alerts that page you at the right threshold.

What CloudPloy Monitors Automatically

CloudPloy collects and exposes several layers of telemetry without any configuration on your part:

What is Monitored Where to See It What to Look For
Container health checks App > Overview (status badge) Green = healthy; Yellow = degraded; Red = failing health check
Build logs App > Deployments > Build Log Composer/npm errors, Dockerfile failures, missing env vars
Application logs App > Logs (real-time stream) PHP errors, Laravel exceptions, Nginx access/error logs
CPU and memory usage Server > Metrics Sustained >80% CPU or memory pressure indicate capacity issues
Disk usage Server > Metrics Alert at 80%; Elasticsearch goes read-only at 90%
Deployment history App > Deployments Which commit is live; when the last deployment ran; rollback targets

CloudPloy polls your app's health endpoint every 30 seconds. Three consecutive failures trigger an automatic container restart. The default health check is GET / expecting a 2xx response. You can override this in your Dockerfile - see the Docker configuration guide for health check syntax.

Part 1: Application Logs

How Logging Works on CloudPloy

CloudPloy captures everything written to stdout and stderr from your container. In PHP, this means:

  • PHP-FPM process errors go to stderr automatically
  • Nginx access and error logs go to stdout/stderr automatically
  • Your application's log output needs to be directed to stderr or a log file that is tailed to stdout

Laravel Logging Configuration

For Laravel on CloudPloy, configure the log channel to write to stderr so logs appear in the CloudPloy dashboard in real-time:

# In your CloudPloy environment variables
LOG_CHANNEL=stderr
LOG_LEVEL=warning    # Use 'debug' only on staging - too verbose for production

If you want structured JSON logs (easier to search and parse), use the stderr driver with the json formatter in config/logging.php:

'stderr' => [
    'driver'    => 'monolog',
    'level'     => env('LOG_LEVEL', 'warning'),
    'handler'   => Monolog\Handler\StreamHandler::class,
    'formatter' => Monolog\Formatter\JsonFormatter::class,
    'with' => [
        'stream' => 'php://stderr',
    ],
],

WordPress Logging

WordPress does not write to stdout by default. Enable debug logging in wp-config.php:

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', '/dev/stderr');  // Route to container stderr
define('WP_DEBUG_DISPLAY', false);      // Never display errors to users

For production, use WP_DEBUG false and rely on PHP error logs which PHP-FPM writes to stderr. PHP fatal errors and warnings still appear in App > Logs even without WP_DEBUG.

Searching and Filtering Logs

The CloudPloy log viewer supports text search. Useful patterns to search for:

Search Pattern What it Finds
ERROR All PHP and Laravel error-level log entries
CRITICAL Fatal application errors requiring immediate attention
Illuminate\Database Laravel database exceptions (connection failures, query errors)
502 Nginx 502 Bad Gateway - PHP-FPM not responding
upstream timed out PHP-FPM worker timeout - slow requests blocking the queue
worker_connections Nginx worker limit hit - too many concurrent connections

Part 2: Error Tracking with Sentry

The CloudPloy log stream is good for real-time investigation, but not for aggregating errors across time or getting alerts when something breaks. Sentry is the standard choice for PHP error tracking - it captures exceptions with full stack traces, groups duplicates, and sends alerts.

Laravel + Sentry Setup

composer require sentry/sentry-laravel

Add your DSN to CloudPloy environment variables:

SENTRY_LARAVEL_DSN=https://your-key@o0.ingest.sentry.io/your-project-id
SENTRY_TRACES_SAMPLE_RATE=0.1    # Capture 10% of requests for performance tracing

Publish the config and add Sentry to the exception handler in app/Exceptions/Handler.php:

public function register(): void
{
    $this->reportable(function (Throwable $e) {
        if (app()->bound('sentry') && $this->shouldReport($e)) {
            app('sentry')->captureException($e);
        }
    });
}

Add user context to error reports so you can see which user experienced each error:

// In a middleware or AppServiceProvider boot()
if (auth()->check()) {
    \Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
        $scope->setUser([
            'id'    => auth()->id(),
            'email' => auth()->user()->email,
        ]);
    });
}

WordPress + Sentry Setup

Install the WP Sentry plugin:

wp plugin install wp-sentry-integration --activate

Configure via CloudPloy environment variables (WordPress reads these via getenv() in wp-config.php):

define('WP_SENTRY_PHP_DSN', getenv('SENTRY_DSN'));
define('WP_SENTRY_ENV', getenv('APP_ENV') ?: 'production');
define('WP_SENTRY_VERSION', getenv('DEPLOY_SHA') ?: 'unknown');

What to Alert On in Sentry

Alert Rule Threshold Why
New issue created Immediately First occurrence of an exception you have not seen before
Issue frequency spike >10 events in 5 min A known error suddenly getting worse - usually a deployment broke something
Error rate >1% of requests Widespread failure - needs immediate response
P95 response time >2000ms 95th percentile slow - most users getting slow responses

Part 3: Laravel Telescope for Development and Staging

Laravel Telescope is a debug assistant that records requests, queries, jobs, emails, and more to a local dashboard. It is ideal for staging environments where you want to understand what your application is doing without external tooling.

composer require laravel/telescope --dev

Install Telescope and run the migrations:

php artisan telescope:install
php artisan migrate

Restrict Telescope to non-production environments in app/Providers/TelescopeServiceProvider.php:

public function register(): void
{
    Telescope::night();

    $this->hideSensitiveRequestDetails();

    // Only record on staging
    Telescope::filter(function (IncomingEntry $entry) {
        if ($this->app->environment('local', 'staging')) {
            return true;
        }
        // On production, only record slow queries and errors
        return $entry->isReportableException() ||
               $entry->isFailedRequest() ||
               ($entry->type === EntryType::QUERY && $entry->content['slow'] ?? false);
    });
}

Access the Telescope dashboard at your-staging-url/telescope. It shows:

  • Every HTTP request and its response time, queries, and cache operations
  • N+1 query detection - queries that run in a loop
  • Queue jobs, their duration, and failure messages
  • Scheduled command execution history
  • Email previews (without actually sending to real users)
  • Exception traces with full context

Part 4: Database Query Monitoring

Laravel Slow Query Logging

Log queries that exceed a threshold in AppServiceProvider::boot():

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

DB::listen(function ($query) {
    if ($query->time > 100) { // Log queries taking more than 100ms
        Log::warning('Slow query detected', [
            'sql'      => $query->sql,
            'bindings' => $query->bindings,
            'time_ms'  => $query->time,
        ]);
    }
});

These entries appear in App > Logs with the Slow query detected prefix. Search for them when investigating response time regressions after deployments.

MySQL Slow Query Log

Enable MySQL's native slow query log for queries over 500ms. Add to your MySQL config:

slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.5
log_queries_not_using_indexes = 1

From the CloudPloy terminal, analyze slow query patterns:

mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

This shows the 10 slowest queries by total time - the most impactful candidates for index optimization.

PHP-FPM Worker Saturation

When all PHP-FPM workers are busy, new requests queue. If the queue fills, Nginx returns 502. Check worker status from the CloudPloy terminal:

curl --silent http://localhost/fpm-status?full | grep -E "idle processes|active processes|listen queue"

If listen queue is consistently non-zero, you are running out of workers. Increase pm.max_children in your PHP-FPM pool config, or optimize slow requests so workers are freed faster.

Part 5: Uptime Monitoring

CloudPloy's internal health checks only verify the container is running - they do not test from an external network location. For true uptime monitoring that catches DNS failures, Cloudflare outages, or SSL expiry, use an external uptime service.

Recommended Uptime Tools

Tool Free Tier Check Interval Best For
Better Uptime Yes (10 monitors) 3 minutes Status pages + on-call routing
UptimeRobot Yes (50 monitors) 5 minutes Simple HTTP monitoring
Checkly Yes (limited) 1 minute API and browser-based checks
Grafana Cloud Yes 1 minute Teams already using Grafana

Building a Useful Health Endpoint

A health endpoint that only returns 200 tells you the app is running - not that it is working. A meaningful health check verifies database connectivity, cache availability, and any critical dependencies:

// routes/web.php
Route::get('/health', function () {
    $checks = [];
    $status = 200;

    // Database check
    try {
        DB::select('SELECT 1');
        $checks['database'] = 'ok';
    } catch (\Exception $e) {
        $checks['database'] = 'error: ' . $e->getMessage();
        $status = 503;
    }

    // Redis/cache check
    try {
        Cache::set('health_check', 1, 5);
        Cache::get('health_check');
        $checks['cache'] = 'ok';
    } catch (\Exception $e) {
        $checks['cache'] = 'error: ' . $e->getMessage();
        $status = 503;
    }

    return response()->json([
        'status'  => $status === 200 ? 'healthy' : 'degraded',
        'checks'  => $checks,
        'version' => env('DEPLOY_SHA', 'unknown'),
    ], $status);
})->name('health');

Point your uptime monitor to /health. Configure CloudPloy's health check to use this endpoint too (in your Dockerfile's HEALTHCHECK directive).

Part 6: Performance Monitoring

Measuring Response Times in Laravel

Track response time per route and log slow requests. Add middleware in app/Http/Middleware/LogSlowRequests.php:

class LogSlowRequests
{
    public function handle(Request $request, Closure $next): Response
    {
        $start    = microtime(true);
        $response = $next($request);
        $duration = round((microtime(true) - $start) * 1000);

        if ($duration > 500) { // Warn on requests over 500ms
            Log::warning('Slow request', [
                'url'         => $request->fullUrl(),
                'method'      => $request->method(),
                'duration_ms' => $duration,
                'user_id'     => auth()->id(),
            ]);
        }

        return $response->header('X-Response-Time', $duration . 'ms');
    }
}

Register it globally in bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->append(LogSlowRequests::class);
})

OPcache Status

OPcache misses cause PHP to reparse files on every request. Check OPcache hit rate from the terminal:

php -r "print_r(opcache_get_status());" | grep -A2 "opcache_statistics"

A hit rate below 90% means either the cache is too small (opcache.memory_consumption) or has too few entries (opcache.max_accelerated_files). See the Docker guide for recommended OPcache settings by server size.

Part 7: Alerting Strategy

The goal of alerting is to get notified about things that require human action - not to log every event. Over-alerting causes fatigue and gets ignored. Under-alerting means you find out from users.

Alert Thresholds by Severity

Metric Warning Critical Response
HTTP error rate >0.5% of requests >2% of requests Check Sentry; review recent deployments
P95 response time >1000ms >3000ms Check slow query log; PHP-FPM worker queue
CPU usage >70% for 5 min >90% for 2 min Profile processes; consider server upgrade
Memory usage >75% >90% Check for memory leaks; increase PHP memory_limit
Disk usage >70% >85% Clean logs; purge old uploads; expand volume
MySQL connections >80% of max_connections >95% Enable PgBouncer/ProxySQL; check for connection leaks

Configuring Alerts on CloudPloy

Set up server-level alerts in Server > Alerts. For application-level alerts (HTTP error rate, response time), these come from your external uptime monitor or Sentry. Connect Sentry and your uptime tool to Slack, PagerDuty, or email via their native integrations.

Frequently Asked Questions

How long are logs retained on CloudPloy?

The real-time log stream in App > Logs shows recent entries. For longer-term log retention and search, configure your application to send logs to an external service: Papertrail, Logtail, Datadog Logs, or AWS CloudWatch. Set the log destination as an environment variable and use Laravel's Monolog handler for the corresponding service.

Does CloudPloy integrate with Datadog or New Relic?

Yes. Both Datadog and New Relic provide PHP agents that install as a PHP extension. Add the agent installation to your Dockerfile, set the API key in CloudPloy environment variables, and the agent reports metrics, traces, and logs automatically. The agents work as Docker containers or as PHP extensions depending on your setup preference.

How do I monitor Laravel queues?

Laravel Horizon provides a dashboard for Redis-backed queues with metrics on job throughput, failure rate, and wait times. Add it as a worker container in CloudPloy (App > Workers) running php artisan horizon. Access the Horizon dashboard at /horizon and configure alerting for queue failure rate spikes.

My app passes health checks but users are getting errors - why?

Health checks verify the app responds - not that specific features work. Common causes: a third-party API your app depends on is down (payment gateway, email service), a specific database table is corrupted while others work fine, or a background job is failing silently. Use Sentry to capture these application-level errors that health checks will never catch.

How do I monitor a WordPress multisite?

Add a health endpoint to your WordPress theme's functions.php that checks the main site and at least one subsite for database connectivity. Monitor each subdomain separately with your uptime tool so you get distinct alerts when specific sites go down rather than treating the whole network as one endpoint.


Need help setting up monitoring for your application? Check the Docker guide for health check configuration, or contact CloudPloy support for monitoring setup assistance.