<?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: Adam Gardner</title>
    <description>The latest articles on DEV Community by Adam Gardner (@agardnerit).</description>
    <link>https://dev.to/agardnerit</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%2F1229306%2F7318dee7-87eb-461f-a5eb-9c7b4d1a56a6.png</url>
      <title>DEV Community: Adam Gardner</title>
      <link>https://dev.to/agardnerit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/agardnerit"/>
    <language>en</language>
    <item>
      <title>The CloudFlare Durable Object that would not die [How a 60-second retry loop ate every Cloudflare KV quota I had (and why it wasn't a hack)]</title>
      <dc:creator>Adam Gardner</dc:creator>
      <pubDate>Mon, 31 Aug 2026 07:25:22 +0000</pubDate>
      <link>https://dev.to/agardnerit/the-cloudflare-durable-object-that-would-not-die-how-a-60-second-retry-loop-ate-every-cloudflare-ahp</link>
      <guid>https://dev.to/agardnerit/the-cloudflare-durable-object-that-would-not-die-how-a-60-second-retry-loop-ate-every-cloudflare-ahp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is a real story, with writing assistance from an AI. I'm working on a side-project for a relative and suddenly CloudFlare blows up the inbox. Queue the panic of "I've been hacked" followed by this investigation...&lt;br&gt;
And yes, the AI did write the buggy code...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Three emails in one day: we'd exceeded the Cloudflare KV free tier limits for put operations, delete operations, AND list operations. The dashboard showed the tell-tale signature: reads, writes, lists and deletes all at ~2.4k in the last 24 hours, against a namespace holding a grand total of four keys. It wasn't an attack. It wasn't a botnet. It was one bug: a demo session's 24-hour self-destruct alarm called a method that doesn't exist on the runtime, failed, and then re-armed itself every 60 seconds. Forever. Two stuck sessions, one alarm each, ~2,400 operations a day of every kind, all three quota types gone in a single UTC day.&lt;/p&gt;




&lt;h2&gt;
  
  
  It started with three emails
&lt;/h2&gt;

&lt;p&gt;If you use Cloudflare's free tier, you probably know the KV limits are small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100,000 reads per day&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1,000 writes per day&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1,000 deletes per day&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1,000 list operations per day&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reads were never in danger. Writes, deletes AND lists all died on the same day (reset at 00:00 UTC — so whatever it was, it was write/delete/list shaped, not "crawler reading pages" shaped).&lt;/p&gt;

&lt;p&gt;My first thought, like yours would be: we're being attacked. Botnet hammering the public demo door? Someone abusing our Durable Objects?&lt;/p&gt;

&lt;p&gt;No. It was much dumber. It was us.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dashboard knew before the code did
&lt;/h2&gt;

&lt;p&gt;The KV namespace dashboard tells most of the story by itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Monthly (Aug 1–31):   Reads 4.39k · Writes 3.11k · Lists 2.39k · Deletes 2.36k
Last 24 hours:        Reads ~2.4k · Writes ~2.4k · Lists ~2.4k · Deletes ~2.4k
KV count:             4        Storage: 1.29 kB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things stand out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The 1:1:1:1 ratio.&lt;/strong&gt; Reads, writes, lists and deletes are all about equal. Real apps don't do that. A rate limiter gives you reads+writes. A cleanup job gives you lists+deletes. Equal amounts of &lt;em&gt;all four&lt;/em&gt; means one loop doing a &lt;code&gt;get&lt;/code&gt;, a &lt;code&gt;put&lt;/code&gt;, a &lt;code&gt;list&lt;/code&gt; and a &lt;code&gt;delete&lt;/code&gt; per iteration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;KV count = 4.&lt;/strong&gt; There are &lt;em&gt;four keys in the namespace&lt;/em&gt;. Any theory about "processing hundreds of keys" is dead on arrival — even sweeping the whole namespace would be four operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monthly ≈ last 24h.&lt;/strong&gt; The lists and deletes basically &lt;em&gt;all&lt;/em&gt; happened in the last day. This behaviour started recently, on an almost-empty namespace.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Add it up: a tight loop, ~100 runs an hour, one of each operation per run. Two sessions at 60-second intervals is 2,880 iterations a day. The arithmetic works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hitting dead ends (and one proper trap)
&lt;/h2&gt;

&lt;p&gt;I eliminated suspects in order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tenant deletion pass?&lt;/strong&gt; The 07:00 cron has a hard-erase step that lists and deletes KV keys per tenant. But the registry has two tenants, both &lt;code&gt;active&lt;/code&gt;, and the &lt;code&gt;retired_slugs&lt;/code&gt; and &lt;code&gt;signup_tokens&lt;/code&gt; tables are both &lt;strong&gt;empty&lt;/strong&gt;. Zero due tenants, zero work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stats rollup?&lt;/strong&gt; The daily cron folds day counters into the database and deletes the consumed keys. One list, one delete. In fact it's how I confirmed the cron ran: the rolled-up D1 row for &lt;code&gt;2026-08-29&lt;/code&gt; exists (50 requests, 0 starts, 1 stop) while the raw KV day key now 404s — the rollup deleted it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The old single-tenant app?&lt;/strong&gt; Different KV namespace entirely. Excluded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bots on the public demo door?&lt;/strong&gt; The demo telemetry says otherwise — almost no starts, no IP diversity, no signup tokens, no tenant traffic. The only two sessions that exist are our own, created at 01:00 and 05:00 UTC the day before (you can see them as two surviving hour counters in the KV).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here's the trap that nearly sent me down the wrong path: &lt;strong&gt;&lt;code&gt;wrangler kv key list&lt;/code&gt; without &lt;code&gt;--remote&lt;/code&gt; reads your local emulated state&lt;/strong&gt;, not production. I enumerated "34 keys" that way, including a suspicious-looking &lt;code&gt;demo:ip:2026-08-29:127.0.0.1&lt;/code&gt; ("loopback traffic! someone's hacking from localhost!"). It was a red herring — local dev artifacts, completely unrelated. Adding &lt;code&gt;--remote&lt;/code&gt; immediately returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your account has reached the free usage limit for this operation for today [code: 10048]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which was, ironically, the best piece of evidence so far: the list quota was &lt;em&gt;still exhausted, live, right now&lt;/em&gt;. The loop was still burning while I was looking for it.&lt;/p&gt;

&lt;p&gt;(For completeness: I also tried the GraphQL analytics API for per-day operation counts and got &lt;code&gt;10404: No route for that URI&lt;/code&gt; — the wrangler OAuth token has no analytics scope. No API shortcut; it had to be dashboard + logs.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The smoking gun
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;wrangler tail&lt;/code&gt; catches everything. After a few minutes, it caught the heartbeat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="nx"&gt;destroy&lt;/span&gt; &lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;retrying&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nx"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleteUnsafe&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every ~60 seconds, from two session IDs, in production, right now. That one log line is the entire incident. Let me show you the code behind it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deleteDemoObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idStr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEMO_SESSIONS&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;deleteUnsafe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DurableObjectId&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteUnsafe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEMO_SESSIONS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;idFromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idStr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;   &lt;span class="c1"&gt;// ← TypeError&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;alarm&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...wipe R2 photos (works), wipe KV slice (works)...&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;deleteDemoObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[demo] session destroy failed, retrying in 60s:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAlarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// ← forever&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the lifecycle was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Session created → 24-hour alarm armed.&lt;/li&gt;
&lt;li&gt;Alarm fires → data wipe &lt;strong&gt;succeeds&lt;/strong&gt; (R2 objects, KV slice, session markers — all gone).&lt;/li&gt;
&lt;li&gt;The final self-destroy throws, because &lt;code&gt;deleteUnsafe&lt;/code&gt; doesn't exist on the runtime.&lt;/li&gt;
&lt;li&gt;The catch re-arms the alarm &lt;strong&gt;60 seconds later&lt;/strong&gt;, unconditionally.&lt;/li&gt;
&lt;li&gt;The DO's own alarm keeps the object alive; the object keeps re-arming the alarm. &lt;strong&gt;It cannot die.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Per stuck session, per iteration: one KV read (summary), one write (summary rewrite), one list (the wipe), one delete (the marker). That's the 1:1:1:1 dashboard, exactly. Two sessions → all three quotas in a day. And here's the sneaky bit: at midnight the quotas reset, the attempts start succeeding again, and the account burns the &lt;em&gt;next&lt;/em&gt; day's allowance. Three emails today, three more tomorrow, forever. A machine that invoices itself in quota.&lt;/p&gt;

&lt;p&gt;The wipe succeeding made it worse to spot: the "not found" half-gone state looked normal, and only the last step — the one that never worked — was failing. The bug was invisible until the first production session reached 24 hours old.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now, about the AI angle
&lt;/h2&gt;

&lt;p&gt;Worth being honest about, because it's relevant to a lot of us: &lt;strong&gt;this codebase is written by an AI coding agent&lt;/strong&gt; (our toolchain is Pi connected to OpenCode Go using &lt;code&gt;deepseek-v4-flash&lt;/code&gt;). And this bug is a perfect example of what that actually means in practice.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;deleteUnsafe&lt;/code&gt; is a real, documented Cloudflare API. The model knew it, and emitted exactly the code you'd write from the docs — plausible, correct-looking, and typed with &lt;code&gt;as unknown as&lt;/code&gt; to shut up the compiler. TypeScript said "fine". The runtime said &lt;code&gt;ns.deleteUnsafe is not a function&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For symmetry, the second AI in this story: the platform's support assistant looked at the same codebase and confidently blamed a "session cleanup loop in the scheduled handler" that lists, reads and deletes keys on every cron run. I checked: that loop doesn't exist. The cron's KV lists total about one or two per day, and there are four keys in the namespace anyway. It read the code correctly and never checked the telemetry. Pattern-matching is the shared weakness of the code author and the debugging assistant here — plausible shape, no evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Small and boring, on purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bounded retry budget — never an unconditional 60s re-arm.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TEARDOWN_MAX_ATTEMPTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TEARDOWN_BACKOFF_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;alarm&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEARDOWN_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;TEARDOWN_MAX_ATTEMPTS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// give up — data already wiped&lt;/span&gt;
  &lt;span class="c1"&gt;// teardown summary written ONCE per session, not once per retry&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;wipeSessionData&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteAlarm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;setAlarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// No DO-self-delete exists — data is wiped; identity stays as a free tombstone.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deployed, then verified with the same tool that exposed the bug: after deploy, each zombie session fired &lt;strong&gt;once&lt;/strong&gt; —&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;destroy&lt;/span&gt; &lt;span class="nf"&gt;deferred &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleteUnsafe&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;stop&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;sweep&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="nx"&gt;finish&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;— and went silent. No more heartbeat. The dashboard flatlines at the next quota reset. (For the record: the "self-destroy deferred" wording in that log line is itself now obsolete — the fixed code no longer attempts the impossible delete at all.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently next time (says the AI)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retry loops need budgets, always.&lt;/strong&gt; An unbounded retry is an immortal process — doubly so when the alarm is re-arming itself. If it can't converge, let it fail outward: backoff, cap, hand off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't swallow telemetry errors.&lt;/strong&gt; Our demo counters all &lt;code&gt;catch(() =&amp;gt; {})&lt;/code&gt; so the system "never breaks for telemetry". Result: it was burning quota in silence, and every monitoring read showed zero activity — while writing. Log the failures you swallow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A type cast on a runtime API you've never called is a ledge.&lt;/strong&gt; If the codegen agent produced it, a human has to run it once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Know the 1:1:1:1 signature.&lt;/strong&gt; Equal read/write/list/delete counts, empty namespace, continuous not bursty — that's a loop doing one of each. It's a fingerprint you'll recognise again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify AI claims against primary data.&lt;/strong&gt; Both AIs in this story — the one that wrote the code and the one that diagnosed it — produced plausible shapes. The dashboard and a tail log disagreed with both.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>debugging</category>
      <category>programming</category>
      <category>serverless</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Semgrep Observability with OpenTelemetry</title>
      <dc:creator>Adam Gardner</dc:creator>
      <pubDate>Tue, 31 Mar 2026 10:49:52 +0000</pubDate>
      <link>https://dev.to/agardnerit/semgrep-observability-with-opentelemetry-4k6o</link>
      <guid>https://dev.to/agardnerit/semgrep-observability-with-opentelemetry-4k6o</guid>
      <description>&lt;p&gt;Semgrep is a great open source security and code validation tool. Semgrep revolves around rules like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;print-to-logger&lt;/span&gt;
    &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;print($VAR)&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use logging.info() instead of print()&lt;/span&gt;
    &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
    &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MEDIUM&lt;/span&gt;
    &lt;span class="na"&gt;fix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;logger.info($MSG)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule above will raise a &lt;code&gt;MEDIUM&lt;/code&gt; severity issue every time a use of &lt;code&gt;print()&lt;/code&gt; is used in your Python code. It will also provide the recommended fix and even take the value inside the print statement and produce the fix content. Thus &lt;code&gt;print("Hello world!")&lt;/code&gt; becomes &lt;code&gt;logger.info("Hello world!")&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;rules.yaml&lt;/code&gt; file is then used to validate one or more (in this case) Python files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;semgrep scan &lt;span class="nt"&gt;-f&lt;/span&gt; rules.yaml app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Capturing Semgrep Output using the OpenTelemetry Collector
&lt;/h2&gt;

&lt;p&gt;Semgrep is capable of producing JSON output which means it's really easy to grab using the OpenTelemetry collector. Let's re-run the previous command with a few more flags to produce JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;semgrep scan &lt;span class="nt"&gt;-f&lt;/span&gt; rules.yaml &lt;span class="nt"&gt;--json&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; out.json app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It produces single line JSON (JSONL) like this (which I've expanded here for readability):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.152.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"results"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Rule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;violations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;listed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;here...&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"check_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"print-to-logger"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"col"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;46&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"col"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"extra"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Use logging.info() instead of print()"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"fix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"logger.info(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;blah&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MEDIUM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"fingerprint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"requires login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"lines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"requires login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"validation_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NO_VALIDATOR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"engine_kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OSS"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rules_parse_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0018589496612548828&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"profiling_times"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"config_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.1376628875732422&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"core_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.2947859764099121&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"ignores_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.291534423828125e-05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"total_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.43769073486328125&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"parsing_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"total_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"per_file_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"mean"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"std_dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"very_slow_stats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"time_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"count_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"very_slow_files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"scanning_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"total_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.009443998336791992&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"per_file_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"mean"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.009443998336791992&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"std_dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"very_slow_stats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"time_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"count_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"very_slow_files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matching_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"total_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"per_file_and_rule_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"mean"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"std_dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"very_slow_stats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"time_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"count_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"very_slow_rules_on_files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tainting_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"total_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"per_def_and_rule_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"mean"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"std_dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"very_slow_stats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"time_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"count_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"very_slow_rules_on_defs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"fixpoint_timeouts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"prefiltering"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"project_level_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"file_level_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"rules_with_project_prefilters_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"rules_with_file_prefilters_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"rules_selected_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"rules_matched_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"targets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"total_bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"max_memory_bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120384832&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure the OpenTelemetry collector to: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Monitor &lt;code&gt;out.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Parse the body text as JSON&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;transform&lt;/code&gt; processor can also be used to process the JSONL lines as they transit through the collector. In this case the rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set both the &lt;code&gt;time&lt;/code&gt; and &lt;code&gt;observed_time&lt;/code&gt; to the current time (since the log line doesn't explicitly state a timestamp&lt;/li&gt;
&lt;li&gt;Adds a new Key/Value attribute pair to each log record of &lt;code&gt;tool: semgrep&lt;/code&gt; (this is useful when the log line hits your Observability backend for filtering)&lt;/li&gt;
&lt;li&gt;Adds another new Key/Value attribute pair to each log record where the key == &lt;code&gt;results_found&lt;/code&gt; and the value is the length of the &lt;code&gt;results&lt;/code&gt; array (again useful for backend processing - your O11y system may be able to compute lengths from an input array, but you can add it here to offload the processing / cost)&lt;/li&gt;
&lt;li&gt;The final two rules effectively overwrite the &lt;code&gt;version&lt;/code&gt; key as &lt;code&gt;semgrep_version&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: The collector cannot rename attribute keys so you actually take the current value of &lt;code&gt;version&lt;/code&gt; (ie. &lt;code&gt;"1.152.0"&lt;/code&gt;), create a new attribute called &lt;code&gt;semgrep_version&lt;/code&gt;, set the value of the value using the existing value and finally delete the existing &lt;code&gt;version&lt;/code&gt; attribute.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;filelog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;out.json&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;start_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;beginning&lt;/span&gt;
    &lt;span class="na"&gt;operators&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;json_parser&lt;/span&gt;
        &lt;span class="na"&gt;parse_from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;body&lt;/span&gt;

&lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;error_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ignore&lt;/span&gt;
    &lt;span class="na"&gt;log_statements&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;statements&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;set(log.time, Now())&lt;/span&gt; 
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;set(log.observed_time, Now())&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;set(log.attributes["tool"], "semgrep")&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;set(log.attributes["results_found"], Len(log.attributes["results"]))&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;set(log.attributes["semgrep_version"], log.attributes["version"])&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;delete_key(log.attributes, "version")&lt;/span&gt;

&lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;verbosity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;detailed&lt;/span&gt;

&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;filelog&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;transform&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;debug&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Save Money by shrinking output on no violations
&lt;/h2&gt;

&lt;p&gt;The output can be a bit wordy even when there are no violations. We can shrink this using the transform processor with an additional rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;set(log.body, "Semgrep scan finished. No issues found.") where Len(log.attributes["results"]) == &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create metrics from log content
&lt;/h2&gt;

&lt;p&gt;Notice there are lots of metric fields in the JSON so use the &lt;code&gt;signal_to_metrics&lt;/code&gt; connector to transform log content to real OpenTelemetry metrics.&lt;/p&gt;

&lt;p&gt;Add this content to the collector YAML (&lt;code&gt;connectors&lt;/code&gt; should be at the same level as &lt;code&gt;receivers&lt;/code&gt; and &lt;code&gt;processors&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then add the &lt;code&gt;signal_to_metrics&lt;/code&gt; as both an output of the &lt;code&gt;logs&lt;/code&gt; pipeline and an input to a &lt;code&gt;metrics&lt;/code&gt; pipeline (that you need to define).&lt;/p&gt;

&lt;p&gt;The idea here is that logs flow into the connector, are transformed to metrics and into the metrics pipeline they go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;connectors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;signal_to_metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;max_memory_bytes&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Extract the first number from the string&lt;/span&gt;
        &lt;span class="na"&gt;gauge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Double(log.attributes["time"]["max_memory_bytes"])&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;profiling_times.config_time&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Extract the first number from the string&lt;/span&gt;
        &lt;span class="na"&gt;gauge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Double(log.attributes["time"]["profiling_times"]["config_time"])&lt;/span&gt;

&lt;span class="nn"&gt;...&lt;/span&gt;

&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;filelog&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;transform&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;debug&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;signal_to_metrics&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;signal_to_metrics&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;debug&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Semgrep is a great security and validation tool and the results are really easy to process using the OpenTelemetry collector.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/@agardnerit?sub_confirmation=1" rel="noopener noreferrer"&gt;Subscribe to me on YouTube for more Observability and OpenTelemetry content&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>observability</category>
      <category>opentelemetry</category>
      <category>devops</category>
    </item>
    <item>
      <title>Trasform your logs to metrics (and do maths with metrics) using the OpenTelemetry Collector</title>
      <dc:creator>Adam Gardner</dc:creator>
      <pubDate>Sat, 07 Mar 2026 03:45:15 +0000</pubDate>
      <link>https://dev.to/agardnerit/trasform-your-logs-to-metrics-using-the-opentelemetry-collector-1jic</link>
      <guid>https://dev.to/agardnerit/trasform-your-logs-to-metrics-using-the-opentelemetry-collector-1jic</guid>
      <description>&lt;p&gt;Logs are everywhere... But they're expensive to store, retrieve and process. And sometimes you only need metrics.&lt;/p&gt;

&lt;p&gt;Did you know the OpenTelemetry is perfectly suited to be the place that you transform your logs to metrics? Log in, metrics out. Plus, did you know you can do maths in the collector?&lt;/p&gt;

&lt;p&gt;In this video I show you how to do it using the &lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/transformprocessor" rel="noopener noreferrer"&gt;transform processor&lt;/a&gt; to extract the values from the log then the &lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/signaltometricsconnector" rel="noopener noreferrer"&gt;signal to metrics connector&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;First I use something structured and easy: JSON.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ "foo": 1234, "bar": "5432", "baz": "Here is the number: 57 status = complete" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next I extract and generate metrics from an unstructured textual log line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Purchase complete. Product price=6. Quantity=3. Product ID=987. user_id=123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally I take the product price and quantity and multiply them (in realtime) to produce a third metric (&lt;code&gt;order total&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/FAaSz46UD7M"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>tutorial</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>Use LD_PRELOAD to inject OpenTelemetry into everything</title>
      <dc:creator>Adam Gardner</dc:creator>
      <pubDate>Wed, 04 Mar 2026 09:15:42 +0000</pubDate>
      <link>https://dev.to/agardnerit/use-ldpreload-to-inject-opentelemetry-into-everything-hmp</link>
      <guid>https://dev.to/agardnerit/use-ldpreload-to-inject-opentelemetry-into-everything-hmp</guid>
      <description>&lt;p&gt;In this video I demonstrate the new &lt;a href="https://github.com/open-telemetry/opentelemetry-injector" rel="noopener noreferrer"&gt;OpenTelemetry injector&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's a mechanism to automatically inject OTEL into your code with zero code or startup script changes. It's potentially a great way to gain Observability of non Kubernetes workloads like VMs. It leverages &lt;code&gt;LD_PRELOAD&lt;/code&gt;. You add the &lt;code&gt;LD_PRELOAD&lt;/code&gt; instruction to your VM at startup and the rest happens automatically.&lt;/p&gt;

&lt;p&gt;Do heed the warnings towards the end of the video though since it's still early days for this tool!&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/AFHbhcciASQ"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>tutorial</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>Why your AGENTS.md files are being ignored (and how to fix it)</title>
      <dc:creator>Adam Gardner</dc:creator>
      <pubDate>Wed, 04 Mar 2026 09:11:11 +0000</pubDate>
      <link>https://dev.to/agardnerit/why-your-agentsmd-files-are-being-ignored-and-how-to-fix-it-5672</link>
      <guid>https://dev.to/agardnerit/why-your-agentsmd-files-are-being-ignored-and-how-to-fix-it-5672</guid>
      <description>&lt;p&gt;So... Like (I assume) everyone, I was experimenting with &lt;code&gt;AGENTS.md&lt;/code&gt; the other day (I know, we're already bored of that and on to skills), but I suddenly realised that I had no idea whether or not the content was actually being used / obeyed.&lt;/p&gt;

&lt;p&gt;Turns out it wasn't most of the time!&lt;/p&gt;

&lt;p&gt;In this video I discuss my own experience, research of 5 leading models + the other industry research in this area.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/4rLJmg9-zow"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
    </item>
    <item>
      <title>Kubernetes Dashboard is Dead!</title>
      <dc:creator>Adam Gardner</dc:creator>
      <pubDate>Sun, 22 Feb 2026 09:13:58 +0000</pubDate>
      <link>https://dev.to/agardnerit/kubernetes-dashboard-is-dead-2a4d</link>
      <guid>https://dev.to/agardnerit/kubernetes-dashboard-is-dead-2a4d</guid>
      <description>&lt;p&gt;The &lt;a href="https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/" rel="noopener noreferrer"&gt;Kubernetes dashboard&lt;/a&gt; is now deprecated and unmaintained. It's time to move on.&lt;/p&gt;

&lt;p&gt;In this video I cover the replacement - a project I've been meaning to cover for a while now since it's so good. The CNCF project &lt;a href="https://headlamp.dev" rel="noopener noreferrer"&gt;Headlamp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/H4jslVL9oFA"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

</description>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Introducing "badtrace". Generate "bad" OpenTelemetry traces easily</title>
      <dc:creator>Adam Gardner</dc:creator>
      <pubDate>Sat, 22 Nov 2025 08:05:50 +0000</pubDate>
      <link>https://dev.to/agardnerit/introducing-badtrace-generate-bad-opentelemetry-traces-easily-2712</link>
      <guid>https://dev.to/agardnerit/introducing-badtrace-generate-bad-opentelemetry-traces-easily-2712</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxhauyyg0r1vb4lrfttb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxhauyyg0r1vb4lrfttb.png" alt="badtrace OpenTelemetry trace generator" width="800" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TLDR: &lt;a href="https://github.com/agardnerIT/badtrace" rel="noopener noreferrer"&gt;badtrace on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I needed a tool that would deliberately generate OpenTelemetry traces that were "bad". I needed this for training, enablement and demo purposes so that I could easily fire a trace with a known issue into my Observability system and thus show "why it was bad" and "how it looks".&lt;/p&gt;

&lt;p&gt;Yes, tools like &lt;a href="https://agardnerit.github.io/tracepusher" rel="noopener noreferrer"&gt;tracepusher&lt;/a&gt; and &lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/cmd/telemetrygen/README.md" rel="noopener noreferrer"&gt;telemetrygen&lt;/a&gt; already exist, but they typically:&lt;/p&gt;

&lt;p&gt;a) Generate "good" or "healthy" traces&lt;br&gt;
b) Generate single traces&lt;/p&gt;

&lt;p&gt;Hence, "badtrace" was born.&lt;/p&gt;

&lt;p&gt;It works based on "scenarios" (you can implement your own in a few lines of Python).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; \
  &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4318&lt;/span&gt; \
  &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;badtrace&lt;/span&gt; \ &lt;span class="c1"&gt;# optional: defaults to "badtrace"
&lt;/span&gt;  &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; \ &lt;span class="c1"&gt;# optional: defaults to 1
&lt;/span&gt;  &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;insecure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt; \
  &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;scenario&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;scenario1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scenarios
&lt;/h2&gt;

&lt;p&gt;As I write this, there are currently 4 scenarios (please come and help implement more) which can be toggled by changing &lt;code&gt;scenario1&lt;/code&gt; above to &lt;code&gt;scenario2&lt;/code&gt;, &lt;code&gt;scenario3&lt;/code&gt; or &lt;code&gt;scenario4&lt;/code&gt; etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 1&lt;/strong&gt; creates a short, small trace. This is quick (in end-to-end time) and short in terms of number of spans. In reality this would be a very low value trace (and thus a candidate to filter or heavily sample).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 2&lt;/strong&gt; creates a trace where some of the spans have errored. This models a trace where some of the operations error (imagine calling third party services and some of them fail).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 3&lt;/strong&gt; creates a "chatty" client to server trace with the client repeatedly calling the same endpoint over and over. This models things like the N+1 query problem where (for example) multiple SELECT statements occur to a database (instead of batching up the SELECTS into fewer statements).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 4&lt;/strong&gt; is similar to 3. It creates a "chatty" client but this time, the connections occur to many different servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check out badtrace
&lt;/h2&gt;

&lt;p&gt;If this sounds useful, go check out &lt;a href="https://github.com/agardnerit/badtrace" rel="noopener noreferrer"&gt;badtrace on GitHub&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>testing</category>
      <category>monitoring</category>
      <category>tooling</category>
      <category>showdev</category>
    </item>
    <item>
      <title>osquery + OpenTelemetry = ❤️</title>
      <dc:creator>Adam Gardner</dc:creator>
      <pubDate>Sun, 16 Nov 2025 07:35:15 +0000</pubDate>
      <link>https://dev.to/agardnerit/osquery-opentelemetry--55c</link>
      <guid>https://dev.to/agardnerit/osquery-opentelemetry--55c</guid>
      <description>&lt;p&gt;As you probably know by now, osquery effectively turns your endpoints into SQL endpoints that you can query: &lt;code&gt;SELECT * FROM processes&lt;/code&gt; or &lt;code&gt;SELECT * FROM users&lt;/code&gt; etc.&lt;/p&gt;

&lt;p&gt;But, that data is much more useful if it's tied to other telemetry data coming from your VMs, endpoints or Kubernetes clusters. This is typically the domain of APM tools.&lt;/p&gt;

&lt;p&gt;Using OpenTelemetry (and specifically the OpenTelemetry collector) we can bring those two worlds together.&lt;/p&gt;

&lt;p&gt;In this video I show you how that's done.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/5c-S4e2YzPU"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

</description>
      <category>devsecops</category>
      <category>tools</category>
      <category>soc</category>
    </item>
    <item>
      <title>Build a Kubernetes Cluster map in real time using DNS</title>
      <dc:creator>Adam Gardner</dc:creator>
      <pubDate>Sun, 16 Nov 2025 07:03:33 +0000</pubDate>
      <link>https://dev.to/agardnerit/build-a-kubernetes-cluster-map-in-real-time-using-dns-nf9</link>
      <guid>https://dev.to/agardnerit/build-a-kubernetes-cluster-map-in-real-time-using-dns-nf9</guid>
      <description>&lt;p&gt;DNS is the way things find other things, but did you know that you can use that fact to build a real time service to service communication / traffic flow on a Kubernetes cluster? That's what the smart folks over at Otterize have done and in this video, I put their network mapper tool on the OpenTelemetry demo to see how well it performs (spoiler: really well!)&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/6tEUzMFhB-Y"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>dns</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Map a Kubernetes cluster with one command</title>
      <dc:creator>Adam Gardner</dc:creator>
      <pubDate>Mon, 10 Nov 2025 23:27:01 +0000</pubDate>
      <link>https://dev.to/agardnerit/map-a-kubernetes-cluster-with-one-command-29g4</link>
      <guid>https://dev.to/agardnerit/map-a-kubernetes-cluster-with-one-command-29g4</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw7rkgjpdyr27md7n6s4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw7rkgjpdyr27md7n6s4.png" alt="Kubernetes network map"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DNS is the way things discover other things on the internet, but did you know there's a cool little network mapping tool that will build a realtime map of your Kubernetes cluster using DNS?&lt;/p&gt;

&lt;p&gt;It's called the network mapper from Otterize and in the video below I run it through its paces - then unleash it on the OpenTelemetry demo to see how it copes (spoiler: well!)&lt;/p&gt;

&lt;p&gt;Here's the code for the little visualisation app I created too: &lt;code&gt;https://github.com/agardnerIT/network-mapper-visualizer&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/6tEUzMFhB-Y"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>dns</category>
      <category>tutorial</category>
      <category>sre</category>
    </item>
    <item>
      <title>Monitor Gemini CLI using OpenTelemetry for realtime usage statistics</title>
      <dc:creator>Adam Gardner</dc:creator>
      <pubDate>Mon, 03 Nov 2025 11:50:05 +0000</pubDate>
      <link>https://dev.to/agardnerit/monitor-gemini-cli-using-opentelemetry-for-realtime-usage-statistics-3li5</link>
      <guid>https://dev.to/agardnerit/monitor-gemini-cli-using-opentelemetry-for-realtime-usage-statistics-3li5</guid>
      <description>&lt;p&gt;In this video, I'll show you how easy it is to monitor your Google Gemini CLI usage in realtime so that you don't overspend.&lt;/p&gt;

&lt;p&gt;Plus, you can show your boss how long you've wasted sitting waiting for AI to answer you!&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/00je5u_N1j8"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gemini</category>
      <category>monitoring</category>
      <category>cli</category>
    </item>
    <item>
      <title>Monitor Gemini CLI using OpenTelemetry for realtime usage statistics</title>
      <dc:creator>Adam Gardner</dc:creator>
      <pubDate>Mon, 03 Nov 2025 10:47:54 +0000</pubDate>
      <link>https://dev.to/agardnerit/monitor-gemini-cli-using-opentelemetry-for-realtime-usage-statistics-3adi</link>
      <guid>https://dev.to/agardnerit/monitor-gemini-cli-using-opentelemetry-for-realtime-usage-statistics-3adi</guid>
      <description>&lt;p&gt;In this video, I'll show you how easy it is to monitor your Google Gemini CLI usage in realtime so that you don't overspend.&lt;/p&gt;

&lt;p&gt;Plus, you can show your boss how long you've wasted sitting waiting for AI to answer you!&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/00je5u_N1j8"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

</description>
      <category>gemini</category>
      <category>cli</category>
      <category>opentelemetry</category>
      <category>monitoring</category>
    </item>
  </channel>
</rss>
