<?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>Forem: Y.Matsuda</title>
    <description>The latest articles on Forem by Y.Matsuda (@ymtdzzz).</description>
    <link>https://forem.com/ymtdzzz</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%2F865632%2F408724f9-2177-494c-90f8-123f6f8a3353.jpeg</url>
      <title>Forem: Y.Matsuda</title>
      <link>https://forem.com/ymtdzzz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ymtdzzz"/>
    <language>en</language>
    <item>
      <title>Introducing lazypkg – A Cross-Package Manager Tool for Effortless Updates</title>
      <dc:creator>Y.Matsuda</dc:creator>
      <pubDate>Sun, 23 Mar 2025 01:49:50 +0000</pubDate>
      <link>https://forem.com/ymtdzzz/introducing-lazypkg-a-cross-package-manager-tool-for-effortless-updates-38b2</link>
      <guid>https://forem.com/ymtdzzz/introducing-lazypkg-a-cross-package-manager-tool-for-effortless-updates-38b2</guid>
      <description>&lt;p&gt;How do you manage packages installed via different package managers?&lt;br&gt;
In my case, I use macOS for work and Linux for personal development, so I manage most of my tools and configurations (like dotfiles) using &lt;strong&gt;Nix flakes + Home Manager&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That said, for tools I just want to quickly try out or tools specific to a single project, I often end up installing them via &lt;code&gt;brew&lt;/code&gt;, &lt;code&gt;npm install -g&lt;/code&gt;, or &lt;code&gt;apt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The problem is—once you’re juggling multiple package managers, it becomes hard to keep track of which packages are outdated. Plus, the command-line options vary across tools (was it &lt;code&gt;update&lt;/code&gt; or &lt;code&gt;upgrade&lt;/code&gt; ...?).&lt;/p&gt;
&lt;h2&gt;
  
  
  So I Built a TUI App Called &lt;code&gt;lazypkg&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To solve this, I built &lt;strong&gt;lazypkg&lt;/strong&gt;—a terminal-based tool that lets you view and update packages across multiple package managers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ymtdzzz/lazypkg" rel="noopener noreferrer"&gt;https://github.com/ymtdzzz/lazypkg&lt;/a&gt;&lt;/p&gt;

&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%2Flo30j4dw0m7mtvu68hj4.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%2Flo30j4dw0m7mtvu68hj4.png" alt="screen of lazypkg" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll dive into usage later, but here’s a quick overview of what it can do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;List upgradable packages:

&lt;ul&gt;
&lt;li&gt;Package name&lt;/li&gt;
&lt;li&gt;Current version&lt;/li&gt;
&lt;li&gt;Latest version&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Update individual packages&lt;/li&gt;
&lt;li&gt;Update all packages at once&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As of March 22, 2025, the following package managers are supported (with a modular design to allow easy additions):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;apt&lt;/li&gt;
&lt;li&gt;gem&lt;/li&gt;
&lt;li&gt;homebrew&lt;/li&gt;
&lt;li&gt;npm&lt;/li&gt;
&lt;li&gt;docker&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Docker support is disabled by default to avoid hitting Docker Hub API rate limits.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;You can install &lt;code&gt;lazypkg&lt;/code&gt; via Homebrew or &lt;code&gt;go install&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;ymtdzzz/tap/lazypkg

&lt;span class="c"&gt;# go install&lt;/span&gt;
go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/ymtdzzz/lazypkg@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check if it’s installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lazypkg &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="c"&gt;# -&amp;gt; lazypkg version 0.0.7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Launch &amp;amp; View Upgradable Packages
&lt;/h3&gt;

&lt;p&gt;Running &lt;code&gt;lazypkg&lt;/code&gt; automatically detects supported package managers, populates the sidebar, and fetches package updates.&lt;/p&gt;

&lt;p&gt;If a package manager (like &lt;code&gt;apt&lt;/code&gt;) requires root, a password prompt will appear.&lt;/p&gt;

&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%2F4jiur4a77lsjg08aok7i.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%2F4jiur4a77lsjg08aok7i.png" alt="password input dialog in lazypkg" width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I updated everything before writing this post, so screenshots below use demo data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Initially, focus is on the sidebar:&lt;/p&gt;

&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%2Fsjg6cvznv8uo6u827m68.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%2Fsjg6cvznv8uo6u827m68.png" alt="initial screen of lazypkg" width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are the basic keybindings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;↑↓&lt;/code&gt; or &lt;code&gt;j/k&lt;/code&gt;: Move focus up/down in the sidebar&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Enter&lt;/code&gt; or &lt;code&gt;→&lt;/code&gt; or &lt;code&gt;l&lt;/code&gt;: Switch focus to the package list pane&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Space&lt;/code&gt;: Add manager to bulk update targets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;r&lt;/code&gt;: Refresh updates for the selected manager&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;u&lt;/code&gt;:

