Laravel monitoring
Anomaly detection for Laravel business metrics, without a data team
Fixed thresholds on orders and signups cry wolf or miss real drops. What a useful business-metric alert has to understand.
Every team that tries to alert on business metrics starts with a fixed threshold: "tell me if we get fewer than 20 orders an hour". And every team finds out the same thing within a week. The threshold that makes sense at Tuesday lunchtime fires every night at 3am, and the one that stays quiet at 3am would sleep straight through a broken checkout at lunchtime.
The problem isn't the number you picked. It's that no single number can describe a business.
Business activity has a shape
Orders, signups and bookings rise in the morning, peak at lunch and in the evening, fall away overnight and look different again at weekends. On top of that shape sit paydays, bank holidays, marketing emails, seasonal peaks and slow months.
A threshold is a flat line drawn across all of that. Wherever you draw it, some part of the week sits on the wrong side of it.
The simplest thing that could work
Here's where most Laravel apps start. A scheduled command, a count and a Slack webhook:
class CheckOrders extends Command { protected $signature = 'orders:check'; public function handle(): void { $orders = Order::where('created_at', '>=', now()->subHour())->count(); if ($orders < 20) { Http::post(config('services.slack.alerts_webhook'), [ 'text' => "Only {$orders} orders in the last hour.", ]); } } }
Schedule it hourly and you have anomaly detection. Sort of. It's a perfectly reasonable place to start, and it will catch a total outage at a busy time of day.
Then the questions start.
But what about...
...3am? Most shops take almost no orders overnight, so this fires every night. Within a week someone mutes it, and a muted alert is worse than none, because everyone still believes they're covered.
...Saturdays? Weekends have a different shape from weekdays. A number that's healthy on Saturday afternoon might be a disaster on Tuesday afternoon, or the other way round.
...last week's outage? Any attempt to learn "normal" from history has to cope with the bad days in that history: outages, sales, bot traffic. Learn from them naively and the alert slowly comes to accept the very problems it exists to catch.
...an hour that's just a bit quiet? Real activity is noisy. Fifteen orders instead of twenty might be chance or might be the start of something. Which is it?
...things that only happen a few times a day? A B2B signup or a nightly export never gets anywhere near 20 an hour. Per-hour counts can't tell you anything about them, but they still matter.
...one odd hour? Chance alone will occasionally push a healthy metric over any line. Alert on every blip and you're back to muting it.
...Christmas? Or Black Friday, or the day the newsletter goes out. Some days don't look like any recent week, and they aren't problems.
...daylight saving? Your customers live in local time and your server probably doesn't. Twice a year the whole weekly shape shifts by an hour.
...when the business actually changes? A new market, a price rise, a big customer leaving. Yesterday's normal is now wrong, and "everything is lower than usual" isn't an incident that should last for a month.
...the metric you started tracking yesterday? It has no history at all, and it still needs protecting.
...signups, renewals, password resets, outgoing emails and imports? Orders were just the first one. Each has its own shape, and each needs the same care.
...the deploy that went out twenty minutes ago? When the alert fires, that's the first thing anyone will ask about, and this command has no idea.
Build it or buy it?
You can answer every one of those questions yourself, and plenty of teams start to. The first version takes an afternoon. Making it trustworthy (quiet when it should be, loud when it matters, across every metric the business depends on) is the part that takes months, and it's rarely anyone's actual job.
Most homegrown alerting doesn't fail because the maths was wrong. It fails because the alerts cried wolf and got muted.
What Marmot does instead
Marmot does this job for every moment in your Laravel app, without you building or tuning anything.
- Every moment is watched automatically. New records on every model, scheduled tasks, queued jobs, mail and notifications, from the moment you install the package.
- Dips, spikes and silences, each judged against what's normal for that moment at that time of the week.
- Built not to cry wolf. An alert that fires too often gets muted, so Marmot is deliberately careful about when it speaks up.
- No cold start. Useful from day one, without weeks of history to build up first.
Alerts arrive in Slack in plain English, with deploy context when there is some. It sends names and counts only, never your data.