Entries tagged Agent ship as a Composer package, so you pick up those changes by running
composer update nightowl/agent. Some agent releases need a follow-up command (a migration or a new env var). Each Agent entry below links to the agent changelog, which lists the exact upgrade steps per version. API and dashboard changes are deployed for you on the hosted platform.Agent
If you run the agent on more than one server against the same database, they no longer deadlock each otherYour agent log may have shown
SQLSTATE[40P01]: Deadlock detected every day or two, naming a summary table such as nightowl_request_rollups or nightowl_job_rollups. Each agent wrote its batch in the order the telemetry happened to arrive, so a web server (whose batches start with a request) and a queue server (whose batches start with a query or a job) could lock the same rows in opposite orders, and PostgreSQL cancelled one of them. No telemetry was lost, because the cancelled batch was written again on the next attempt. Every agent now writes in the same fixed order, and the same fix covers the issues and users tables, which had the same problem.Upgrade every agent that writes to the same database. An agent still on an older version keeps its old write order, so it can go on deadlocking against the upgraded ones until the last one is updated.One deadlock no longer leaves a warning on your Agent Health page until you restartA deadlock used to count as a failed batch for the rest of the agent’s life, and the drain error rate was measured over that whole lifetime. On a quiet server, a single deadlock was enough to show a DRAIN_ERRORS warning (“Drain worker experiencing batch failures”) that stayed until the agent restarted, even though nothing was failing any more. The rate now covers the last 15 minutes, so a warning clears once the drain has recovered. A deadlock is also no longer counted as a failure at all, since nothing was wrong with your data or your database.Deadlocks are still tracked. If they keep happening, the new DRAIN_CONTENTION diagnosis appears. It is a warning when some batches are being cancelled, and critical when the drain is committing nothing. A single deadlock stays quiet.If you set a job threshold, it may now trigger slightly soonerEach batch used to check your job threshold twice, once against the queued jobs and once against the job runs, so a batch just over your limit could pass both checks. Both now go through a single check. Apps without a job threshold see no change.Upgrading the agent: run composer update nightowl/agent and restart the agent. No migration is needed. See the agent changelog (2.4.6) for details.Agent
A quiet app no longer shows a critical “rollup tables stopped being written” warningIf your app goes more than 15 minutes without an exception, a sent mail or a scheduled command, your Agent Health page could stay degraded at a score of 75. It listed those summary tables as having stopped being written, and advised you to look for an error that did not exist. The check assumed every kind of telemetry arrives all the time, so it read “nothing happened” as “the agent stopped writing”. Each summary table is now compared with its own raw data, so it is flagged only when raw rows exist that never reached it. If you worked around this by setting
NIGHTOWL_TABLE_STATS=false, you can remove that setting, which also brings back the hourly table report.Two smaller fixes. On PHP 8.5, the agent log no longer fills with a pgsqlCopyFromArray() is deprecated warning on every write. And nightowl:backfill-rollups no longer occasionally leaves the hourly summary one row short.Upgrading the agent: run composer update nightowl/agent and restart the agent. See the agent changelog (2.4.5) for details.Agent
If
composer update failed on the agent with “Too few arguments to function Laravel\Nightwatch\Ingest::__construct()”, 2.4.4 fixes itNightwatch 1.29 changed how its ingest is constructed, and the agent still built it the old way, so updating both packages together stopped on package:discover before anything was installed. Nothing was changed on your side and no telemetry was lost: the update never completed. Run composer update nightowl/agent again to pick up 2.4.4, then php artisan nightowl:migrate and restart the agent as usual. If you run the agent alongside Nightwatch’s own reporting, Nightwatch’s new ingest listeners fire once per event, not twice.Agent
If yesterday’s fix did not stop your telemetry cutting out, this is whyYesterday’s release added a safeguard that trims any value too long for its column, so no single oversized route or file path can stop your drain. It worked where your NightOwl tables live in the Patterns are matched against the route’s path, name and action;
public schema. If they are reached through a database search path instead, the safeguard measured the wrong tables, found nothing to trim, and the drain still stopped on the first long value — and in that state nothing on that connection was being trimmed at all. The safeguard now measures the table it actually writes to, the same way PostgreSQL resolves the name. NightOwl’s tables are expected in the public schema: if yours live only somewhere else, raw telemetry will arrive but the summary tables the charts read from are not maintained there, and nightowl:migrate and the cleanup commands act on public — that layout is not supported. (During review we caught a candidate fix that would have broken the opposite layout — a schema named after your database role, which PostgreSQL’s default search path anticipates — before it shipped.)Two related fixes. A column widened while the agent was running was not noticed until a restart, so an agent started before the migration kept trimming to the old width; widths are now re-checked once a minute. And a value trimmed earlier is now repaired in place the next time that route or query appears, in the running agent, within a minute of the migration — including for anything first seen in the gap between composer update and php artisan nightowl:migrate.One thing to check if you run the agent under a restricted database role: the agent now needs UPDATE on nightowl_dict_route and nightowl_dict_sql (the rollup, issue and user tables already required it; the raw telemetry tables still need no UPDATE). Without it the drain stops with permission denied. Column-level grants are enough: GRANT UPDATE (method, domain, path, name, action, methods) ON nightowl_dict_route and GRANT UPDATE (file) ON nightowl_dict_sql.Livewire pages no longer inflate your route listLivewire’s update endpoint reports every component in the request as the route’s “action”, one entry per component — a page whose 78 copies of one component all updated in the same tick reported the same class name 78 times, and every variation was stored as a new route. NightOwl now reduces that list to its distinct components, in a stable order, before storing it. Pages that differ only in how many of a component they show now share one entry. Pages that contain genuinely different sets of components remain separate; if your pages vary their components a lot, use the ignore setting below.You can now tell the agent to skip routes entirelySet NIGHTOWL_IGNORE_ROUTES to a comma-separated list of patterns, and matching requests are never recorded, together with the queries, logs and cache events they made:* is the wildcard. Use the route name for Livewire — *livewire.update matches both Livewire 3 (default.livewire.update) and Livewire 4 (default-livewire.update), while livewire/* matches only Livewire 3’s path. A skipped request never takes an exception with it (you asked to ignore a noisy route, not its errors — the exception’s link to its request will lead nowhere), nor a job it dispatched, nor anything that job recorded while running.Worth knowing before you use it: this is not sampling. Nothing about a skipped request reaches your database, so those requests are absent from your request counts, your threshold alerts and your error rate — in whichever direction those routes were heading. Skip a busy endpoint that always succeeds and your error rate goes up. Skip one that is failing and your error rate goes down and hides the problem. So the agent counts what it skips and, on the Agency plan’s app cards, shows it beside the rate with that caveat. On an older agent the line does not appear, which means it cannot tell you, not that nothing was skipped; the same is true under --driver=sync, which has no health reporting. One limit: a request that produces more than 500 records of any kind ships the first 500 in a separate payload before the request itself, and if that payload drains first those rows are kept without their request.Reclaiming route entries you no longer needphp artisan nightowl:gc-dict-routes removes stored routes that no request refers to any more, for a route list a Livewire app inflated before this release. Run --dry-run first. Stop the agent everywhere it runs before running it — a running agent keeps route references in memory and can write one after the row is gone. The command checks for a running agent, but it cannot see an agent on another machine or one started with --driver=sync, and because stopping the agent leaves fresh files behind, it will refuse for up to ten minutes after you stop it and tell you how long to wait. Afterwards run VACUUM FULL nightowl_dict_route to actually return the space — plain VACUUM will not, because the removed rows sit between live ones. The command holds a lock on your request table while it runs; if your scheduled nightowl:prune starts during that time it waits a few seconds, leaves that table for its next run and carries on, rather than queuing behind the cleanup — a queued prune would block the dashboard’s reads of that table until the cleanup finished.Upgrading the agent: run composer update nightowl/agent, then php artisan nightowl:migrate, and restart the agent. See the agent changelog (2.4.3) for details.Agent
A single long route or file path could stop your telemetry from arriving at allNightOwl stores each distinct route, SQL origin and label once and refers back to it, which is what keeps your telemetry small. Four of those columns were sized at 512 characters. If your application produced a route path, controller action, query file path or label longer than that — a deeply nested resource route, a long namespace, a generated route group — PostgreSQL refused the write, and the agent retried the same batch on every loop instead of moving past it.The effect was worse than one missing row. Everything queued behind that batch stopped too, so telemetry simply stopped arriving from the moment the first long value appeared, and the dashboard showed a hard stop at that date. What was buffered stays on your own server and drains once the agent can write again — but the buffer has a limit (100,000 payloads by default), and once a wedge fills it the agent starts refusing new telemetry, which is gone. If your agent log shows “Back-pressure ON”, some was.The agent now trims any value that will not fit and writes the batch anyway, and names the exact column in its log if it ever has to. (Correction, September 9: this release only measured tables in the
public schema — see the next entry if yours are elsewhere.) This release also widens the route path, controller action and query file columns so long values are stored in full rather than trimmed.A failure of this kind also now reaches your Agent Health page, naming the error code and the table it happened on. Until now it was recorded as a local buffering problem, so no diagnosis was raised and there was nothing on the page to see.Upgrading the agent: run composer update nightowl/agent, then php artisan nightowl:migrate, and restart the agent. The restart matters here — the running agent reads your column widths once and keeps them, so it will not notice the wider columns until it starts again. The migration changes three column types and moves no data; it is safe to run while the agent is draining. See the agent changelog (2.4.2) for details.AgentDashboard
You can now search your logs by what’s in the context, not just the messageA Laravel log line has two halves.
Log::error('Payment failed', ['order_id' => 8412]) writes the message Payment failed and the context ['order_id' => 8412]. NightOwl has always stored and displayed both, but the search box on the Logs page only ever matched the message — so searching for 8412 to find that order’s log line came back empty, as if it had never been logged.The reason is that NightOwl stores log context compressed, and PostgreSQL cannot decompress it while running a query, so there was no way to look inside it. Rather than search the message and quietly pretend that was the whole answer, the page now tells you when that is happening: search your logs and you’ll see a “Narrowed search” note saying the results matched on the message alone.To make context searchable, set NIGHTOWL_LOG_CONTEXT_SEARCHABLE=true in the app your agent runs in and restart it. From then on, searching 8412 finds that line.This trades storage for searchability, and how much depends on what your app logs — small contexts barely compress at all, large ones compress well. Before turning it on, run php artisan nightowl:backfill-log-context --dry-run: it measures your own logs and reports what they occupy now and what they would occupy uncompressed, without changing anything.Turning the setting on only affects logs written from that point. To make the logs you already have searchable, run php artisan nightowl:backfill-log-context. It converts newest first, so the logs you’re most likely to search work first, and you can stop it and re-run it later to pick up where it left off. The “Narrowed search” note keeps appearing for the date ranges it hasn’t reached yet, so you always know which of your history the search actually covered. --since limits how far back it goes if you only care about recent logs.Upgrading the agent: run composer update nightowl/agent, then php artisan nightowl:migrate, and restart the agent. The migration adds one empty column and moves no data. Nothing changes until you turn the setting on. See the agent changelog (2.4.1) for details.AgentPerformance
NightOwl’s summary tables were being cleaned continuously, costing your database tens of gigabytes an hour in disk readsEvery drain writes three levels of summary at once: per-minute, per-hour, and per-day. All three receive the same number of updates, but the daily table holds up to 1,440 times fewer rows than the per-minute one. NightOwl told PostgreSQL to clean each summary table once 2% of its rows had been superseded, which is right for a table with a million rows and wrong for one with a few hundred — on a daily table, 2% is a few dozen rows, and that threshold is crossed within seconds. PostgreSQL re-read those tables from disk about once a minute, day and night.On one customer database, a 574-row daily table had been cleaned 3,227 times against 32 for the 1.3-million-row per-minute table sitting beside it, and the summary tables together were reading roughly 45 GB an hour that nothing needed. That competes for the same memory and disk the agent uses to write your telemetry, so on a database without much headroom it showed up as telemetry arriving late during your busiest hours, and a gap at the right-hand edge of your charts that filled in a few minutes later. No telemetry was lost to this. The threshold on the hourly and daily tables is now a fixed number of rows instead of a percentage, so it stops getting stricter as a table gets smaller. Per-minute tables are unchanged.Also fixed:
php artisan nightowl:repair-cache-rollup-keys was resetting the storage tuning on the cache summary tables it rebuilt, leaving them on PostgreSQL’s defaults instead of NightOwl’s. If you have ever run that command, this release puts those settings back.Upgrading the agent: run composer update nightowl/agent, then php artisan nightowl:migrate, and restart the agent. The migration only changes table settings — it moves no data and is safe to run while the agent is draining. See the agent changelog (2.3.3) for details.Dashboard
Team members on a paid account were redirected away from Agent Health and Data ManagementIf you were invited onto someone else’s team and your own account was on the Hobby plan, the Agent Health and Data Management pages were unreachable. Both links appeared in the sidebar, and clicking either one sent you straight back to the app overview with no explanation. The pages were never restricted to you: NightOwl bills these features to the account that owns the app, so a Team or Agency owner’s apps include them for everyone invited to that team, and the API was answering those requests correctly the whole time. The dashboard was asking two different questions — the sidebar asked what the app’s owner pays for, the page itself asked what you pay for — and the answers only diverged for people invited into a plan better than their own. Both now read the owner’s plan. Team owners, and invitees whose own plan already covered these pages, were never affected.This one is already live on the dashboard and needs nothing from you.
Agent
The agent could accept no connections on servers with the
ext-uv PHP extension installedIf the uv PHP extension is loaded — common on servers running Laravel Reverb, whose docs recommend it — the agent could go deaf: the process ran, the ports were bound, the supervisor saw it as healthy, but no telemetry ever arrived and nothing was logged anywhere. Your application was never slowed down (the SDK is built to fail open rather than block a request), but everything it sent was silently dropped. The cause: with ext-uv present, the agent’s event loop library silently switches to a libuv-backed loop, which keeps its bookkeeping in a kernel object that the agent’s worker processes share with the parent — when a worker closed its inherited copy of the listening socket, it unregistered the parent’s listener too. Depending on the libuv version this hit at startup or on the first drain-worker restart, so an agent could work for days and then fall silent after a routine restart. The agent now pins the fork-safe select loop regardless of which extensions the host has loaded, so ext-uv stays available to Reverb and stops affecting the agent. There is no performance cost: every published throughput number was already measured on the select loop. Thanks to @refringe for the exceptional report and root-cause analysis in issue #6.Upgrading the agent: run composer update nightowl/agent and restart the agent. No migration and no new configuration. See the agent changelog (2.3.1) for details.AgentAPI
Slack notifications arrive with no preview, and a repair for oversized cache summary tables
- Slack alerts no longer push a notification that says “[no preview available]”. NightOwl’s Slack messages carried all of their content in Slack’s rich block format, and Slack builds desktop and phone notification previews from a separate plain-text field — on mobile it uses nothing else. With that field empty there was nothing for Slack to show, so every alert buzzed your phone without telling you which app or which exception it was about; you had to open Slack to find out. Alerts now carry a one-line summary (severity, app, exception, message) for the notification to display. The message inside the channel looks exactly as it did before. This covers both the alerts the agent sends from your own infrastructure (new and reopened issues) and the ones the platform sends (resolved, assigned, commented, and the “Send test” button on the alert channel settings page).
- New:
php artisan nightowl:repair-cache-rollup-keys— shrinks cache summary tables left oversized by an upgrade from before 2.0. Agent 2.0 started grouping cache keys by pattern, so that a million keys likeuser:1:profileare summarized as oneuser:*:profilerow instead of a million of them. That only applies to telemetry written after the upgrade — rows already on disk kept their one-off keys, and because your daily and hourly summaries are built from those rows, the bloat was copied into all three tables rather than collapsing. One customer database held 45.6M rows of per-minute cache summaries and got a 45.3M-row hourly and 45.4M-row daily table out of them. Once tables that size no longer fit in PostgreSQL’s cache (a database restart is enough to tip it), writes slow below the rate telemetry arrives, the agent’s disk buffer fills, and it starts refusing new telemetry. The new command re-groups those old rows onto their patterns, adding the counts together — no bucket, no counter, and no point on a chart is lost, and no history is shortened. A measured run collapsed 135,040 rows to 30,765 in 1.3 seconds, reclaiming 73.6 MB to 7.6 MB, while the agent kept draining throughout. Run--dry-runfirst to see what it would collapse. You only need this if your cache summary tables are large and your agent was writing telemetry before 2.0; checknightowl_cache_rollupsif you are unsure.
composer update nightowl/agent and restart the agent. No migration and no new configuration for the Slack fix. Run php artisan nightowl:repair-cache-rollup-keys only if the cache summary tables above apply to you — it needs roughly the largest of those tables’ size in free disk, and --in-place is the slower alternative if you don’t have it. The platform-side Slack fix is already live and needs nothing from you. See the agent changelog (2.2.0) for details.AgentAPIPerformance
Summaries could go missing when several servers report to one database, and wide-range list pages were doing twice the work
- Running the agent on more than one server could silently lose summary data. If two or more of your app servers drain into the same NightOwl database, each one writes its per-minute summaries in whatever order that batch happened to arrive in. When two batches covered any of the same summary rows, they took the rows in opposite orders, PostgreSQL detected the standoff and killed one of them outright. The killed batch lost its whole set of summaries. Nothing appeared to be wrong: the raw telemetry from that batch had already been stored, so counts and lists stayed correct and only the wide-range charts, which read the summaries, came up short. Every writer now takes the rows in the same fixed order, so overlapping batches wait for each other instead. You only need this if you run more than one agent against one database — a single agent was never affected, whatever
NIGHTOWL_DRAIN_WORKERSwas set to. - List pages over long ranges are much faster, and some no longer time out. Any grouped list — routes, queries, jobs, cache keys, mail, notifications, commands, scheduled tasks, exceptions, users — was computing its totals twice for every page you loaded: once to count how many groups there were, once to actually build the page. On a database with a lot of distinct keys, the doubled work is what pushed a 30-day list past the statement timeout and left you with an error instead of a table. Counting now skips the arithmetic it never needed. Separately, those lists were reading finer-grained summaries than they had any use for, because they were sharing the chart’s choice of granularity even though a list has no time axis at all. A 30-day list now reads about 30 daily summary rows per group instead of about 720 hourly ones. Charts are unchanged, so the numbers on a page still agree with each other.
composer update nightowl/agent and restart the agent. No migration and no new configuration. The list improvements are on the hosted dashboard already and need nothing from you. See the agent changelog (2.1.1) for details.AgentAPIPerformance
A summary table could stop updating silently, and wide-range charts time outIf your 7-day or 30-day charts have started timing out, or the Peak Concurrency chart has flat-lined, this release is the fix.
- A summary table could stop being written and never say so. The per-minute concurrency summary read one of the raw tables by name. Once your retention window passed the 2.0 storage switchover and
nightowl:pruneretired that old table, the summary hit a missing-table error every minute and wrote nothing from then on — while every other summary stayed current, so nothing looked wrong. The dashboard then fell back to scanning raw telemetry for wide ranges, which is what makes a 14-day chart time out. The agent now checks for both storage formats on every pass instead of assuming, andnightowl:backfill-rollupsdoes the same, so the repair itself can’t fail the same way. - A deploy now repairs a summary that stopped, not just one that started late.
nightowl:migratehas always rebuilt incomplete summaries for you, but it only ever checked whether history was missing from the start — and a table that freezes still has all of its early history, so it looked healthy on every subsequent deploy. It now also checks the newest bucket against the newest raw telemetry, and rebuilds anything more than two hours behind. The tolerance is deliberately generous so that a busy agent working through a backlog is never mistaken for a broken one. - Percentile charts over wide ranges are dramatically faster. The function behind p50/p95/p99 got slower with every row it read, so a long range didn’t just take longer — it hit your database’s statement timeout and returned nothing at all. A 14-day per-route read went from 103 seconds to 0.48, and a case that never finished inside two minutes now takes 2. Results are byte-identical to before. This needs
php artisan nightowl:migrate. - An unreachable alert receiver no longer stalls the agent. Sending health alerts ran inline on the agent’s main loop, so one unroutable SMTP or webhook host held everything — ingest, drain, health reporting — for the full connect timeout, measured at 30 seconds. Alerts now go out in a separate short-lived process. The symptom was an agent at almost no CPU reporting heavy lag.
- New health signal: a summary table that stops updating while its peers keep going now reaches your dashboard and alert channels instead of only the agent’s log file. It grades each table against others of the same granularity, so it needs no assumption about which telemetry your app emits, and it stays quiet when everything stopped together — that case already has a clearer signal.
nightowl:drop-v1-histogramsis usable again. 2.0.2 made it refuse to run, because dropping that data left the percentile function above as the only source and it couldn’t serve a wide range. That’s fixed, so the command works again; it now asks you to runnightowl:migratefirst if the faster function isn’t in place yet. It’s still irreversible.- Stale critical alerts no longer stick to your dashboard forever. An agent announced a problem as resolved using an identity that includes its process ID, so any restart, deploy or crash left the old warning with nothing to ever clear it — one app was showing a critical drain alert raised by a process last seen 15 days earlier. Warnings that no running agent is still reporting are now retired automatically after 15 minutes. They’re marked resolved rather than deleted, so the history stays.
composer update nightowl/agent, then php artisan nightowl:migrate, then restart the agent. The migrate step is required — it installs the faster percentile function and rebuilds any summary that stopped. If a summary froze more than a few days ago, run it soon: the rebuild needs raw telemetry from that period, and once your retention window passes it the gap can’t be filled. See the agent changelog (2.1.0) for details.Agent
An upgrade could stop the agent accepting telemetry on a high-volume appIf you upgraded to 2.0 on a busy application, please take this one.
- The post-upgrade summary rebuild could lock out the drain. After an upgrade the agent rebuilds the per-minute summaries behind the wide-range charts. It worked through history in fixed time slices, and a time slice is a poor guess at how much work it contains: on an app writing tens of thousands of rows a minute, one slice could hold a summary table long enough that the agent’s own writer couldn’t get in. Telemetry then queued on disk, correctly and safely, until the buffer hit its limit — after which the agent began refusing new telemetry outright, and that data is not recoverable. Slices are now measured rather than guessed, and resized continuously so the writer is never locked out for more than about a second. On the workload that reproduced this, failed writes went from 4 in 8 to 0 in 56, and the longest period with no telemetry written at all went from 23.6 seconds to none.
- Contention no longer costs you telemetry. Independently of the above, a write that can’t get at a summary table now stores its raw telemetry anyway and notes what summaries it still owes. The next
nightowl:migraterebuilds them. Previously the whole batch was postponed, which is what allowed the queue to build in the first place — so the worst case is now a temporary dip in a wide-range chart rather than data you never get back. - Two new health signals. Both of these states were previously visible only in the agent’s own log file. The agent now reports when it owes a summary repair, naming the tables and the command that fixes them, and reports while a post-upgrade rebuild is still running — as information, escalating to a warning after six hours, by which point the likelier explanation is that it never finished.
- New:
NIGHTOWL_AUTO_BACKFILL=falseif you would rather run the rebuild yourself in a maintenance window. Leave it on unless you have a reason: an empty summary table makes wide-range charts read zero rather than falling back to raw telemetry, and only this pass fills it. With it off, the agent tells you each boot which tables are still empty.
composer update nightowl/agent and restart the agent. If your dashboard is showing a gap in any wide-range chart from an earlier 2.0 upgrade, run php artisan nightowl:migrate afterwards to rebuild the affected summaries. See the agent changelog (2.0.1) for details.AgentDashboardPerformance
NightOwl agent 2.0 — a new storage format, and a drain that survives a database outageThis is a major version. The normal upgrade is still
composer update plus a restart, and your history stays visible in the dashboard throughout — but two changes are worth reading if you query the telemetry tables yourself.- Telemetry is written in a new, much smaller format. Every raw table now has a
_v2twin that the agent writes instead: event times stored as numbers rather than text, IDs as nativeuuid, and repeated values (environment, server, route, SQL statement, stack trace) stored once in a lookup table and referenced by ID. One production sample carried 28M query rows a day across just 225 distinct statements — those are now stored once each. Nothing is lost: every value from the old format is exactly recoverable. The dashboard reads both formats, so you see no gap. What breaks is anything of yours pointed at the old table names — a BI extract, a report, apsqlquery — which will keep returning only rows written before the upgrade, with no error to warn you. The upgrade guide has the equivalent query for each renamed column.NIGHTOWL_STORAGE_V2=falsereverts to the old format with no schema change if you need time. - The old tables are eventually retired. Once the switchover is older than your retention window and an old table is genuinely empty,
nightowl:prunedrops it. Emptiness is required, not caused, so no rows are ever destroyed and an older agent still writing to those tables can’t be pruned out from under.--keep-v1disables this permanently — use it if you have a view, foreign key, or monitoring query naming those tables. - A PostgreSQL blip no longer freezes your dashboard until you restart the agent. If the database went away underneath an open transaction, the agent could keep reusing a dead connection forever: 60 seconds after PostgreSQL was healthy again, a measured test still had the drain failing ~15 times a second and burning ~30% of a core while writing nothing. Your data was never lost — it stayed buffered on disk — but it stopped arriving, so the dashboard sat frozen at the moment of a blip you’d already fixed, and only restarting the process cured it. Fixed, and covered by a test that reproduces the exact failure.
- An unreachable database no longer triggers a retry storm. One 90-second outage previously produced 1,369 near-identical error lines and ~27 CPU-seconds of retries. Reconnects now back off from 100ms to a 10-second ceiling, and the log collapses to about one line per doubling. The first successful batch clears the backoff immediately.
- The buffer file gives disk space back. SQLite reuses deleted pages but never returns them, so the buffer stayed at its high-water mark — a load test measured it at 3.8 GB and it stayed 3.8 GB long after those rows had drained. On a host where the buffer shares a volume with your database, one traffic spike could fill the disk permanently. New buffers reclaim space incrementally on the drain’s cleanup tick; existing ones get a one-time compaction while idle.
- Failed alerts now tell you they failed. Webhook, Slack and Discord alerts were sent with the response thrown away, so a revoked webhook or a receiver returning 500 looked exactly like a delivered alert. Failures now carry the status and the receiver’s own response (with the URL redacted, since a Slack webhook path is itself a credential). Agent email had four SMTP conformance bugs that each cost real mail — a
HELOname that Exchange, Office 365 and many Postfix configs refuse outright; capabilities not re-read afterSTARTTLS; always tryingLOGINon relays offering onlyPLAIN; and missingDate/Message-IDheaders, which get a message accepted and then silently spam-filed. The dispatch budget rose from 5 to 30 seconds, which was less than one real TLS handshake plus authentication. - New:
php artisan nightowl:test-alert. The dashboard’s “Send test” button and your triage alerts go out from our API; new-exception and reopened-exception alerts go out from the agent on your own machine, over its own SMTP and HTTP. Two transports reading one configuration row means you could test green in the dashboard and still never be told about a new exception. This command exercises the second one and reports pass/fail per channel with the reason. It also warns when a channel passes but has new-exception alerts switched off. - The Peak Concurrency chart is no longer capped at 6 hours. It’s now backed by a per-minute summary instead of scanning raw requests, so it covers any range you can select.
- The cache page groups by key shape.
user:8213:profileanduser:9147:profilewere separate rows, so an app keying cache by ID filled the page with near-identical entries. IDs, UUIDs, emails and timestamps in a key now collapse to placeholders (user:{int}:profile). Raw cache events keep the literal key, andNIGHTOWL_CACHE_KEY_TEMPLATE=falseturns it off. - Mail and notification detail pages got the index they were missing.
- A rollup table could go stale for the life of the process after one failed probe. A check that couldn’t run because the connection was dead was cached as “table missing”, so that table’s summaries were skipped for every later batch — under a log line blaming a migration that had never been the problem. Wide-range views read summaries when they exist, so the damage outlived the blip as a growing hole. Only a check that actually answered is cached now.
- Long list views could skip or repeat a row while an app held telemetry in both storage formats. Fixed.
- New: hourly database statistics reported to NightOwl (
NIGHTOWL_TABLE_STATS=falseto opt out). This exists so that a wedged drain, a gap in your summaries, a missing index or a disk filling up is something we diagnose from our side, instead of asking you to run SQL against your own database. It reads PostgreSQL’s catalog and statistics views only — table sizes, row counts, scan and write counters, vacuum timestamps, index usage, connection and lock state, server settings and version.pg_stat_statementsand query text are excluded on principle and the exclusion is enforced by a test: a tenant database can be shared with your own application, and those are content rather than counts. Row counts are still information, so this is disclosed in the privacy policy. It runs on its own short-lived connection, never the drain’s.
- Detail-page lists now page with Previous / Next. The tables on a route, query, job, command, mail, notification, outgoing-request or exception detail page moved from numbered pages to cursor paging, which stays correct while new telemetry is arriving underneath you — numbered pages could show you the same row twice, or skip one, as rows shifted between requests. Changing the time range, environment or user filter resets paging rather than stranding you on a stale position.
- Log search tells you when it only matched part of the row. A
?search=on the logs list can’t always reach every column it looks like it should, and nothing said so — a term that lives only in a log’s context read as “no such log”. The page now shows, above the table, which columns were actually searched and why the scope narrowed. - A failed connection test says what actually failed. Adding or editing an app always answered “Check your credentials”, which was simply wrong for a host rejected by our SSRF guard, an unreachable port, or a missing database. It now shows the real reason, in red for a hard failure alongside the existing amber warnings for schema and permission problems.
- The storage breakdown names the four dictionaries instead of showing “Dict string”. They hold real disk and survive a clear, and the caption now says so.
- Revoking an MCP token clears its reveal card, which previously stayed on screen offering a dead credential to copy.
- Fixed a console error on the sidebar that could make the app list fail to refetch after creating or renaming an app, switching teams, or accepting a transfer.
composer update nightowl/agent and restart the agent — migrations run on boot (or run php artisan nightowl:migrate yourself if you’ve set NIGHTOWL_AUTO_MIGRATE=false). Read the upgrade guide first if you query the telemetry tables directly. See the agent changelog (2.0.0) for full details.Agent
The agent migrates itself — one less step after every update
- Migrations run themselves. On startup the agent applies any pending
nightowl:migratemigrations before it accepts traffic, so schema changes arrive with the code that needs them and you no longer have to remember the migrate step after acomposer update. It runs under a hard deadline, so a busy or locked database can never hold the ingest port closed — if the migration can’t finish in time the agent starts anyway and retries on the next boot. Long rollup backfills continue in the background without delaying ingest, and are retried on the next start if one dies partway. - A heads-up when the agent is running older code than you installed. After
composer updatethe running process keeps the version it booted with. The agent now notices the newer version on disk and says so in its log, so a restart that got skipped doesn’t go unnoticed. It won’t restart itself — when to bounce the process stays your call. - Opt-outs:
NIGHTOWL_AUTO_MIGRATE=false(use it when the agent’s database role can’t run DDL),NIGHTOWL_UPDATE_CHECK=false.
composer update nightowl/agent and restart the agent — the migration step now happens on boot. Shipped in 2.0.0; see the agent changelog (2.0.0) for details.AgentAPI
Partition conversion survives interruption, and billing access is more reliable
nightowl:partitionis now safe to interrupt and safe to run twice. Two runs at once no longer collide — the second reports its tables as skipped and exits3, leaving them untouched. A run killed outright is repaired by the agent within about a minute, instead of leaving a table that rejects every write until you found it by hand.- It refuses to run through a transaction pooler. Pointed at PgBouncer or Supavisor in transaction mode, the command now stops with an explanation instead of converting unsafely. Point it at your database port.
- Clearer results. New exit code
4means every table converted but some daily partitions are still owed — nothing is lost, and a running agent creates them within the hour. Exit1now means a table genuinely failed. - Billing: revoked and recovered subscriptions are tracked correctly. A failed renewal that you then fix restores access immediately, webhooks that arrive out of order can no longer undo a newer decision, and an account that subscribed and lapsed is no longer reported back to itself as being on a trial.
composer update nightowl/agent and restart the agent — no migration needed. See the agent changelog (1.4.1) for details.AgentDashboard
Know when you need more workers — concurrency charts and a saturation alert
- Two new charts on the Requests page answer “do I need more Octane/FPM workers?”. Avg Concurrency shows how many requests were in flight on average per interval (computed from your existing telemetry — a request occupies a worker for its full duration). Peak Concurrency reconstructs the true maximum of simultaneous requests from individual request timings, catching short bursts the average hides. Peak scans raw data, so it covers up to the most recent 6 hours and says so on the card.
- Tell NightOwl your worker counts. A new HTTP Workers tab in app settings takes a per-environment total (Octane workers, or
pm.max_childrenunder FPM — the total across all servers in the environment). Both charts then draw a threshold line at that count, so saturation is visible at a glance. - Worker saturation alert. Opt-in, with a configurable trigger: when average concurrency holds at or above your chosen percentage of the worker count for your chosen number of consecutive minutes, the agent opens a performance issue and notifies your alert channels — same lifecycle as duration thresholds (dedup, resolve, auto-reopen). Environments without a worker count are never alerted. Configure it on the HTTP Workers tab.
composer update nightowl/agent and restart the agent (no migration needed). See the agent changelog (1.4.0) for details.AgentDashboard
Pruning shows its progress, and the storage page tells the right disk story
- Agent: pruning a large backlog no longer looks stuck. The first prune after converting to partitioning deletes all your pre-conversion history — previously one silent, minutes-long delete. It now deletes in bounded batches and prints progress as it goes (
--delete-chunktunes the batch size). - Dashboard: the Storage tab’s explainer now matches your schema. Converted apps see the real story — expired days are dropped whole and their space returns to disk, with pre-conversion history freeing as it ages past your retention window. Unconverted apps keep the previous guidance.
composer update nightowl/agent — no migration needed. See the agent changelog (1.3.2) for details.AgentPerformanceDashboard
Faster long-range charts, sharper percentiles, and a lighter write load on your database
- Long-range charts are much faster. NightOwl now keeps hourly and daily summaries next to the per-minute ones, so a 30- or 90-day chart reads far fewer rows — up to 1440× fewer on the widest views — instead of scanning every minute.
- More accurate percentiles. Duration percentiles (p95/p99) are now backed by a DDSketch estimator with about 1% relative error, down from roughly 2.8% on the previous histogram — with no change to how you read them.
- Lighter footprint on your PostgreSQL. A reader audit removed 24 unused indexes from the raw telemetry tables and tuned how the summary tables pack and vacuum, so the agent does less work per row it writes.
- Raw tables are partitioned by day. Pruning old telemetry becomes an instant drop of a day’s partition instead of a slow delete. Existing installs convert with one command (see below).
- Storage breakdown in Data Management now reflects the tiered summaries.
composer update nightowl/agent, then in the monitored app run php artisan nightowl:migrate and restart the agent. Migrate populates the new hourly/daily summaries from your existing data automatically, so the faster long-range charts apply to your history too — no manual backfill step. To convert an existing install’s raw tables to partitioning, also run php artisan nightowl:partition — do this in a quiet window if your logs table is large (it rewrites that one table under a lock). See the agent changelog (1.3.1) for full details and tuning options.AgentDashboardAPI
A network stall can no longer wedge the agent’s writes, and clearing data now runs in the background
- Fixed: a network problem while the agent was saving telemetry could stall it for around 15 minutes. The agent had no working timeout on writes to your database, so if the network path dropped without closing the connection, the agent sat blocked until the operating system gave up. While it was stuck, its local buffer filled and it began refusing new telemetry rather than queuing it, so data was lost rather than delayed. Writes are now bounded by a deadline on the connection itself, typically clearing a dead path in about 30 seconds and re-sending the buffered data. Nothing in the buffer is lost.
- A new health diagnosis,
Drain wedged, names the exact database call the agent is stuck in, if it ever happens again. On Linux with libpq 12 or newer it should stay dormant — the agent warns at startup if your setup can’t enforce the deadline. - Clearing data no longer needs the page open. Deletions now run in the background: you get a progress card you can leave and come back to, on any device, plus a cancel button. Previously a large clear could run past the request limit and die halfway with nothing to tell you what had been removed.
- Preview now shows which filters a table can’t honour. Filters like route or status code don’t exist on every data type, so they silently didn’t narrow those tables — the whole window went. Preview now marks this per table, and clearing asks you to confirm before proceeding.
- Fixed: paying through Polar under a different email than your NightOwl account no longer leaves the account locked. Checkouts now carry your account ID, so payment is matched to you regardless of the address entered on the payment form.
composer update nightowl/agent and restart the agent (no migration needed). See the agent changelog (1.2.14) for the tuning options and details.AgentDashboardAPI
Clear guidance when NightOwl’s database role can’t read your tables, and faster issue counts
- NightOwl now tells you when its database role is missing read access. If the role NightOwl connects with can reach your database and the
nightowl_*tables exist, but the role was never grantedSELECTon them, you’d previously hit a generic error on every dashboard read. Now Test Connection flags it directly (“Connected — no read access”), and if it’s discovered while you’re using the app, the dashboard sends you to a dedicated page with the exactGRANTto run (including default privileges so future rollup tables are covered too) instead of stranding you on a raw error. - The sidebar issues badge no longer times out on large issue tables. The issue-count query now runs off a dedicated index in a single pass instead of scanning the full table, so the badge and issue tab counts stay fast even with a large backlog on a remote database. Requires the agent update below.
composer update nightowl/agent, then in the monitored app run php artisan nightowl:migrate to create the new index (built online — it won’t block the agent). See the agent changelog (1.2.13) for details.AgentDashboard
Data Management is now rollup-aware, and command/scheduled-task history survives cleanup
- The Data Management page was rewritten around what each table keeps versus loses. Every data type shows its rollup coverage, so you can clear raw telemetry to reclaim disk knowing your charts, totals, and percentiles are safe. It’s split into Storage / Retention / Clear tabs, with a working range delete and a one-week protected floor on the most recent data.
- Commands and Scheduled Tasks now keep their aggregates when you clear the raw rows — success/failure counts, duration charts, and percentiles are preserved. Previously, clearing them lost everything.
- Fixed: malformed log timestamps could leave log rows that pruning would never delete. Such rows now get a valid timestamp so retention can clean them up.
composer update nightowl/agent, then in the monitored app run php artisan nightowl:migrate and php artisan nightowl:backfill-rollups, and restart the agent. See the agent changelog for the exact steps.Agent
The agent no longer over-counts telemetry on some databases
- Fixed: on certain setups the agent could write the same requests to your database many times over — inflating request and query counts (by tens to hundreds of times) and filling your database far faster than your real traffic would. After saving a batch of telemetry, the agent marked it “already sent” in a single operation that older SQLite versions reject as too large; the mark failed, so the agent re-sent the same batch on every cycle. It now records that progress in small chunks that work on any SQLite version. Your capture and batch-size settings are unchanged — nothing about how much you collect changes.
- The agent now refuses to start if it can’t write its local buffer — a full disk, an exhausted disk quota, a read-only mount, or the buffer file being owned by a different user than the agent runs as. Instead of starting and silently re-sending data, it stops with a message telling you exactly what to fix. Together with the July 6 change, a buffer the agent can’t write can no longer cause duplicated telemetry.
php artisan nightowl:clearnow removes all NightOwl data, including the pre-aggregated summary tables it previously left behind. Clearing your telemetry no longer leaves wide time-range dashboard views reading stale numbers.
composer update nightowl/agent and restart the agent (no migration needed). See the agent changelog (1.2.11) for details.Agent
Dashboard counts no longer drop to zero after updating the agent
- Fixed: after updating the agent and running
php artisan nightowl:migrate, dashboard numbers like jobs, authenticated users, and mail could read 0 even though your data was intact. Migrate created the pre-aggregated summary tables the dashboard reads from but left them empty until you separately rannightowl:backfill-rollups, and the dashboard reads an empty summary as zero. Migrate now populates those tables from your existing telemetry automatically, so the counts are right as soon as it finishes. Nothing was ever lost — only the wide-range view was affected. (Pass--no-backfillif you’d rather backfill manually on a very large database.) - The agent won’t duplicate telemetry if its local disk fills up. When the agent’s local buffer disk is full, data it had already saved to your database could be written a second time. The agent now pauses draining until the disk is writable again instead of re-sending, and the stall is now visible on the health page rather than silent.
composer update nightowl/agent, then php artisan nightowl:migrate (which now backfills the summary tables for you) and restart the agent. See the agent changelog (1.2.10) for details.DashboardAPI
See how much space NightOwl is using in your database
- App settings has a new Storage tab showing the on-disk footprint of your NightOwl telemetry in your own PostgreSQL — a total, plus a per-table breakdown (requests, jobs, queries, exceptions, and the rest) with indexes included. It reads the database’s own size catalog rather than scanning your tables, so it’s instant no matter how much history you’ve collected, and it only ever reports NightOwl’s own
nightowl_*tables — never the size of your application data or your database as a whole.
AgentAPIDashboardPerformance
Run NightOwl on Laravel Vapor, and faster wide time-range pages
- You can now run NightOwl on serverless hosts like Laravel Vapor. The instrumented app used to require a NightOwl agent running on the same machine, which Vapor and AWS Lambda can’t do. You can now point the app at an agent running on a separate always-on box in the same private network by setting
NIGHTOWL_INGEST_URI=host:port. Existing single-host installs are unchanged. See the new Laravel Vapor guide. - The Users, Mail, Notifications, and Exceptions pages now stay fast over wide time ranges on high-volume apps. Their lists, overview stats, and charts now read from compact per-minute summaries the agent maintains, instead of scanning your full raw telemetry for the selected window — the same speed-up already in place for the Queries and Requests pages. The numbers match your raw data (with histogram-estimated percentiles on wide ranges, as elsewhere in the app).
- The query and exception detail pages stay fast on high-volume apps too. A few detail-page spots still scanned your full raw telemetry for the busiest individual queries and errors — a query’s environment filter and slow-query (duration) filter, an exception’s environment filter and occurrence chart, and the exception “Copy as Markdown” export — and could time the page out. They now read the same per-minute summaries the rest of the app uses, so they load no matter how much history you’ve collected. The numbers match your raw data.
composer update nightowl/agent, then php artisan nightowl:migrate (adds the new rollup tables) and restart the agent. To backfill history for time ranges before the upgrade, run php artisan nightowl:backfill-rollups. See the agent changelog (1.2.7) for details.AgentDashboardPerformance
Faster job detail pages, and the IP to allowlist for BYO databases
- Job attempt detail pages load again on job-heavy apps. Opening a job attempt pieces together the job’s family — its retries, the job that dispatched it, and any jobs it dispatched — through a series of lookups. On databases holding a large volume of job history those lookups were slow enough to hit the request time limit and fail the page with a timeout. A new database index makes each lookup fast, so the page loads regardless of how much job history you’ve collected. The same index also speeds command, scheduled-task, and request detail pages.
- If your PostgreSQL sits behind a firewall or IP allowlist, the Quick Start now lists the static IP the dashboard connects from (
178.156.227.16/32) so you can whitelist it on your database’s security group, network restrictions, or firewall.
composer update nightowl/agent, then php artisan nightowl:migrate (adds a job index) and restart the agent. See the agent changelog (1.2.6) for details.AgentDashboardAPI
Accurate timelines after an outage, clearer database errors, and correct job counts
- Telemetry is now dated by when each event actually happened, not when it reached your database. After a database outage, the backlog the agent catches up on lands in the minutes the events occurred instead of bunching at “now” — so time-range charts and percentiles stay accurate across a recovery.
- The health page now tells “can’t connect to your database” apart from “connected but can’t write,” so a network/credential problem and a not-yet-migrated schema no longer show the same message.
- If the agent can’t start because its port is already in use — most often because Nightwatch’s agent already holds the shared default port 2407 — it now prints a clear message and how to run the two side by side, instead of a stack trace.
- Job counts and durations are now correct. Job totals across the dashboard count actual job attempts rather than double-counting the dispatch and the attempt, and reported minimum/p95 job durations are no longer dragged down by queued-dispatch rows.
- The Source badge on mail, notification, and log lists now deep-links to the parent request or job it ran inside.
- Fixed a case where a detail page could briefly show another entity’s cached data, and made detail-list lookups for job-sourced rows faster (a new database index).
- New
nightowl:prune --hoursoption for retaining only a few hours of raw telemetry, and the agent no longer re-sends or double-counts data if a local buffer write fails after the rows were already saved to your database.
composer update nightowl/agent, then php artisan nightowl:migrate (adds a job index) and restart the agent. See the agent changelog (1.2.5) for details.AgentDashboardAPI
Clearer answers when telemetry stops flowing
- When the agent can reach your database but can’t write to it — the NightOwl tables aren’t migrated, the database role can’t INSERT, or the credentials/database name are wrong — the health page now names the exact cause and the fix (for example, “Run
php artisan nightowl:migrate”) instead of a generic “Postgres may be unreachable.” Only the error code and table name are reported; your row data never leaves your database. - Test connection when adding or editing an app now also checks that the NightOwl tables exist, so a not-yet-migrated database is caught at setup instead of showing an empty dashboard later.
- New opt-in setting (
NIGHTOWL_DRAIN_QUARANTINE) lets the agent set aside an individual telemetry row your database rejects so the rest of your data keeps flowing, instead of the pipeline stalling on one bad row; anything set aside is reported on the health page.
composer update nightowl/agent and restart the agent. See the agent changelog (1.2.4) for details.DashboardAPI
Agency tier: run every client from one place
- App-scoped access. Limit a team member to specific apps instead of your whole fleet — handy for contractors or per-client staff. Invite several people across several apps with a single shareable, email-gated invite link.
- Fleet portfolio overview. One page showing request volume, error rates, and open issues across all your apps, so you can spot the client that needs attention without opening each app. Live traffic counts in this view come from the agent’s health report (agent 1.2.3+ — see below); no request content ever leaves your database.
- Onboarding templates. Save a new-app setup as a reusable template instead of re-entering the same configuration for each new client.
Agent
More accurate timestamps and percentiles
- Fixed telemetry timestamps drifting on agents or databases not set to UTC. The drift could make recent data vanish from short time-range filters (1H/6H) or show “last seen” times in the future. Every telemetry table is now stamped in UTC. Rows written by older agents on a non-UTC server stay skewed — let them age out via
nightowl:prune, or runnightowl:clearon a throwaway dataset; there’s no automatic correction. - High percentiles (p95/p99) over wide time ranges no longer overshoot past the slowest query actually observed.
- The agent’s health report now carries lightweight per-app request, error, and exception counts (plus an open-issue gauge) that power the new Agency portfolio overview, and a new
nightowl_reportstable is added by the migration. Both are counts/snapshots only — no request content leaves your database.
composer update nightowl/agent, then php artisan nightowl:migrate (adds the reports table) and restart the agent. See the agent changelog (1.2.3) for details; the timezone fix completes one started in 1.2.2.DashboardAPIPerformance
Profile images, environment colors, and faster 1-hour views
- Upload a profile avatar, and give each app its own logo.
- Assign a color to each environment (production, staging, and so on) in settings, so they’re easy to tell apart across the dashboard.
- The 1-hour view now reads from the same pre-aggregated rollups as the wider ranges, so it loads quickly instead of timing out on high-volume apps (charts now use 60-second buckets). Filter dropdowns are also bounded to rollups so they no longer time out on busy databases.
DashboardAPI
Time format preference and steadier dashboards on flaky databases
- Choose 12-hour or 24-hour time display in your preferences (defaults to your browser’s locale).
- If your database server freezes mid-query — host out-of-memory, a network partition — the dashboard now returns a clear error quickly instead of hanging until the request is killed.
APIPerformance
Wide-range charts no longer crash high-volume apps
- Percentile, duration, and count charts over wide time ranges are now served entirely from rollup summaries, eliminating the out-of-memory crashes and timeouts that hit busy tenants when these were computed from raw telemetry.
- The MCP (Claude) data tools now work with transaction-mode connection poolers such as Supavisor, PgBouncer, and RDS Proxy.
AgentAPI
Health reports no longer dropped on long hostnames or extreme metrics
- If you run the agent on Kubernetes or a host with a long FQDN, its instance identifier (
hostname:pid) could be too long for the platform to store, which silently rejected the entire health report — so that instance never showed up under agent health. The platform now accepts longer instance IDs, and the agent caps its own identifier to fit. - Two health gauges, Postgres write latency and buffer utilization, could overflow the platform’s columns under a stalled database or a very low
NIGHTOWL_MAX_PENDING_ROWS, again dropping the report just when you most needed the signal. Both are now clamped on the agent and stored in wider columns on the platform.
composer update nightowl/agent, then restart the agent. See the agent changelog (1.2.1) for details.AgentAPIPerformance
Fast dashboards on high-volume apps
- The agent now maintains per-minute rollup tables alongside your raw telemetry, and the dashboard reads them for wide time ranges instead of scanning every raw row. Queries, requests, jobs, outgoing requests, and cache views now load quickly even on busy apps where wide ranges used to time out. Percentile charts over long ranges are served from a built-in histogram.
- To make historical ranges fast right after upgrading, run
php artisan nightowl:backfill-rollupsonce (it’s throttled and safe to run alongside a live agent). New telemetry is rolled up automatically as it drains. - Rollups are tiny, so they’re kept far longer than raw telemetry (90 days by default, via
NIGHTOWL_ROLLUP_RETENTION_DAYS) — keep long-range trend charts while pruning raw data aggressively. - When a tenant query against a very large table runs too long, the dashboard now returns a clear “try a narrower time range” message instead of failing with a generic error.
- Added Sign in with GitHub alongside Google.
composer update nightowl/agent, then php artisan nightowl:migrate and restart the agent. To backfill historical rollups, also run php artisan nightowl:backfill-rollups. See the agent changelog (1.2.0) for details.APIPerformance
Faster duration charts on large datasets
- Percentile and duration charts are now computed inside PostgreSQL instead of loading every row into memory. This fixes out-of-memory errors on apps with high request volume and makes the charts noticeably faster.
- Raised the API worker memory ceiling so heavy dashboards stay responsive under load.
- Quickstart now documents routing your application logs into NightOwl with
LOG_STACK.
AgentDashboard
Migration tracking and a master on/off switch
- New
nightowl:migratecommand applies the agent’s schema and records its migration history in your own database, so shared-database setups and upgrades are predictable. The newnightowl:installstep is documented in the quickstart. - Added
NIGHTOWL_ENABLEDas a single master switch for the agent, plusNIGHTOWL_RUN_MIGRATIONSto opt out of automatic migrations. - Fixed an integer overflow on duration and size columns that could occur on very large values.
- When your tenant database is unreachable, the dashboard now shows a clear status page instead of hanging, and lets you remove the app from there.
composer update nightowl/agent, then php artisan nightowl:migrate. See the agent changelog (1.1.0) for details.AgentDashboard
Drain stability and a smoother first-run experience
- Fixed the drain worker pinning a CPU core to 100% when running under Octane/Swoole.
- Added a watchdog so a stalled PostgreSQL SSL handshake or COPY no longer hangs the drain worker.
- Added
NIGHTOWL_DB_SSLMODEso you can set the PostgreSQL SSL mode the agent uses (defaults toprefer). - New onboarding page: if your database schema isn’t initialized yet, the dashboard guides you through setup instead of erroring out.
composer update nightowl/agent and restart the agent. See the agent changelog for the 1.0.6–1.0.10 fixes.APIPerformance
More accurate P95
- P95 latency is now calculated with PostgreSQL’s
percentile_disc, giving exact percentiles on query and request detail pages. Added aavgsort option alongside it.
Agent
Cleaner dependencies
- Removed the
react/httpdependency in favor of raw sockets. This drops an oldpsr/http-messageversion pin that could conflict with modern Laravel packages, so the agent installs cleanly alongside more of your dependencies.
composer update nightowl/agent and restart the agent. See the agent changelog (1.0.3).API
Signup anti-abuse
- Added honeypot fields, disposable-email blocking, and Gmail dot-normalization to signup, login, and password reset, cutting down on spam and duplicate accounts.
NightOwl is liveThe self-hosted monitoring platform for Laravel. Laravel Nightwatch collects your telemetry, the NightOwl agent buffers it and drains it into your own PostgreSQL database, and the NightOwl dashboard gives you a full monitoring UI on top. Your request data never touches NightOwl’s infrastructure.Monitoring
- All 12 Laravel event types: requests, exceptions, queries, jobs, commands, scheduled tasks, cache, mail, notifications, outgoing requests, logs, and users.
- P50/P95/P99 percentile charts and sortable, filterable lists for every event type.
- Detail pages with full traces, including the dispatched-job tree and job-attempt history.
- Environment filtering across exceptions, issues, and queries.
- Exceptions grouped by fingerprint and slow routes opened automatically when they cross the p95 thresholds you configure.
- Status (open / resolved / ignored), priority, assignment, comments, and a full activity timeline.
- Bulk actions to resolve, ignore, reopen, prioritize, and assign in one step.
- Automatic reopen when a resolved issue recurs (regression detection).
- Multi-channel notifications over Slack, Discord, Email (bring your own SMTP), and Webhook (HMAC-signed).
- Three flat tiers: 15 Team, $69 Agency. Bring your own PostgreSQL, with no event limits or retention caps.
- Teams with shared apps, owner-pays billing so invitees inherit the owner’s plan, and Agency-tier app ownership transfers.
- MCP server with full dashboard parity, so Claude Code, Codex, Cursor, Windsurf, and Zed can query and triage your monitoring data over personal access tokens.
- Google OAuth sign-in.
- High-throughput ReactPHP ingest with a local SQLite WAL buffer and a PostgreSQL drain over the COPY protocol.
- Runs standalone or in parallel mode alongside an existing Laravel Nightwatch Cloud setup.
- Multiple drain workers, configurable batch sizes, and per-instance health monitoring.
- Laravel 11, 12, and 13 supported.
composer require nightowl/agent. See the agent changelog (1.0.0) and the quickstart.