&lt;ul&gt;
&lt;li&gt;With no bulk target: Update all packages under current manager&lt;/li&gt;
&lt;li&gt;With bulk target(s): Update all selected managers’ packages&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;These operations mostly work the same in the package list pane as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Cases
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Updating a Specific Package
&lt;/h4&gt;

&lt;p&gt;Say you want to update only &lt;code&gt;terraform&lt;/code&gt; under &lt;code&gt;homebrew&lt;/code&gt;.&lt;br&gt;
Focus on &lt;code&gt;homebrew&lt;/code&gt; in the sidebar, then press &lt;code&gt;Enter&lt;/code&gt; or &lt;code&gt;→&lt;/code&gt; or &lt;code&gt;l&lt;/code&gt; to move to the package list.&lt;/p&gt;

&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%2Fs20348sto37u7i69ak2r.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%2Fs20348sto37u7i69ak2r.png" alt="focus on the package list pane" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Focus on &lt;code&gt;terraform&lt;/code&gt;, press &lt;code&gt;u&lt;/code&gt;, and a confirmation dialog appears.&lt;/p&gt;

&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%2Ferja52awv2gtd9fej0kn.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%2Ferja52awv2gtd9fej0kn.png" alt="focus on terraform package" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Press &lt;code&gt;Enter&lt;/code&gt; to execute. Logs appear in real-time at the bottom (&lt;code&gt;ctrl+j&lt;/code&gt; / &lt;code&gt;ctrl+k&lt;/code&gt; to scroll).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The demo just fakes logs—actual command would be &lt;code&gt;brew upgrade terraform&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&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%2F40qwvcks3mr0f6ew9clq.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%2F40qwvcks3mr0f6ew9clq.png" alt="updating screen" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once done, the package list refreshes and removes &lt;code&gt;terraform&lt;/code&gt; from the upgrade list.&lt;/p&gt;
&lt;h4&gt;
  
  
  Updating Multiple Packages
&lt;/h4&gt;

&lt;p&gt;You can select multiple packages with &lt;code&gt;Space&lt;/code&gt;. Let’s try selecting &lt;code&gt;ffmpeg&lt;/code&gt;, &lt;code&gt;terraform&lt;/code&gt;, and &lt;code&gt;wget&lt;/code&gt;.&lt;/p&gt;

&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%2Fb4hprixvvh2dew2k02uj.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%2Fb4hprixvvh2dew2k02uj.png" alt="multiple package selection" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then press &lt;code&gt;u&lt;/code&gt;, confirm, and they’ll all be updated in one go.&lt;/p&gt;
&lt;h4&gt;
  
  
  Update All Packages under a Package Manager
&lt;/h4&gt;

&lt;p&gt;To update all packages managed by, say, &lt;code&gt;apt&lt;/code&gt;:&lt;br&gt;
Return focus to the sidebar with &lt;code&gt;Backspace&lt;/code&gt;, &lt;code&gt;←&lt;/code&gt;, or &lt;code&gt;h&lt;/code&gt;, focus on apt, and press &lt;code&gt;u&lt;/code&gt;. That’s it.&lt;/p&gt;

&lt;p&gt;Alternatively, pressing &lt;code&gt;a&lt;/code&gt; in the package list achieves the same thing.&lt;/p&gt;



&lt;p&gt;That’s the gist of using &lt;code&gt;lazypkg&lt;/code&gt;. Next up, the motivation and implementation details.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Build This When Tools Like &lt;code&gt;topgrade&lt;/code&gt; Exist?
&lt;/h2&gt;

&lt;p&gt;There are already some tools which have the same concept like &lt;code&gt;topgrade&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/topgrade-rs/topgrade" rel="noopener noreferrer"&gt;https://github.com/topgrade-rs/topgrade&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s a fantastic tool and I used it for a while myself. But &lt;code&gt;topgrade&lt;/code&gt; doesn’t show you which packages can be updated or what the new versions will be before updating—it just updates everything.&lt;/p&gt;

&lt;p&gt;While that simplicity is great, I wanted a tool that takes a slightly different approach.&lt;/p&gt;

&lt;p&gt;When it comes to globally installed packages, despite the interface differences, our needs are often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;View installed packages&lt;/li&gt;
&lt;li&gt;Check for updates&lt;/li&gt;
&lt;li&gt;Update selected packages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And frankly, I don’t care which manager is handling it—as long as something like &lt;code&gt;lazypkg&lt;/code&gt; abstracts over the differences, the UX becomes so much smoother.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I decided to skip the “list installed packages” feature, since just knowing what’s outdated is usually enough.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Implementation Notes
&lt;/h2&gt;

