<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Marcin Szterling</title>
    <description>The latest articles on DEV Community by Marcin Szterling (@php4u).</description>
    <link>https://dev.to/php4u</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4089609%2F0acc3b44-52ff-4ac8-afd5-e3775ae1474e.png</url>
      <title>DEV Community: Marcin Szterling</title>
      <link>https://dev.to/php4u</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/php4u"/>
    <language>en</language>
    <item>
      <title>Two Monolog majors, one PHP class: adding exception-log monitoring across Magento 2.4.7-2.4.9</title>
      <dc:creator>Marcin Szterling</dc:creator>
      <pubDate>Fri, 18 Sep 2026 19:28:45 +0000</pubDate>
      <link>https://dev.to/php4u/two-monolog-majors-one-php-class-adding-exception-log-monitoring-across-magento-247-249-i7c</link>
      <guid>https://dev.to/php4u/two-monolog-majors-one-php-class-adding-exception-log-monitoring-across-magento-247-249-i7c</guid>
      <description>&lt;p&gt;I just shipped a new signal in Watchtower, my Magento monitoring tool:&lt;br&gt;
&lt;code&gt;application_errors&lt;/code&gt;, which watches for a rising rate of caught exceptions&lt;br&gt;
your own code is logging, before that shows up anywhere else, like checkout.&lt;br&gt;
The feature itself is simple. Getting there across three supported Magento&lt;br&gt;
versions was not, and the two bugs I hit only showed up under live&lt;br&gt;
verification, not unit tests. Here's what happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The goal: count exceptions without reading a log file
&lt;/h2&gt;

&lt;p&gt;Every existing signal in my connector observes Magento's event bus or polls&lt;br&gt;
a status table. This one is different: I wanted to know when a store's own&lt;br&gt;
code starts throwing more than usual, at the point Magento's logger actually&lt;br&gt;
logs it.&lt;/p&gt;

&lt;p&gt;The obvious approach is tailing &lt;code&gt;exception.log&lt;/code&gt;. I rejected it early: log&lt;br&gt;
rotation, file permissions, and offset tracking are all real failure modes,&lt;br&gt;
and none of them should ever be able to produce a false anomaly. Instead I&lt;br&gt;
hook Magento's own Monolog handler stack directly, via &lt;code&gt;di.xml&lt;/code&gt;, so I&lt;br&gt;
intercept each log record in memory at the moment it's dispatched, the same&lt;br&gt;
moment Magento's own &lt;code&gt;Handler\System&lt;/code&gt; and &lt;code&gt;Handler\Exception&lt;/code&gt; do. I never&lt;br&gt;
open, read, or track an offset into any file. Whatever happens to the&lt;br&gt;
physical log afterward has zero effect on the counter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 1: Monolog 2 vs Monolog 3
&lt;/h2&gt;

&lt;p&gt;Magento 2.4.7 ships Monolog 2.x, where a handler's &lt;code&gt;$record&lt;/code&gt; is a plain&lt;br&gt;
array. Magento 2.4.8 and 2.4.9 ship Monolog 3.x, where it's an immutable&lt;br&gt;
&lt;code&gt;LogRecord&lt;/code&gt; object. &lt;code&gt;HandlerInterface&lt;/code&gt;'s method signatures are incompatible&lt;br&gt;
between the two majors, so one PHP class cannot implement both.&lt;/p&gt;

&lt;p&gt;My first attempt was the obvious one: two concrete classes, &lt;code&gt;class_alias()&lt;/code&gt;'d&lt;br&gt;
to a common name at runtime depending on which Monolog major was loaded.&lt;br&gt;
That worked fine under normal autoloading. It fataled under&lt;br&gt;
&lt;code&gt;bin/magento setup:di:compile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The reason: Magento's compiler eagerly &lt;code&gt;require&lt;/code&gt;s every class file in every&lt;br&gt;
module for reflection (repository/proxy/interceptor generation), regardless&lt;br&gt;
of whether anything actually references that class. Both concrete handler&lt;br&gt;
files declared a top-level &lt;code&gt;class ... implements HandlerInterface&lt;/code&gt;, and PHP&lt;br&gt;
interface-checks a top-level class declaration the moment its file is&lt;br&gt;
parsed. So the "wrong" file, whichever Monolog major wasn't installed where&lt;br&gt;
compilation ran, always fataled immediately. The &lt;code&gt;class_alias()&lt;/code&gt; gate never&lt;br&gt;
got a chance to run, because compilation never went through it at all.&lt;/p&gt;

&lt;p&gt;The fix: put both implementations in one file, each declared inside its own&lt;br&gt;
branch of a runtime&lt;br&gt;
&lt;code&gt;if (class_exists(\Monolog\LogRecord::class)) { class Handler ... } else { class Handler ... }&lt;/code&gt;.&lt;br&gt;
PHP hoists and interface-checks a top-level &lt;code&gt;class&lt;/code&gt; statement immediately,&lt;br&gt;
but a class declared inside a conditional block is bound only when that&lt;br&gt;
branch actually executes. The untaken branch is never checked against&lt;br&gt;
whichever &lt;code&gt;HandlerInterface&lt;/code&gt; happens to be loaded, compiler included. Both&lt;br&gt;
branches declare the literal same class name, so &lt;code&gt;di.xml&lt;/code&gt;'s reference&lt;br&gt;
resolves correctly either way, no alias needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 2: di.xml doesn't merge the way you'd expect
&lt;/h2&gt;

