DEV Community

Cover image for Laravel Nightwatch: First-Party APM and What It Actually Replaces
Gabriel Anhaia
Gabriel Anhaia

Posted on

Laravel Nightwatch: First-Party APM and What It Actually Replaces


You already run three tools that half-cover this job. Pulse gives you a live wall on a local route. Datadog runs an agent and prices on host and usage volume, so the bill scales with your infrastructure. Sentry catches the exceptions after they already hurt someone. And none of them can tell you the one thing you actually asked: the checkout request that took 900ms at 14:03 dispatched a job, that job ran a query, and the query is what timed out.

Laravel Nightwatch reached general availability in 2025 as the framework's own APM, aimed straight at that gap. It is worth knowing exactly what it captures, what it charges, and where its knowledge of your app stops and yours begins.

What Nightwatch actually is

Two moving parts. A Composer package inside your app, and a separate agent process that ships the data.

composer require laravel/nightwatch
Enter fullscreen mode Exit fullscreen mode

The package writes events to a local socket. The agent listens on 127.0.0.1:2407, batches what it receives, and sends it to Nightwatch's cloud. Because the agent runs outside your request cycle, the request thread is not blocked waiting on a network call to a telemetry backend. Laravel puts the added cost at under 3ms per request; take that as a starting figure and measure your own before you trust it.

# environment token per app + environment
NIGHTWATCH_TOKEN=your-env-token

# start the collector (keep it running under a
# process monitor: Forge daemon, Vapor, supervisor)
php artisan nightwatch:agent

# confirm it is alive and receiving
php artisan nightwatch:status
Enter fullscreen mode Exit fullscreen mode

One detail that bites people: the agent has to be running for anything to arrive. In local dev you start it by hand. In production it belongs under supervisor, a Forge daemon, or Vapor, the same way you already run Horizon.

What it captures without you writing a line

Nightwatch hooks Laravel's own event dispatcher, so it sees framework concepts by name rather than as anonymous system calls. Out of the box it records:

  • Incoming requests, with route, status, and duration
  • Outgoing HTTP calls
  • Database queries
  • Queued jobs and batches
  • Mail and notifications
  • Cache reads and writes
  • Artisan commands
  • Scheduled tasks
  • Exceptions and logs
  • The authenticated user on the request

The value is not the list. It is the stitching. Nightwatch ties a request to the queries it ran, the job it queued, and the mail that job later sent, into one trace. Wiring that correlation together by hand is most of the work in a generic APM. Here it is the default.

Nightwatch vs Pulse

Pulse and Nightwatch are both from the Laravel team, and they answer different questions.

Pulse is an aggregate dashboard that lives in your own app, backed by your database or Redis. It shows slow queries, busiest routes, and top exceptions as rolling counts. It is free, self-hosted, and gives you a "is the app healthy right now" view. What it does not give you is a single request's timeline or history beyond its retention window. You cannot open the request from 14:03 and walk it.

Nightwatch is per-event and hosted, built for the retrospective question: why was that request slow. Keep both. Pulse stays the free live wall on an internal route; Nightwatch is where you go when someone reports a slow checkout yesterday and you need the trace.

Nightwatch vs Datadog and other generic APMs

Datadog, New Relic, and raw OpenTelemetry are language-agnostic by design. They see PHP-FPM workers, HTTP spans, and SQL statements. What they do not see is Laravel. A queued job is just a long-running CLI process. A Horizon supervisor is an opaque box. An Eloquent N+1 is thirty anonymous SELECT statements with no hint they came from one loop. You buy that context back through custom instrumentation and dd-trace tuning, and you maintain it forever.

Nightwatch knows what a job, a batch, a notification, and a scheduled command are, because it is the framework watching itself. That is the trade you are making. Nightwatch is Laravel-only and fully managed, with data residency in the US, EU, or Australia and no self-hosted option. If you run polyglot infrastructure and need one pane over a Go service, a Laravel app, and a Node worker, Datadog still wins on breadth. If Laravel is the whole application, Nightwatch gives you more signal for far less configuration.