&lt;p&gt;Still a bit rough, but I want to highlight some design choices. The tool is written in Go, which I’m most comfortable with.&lt;/p&gt;
&lt;h3&gt;
  
  
  Chose &lt;code&gt;bubbletea&lt;/code&gt; for TUI Framework
&lt;/h3&gt;

&lt;p&gt;I’ve built other TUI tools, like &lt;a href="https://github.com/ymtdzzz/otel-tui" rel="noopener noreferrer"&gt;otel-tui&lt;/a&gt;, which uses &lt;code&gt;tview&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rivo/tview" rel="noopener noreferrer"&gt;https://github.com/rivo/tview&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;otel-tui&lt;/code&gt; needed a custom frame graph renderer, and &lt;code&gt;tview&lt;/code&gt; was perfect for that.&lt;br&gt;
But this time, I wanted an Elm-style architecture with clear state transitions, so I chose bubbletea.&lt;/p&gt;

&lt;p&gt;It wasn’t a heavily researched choice (it’s personal dev, after all), but I figured the event-driven model would better suit async tasks like package updates.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;bubbletea&lt;/code&gt;, you modify state (Model) only via messages. For example, updating a package involves two messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// https://github.com/ymtdzzz/lazypkg/blob/85b8a4e01fb5c5a75797f530e8893e871759104b/components/messages.go#L27C1-L36C2&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;updatePackagesStartMsg&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;updatePackagesFinishMsg&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt;  &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On update finish, I hide the loading spinner and re-fetch updates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// https://github.com/ymtdzzz/lazypkg/blob/85b8a4e01fb5c5a75797f530e8893e871759104b/components/packages.go#L149-L157&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;updatePackagesFinishMsg&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg&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;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pkg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pkgToIdx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;cmds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPackagesCmd&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;Compared to tview, which offers a lot of flexibility and lets you structure state management your own way, bubbletea provides more built-in guidance with its Elm-style architecture—making it easier to follow a clear path, especially in personal projects.&lt;/p&gt;

&lt;p&gt;Still, as the app grows, centralizing all messages in one file won’t scale. That’s a future refactor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extensible Design
&lt;/h3&gt;

&lt;p&gt;Right now, only a few package managers are supported, but I’ve made it easy to extend.&lt;/p&gt;

&lt;p&gt;Each package manager is implemented as an &lt;code&gt;executor&lt;/code&gt; that fulfills this interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// https://github.com/ymtdzzz/lazypkg/blob/85b8a4e01fb5c5a75797f530e8893e871759104b/executors/executor.go#L18-L39&lt;/span&gt;
&lt;span class="c"&gt;// Executor defines the interface for package management operations&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Executor&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// GetPackages retrieves a list of available package updates.&lt;/span&gt;
    &lt;span class="c"&gt;// The password parameter is required for package managers that need elevated privileges.&lt;/span&gt;
    &lt;span class="n"&gt;GetPackages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;PackageInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Update performs an update operation on a single package.&lt;/span&gt;
    &lt;span class="c"&gt;// If dryRun is true, it will only simulate the update without making actual changes.&lt;/span&gt;
    &lt;span class="c"&gt;// The password parameter is required for package managers that need elevated privileges.&lt;/span&gt;
    &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dryRun&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;

    &lt;span class="c"&gt;// BulkUpdate performs update operations on multiple packages simultaneously.&lt;/span&gt;
    &lt;span class="c"&gt;// If dryRun is true, it will only simulate the updates without making actual changes.&lt;/span&gt;
    &lt;span class="c"&gt;// The password parameter is required for package managers that need elevated privileges.&lt;/span&gt;
    &lt;span class="n"&gt;BulkUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dryRun&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;

    &lt;span class="c"&gt;// Valid checks if the package manager is available and usable on the current system.&lt;/span&gt;
    &lt;span class="n"&gt;Valid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;

    &lt;span class="c"&gt;// Close performs any necessary cleanup operations when the executor is no longer needed.&lt;/span&gt;
    &lt;span class="n"&gt;Close&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;If you're curious, &lt;a href="https://github.com/ymtdzzz/lazypkg/blob/85b8a4e01fb5c5a75797f530e8893e871759104b/executors/homebrew.go#L20-L48" rel="noopener noreferrer"&gt;here’s the Homebrew executor implementation&lt;/a&gt;, where &lt;code&gt;brew outdated --verbose&lt;/code&gt; output is parsed via regex (yep, kind of hacky).&lt;/p&gt;