&lt;p&gt;Magento's &lt;code&gt;app/etc/di.xml&lt;/code&gt; is the primary bootstrap configuration, and it&lt;br&gt;
already registers three core handlers on&lt;br&gt;
&lt;code&gt;Magento\Framework\Logger\Monolog&lt;/code&gt;: &lt;code&gt;system&lt;/code&gt;, &lt;code&gt;debug&lt;/code&gt;, &lt;code&gt;syslog&lt;/code&gt;. My first&lt;br&gt;
working version of the module's own &lt;code&gt;di.xml&lt;/code&gt; declared only my new handler as&lt;br&gt;
a fourth array item, assuming Magento would merge it into the existing three&lt;br&gt;
by item name, the normal behavior for a per-module &lt;code&gt;di.xml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It doesn't, at this specific precedence boundary. A module-level&lt;br&gt;
&lt;code&gt;&amp;lt;argument name="handlers"&amp;gt;&lt;/code&gt; replaces &lt;code&gt;app/etc/di.xml&lt;/code&gt;'s array wholesale&lt;br&gt;
rather than merging into it. My first version silently dropped &lt;code&gt;system&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;debug&lt;/code&gt;, and &lt;code&gt;syslog&lt;/code&gt;. I confirmed it live with&lt;br&gt;
&lt;code&gt;Monolog\Logger::getHandlers()&lt;/code&gt;: one handler instead of four. That would&lt;br&gt;
have disabled &lt;code&gt;exception.log&lt;/code&gt;, &lt;code&gt;system.log&lt;/code&gt;, and &lt;code&gt;debug.log&lt;/code&gt; for every&lt;br&gt;
install running this version.&lt;/p&gt;

&lt;p&gt;No unit test caught it. PHPStan didn't catch it. What caught the bug was&lt;br&gt;
running the module against a real Magento install and counting handlers,&lt;br&gt;
not trusting a green test suite. Fixed by explicitly redeclaring all four&lt;br&gt;
items.&lt;/p&gt;

&lt;h2&gt;
  
  
  What shipped
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;application_errors&lt;/code&gt; is now my 10th tracked signal: install-scoped (a&lt;br&gt;
logged exception has no reliable store-view attribution), one-directional&lt;br&gt;
(a quiet hour is never anomalous, only a spike is), and classified with the&lt;br&gt;
same median/MAD dispersion baseline my three rate-based signals already&lt;br&gt;
use. It only counts records carrying exception context at Warning level or&lt;br&gt;
above, mirroring the same condition Magento core's own &lt;code&gt;Handler\System&lt;/code&gt;&lt;br&gt;
uses to route a record to &lt;code&gt;exception.log&lt;/code&gt; in the first place. It never&lt;br&gt;
reads the exception message or stack trace, count only.&lt;/p&gt;

&lt;p&gt;Both bugs here were invisible to static analysis and to a green PHPUnit&lt;br&gt;
suite. The thing that found them was doing the boring thing: installing the&lt;br&gt;
actual module on actual Magento 2.4.7, 2.4.8, and 2.4.9 containers and&lt;br&gt;
checking what really happened.&lt;/p&gt;

</description>
      <category>magento</category>
      <category>monitoring</category>
      <category>opensource</category>
      <category>php</category>
    </item>
    <item>
      <title>Why I stopped trusting uptime monitors for Magento stores</title>
      <dc:creator>Marcin Szterling</dc:creator>
      <pubDate>Sat, 22 Aug 2026 11:23:57 +0000</pubDate>
      <link>https://dev.to/php4u/why-i-stopped-trusting-uptime-monitors-for-magento-stores-1pg5</link>
      <guid>https://dev.to/php4u/why-i-stopped-trusting-uptime-monitors-for-magento-stores-1pg5</guid>
      <description>&lt;p&gt;I have been working with Magento for about a decade now, across agency and freelance work, and I have lost count of how many times a client called me in a panic over a store that was, according to every monitoring tool they had, perfectly healthy.&lt;/p&gt;

&lt;p&gt;That gap between what a monitor reports and what a merchant actually experiences is what pushed me to start building something new. This article is about the gap itself, not the product, though I will mention what I am building at the end for anyone curious.&lt;/p&gt;

&lt;h3&gt;
  
  
  The homepage lies
&lt;/h3&gt;

&lt;p&gt;Most uptime monitors work the same way. They ping a URL, usually the homepage, on a schedule. If it returns a 200, everything is green. If it times out or returns an error, you get an alert.&lt;/p&gt;

&lt;p&gt;The problem is that a Magento homepage is one of the least demanding pages in the entire store. It is heavily cached, often served straight from Varnish or Fastly without ever touching PHP. It can return a 200 in twenty milliseconds while checkout, three layers deeper in the application, is throwing exceptions on every single attempt.&lt;/p&gt;

