<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Adam Schapekahm — Writing</title>
  <subtitle>Notes on enterprise integration, platform strategy, and engineering leadership.</subtitle>
  <link href="https://www.adamschapekahm.com/feed.xml" rel="self"/>
  <link href="https://www.adamschapekahm.com/blog/"/>
  <id>https://www.adamschapekahm.com/blog/</id>
  <author>
    <name>Adam Schapekahm</name>
  </author>
  <updated>2026-09-18T00:00:00.000Z</updated>
  <entry>
    <title>Changing our logging means changing one thing, not 150</title>
    <link href="https://www.adamschapekahm.com/blog/posts/logging-hook/"/>
    <id>https://www.adamschapekahm.com/blog/posts/logging-hook/</id>
    <updated>2026-09-18T00:00:00.000Z</updated>
    <summary>Our Boomi portfolio is past 150 integrations. When we need to change how logging works across all of them, we change one thing. We built the hook early, and it has paid back more than any other sequencing call on the platform.</summary>
    <content type="html">&lt;p class=&quot;post__lede&quot;&gt;
Our Boomi portfolio is past 150 integrations. When we need to change how logging
works across all of them, we change one thing.
&lt;/p&gt;
&lt;p&gt;We built the hook early, before the estate got big enough to make retrofitting it
painful. Of all the sequencing calls we made on the platform, that one has paid back
the most.&lt;/p&gt;
&lt;p&gt;The first dozen integrations are easy to support without anything like it. Someone on
the team built each one and still remembers how it works. Failures are rare enough to
be interesting. When a file doesn&#39;t land, a business user calls and you go look.&lt;/p&gt;
&lt;p&gt;Nothing tells you when that stops working. You just start noticing that support is
eating the sprint.&lt;/p&gt;
&lt;h2&gt;The failures that hurt don&#39;t throw&lt;/h2&gt;
&lt;p&gt;A refused connection gets caught, logged, and retried. An expired credential is fixed
inside an hour. Those are fine, and they are what most error handling is actually
built for.&lt;/p&gt;
&lt;p&gt;What costs you is an integration that finishes successfully and moves the wrong data.
A batch that processes 900 of 1,000 records and reports success. A retry that
duplicates rows downstream because the target was never idempotent. A field that
stopped mapping three weeks ago when someone upstream changed a schema and didn&#39;t
think to mention it.&lt;/p&gt;
&lt;p&gt;Nobody gets paged for any of that. You find out when Finance asks why a number looks
wrong, and by then you are reconstructing three weeks of runs to work out where it
went sideways. Catching it earlier is a logging problem before it is anything else.&lt;/p&gt;
&lt;h2&gt;What we built&lt;/h2&gt;
&lt;p&gt;One hook, reused everywhere. Every integration plugs into it.&lt;/p&gt;
&lt;p&gt;It structures every message the same way and writes it to one central place. That is
most of the value right there. Because the shape is consistent and the destination is
single, the logs are actually queryable, and a dashboard over them is half a day of
work rather than a project.&lt;/p&gt;
&lt;p&gt;It is event based, so it carries informational messages alongside errors. That matters
more than it sounds like it should. A lot of what you want to know about an
integration is not a failure, it is a checkpoint. This batch started. This many
records came back. This file landed.&lt;/p&gt;
&lt;p&gt;And it is plug and play. A developer building a new integration does not decide how to
log or how to handle an error. They drop the hook in and they are done. Every
integration ends up with the same error handling and the same logging whether or not
anyone was thinking about observability that week.&lt;/p&gt;
&lt;h2&gt;One place to change it&lt;/h2&gt;
&lt;p&gt;This is the part I underrated going in.&lt;/p&gt;
&lt;p&gt;Logging requirements move. Someone wants a new field on every message. The destination
changes. A new environment needs different routing. Compliance asks for something the
original design never anticipated.&lt;/p&gt;
&lt;p&gt;When that logic lives inside each integration, every one of those requests is a
project. You are opening 150 integrations, making the same small edit 150 times,
testing 150 things, and rolling them out in waves because nobody deploys 150
integrations at once on purpose. The cost of a change scales with the size of the
estate, which is exactly backwards from what you want.&lt;/p&gt;
&lt;p&gt;When it lives in the hook, it is one change. The estate picks it up without anyone
opening the integrations themselves.&lt;/p&gt;
&lt;h2&gt;The support math&lt;/h2&gt;
&lt;p&gt;The usual pitch for shared frameworks is that you write it once and reuse it. That is
true and it is the smaller half of the argument.&lt;/p&gt;
&lt;p&gt;The bigger half is that consistency decides who can support what. If every integration
logs the same way and raises the same health signals, an engineer who has never opened
a particular integration can still triage it at 2am. If they don&#39;t, supporting
integration X requires knowing integration X, and you have quietly made specific
people load-bearing.&lt;/p&gt;
&lt;p&gt;That is survivable at twenty integrations. At a hundred and fifty it means your
on-call rotation does not really work, and you find that out on a weekend.&lt;/p&gt;
&lt;h2&gt;If you are already past the easy point&lt;/h2&gt;
&lt;p&gt;We got to build ours early. Most teams reading this will be retrofitting, and that is
still worth doing.&lt;/p&gt;
&lt;p&gt;Don&#39;t wait for a rewrite. It isn&#39;t coming, and the hook does not need one. It is
additive. It does not change how any integration moves data.&lt;/p&gt;
&lt;p&gt;Sequence by blast radius instead. Anything touching money or a customer commitment
gets retrofitted first. The rest can trickle in as people happen to be in those
integrations for other reasons, which is slower but costs nobody a dedicated sprint.&lt;/p&gt;
</content>
  </entry>
</feed>