&lt;p&gt;As for the &lt;code&gt;password&lt;/code&gt; argument—I've built in a prompt that’s triggered automatically when elevated privileges are needed. The flow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Attempt command with empty password (expecting failure)&lt;/li&gt;
&lt;li&gt;Detect error output, show password dialog&lt;/li&gt;
&lt;li&gt;On user input, re-run command with provided password&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Passwords aren’t stored in structs or memory unnecessarily—they only exist within specific function scopes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;lazypkg&lt;/code&gt; is a pretty simple tool, but I use it every day and find it genuinely useful.&lt;br&gt;
There are tons of features I’d like to add—UI polish, support for more managers—but it’s already functional and I plan to keep maintaining it.&lt;/p&gt;

&lt;p&gt;If you face similar pain points with managing packages, I hope &lt;code&gt;lazypkg&lt;/code&gt; helps.&lt;/p&gt;

&lt;p&gt;Feedback is more than welcome!&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>go</category>
    </item>
    <item>
      <title>otel-tui: A TUI Tool for Viewing OpenTelemetry Traces</title>
      <dc:creator>Y.Matsuda</dc:creator>
      <pubDate>Sat, 29 Jun 2024 06:08:17 +0000</pubDate>
      <link>https://forem.com/ymtdzzz/otel-tui-a-tui-tool-for-viewing-opentelemetry-traces-2e7n</link>
      <guid>https://forem.com/ymtdzzz/otel-tui-a-tui-tool-for-viewing-opentelemetry-traces-2e7n</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;The OpenTelemetry ecosystem has grown significantly busier compared to a while ago. I've been contributing and writing blog posts about it for about two years now, and I'm hoping to participate in some synchronous events in the future.&lt;/p&gt;

&lt;p&gt;Now, when it comes to instrumenting applications or writing libraries for instrumentation, how do you usually verify the local operation?&lt;/p&gt;

&lt;p&gt;From my experience, many people run Jaeger or Grafana locally to check signals. Today, I’d like to introduce a new method: my custom tool "otel-tui," which allows you to view OpenTelemetry in your terminal!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ymtdzzz/otel-tui" rel="noopener noreferrer"&gt;https://github.com/ymtdzzz/otel-tui&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Motivation
&lt;/h1&gt;

&lt;h2&gt;
  
  
  The Need for a "Just Right" Tool for Local Development
&lt;/h2&gt;

&lt;p&gt;Based on my experience, here are some use cases for verifying signals locally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensuring traces are linked as intended

&lt;ul&gt;
&lt;li&gt;For example, verifying implementation at the level of context&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Checking that attributes are set as expected

&lt;ul&gt;
&lt;li&gt;Attributes specified by the vendor may not display correctly if missing&lt;/li&gt;
&lt;li&gt;Custom attributes may be specified&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Currently, there are several ways to verify signals (mainly traces) locally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run Jaeger (all-in-one with UI) or Grafana Tempo containers locally to send and view signals&lt;/li&gt;
&lt;li&gt;Use an exporter to output to standard output or files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, these methods have limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running Jaeger or Grafana Tempo containers locally is resource-intensive&lt;/li&gt;
&lt;li&gt;Production-grade features are often too much for local use (e.g., service maps)&lt;/li&gt;
&lt;li&gt;It’s challenging to view outputs in real-time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using an exporter to output to standard output or files also has limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low readability (e.g., grepping for Trace ID or parent Span ID to check trace linkage)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given these challenges, I wanted a tool that could offer the best of both worlds—one that wasn’t too heavy for local use and provided readable outputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  There're Similar Tools but...
&lt;/h2&gt;

&lt;p&gt;There’s also "&lt;a href="https://github.com/CtrlSpice/otel-desktop-viewer" rel="noopener noreferrer"&gt;otel-desktop-viewer&lt;/a&gt;," which focuses on viewing OpenTelemetry signals locally and is very user-friendly.&lt;/p&gt;

&lt;p&gt;However, with a significant volume of data over a long period, it can become sluggish in terms of query and screen rendering (depending on machine specs). This inspired me to create my own tool that could view data in real-time without opening a browser and maintain stable performance.&lt;/p&gt;

&lt;h1&gt;
  
  
  Features
&lt;/h1&gt;

&lt;p&gt;Here's what the screen looks like:&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media.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%2Fwe54oiitof1m9gbwyf6u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fwe54oiitof1m9gbwyf6u.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Ficyaw0uxj2l6jfmjedbp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Ficyaw0uxj2l6jfmjedbp.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media.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%2Fpa2o97fhielez5fbasyr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fpa2o97fhielez5fbasyr.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Traces&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;List view of service spans and search by service name&lt;/li&gt;
&lt;li&gt;Detailed attribute information&lt;/li&gt;
&lt;li&gt;Trace graph display (waterfall diagram)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Logs&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;List view and search&lt;/li&gt;
&lt;li&gt;Jump from logs to corresponding traces&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Performance&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Lightweight and real-time screen updates (asynchronous signal reception and rendering)&lt;/li&gt;
&lt;li&gt;No memory leaks even with a steady flow of data over a long period (data rotation)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  TODOs
&lt;/h2&gt;