What it costs, and the trap in the pricing

Nightwatch bills per event, not per host. The published tiers, as of mid-2026:

  • Free: $0, 300k events, 14-day lookback
  • Pro: $20/mo, 7.5m events, 30-day lookback
  • Team: $60/mo, 30m events, 60-day lookback
  • Business: $300/mo, 180m events, 90-day lookback

Overage runs $0.35 per 100k on the paid lower tiers, dropping to $0.20 at the Business tier.

Now read "event" carefully. A request is an event. So is each query it runs, each job it dispatches, each cache hit, each outgoing call. One ordinary request can emit a dozen events. Do the arithmetic: 300k events at ten events per request is 30k requests, which a modest production app can burn through in a day. The free tier is generous for a side project and thin for anything with real traffic.

The lever is sampling, and Nightwatch gives you a spending cap that stops ingesting once you hit it. You fail closed into missing data, not open into a surprise invoice. That is a different failure mode from host-based pricing, and a friendlier one.

The instrumentation you still own

Zero-config gets you the framework's view. It never gets you your application's view. Three things Nightwatch cannot infer, and how you supply them.

1. What Nightwatch records about the user. Nightwatch reads the authenticated user on its own, but you decide which fields identify them. Register a resolver once in AppServiceProvider. This is where you map an account to a readable name, or prefix the ID with a tenant so two customers whose id is 5 do not collide in your traces.

use Illuminate\Contracts\Auth\Authenticatable;
use Laravel\Nightwatch\Facades\Nightwatch;

// in AppServiceProvider::boot()
Nightwatch::user(fn (Authenticatable $user) => [
    'id' => $user->id,
    'name' => $user->name,
    'email' => $user->email,
]);
Enter fullscreen mode Exit fullscreen mode

2. What a request means to the business. Nightwatch sees that POST /checkout took 900ms. It has no idea that 900ms breached your payment SLA, or that this path is the one that makes money. Sampling is where you encode that priority, because entry points (requests, commands, scheduled tasks) are what you sample.

use Laravel\Nightwatch\Facades\Nightwatch;

// keep every trace for staff, sample the rest at 20%
if ($request->user()?->isStaff()) {
    Nightwatch::sample(rate: 1.0);
} else {
    Nightwatch::sample(rate: 0.2);
}
Enter fullscreen mode Exit fullscreen mode

3. Domain operations that do not map to a framework primitive. "Reserve inventory" might be three services, two queries, and a dispatched event. To Nightwatch it is a scatter of queries under one request, with no name tying them together. The framework can capture a query; it cannot know your domain calls that sequence a reservation. If you want that concept to show up, you express it through the signals Nightwatch does collect, structured logs and events, so the trace carries a word your team recognizes.

Log::withContext([
    'domain_op' => 'reserve_inventory',
    'sku' => $sku,
])->info('inventory reservation started');
Enter fullscreen mode Exit fullscreen mode

That last point is the one to sit with. First-party APM erases the plumbing work of correlation. It does not erase the work of naming what your software does. That naming was always yours to do.


Nightwatch is a good default because it lives at the framework boundary and asks nothing of your domain code. That is exactly why the domain still needs its own vocabulary: the concepts that matter to your business are not framework events, and no zero-config tool will name a "reserve inventory" use case for you. Keeping observability of the framework at the edge, while your domain operations stay explicit and independent of it, is the separation Decoupled PHP is built around.

Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework

Available on Kindle, Paperback, and Hardcover. English, German, and Japanese editions out now — Portuguese and Spanish coming soon.

Top comments (1)

Collapse
 
daniel_trix_smith profile image
Daniel Trix Smith

This was a refreshing take. The pricing examples and the reminder that an "event" isn't the same as a "request" make the trade-offs much clearer. It would also be interesting to see how you approach monitoring in hybrid environments where Laravel, Go, and Node services coexist. Thanks for sharing your insights—I always learn something new from your posts.