&lt;p&gt;I have seen this exact scenario more than once. A payment method integration silently breaks after a third party API changes something. The homepage keeps loading fine. The category pages keep loading fine. The only place the failure shows up is at the final step of checkout, which is precisely the page an uptime monitor never checks because it requires a session, a cart, and usually a real transaction to reach.&lt;/p&gt;

&lt;p&gt;By the time anyone notices, it is not a monitoring alert that catches it. It is a customer complaint, or worse, a quiet multi hour dip in orders that nobody clocks until someone pulls the daily sales report.&lt;/p&gt;

&lt;h3&gt;
  
  
  APM tools were not built with Magento in mind
&lt;/h3&gt;

&lt;p&gt;The next instinct is usually to reach for a generic APM tool. These are genuinely useful for a lot of things, response time tracking, error rate monitoring, infrastructure metrics. But they are built to be platform agnostic, which means they have no concept of what actually matters inside a Magento store.&lt;/p&gt;

&lt;p&gt;A generic APM will tell you that a request took 400 milliseconds. It will not tell you that the request was a reindex job that stalled halfway through, leaving your catalog in an inconsistent state. It will not tell you that a cron job responsible for order status sync has not run successfully in six hours. It will not tell you that your Redis cache is being invalidated far more aggressively than it should be, quietly increasing database load on every page view.&lt;/p&gt;

&lt;p&gt;These are Magento specific failure modes, and they need Magento specific context to detect. A tool that treats every application the same way will always miss the things that are unique to how this platform actually behaves in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  The failures that matter most, and why they hide
&lt;/h3&gt;

&lt;p&gt;A few patterns I have run into repeatedly, that generic monitoring consistently misses:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Background jobs that fail silently.&lt;/strong&gt; Magento relies heavily on cron for indexing, order processing, and various scheduled tasks. When a cron job fails, it often does not throw a loud error. It just does not run, or runs partially, and the next scheduled run may or may not recover cleanly depending on what state it left things in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Index staleness.&lt;/strong&gt; If reindexing falls behind or fails, customers can see stale pricing, out of stock items shown as available, or search results that do not reflect recent catalog changes. None of this trips an uptime check. The site is up. It is just wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Payment method degradation.&lt;/strong&gt; Third party payment integrations change their APIs, rotate certificates, or have partial outages. A payment method can go from working to failing for a subset of transactions, which is much harder to catch than a full outage and much more damaging because it looks like normal traffic with a quietly climbing failure rate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache invalidation storms.&lt;/strong&gt; A misconfigured extension or a bad deployment can cause full page cache to be invalidated far more often than intended. Traffic looks the same. Server load quietly climbs. Nobody notices until response times degrade under peak load.&lt;/p&gt;

&lt;p&gt;Every one of these has a common thread. The store is technically up. Nothing in a standard monitoring stack fires an alert. The damage accumulates quietly until it becomes visible in a place nobody was watching, usually the sales numbers or a support inbox full of complaints.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actual Magento monitoring needs to look like
&lt;/h3&gt;

&lt;p&gt;The conclusion I kept coming back to is that Magento specific problems need Magento specific baselines, not generic thresholds borrowed from a platform agnostic tool.&lt;/p&gt;

&lt;p&gt;A fixed threshold, alert if response time exceeds two seconds, alert if error rate exceeds five percent, treats every store the same regardless of its normal traffic pattern, catalog size, or extension footprint. What is normal for a high traffic B2C store with thousands of SKUs is very different from what is normal for a lower traffic B2B store with complex custom pricing.&lt;/p&gt;

&lt;p&gt;What actually works is learning what normal looks like for a specific store, and alerting on meaningful deviation from that baseline rather than an arbitrary number picked in advance. A cron job that usually completes in ninety seconds and suddenly takes fifteen minutes is worth flagging even if fifteen minutes would be unremarkable for a different, larger catalog elsewhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I am building
&lt;/h3&gt;

&lt;p&gt;This is the problem I have been working on solving, on evenings and weekends for a while now. It is called Watchtower Commerce, a monitoring platform built specifically for Magento and Adobe Commerce internals rather than a generic APM with a Magento label stuck on it.&lt;/p&gt;

&lt;p&gt;It uses a lightweight connector installed on the store, built with a privacy first approach so store data does not need to leave the merchant's infrastructure to be useful. The dashboard surfaces the vitals that actually matter for this platform, and alerting is based on learned baselines rather than fixed thresholds.&lt;/p&gt;

&lt;p&gt;It is close to ready, still in testing and polishing. If you run or work with Magento stores and any of this sounds familiar, I would like to hear from you, and I am looking for a few beta testers once it is ready.&lt;/p&gt;

&lt;p&gt;Give it a go at &lt;a href="https://watchtower-commerce.com" rel="noopener noreferrer"&gt;https://watchtower-commerce.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to talk through any of the technical decisions in the comments, particularly around the baseline learning approach or the privacy architecture of the connector.&lt;/p&gt;

</description>
      <category>magento</category>
      <category>adobecommerce</category>
      <category>monitoring</category>
      <category>saas</category>
    </item>
  </channel>
</rss>