&lt;p&gt;There are still many features missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flexible settings

&lt;ul&gt;
&lt;li&gt;Screen update frequency&lt;/li&gt;
&lt;li&gt;Buffer size&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Metric collection and display&lt;/li&gt;

&lt;li&gt;Stability

&lt;ul&gt;
&lt;li&gt;Occasional crashes after prolonged use&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Consistent keybindings&lt;/li&gt;

&lt;li&gt;UI improvements

&lt;ul&gt;
&lt;li&gt;Navigation (e.g., back and forth between traces and log details)&lt;/li&gt;
&lt;li&gt;Text wrapping, waterfall diagram&lt;/li&gt;
&lt;li&gt;Debug logs&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  How to Use
&lt;/h1&gt;

&lt;p&gt;To demonstrate usage, I’ll use signals from the "opentelemetry-demo."&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing otel-tui
&lt;/h2&gt;

&lt;p&gt;You can install it via Homebrew:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ brew install ymtdzzz/tap/otel-tui


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If Homebrew isn’t available, download the binary from the &lt;a href="https://github.com/ymtdzzz/otel-tui/releases" rel="noopener noreferrer"&gt;release page&lt;/a&gt; or build from source.&lt;/p&gt;

&lt;p&gt;Verify installation with:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;$ otel-tui -v&lt;br&gt;
otel-tui version 0.1.2&lt;/p&gt;

&lt;p&gt;$ otel-tui -h&lt;br&gt;
Usage:&lt;br&gt;
  otel-tui [flags]&lt;/p&gt;

&lt;p&gt;Flags:&lt;br&gt;
      --grpc int      The port number on which we listen for OTLP grpc payloads (default 4317)&lt;br&gt;
  -h, --help          help for otel-tui&lt;br&gt;
      --host string   The host where we expose all endpoints (OTLP receivers and browser) (default "0.0.0.0")&lt;br&gt;
      --http int      The port number on which we listen for OTLP http payloads (default 4318)&lt;br&gt;
  -v, --version       version for otel-tui&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Changing the Target of the Instrumented Application&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Clone the "opentelemetry-demo" repository and configure it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modify Collector Target
&lt;/h3&gt;

&lt;p&gt;The opentelemetry-demo consists of multiple microservices, all signals are aggregated in the OpenTelemetry Collector. Modify the collector's exporter target to point to otel-tui.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="c1"&gt;# src/otelcollector/otelcol-config-extras.yml&lt;/span&gt;&lt;br&gt;
&lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
  &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host.docker.internal:4317&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;traces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&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;spanmetrics&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&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;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Allow Container to Host Port Communication (if not using Docker Desktop)&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;Set up host.docker.internal to be resolvable from within the container.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="c1"&gt;# docker-compose.minimal.yml&lt;/span&gt;&lt;br&gt;
&lt;span class="c1"&gt;# OpenTelemetry Collector&lt;/span&gt;&lt;br&gt;
&lt;span class="na"&gt;otelcol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${COLLECTOR_CONTRIB_IMAGE}&lt;/span&gt;&lt;br&gt;
  &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel-col&lt;/span&gt;&lt;br&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;&lt;br&gt;
  &lt;span class="na"&gt;extra_hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host.docker.internal:host-gateway"&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Starting otel-tui and the Instrumented Application&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Start them in separate tabs.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;h1&gt;
  
  
  From any directory where otel-tui is in the PATH
&lt;/h1&gt;

&lt;p&gt;$ otel-tui&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;h1&gt;
  
  
  From the root of the opentelemetry-demo repository
&lt;/h1&gt;

&lt;p&gt;$ make start-minimal&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Exploring Data&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;After a short while, signals should start flowing into otel-tui.&lt;/p&gt;

