<?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.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>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>
    <item>
      <title>How Distributed Tracing Really Works</title>
      <dc:creator>Adam Gardner</dc:creator>
      <pubDate>Tue, 21 Oct 2025 20:31:32 +0000</pubDate>
      <link>https://dev.to/agardnerit/how-distributed-tracing-really-works-15b8</link>
      <guid>https://dev.to/agardnerit/how-distributed-tracing-really-works-15b8</guid>
      <description>&lt;p&gt;It's relatively simple these days to spin up an OpenTelemetry demo and / or auto-instrument services with OpenTelemetry.&lt;/p&gt;

&lt;p&gt;That's fine - when it works - but it's when something breaks that perhaps you realise you don't &lt;em&gt;quite&lt;/em&gt; understand what's going on under the covers. I've lost count of the number of times customers have reported "broken traces" to me as they flow through an uninstrumented tier, a load balancer or a queue.&lt;/p&gt;

&lt;p&gt;In the following video I show precisely how distributed tracing using OpenTelemetry is able to do what it does (for HTTP) by spinning up two &lt;strong&gt;Python&lt;/strong&gt; &lt;strong&gt;FastAPI&lt;/strong&gt; applications, instrumenting them using OpenTelemetry and demonstrating that the traces are "broken" in &lt;strong&gt;Jaeger&lt;/strong&gt;. Then I solve the issue (hint: it's all about propagation).&lt;/p&gt;

&lt;p&gt;The code for all of this can be found here: &lt;a href="https://github.com/agardnerIT/python-fastapi-requests-traceparent" rel="noopener noreferrer"&gt;https://github.com/agardnerIT/python-fastapi-requests-traceparent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;

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


&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>monitoring</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
  </channel>
</rss>
