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
stderrautomatically - Nginx access and error logs go to
stdout/stderrautomatically - Your application's log output needs to be directed to
stderror 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.