&lt;p&gt;You can switch between traces and logs using the &lt;code&gt;Tab&lt;/code&gt; key. Navigate through panes using keys listed in the titles (e.g., &lt;code&gt;Details(d)&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;To filter the spans, use the search box (&lt;code&gt;/&lt;/code&gt; key) and search for something like "product."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fnjdxkcqbe2h566lm65x2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fnjdxkcqbe2h566lm65x2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Spans related to services with the prefix "product" are filtered. Details of the currently focused span are displayed in the Details pane. You can collapse the tree in the Details pane by pressing &lt;code&gt;Enter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Select a span in the Traces pane by pressing &lt;code&gt;Enter&lt;/code&gt; to view the trace details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fcrxgpln5fsvp11d879o7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fcrxgpln5fsvp11d879o7.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navigate the Trace Timeline using arrow keys. Details of the focused span appear in the Details pane. Related logs appear in the Logs pane.&lt;/p&gt;

&lt;p&gt;Press &lt;code&gt;Esc&lt;/code&gt; to return to the trace list, and &lt;code&gt;Tab&lt;/code&gt; to switch to the logs view.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fqysnh6amoqcv19fb0tfy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fqysnh6amoqcv19fb0tfy.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Search for a term and inspect the details. If a log has a linked trace (indicated by a link emoji 🔗), press &lt;code&gt;Enter&lt;/code&gt; to jump to the trace details.&lt;/p&gt;

&lt;p&gt;This tool helps verify if the instrumented application sends the intended information and if it’s correctly linked.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;While there are still many rough edges and missing features, I find it quite user-friendly and enjoyable to use.&lt;/p&gt;

&lt;p&gt;I’d appreciate any feedback via &lt;a href="https://github.com/ymtdzzz/otel-tui/issues" rel="noopener noreferrer"&gt;https://github.com/ymtdzzz/otel-tui/issues&lt;/a&gt; or &lt;a href="https://twitter.com/ymtdzzz" rel="noopener noreferrer"&gt;https://twitter.com/ymtdzzz&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>observability</category>
      <category>opentelemetry</category>
      <category>webdev</category>
    </item>
    <item>
      <title>[OpenTelemetry] Observability of Async Processes with Custom Propagator</title>
      <dc:creator>Y.Matsuda</dc:creator>
      <pubDate>Thu, 22 Dec 2022 23:57:21 +0000</pubDate>
      <link>https://forem.com/ymtdzzz/opentelemetry-observability-of-async-processes-with-custom-propagator-2043</link>
      <guid>https://forem.com/ymtdzzz/opentelemetry-observability-of-async-processes-with-custom-propagator-2043</guid>
      <description>&lt;p&gt;It’s the 23rd day of &lt;a href="https://adventar.org/calendars/8496" rel="noopener noreferrer"&gt;Makuake Advent Calendar 2022&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;As we know OpenTelemetry is an observability framework to generate and export telemetry data. As more companies adopt microservices and SLI/SLOs, we need it to answer new, never-before-seen (and complex) questions.&lt;/p&gt;

&lt;p&gt;In that context, observability of systems that communicate using asynchronous messaging is as important as  systems communicates using synchronous messagin like HTTP or gRPC.&lt;/p&gt;

&lt;p&gt;How about a notification system as an example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How long does it take for the end-user to receive the notification after the notification event is fired?&lt;/li&gt;
&lt;li&gt;What’s the error rate access the notification lifecycle?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To answer those questions, we may need expensive and unique structures.&lt;/p&gt;

&lt;p&gt;Instead, in this article, I’ll demonstrate an easy way to do that with OpenTelemetry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Case
&lt;/h2&gt;

&lt;p&gt;All source code is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ymtdzzz/batch-tracing-sample" rel="noopener noreferrer"&gt;https://github.com/ymtdzzz/batch-tracing-sample&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fvaflfeydc9jxd5s9jie3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fvaflfeydc9jxd5s9jie3.png" alt="example case"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Processing flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enqueue the notification content as a message into a queue (Rabbit MQ) in batch&lt;/li&gt;
&lt;li&gt;A worker (consumer) asynchronously receives the message and send a request to the notification server (/email or /push)&lt;/li&gt;
&lt;li&gt;In the notification server, it responses 200 or 500&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s assumed that the instrumentation of each component has been completed, and the HTTP communication has also been instrumented by &lt;a href="https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/net/http/otelhttp" rel="noopener noreferrer"&gt;net/http auto instrumentation library&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fqnfiz1rooed6t2jkygpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fqnfiz1rooed6t2jkygpa.png" alt="problem"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the current state, batch processing and subsequent processing (worker) cannot be traced.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F4zb840bt52j1fv3l1u94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F4zb840bt52j1fv3l1u94.png" alt="trace a"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fc3lh8rrf11lsn6lla528.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fc3lh8rrf11lsn6lla528.png" alt="trace b"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moreover, there doesn’t seem to be an instrumentation library for RabbitMQ in Golang.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/registry/?s=rabbitmq&amp;amp;component=&amp;amp;language=" rel="noopener noreferrer"&gt;https://opentelemetry.io/registry/?s=rabbitmq&amp;amp;component=&amp;amp;language=&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What Should We Do?
&lt;/h3&gt;

&lt;p&gt;To propagate context, we can use OpenTelemetry &lt;code&gt;Propagator&lt;/code&gt; for both sync and async types of messaging!&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement Custom Propagator for RabbitMQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What’s Propagator?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/docs/reference/specification/context/api-propagators/" rel="noopener noreferrer"&gt;https://opentelemetry.io/docs/reference/specification/context/api-propagators/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Propagator API is interface definitions for propagating contexts across process - how sender *Inject*s context into message and how receiver *Extract*s it from message. Propagator has Carrier which has a responsibility to actual injection and extraction from any type of messages.&lt;/p&gt;

&lt;p&gt;Fortunately, RabbitMQ allows to put &lt;code&gt;key-value&lt;/code&gt; format &lt;code&gt;Headers&lt;/code&gt; in messages (&lt;a href="https://www.rabbitmq.com/publishers.html#message-properties" rel="noopener noreferrer"&gt;docs&lt;/a&gt;), so we can use &lt;code&gt;TextMapPropagator&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fyy6p88ucojjyfdace1a3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fyy6p88ucojjyfdace1a3.png" alt="propagation flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Propagator Implementation
&lt;/h3&gt;

&lt;p&gt;Actually, since it is not Propagator but the Carrier that manipulates the &lt;code&gt;TextMap&lt;/code&gt;, all we have to do is implementing the struct that satisfies the &lt;code&gt;TextMapCarrier&lt;/code&gt; interface!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TextMapCarrier&lt;/code&gt; interface (&lt;a href="https://pkg.go.dev/go.opentelemetry.io/otel@v1.11.2/propagation#TextMapCarrier" rel="noopener noreferrer"&gt;doc&lt;/a&gt;):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TextMapCarrier&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c"&gt;// Get returns the value associated with the passed key.&lt;/span&gt;
    &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

    &lt;span class="c"&gt;// Set stores the key-value pair.&lt;/span&gt;
    &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Keys lists the keys stored in this carrier.&lt;/span&gt;
    &lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Carrier implementation for this interface (&lt;a href="https://github.com/ymtdzzz/batch-tracing-sample/blob/main/notification-manager/internal/carrier.go" rel="noopener noreferrer"&gt;source code&lt;/a&gt;):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;AMQPCarrier&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="n"&gt;amqp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Table&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;AMQPCarrier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;AMQPCarrier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;AMQPCarrier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;amqp.Table&lt;/code&gt; is just &lt;code&gt;map[string]interface{}&lt;/code&gt; . &lt;code&gt;Get()&lt;/code&gt; implementation is a little rough but it’s enough for example… ;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Sender Side Implementation
&lt;/h3&gt;

&lt;p&gt;At the sender side, we can inject context into header and just send the message (&lt;a href="https://github.com/ymtdzzz/batch-tracing-sample/blob/main/notification-manager/cmd/batch/main.go#L124-L142" rel="noopener noreferrer"&gt;source code&lt;/a&gt;).&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;span class="c"&amp;gt;// Create an empty amqp.Tables&amp;lt;/span&amp;gt;
&amp;lt;span class="n"&amp;gt;headers&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;:=&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;amqp&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;NewConnectionProperties&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;()&amp;lt;/span&amp;gt;   
&amp;lt;span class="c"&amp;gt;// Assign it to custom Carrier&amp;lt;/span&amp;gt;
&amp;lt;span class="n"&amp;gt;carrier&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;:=&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;internal&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;NewAMQPCarrier&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;headers&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;)&amp;lt;/span&amp;gt;
&amp;lt;span class="c"&amp;gt;// Inject the context&amp;lt;/span&amp;gt;
&amp;lt;span class="n"&amp;gt;otel&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;GetTextMapPropagator&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;()&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;Inject&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;ctx&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;carrier&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;)&amp;lt;/span&amp;gt;
&amp;lt;span class="n"&amp;gt;err&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;ch&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;PublishWithContext&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;
    &amp;lt;span class="n"&amp;gt;ctx&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt;
    &amp;lt;span class="s"&amp;gt;""&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt;
    &amp;lt;span class="n"&amp;gt;q&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;Name&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt;
    &amp;lt;span class="no"&amp;gt;false&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt;
    &amp;lt;span class="no"&amp;gt;false&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt;
    &amp;lt;span class="n"&amp;gt;amqp&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;Publishing&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;{&amp;lt;/span&amp;gt;
        &amp;lt;span class="n"&amp;gt;ContentType&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;:&amp;lt;/span&amp;gt; &amp;lt;span class="s"&amp;gt;"application/octet-stream"&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt;
        &amp;lt;span class="n"&amp;gt;Body&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;:&amp;lt;/span&amp;gt;        &amp;lt;span class="n"&amp;gt;msg&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt;
        &amp;lt;span class="n"&amp;gt;Headers&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;:&amp;lt;/span&amp;gt;     &amp;lt;span class="n"&amp;gt;headers&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt; &amp;lt;span class="c"&amp;gt;// Assign the context injected headers&amp;lt;/span&amp;gt;
    &amp;lt;span class="p"&amp;gt;},&amp;lt;/span&amp;gt;
&amp;lt;span class="p"&amp;gt;)&amp;lt;/span&amp;gt;
&amp;lt;span class="k"&amp;gt;if&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;err&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;!=&amp;lt;/span&amp;gt; &amp;lt;span class="no"&amp;gt;nil&amp;lt;/span&amp;gt; &amp;lt;span class="p"&amp;gt;{&amp;lt;/span&amp;gt;
    &amp;lt;span class="nb"&amp;gt;panic&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;err&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;)&amp;lt;/span&amp;gt;
&amp;lt;span class="p"&amp;gt;}&amp;lt;/span&amp;gt;
&amp;lt;span class="n"&amp;gt;log&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;Println&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="s"&amp;gt;"Message has been sent"&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;)&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Receiver Side Implementation&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;Receiver side is the same.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;span class="c"&amp;gt;// Assign the received headers to custom Carrier&amp;lt;/span&amp;gt;
    &amp;lt;span class="n"&amp;gt;carrier&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;:=&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;internal&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;NewAMQPCarrier&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;d&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;Headers&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;)&amp;lt;/span&amp;gt;
    &amp;lt;span class="c"&amp;gt;// Extract the context&amp;lt;/span&amp;gt;
    &amp;lt;span class="n"&amp;gt;ctx&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;:=&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;otel&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;GetTextMapPropagator&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;()&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;Extract&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;context&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;Background&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(),&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;carrier&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;)&amp;lt;/span&amp;gt;
    &amp;lt;span class="c"&amp;gt;// Generate child Span with received context as parent Span&amp;lt;/span&amp;gt;
    &amp;lt;span class="n"&amp;gt;ctx&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;span&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;:=&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;otel&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;Tracer&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="s"&amp;gt;"notification"&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;)&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;Start&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;ctx&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt; &amp;lt;span class="s"&amp;gt;"consume"&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;)&amp;lt;/span&amp;gt;

    &amp;lt;span class="n"&amp;gt;msg&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;err&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;:=&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;internal&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;DecodeNotificationMessage&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;d&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;Body&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;)&amp;lt;/span&amp;gt;
    &amp;lt;span class="k"&amp;gt;if&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;err&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;!=&amp;lt;/span&amp;gt; &amp;lt;span class="no"&amp;gt;nil&amp;lt;/span&amp;gt; &amp;lt;span class="p"&amp;gt;{&amp;lt;/span&amp;gt;
        &amp;lt;span class="nb"&amp;gt;panic&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;err&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;)&amp;lt;/span&amp;gt;
    &amp;lt;span class="p"&amp;gt;}&amp;lt;/span&amp;gt;
    &amp;lt;span class="n"&amp;gt;log&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;Printf&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="s"&amp;gt;"received msg: %v&amp;lt;/span&amp;gt;&amp;lt;span class="se"&amp;gt;\n&amp;lt;/span&amp;gt;&amp;lt;span class="s"&amp;gt;"&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;msg&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;)&amp;lt;/span&amp;gt;

    &amp;lt;span class="n"&amp;gt;internal&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;CallServer&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;(&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;ctx&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt; &amp;lt;span class="o"&amp;gt;&amp;amp;amp;&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;client&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;,&amp;lt;/span&amp;gt; &amp;lt;span class="n"&amp;gt;msg&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;)&amp;lt;/span&amp;gt;

    &amp;lt;span class="n"&amp;gt;span&amp;lt;/span&amp;gt;&amp;lt;span class="o"&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class="n"&amp;gt;End&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;()&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  We’re All Set 🎉&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Now, let’s start the apps and check the Jaeger UI endpoint (&lt;a href="http://localhost:16686/" rel="noopener noreferrer"&gt;http://localhost:16686/&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fa9ro4bu6cn4rf70la79m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fa9ro4bu6cn4rf70la79m.png" alt="final result a"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By connecting the traces, we can now investigate any errors throughout the notification lifecycle easily.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F4jz1ussjrxhfbmgvs26c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F4jz1ussjrxhfbmgvs26c.png" alt="final result b"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moreover, since the duration of the entire Trace is able to be measured, we can analyze bottlenecks of performance decreasing and notice slow notifications based on user experience.&lt;/p&gt;

&lt;p&gt;I hope you learned something new from this post, please let me know if you have any feedback in the comments or &lt;a href="https://twitter.com/ymtdzzz" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>go</category>
      <category>observability</category>
      <category>microservices</category>
    </item>
  </channel>
</rss>
