<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Posts on Prskavec.Net</title><link>https://www.prskavec.net/post/</link><description>Personal site of Ladislav Prskavec — Software Engineer &amp; Site Reliability Engineer in Prague. Writing, conference talks, an OnCall design guide, and the You Build It You Run It podcast.</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>ladislav@prskavec.net (Prskavec.Net)</managingEditor><webMaster>ladislav@prskavec.net (Prskavec.Net)</webMaster><copyright>&amp;copy; {year} Ladislav Prskavec</copyright><atom:link href="https://www.prskavec.net/post/index.xml" rel="self" type="application/rss+xml"/><item><title>A Knowledge Format Is Not a Knowledge Base</title><link>https://www.prskavec.net/post/a-knowledge-format-is-not-a-knowledge-base/</link><pubDate>Thu, 16 Jul 2026 09:00:00 +0200</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/a-knowledge-format-is-not-a-knowledge-base/</guid><description>Google's Open Knowledge Format standardizes the container, not the relationships and curation that make a knowledge base useful. Existing document formats already cover the authoring layer; a portable knowledge base needs a stronger, syntax-independent model.</description><content:encoded>&lt;p&gt;&lt;img src="https://www.prskavec.net/post/a-knowledge-format-is-not-a-knowledge-base/./featured.png"
alt=""
loading="lazy"
decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing"&gt;Google&amp;rsquo;s Open Knowledge Format&lt;/a&gt;
sounded relevant to me immediately. OKF v0.1 is a directory of Markdown files
with YAML frontmatter, stored in Git and readable by people and agents. That is
close to how I already &lt;a href="https://www.prskavec.net/post/how-i-use-logseq-as-a-personal-knowledge-base/"&gt;use Logseq, Codex, and Claude Code&lt;/a&gt;
with my own knowledge base.&lt;/p&gt;
&lt;p&gt;After reading the specification, I do not think I should adopt it. OKF could be
an export format one day, but it leaves out too much of what makes a knowledge
base useful.&lt;/p&gt;
&lt;p&gt;First, a correction: OKF is not another markup language. It is a small profile
on top of Markdown and YAML. The required surface is deliberately thin: every
concept is a file, its path is its identity, and its frontmatter must contain a
&lt;code&gt;type&lt;/code&gt;. Titles, descriptions, tags, resources, timestamps, indexes, logs, and
citations are optional or recommended.&lt;/p&gt;
&lt;p&gt;That simplicity is attractive. It also standardizes the least difficult part of
a knowledge system.&lt;/p&gt;
&lt;h2 id="backlinks-are-part-of-the-data-model"&gt;Backlinks are part of the data model&lt;/h2&gt;
&lt;p&gt;OKF defines a link from concept A to concept B as a directed, untyped edge. The
meaning, such as &amp;ldquo;depends on&amp;rdquo;, &amp;ldquo;references&amp;rdquo;, or &amp;ldquo;joins with&amp;rdquo;, stays in the
surrounding prose. A consumer may scan the bundle and compute the reverse edge,
but the format does not require it.&lt;/p&gt;
&lt;p&gt;This distinction matters. In Logseq, a backlink is not decoration in a graph
view. When I open B, I can see every page that mentions B, including connections
I did not remember to encode on B itself. The forward link records what the
author of A knew. The backlink shows how the rest of the corpus has accumulated
around B.&lt;/p&gt;
&lt;p&gt;Adding an explicit link from B back to A is not the same thing. It duplicates
the edge and may misstate the relationship: &amp;ldquo;A references B&amp;rdquo; does not imply &amp;ldquo;B
references A&amp;rdquo;. It also creates a synchronization problem when pages move or
relationships change.&lt;/p&gt;
&lt;p&gt;An OKF consumer can reconstruct backlinks. Google&amp;rsquo;s reference visualizer does
this and presents them as &amp;ldquo;Cited by&amp;rdquo;. But another consumer can ignore reverse
links and still conform to the specification. Both tools support the same format
while exposing materially different knowledge bases.&lt;/p&gt;
&lt;h2 id="conformance-does-not-imply-shared-meaning"&gt;Conformance does not imply shared meaning&lt;/h2&gt;
&lt;p&gt;The rest of the interoperability contract is similarly thin. &lt;code&gt;type&lt;/code&gt; is the only
required field, but type names are not registered. Consumers must tolerate
unknown values. Links are untyped. Citations are recommended rather than
required. Broken links are explicitly permitted.&lt;/p&gt;
&lt;p&gt;These are sensible choices for a forgiving file exchange format. They also mean
that two conformant bundles can agree on syntax while disagreeing on nearly all
of the semantics. One producer&amp;rsquo;s &lt;code&gt;Playbook&lt;/code&gt; may not resemble another&amp;rsquo;s. A
consumer can parse both files without knowing how to compare, validate, or use
them.&lt;/p&gt;
&lt;p&gt;That is portability of text. It is not yet portability of knowledge.&lt;/p&gt;
&lt;h2 id="we-already-have-document-formats"&gt;We already have document formats&lt;/h2&gt;
&lt;p&gt;If the problem is how to store rich, readable technical documents in plain text,
we already have mature options.
&lt;a href="https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html"&gt;reStructuredText&lt;/a&gt;
has field lists, citations, hyperlink targets, directives, and
&lt;a href="https://docutils.sourceforge.io/docs/ref/rst/roles.html"&gt;extensible interpreted-text roles&lt;/a&gt;.
AsciiDoc has &lt;a href="https://docs.asciidoctor.org/asciidoc/latest/attributes/document-attributes/"&gt;document attributes&lt;/a&gt;,
&lt;a href="https://docs.asciidoctor.org/asciidoc/latest/macros/inter-document-xref/"&gt;document-to-document cross-references&lt;/a&gt;,
and &lt;a href="https://docs.asciidoctor.org/asciidoc/latest/directives/include/"&gt;includes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Neither format gives every tool Logseq-style backlinks automatically. That is
the point: backlinks are a corpus-level index and navigation contract, not an
inline-markup feature. Putting YAML in front of Markdown does not solve that
layer either.&lt;/p&gt;
&lt;p&gt;Markdown plus YAML is a reasonable transport choice with modest parsing
requirements. But choosing a transport is different from defining a knowledge
architecture. If a team prefers AsciiDoc or reStructuredText, it should not need
to convert its source documents just to participate in a knowledge exchange. A
useful standard could define concepts, identifiers, relationships, provenance,
and lifecycle independently, then provide mappings for several text formats.&lt;/p&gt;
&lt;h2 id="markdown-is-the-least-interesting-part-of-my-knowledge-base"&gt;Markdown is the least interesting part of my knowledge base&lt;/h2&gt;
&lt;p&gt;My setup works because it is selective. Journals are a raw inbox. External
sources are retained locally when needed. Durable pages are synthesized rather
than copied. Claims carry source notes with searchable anchors. A curation log
records what was promoted, skipped, or left for later. Every page edit is
reviewed against its sources instead of trusted because the prose sounds
coherent.&lt;/p&gt;
&lt;p&gt;None of that follows from the file extension.&lt;/p&gt;
&lt;p&gt;A directory can be perfectly conformant with OKF and still be a bad knowledge
base: duplicated notes, stale claims, missing provenance, disconnected pages,
and generated summaries nobody has checked. An agent can parse the corpus, but
parsing is not the same as trusting or navigating it.&lt;/p&gt;
&lt;p&gt;For me, a stronger interchange model would need at least:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stable concept identifiers that survive file moves&lt;/li&gt;
&lt;li&gt;standardized backlink or reverse-edge behavior&lt;/li&gt;
&lt;li&gt;relationship types that consumers can understand without reading prose&lt;/li&gt;
&lt;li&gt;claim-level provenance, not only an optional citations section&lt;/li&gt;
&lt;li&gt;a way to distinguish raw capture, durable synthesis, and deprecated material&lt;/li&gt;
&lt;li&gt;mappings for existing systems and formats instead of one mandatory source syntax&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This does not all need to live inside every document. A manifest or generated
graph index may be better than duplicating metadata across files. The important
part is that the behavior belongs to the contract, so each consumer does not
invent it again.&lt;/p&gt;
&lt;h2 id="where-okf-could-still-fit"&gt;Where OKF could still fit&lt;/h2&gt;
&lt;p&gt;OKF may become useful as a lowest-common-denominator export. If an agent or
catalog I want to use accepts OKF, a Logseq plugin could map page properties to
YAML, convert wiki links to Markdown links, and generate a backlink index. My
knowledge base would remain the source of truth.&lt;/p&gt;
&lt;p&gt;I would not build that adapter before there is a consumer worth using. OKF is
still a v0.1 draft, and changing a working knowledge base to satisfy a thin
interchange profile would add maintenance without improving the knowledge.&lt;/p&gt;
&lt;p&gt;The problem is real: agents need portable context, and knowledge should not be
locked inside one vendor&amp;rsquo;s catalog. But a collection of readable files is only
the substrate. A great knowledge base also needs relationships, provenance,
selection, and maintenance. Another file profile does not provide those by
itself.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Google Cloud, &lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing"&gt;&lt;em&gt;Introducing the Open Knowledge Format&lt;/em&gt;&lt;/a&gt; (2026-06-12)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md"&gt;Open Knowledge Format v0.1 draft specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf"&gt;Open Knowledge Format reference implementation and visualizer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Docutils, &lt;a href="https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html"&gt;&lt;em&gt;reStructuredText Markup Specification&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Docutils, &lt;a href="https://docutils.sourceforge.io/docs/ref/rst/roles.html"&gt;&lt;em&gt;reStructuredText Interpreted Text Roles&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Asciidoctor, &lt;a href="https://docs.asciidoctor.org/asciidoc/latest/attributes/document-attributes/"&gt;&lt;em&gt;Document Attributes&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Asciidoctor, &lt;a href="https://docs.asciidoctor.org/asciidoc/latest/macros/inter-document-xref/"&gt;&lt;em&gt;Document to Document Cross References&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Asciidoctor, &lt;a href="https://docs.asciidoctor.org/asciidoc/latest/directives/include/"&gt;&lt;em&gt;Includes&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>How I Use Logseq as a Personal Knowledge Base</title><link>https://www.prskavec.net/post/how-i-use-logseq-as-a-personal-knowledge-base/</link><pubDate>Sat, 27 Jun 2026 09:00:00 +0200</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/how-i-use-logseq-as-a-personal-knowledge-base/</guid><description>How I use Logseq journals, local sources, durable pages, and agent-assisted curation to keep a personal knowledge base useful without turning every note into permanent documentation.</description><content:encoded>&lt;p&gt;This setup was inspired by Andrej Karpathy&amp;rsquo;s &lt;a href="https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f"&gt;LLM Wiki&lt;/a&gt;,
but my version is deliberately local and boring: Logseq journals as the inbox,
source files as the archive, and compact pages as the durable layer.&lt;/p&gt;
&lt;p&gt;Google is making a similar argument with &lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing"&gt;Open Knowledge Format&lt;/a&gt;:
&lt;a href="https://spec.commonmark.org/"&gt;Markdown&lt;/a&gt; plus &lt;a href="https://jekyllrb.com/docs/front-matter/"&gt;YAML front matter&lt;/a&gt;
as a shareable knowledge format. I think the format is the easy part.
&lt;a href="https://spec.commonmark.org/"&gt;Markdown&lt;/a&gt;, &lt;a href="https://docutils.sourceforge.io/rst.html"&gt;reStructuredText&lt;/a&gt;,
&lt;a href="https://docs.asciidoctor.org/asciidoc/latest/"&gt;AsciiDoc&lt;/a&gt;, or any plain text
format with metadata can work. For company-wide use, the harder part is the
documentation pipeline: how you ingest sources from different places, keep them
fresh, and assign curators, human or LLM, to maintain the useful layer.&lt;/p&gt;
&lt;p&gt;My Logseq graph is not where every thought becomes permanent. It is where raw
material waits until I decide whether it deserves a second life.&lt;/p&gt;
&lt;p&gt;The split in this repository is blunt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;journals/&lt;/code&gt; is the raw inbox.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sources/&lt;/code&gt; is where bulky external material lives.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pages/&lt;/code&gt; is the durable knowledge layer.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;log.md&lt;/code&gt; is the audit trail of curation work.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Day to day, that is not formal. I capture links, screenshots, meeting fragments,
commands, papers, talks, and half-formed notes into Logseq journals. Later, I run
a curation pass and promote only the material I expect to search for again.&lt;/p&gt;
&lt;h2 id="the-flow"&gt;The flow&lt;/h2&gt;
&lt;img src="https://www.prskavec.net/img/mermaid/post-how-i-use-logseq-as-a-personal-knowledge-base-1.png" alt="Diagram (rendered from Mermaid)"&gt;
&lt;h2 id="journals-are-the-inbox"&gt;Journals are the inbox&lt;/h2&gt;
&lt;p&gt;Daily journals are intentionally messy. They contain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;links I may or may not read later&lt;/li&gt;
&lt;li&gt;meeting scraps&lt;/li&gt;
&lt;li&gt;debugging notes&lt;/li&gt;
&lt;li&gt;command fragments&lt;/li&gt;
&lt;li&gt;screenshots&lt;/li&gt;
&lt;li&gt;TODOs&lt;/li&gt;
&lt;li&gt;short observations&lt;/li&gt;
&lt;li&gt;notes for work projects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I do not try to make journals beautiful. If I had to polish every note at
capture time, I would capture less.&lt;/p&gt;
&lt;p&gt;The rule is that journals are source material, not the knowledge base.
Most journal bullets should never be promoted. TODO-only items, meeting
logistics, unexplained fragments, and one-off status updates usually stay where
they are.&lt;/p&gt;
&lt;p&gt;What gets promoted is the material with reuse value:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;architecture decisions&lt;/li&gt;
&lt;li&gt;operational patterns&lt;/li&gt;
&lt;li&gt;incident/debugging lessons&lt;/li&gt;
&lt;li&gt;recurring themes&lt;/li&gt;
&lt;li&gt;commands with explanation&lt;/li&gt;
&lt;li&gt;glossary terms&lt;/li&gt;
&lt;li&gt;papers, talks, docs, and articles worth finding again&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="external-sources-need-a-local-copy"&gt;External sources need a local copy&lt;/h2&gt;
&lt;p&gt;Some source material is too large, too valuable, or too likely to disappear
behind a login wall. The graph has a &lt;code&gt;sources/&lt;/code&gt; directory for that.&lt;/p&gt;
&lt;p&gt;For conference talks, I fetch subtitles with &lt;code&gt;yt-dlp&lt;/code&gt; and store &lt;code&gt;.vtt&lt;/code&gt; files under
&lt;code&gt;sources/transcripts/&amp;lt;conference&amp;gt;-&amp;lt;year&amp;gt;/&lt;/code&gt;. Each talk gets a small metadata file
with the URL, title, speakers, runtime, transcript path, and the page that
summarizes it.&lt;/p&gt;
&lt;p&gt;The summary goes into &lt;code&gt;pages/&lt;/code&gt;. The transcript stays in &lt;code&gt;sources/&lt;/code&gt; so future
search can still find an exact phrase, command, or speaker quote that did not
make the summary.&lt;/p&gt;
&lt;p&gt;X.com needs a different path because direct fetching is unreliable from this
workspace. Long posts and threads are exported locally as Markdown into
&lt;code&gt;sources/x-com-posts/&lt;/code&gt;. The stable join key is the numeric status ID. When a
journal has an X link, I grep the exports for that ID and cite the local file.&lt;/p&gt;
&lt;p&gt;Attachments such as screenshots, PDFs, CSVs, and images live in &lt;code&gt;assets/&lt;/code&gt;, using
Logseq&amp;rsquo;s normal local asset links.&lt;/p&gt;
&lt;p&gt;The result is boring in a useful way: a broken website or login wall does not
erase the source trail.&lt;/p&gt;
&lt;h2 id="pages-are-the-knowledge-layer"&gt;Pages are the knowledge layer&lt;/h2&gt;
&lt;p&gt;The durable pages are compact Markdown notes. They are not dumps from journals.
They answer the questions I expect a future reader, including a future AI agent,
to ask:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What is this?&lt;/li&gt;
&lt;li&gt;Why does it matter?&lt;/li&gt;
&lt;li&gt;How does it work?&lt;/li&gt;
&lt;li&gt;What are the gotchas?&lt;/li&gt;
&lt;li&gt;Where did this come from?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The common shape is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;## Summary&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;## Key Ideas&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;## Operational Notes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;## Gotchas&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;## Links&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;## Source Notes&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not every page needs every heading. The format is only there to force the useful
work: make the page searchable, connect it to related pages, and show where the
material came from.&lt;/p&gt;
&lt;p&gt;A recent addition to that shape is claim-level footnotes. &lt;code&gt;## Links&lt;/code&gt; is a fine
roster of further reading, and &lt;code&gt;## Source Notes&lt;/code&gt; records which journal a page
grew from, but neither tells a reader which specific source backs a specific
sentence. When a page states a number, a benchmark result, or a direct
attribution, I now tie it to its source with a footnote at the point of the
claim, so the reader lands on the primary source instead of guessing which of
ten links in the list is the right one. Logseq renders standard Markdown
footnotes: &lt;code&gt;[^1]&lt;/code&gt; next to the claim, and &lt;code&gt;[^1]: Title, date, URL&lt;/code&gt; collected at
the bottom of the page. The &lt;code&gt;curate-journals&lt;/code&gt; skill treats this as a rule now:
any paper, post, or video a page relies on has to be reachable from the page
itself, because the journal it came from usually holds only a bare link.&lt;/p&gt;
&lt;p&gt;Logseq links are the connective tissue. A page about local models can point to
&lt;code&gt;[[Claude Code]]&lt;/code&gt;, &lt;code&gt;[[AI/Harness engineering]]&lt;/code&gt;, or &lt;code&gt;[[Observability/LLM observability]]&lt;/code&gt;
without needing a separate taxonomy project. If a page name contains a slash,
the file uses Logseq&amp;rsquo;s triple-lowbar encoding, so &lt;code&gt;AI/Local models and RAG&lt;/code&gt;
lives as &lt;code&gt;pages/AI___Local models and RAG.md&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="the-curation-pass"&gt;The curation pass&lt;/h2&gt;
&lt;p&gt;The curation workflow is deliberately mechanical:&lt;/p&gt;
&lt;p&gt;The repo has a Claude skill for this: &lt;code&gt;.claude/skills/curate-journals/SKILL.md&lt;/code&gt;.
&lt;code&gt;AGENTS.md&lt;/code&gt; says what the graph is and what good curation looks like; the skill
turns that into the repeatable pass.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find unprocessed journal changes from the last &lt;code&gt;Curation hash:&lt;/code&gt; in &lt;code&gt;log.md&lt;/code&gt;,
plus any untracked journal files.&lt;/li&gt;
&lt;li&gt;Read each journal in full and note the &lt;code&gt;[[tag]]&lt;/code&gt; blocks.&lt;/li&gt;
&lt;li&gt;Classify each bullet as durable, raw, or already promoted.&lt;/li&gt;
&lt;li&gt;Map durable items to existing pages before creating new pages.&lt;/li&gt;
&lt;li&gt;Investigate external links, transcripts, and local X.com exports.&lt;/li&gt;
&lt;li&gt;Write synthesis into &lt;code&gt;pages/&lt;/code&gt;, not a dump of journal bullets.&lt;/li&gt;
&lt;li&gt;Cross-link related pages.&lt;/li&gt;
&lt;li&gt;Append a new batch entry to &lt;code&gt;log.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;pages/contents.md&lt;/code&gt; if a new durable page was created.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The last step saves future work. &lt;code&gt;log.md&lt;/code&gt; records which journals were processed,
which pages changed, which external links were investigated, which videos or
transcripts were fetched, what was skipped, and what backlog remains.&lt;/p&gt;
&lt;p&gt;That makes curation resumable. A later pass can see why a link was ignored or
whether a conference playlist was already fetched.&lt;/p&gt;
&lt;p&gt;The git hash is the part that makes this work for real daily notes. A date scan
only finds journal files newer than the last processed date. That misses the
normal Logseq habit: I open an old day, add one new block, and save it. The
&lt;code&gt;curate-journals&lt;/code&gt; skill reads the previous &lt;code&gt;Curation hash:&lt;/code&gt; and diffs
&lt;code&gt;journals/&lt;/code&gt; against it, so a re-edited &lt;code&gt;journals/2026_06_24.md&lt;/code&gt; shows up even if
&lt;code&gt;journals/2026_06_25.md&lt;/code&gt; was already processed.&lt;/p&gt;
&lt;p&gt;The pass starts with roughly this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git diff --name-only &amp;lt;stored-hash&amp;gt; HEAD -- journals/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git status --short journals/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The union is the work list: journal files changed since the last curation pass,
plus new untracked journals. The curation log then records the next hash so the
following pass has a new high-water mark.&lt;/p&gt;
&lt;p&gt;The skill also keeps the pass conservative. It explicitly says to leave TODOs,
meeting logistics, PR breadcrumbs, isolated IDs, and ungrounded fragments in the
journal. That matters because the easiest way to ruin a personal knowledge base
is to promote everything.&lt;/p&gt;
&lt;h2 id="agents-make-the-graph-more-useful"&gt;Agents make the graph more useful&lt;/h2&gt;
&lt;p&gt;This graph is written for humans, but I also expect coding agents to read it.&lt;/p&gt;
&lt;p&gt;The repository has explicit instructions for Codex and Claude Code. They say,
in effect: this is a private Logseq knowledge base, not an app repo; journals
are the raw inbox; pages are the durable layer; do not copy raw fragments into
pages; synthesize, cite sources, and keep sensitive details private.&lt;/p&gt;
&lt;p&gt;For journal work, the agent loads &lt;code&gt;curate-journals&lt;/code&gt; and follows the same checklist
every time. That gives the agent a smaller job than &amp;ldquo;organize my notes&amp;rdquo;: find the
new journal material, decide what has durable value, update the right page, and
write down what happened.&lt;/p&gt;
&lt;p&gt;That changes how I use AI tools. Instead of asking an agent to remember context
from a chat, I ask it to inspect the repo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;search journals for a topic&lt;/li&gt;
&lt;li&gt;find the existing page&lt;/li&gt;
&lt;li&gt;read source notes&lt;/li&gt;
&lt;li&gt;summarize a talk transcript&lt;/li&gt;
&lt;li&gt;connect related pages&lt;/li&gt;
&lt;li&gt;draft a post from the durable layer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The agent does not need everything pasted into the prompt. It can search the
repository, read the source material, and leave behind better Markdown for the
next run.&lt;/p&gt;
&lt;h2 id="what-i-do-not-promote"&gt;What I do not promote&lt;/h2&gt;
&lt;p&gt;The part that keeps the system usable is omission.&lt;/p&gt;
&lt;p&gt;I do not move every note into &lt;code&gt;pages/&lt;/code&gt;. Most raw material stays raw. That keeps
the durable layer useful.&lt;/p&gt;
&lt;p&gt;I leave behind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tasks without reusable context&lt;/li&gt;
&lt;li&gt;meeting logistics&lt;/li&gt;
&lt;li&gt;temporary debugging noise&lt;/li&gt;
&lt;li&gt;private operational details that do not need to be repeated&lt;/li&gt;
&lt;li&gt;links that looked interesting but did not turn into a durable idea&lt;/li&gt;
&lt;li&gt;source material that I cannot verify&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The durable layer works because it is selective.&lt;/p&gt;
&lt;h2 id="the-result"&gt;The result&lt;/h2&gt;
&lt;p&gt;I am not trying to build a perfect wiki. I want a working memory system with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fast capture in journals&lt;/li&gt;
&lt;li&gt;source retention for long-form material&lt;/li&gt;
&lt;li&gt;compact durable pages&lt;/li&gt;
&lt;li&gt;explicit source notes&lt;/li&gt;
&lt;li&gt;a curation log&lt;/li&gt;
&lt;li&gt;enough structure for Logseq, Codex, Claude Code, and future me to navigate it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The design choice is to treat personal knowledge as ingestion, not filing.
Capture stays cheap because journals can stay messy. Curation happens later,
when there is enough signal to write a page worth keeping.&lt;/p&gt;
&lt;p&gt;That delay is the trick. It keeps the inbox easy and the knowledge layer clean
enough to be useful months later.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.prskavec.net/post/how-i-use-logseq-as-a-personal-knowledge-base/logseq-graph-view.png"
alt="Logseq graph view showing the knowledge base connections"
loading="lazy"
decoding="async"&gt;
&lt;/p&gt;</content:encoded></item><item><title>Agent Harnesses: Why You Shouldn't Bet Your Company on Claude Code or Codex</title><link>https://www.prskavec.net/post/agent-harnesses/</link><pubDate>Tue, 02 Jun 2026 09:00:00 +0100</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/agent-harnesses/</guid><description>Coding agents are great, but locking your company into Claude Code or Codex is a strategic mistake. Here is why agent harnesses matter and what to do instead.</description><content:encoded>&lt;p&gt;In less than two years, coding agents went from novelty to daily driver. &lt;a href="https://www.anthropic.com/claude-code"&gt;Claude Code&lt;/a&gt; and &lt;a href="https://openai.com/codex/"&gt;OpenAI Codex&lt;/a&gt; read your repo, run commands, edit files, and finish real work. I use them every day. You probably do too.&lt;/p&gt;
&lt;p&gt;Using a tool is one thing. Standardizing your whole company on a single vendor&amp;rsquo;s closed agent is another. That decision is easy to make and very expensive to walk back, so it is worth thinking through before you make it.&lt;/p&gt;
&lt;h2 id="what-is-an-agent-harness"&gt;What is an agent harness?&lt;/h2&gt;
&lt;p&gt;When people say &amp;ldquo;Claude Code&amp;rdquo; or &amp;ldquo;Codex,&amp;rdquo; they usually mean two things glued together:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The model&lt;/strong&gt;, the LLM that does the reasoning (Claude, GPT, and so on).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The harness&lt;/strong&gt;, everything around the model: the tool-calling loop, file editing, command execution, context management, permissions, prompts, and the UI.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The harness is what turns a chat model into an &lt;em&gt;agent&lt;/em&gt;. It decides which tools exist, how context is assembled, when to ask for permission, and how errors get recovered. The model is a commodity you rent by the token. The harness is where your actual workflow lives.&lt;/p&gt;
&lt;p&gt;And the harness is the valuable part. The vendor owns it.&lt;/p&gt;
&lt;h2 id="the-case-against-vendor-lock-in"&gt;The case against vendor lock-in&lt;/h2&gt;
&lt;h3 id="1-you-couple-your-workflow-to-someone-elses-roadmap"&gt;1. You couple your workflow to someone else&amp;rsquo;s roadmap&lt;/h3&gt;
&lt;p&gt;Adopt Claude Code or Codex wholesale and your engineers&amp;rsquo; muscle memory, your CI integrations, your custom commands, and your prompts all get shaped by that one harness. When the vendor changes pricing, drops a feature, throttles your usage, or pivots, you pay for it. You don&amp;rsquo;t control the roadmap and you can&amp;rsquo;t fork it.&lt;/p&gt;
&lt;h3 id="2-the-model-is-the-easy-part-to-swap-if-the-harness-lets-you"&gt;2. The model is the easy part to swap, if the harness lets you&lt;/h3&gt;
&lt;p&gt;A new best model shows up every few months. The model that fits your codebase best in Q1 may be second-best by Q3. A good setup lets you switch when the price/performance curve moves. A closed vendor harness ties you to their model family, and that costs you the biggest lever you have: picking the best model for the task at the best price.&lt;/p&gt;
&lt;h3 id="3-data-and-compliance-boundaries-are-not-negotiable-for-many-companies"&gt;3. Data and compliance boundaries are not negotiable for many companies&lt;/h3&gt;
&lt;p&gt;Where does your code go? What gets logged, retained, and used for training? With a closed harness you take the vendor&amp;rsquo;s answers as given. For regulated industries, air-gapped environments, or anyone with serious IP concerns, that is not enough. An open harness lets you choose providers, route to self-hosted or enterprise endpoints, and keep an audit trail you actually own.&lt;/p&gt;
&lt;h3 id="4-lock-in-is-cheap-to-enter-and-expensive-to-leave"&gt;4. Lock-in is cheap to enter and expensive to leave&lt;/h3&gt;
&lt;p&gt;The start is great. Then you have hundreds of engineers with vendor-specific habits, internal tooling built against a proprietary CLI, and no abstraction layer. Migrating later means retraining people and rebuilding integrations. That switching cost is exactly what gives the vendor pricing power over you.&lt;/p&gt;
&lt;h3 id="5-your-competitive-edge-should-not-be-a-saas-subscription"&gt;5. Your competitive edge should not be a SaaS subscription&lt;/h3&gt;
&lt;p&gt;If everyone in your industry runs the same off-the-shelf agent the same way, where is your advantage? The leverage is in &lt;em&gt;how&lt;/em&gt; you wire agents into your codebase, your domain knowledge, your tests, and your review process. That belongs in something you control.&lt;/p&gt;
&lt;h2 id="the-alternative-own-the-harness-rent-the-model"&gt;The alternative: own the harness, rent the model&lt;/h2&gt;
&lt;p&gt;This is not an argument against AI coding agents. It is an argument for putting the boundary in the right place. Here is the setup I recommend.&lt;/p&gt;
&lt;h3 id="own-the-harness-with-pi"&gt;Own the harness with pi&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://pi.dev"&gt;pi&lt;/a&gt; is a minimal, extensible terminal coding harness. The idea is to adapt the tool to your workflow instead of the other way around, and you do that without forking the agent&amp;rsquo;s internals. You extend it with TypeScript extensions, skills, prompt templates, and themes, then package and share them across your team over npm or git.&lt;/p&gt;
&lt;p&gt;That gives you what you want from a company harness:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your custom tools, prompts, permissions, and skills become version-controlled artifacts in your repo, not settings trapped in a vendor&amp;rsquo;s cloud.&lt;/li&gt;
&lt;li&gt;It runs in interactive, print/JSON, RPC, and SDK modes, so the same harness powers your engineers&amp;rsquo; terminals and your CI pipelines and internal apps.&lt;/li&gt;
&lt;li&gt;It ships with sensible defaults but stays small, so you add only the complexity you need.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You get the ergonomics of Claude Code or Codex, but the workflow logic is yours and travels with your codebase.&lt;/p&gt;
&lt;h3 id="rent-the-model-through-a-gateway"&gt;Rent the model through a gateway&lt;/h3&gt;
&lt;p&gt;Keep the model pluggable. The simplest path is &lt;a href="https://openrouter.ai"&gt;OpenRouter&lt;/a&gt;, a model gateway that gives you access to basically every model worth using behind one interface. Swap Anthropic, OpenAI, Google, and open-weight models on price/performance without rewriting anything.&lt;/p&gt;
&lt;p&gt;If your data or compliance requirements are stricter, and for many companies they are, go through a cloud provider gateway instead. &lt;a href="https://aws.amazon.com/bedrock/"&gt;Amazon Bedrock&lt;/a&gt; and &lt;a href="https://azure.microsoft.com/en-us/products/ai-foundry"&gt;Azure AI Foundry&lt;/a&gt; give you the same model menu inside your existing cloud account, with your org&amp;rsquo;s networking, IAM, logging, and data-residency guarantees already in place. For regulated or IP-sensitive work, that is often the right answer.&lt;/p&gt;
&lt;p&gt;Either way, standardize on the &lt;a href="https://platform.openai.com/docs/api-reference"&gt;OpenAI-compatible API&lt;/a&gt; shape as the lowest common denominator, so swapping providers is a config change and not a migration.&lt;/p&gt;
&lt;h3 id="measure-and-route"&gt;Measure and route&lt;/h3&gt;
&lt;p&gt;Once the model is a swappable component, you can benchmark cost and quality per task and route on purpose: a cheap fast model for boilerplate, a strong one for tricky refactors. With pi owning the loop and a gateway in front, that routing is yours to tune.&lt;/p&gt;
&lt;h2 id="but-claude-code--codex-is-just-better-right-now"&gt;&amp;ldquo;But Claude Code / Codex is just better right now&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;Maybe today. The gap between the leading harness and the open ones is closing fast, and the open ones are built out in the open. Optimizing for &amp;ldquo;best this quarter&amp;rdquo; while you lock yourself in for years is a bad trade, especially for something as foundational as how your company writes software.&lt;/p&gt;
&lt;p&gt;Use the good tools. Just don&amp;rsquo;t let one of them become the thing you can&amp;rsquo;t leave.&lt;/p&gt;
&lt;h2 id="takeaways"&gt;Takeaways&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The harness, not the model, is where your workflow lives, and closed vendors own it.&lt;/li&gt;
&lt;li&gt;Treat the model as a swappable commodity. Lock-in takes that lever away.&lt;/li&gt;
&lt;li&gt;Data, compliance, and cost control all get harder when the harness is a black box.&lt;/li&gt;
&lt;li&gt;Use an extensible harness like &lt;a href="https://pi.dev"&gt;pi&lt;/a&gt; and keep your prompts, tools, and skills version-controlled in your own repo.&lt;/li&gt;
&lt;li&gt;Rent models through a gateway: &lt;a href="https://openrouter.ai"&gt;OpenRouter&lt;/a&gt; for breadth, or &lt;a href="https://aws.amazon.com/bedrock/"&gt;Bedrock&lt;/a&gt; / &lt;a href="https://azure.microsoft.com/en-us/products/ai-foundry"&gt;Azure AI Foundry&lt;/a&gt; when compliance and data residency matter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Adopt agents fast, but keep the boundary, the harness and the choice of model, in your own hands.&lt;/p&gt;</content:encoded></item><item><title>Schema-First Telemetry in Go: A New Approach to Observability</title><link>https://www.prskavec.net/post/schema_first_telemetry/</link><pubDate>Fri, 18 Apr 2025 08:05:13 +0200</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/schema_first_telemetry/</guid><description>Modern Go services generate telemetry (metrics, logs, traces) with instrumentation embedded directly in code. Usually this is code-first: developers call APIs (counters, loggers, tracers) and attach key-value attributes on the fly. It works, but at scale it leads to fragmented data and missing context. Schema-first telemetry turns that around: you define a schema (like an IDL file) for your telemetry data up front, including its semantic metadata, and emit telemetry from generated code.</description><content:encoded>&lt;p&gt;Modern Go services generate &lt;strong&gt;telemetry&lt;/strong&gt; (metrics, logs, traces) with instrumentation embedded directly in code. Usually this is a &lt;strong&gt;code-first&lt;/strong&gt; approach: developers call APIs (counters, loggers, tracers) and attach key-value attributes on the fly. It works, but at scale it leads to fragmented data and missing context. &lt;strong&gt;Schema-first telemetry&lt;/strong&gt; turns that around: you define a schema (like an IDL file) for your telemetry data up front, including its semantic metadata, and emit telemetry from generated code. This post introduces schema-first telemetry through Meta’s research, compares it to how OpenTelemetry handles schemas today, looks at what others in the community are doing, and shows Go examples of how you might implement it on top of OpenTelemetry.&lt;/p&gt;
&lt;h2 id="the-limitations-of-code-first-telemetry"&gt;The Limitations of Code-First Telemetry&lt;/h2&gt;
&lt;p&gt;Most of us instrument applications in a &lt;strong&gt;code-first&lt;/strong&gt; fashion. For example, using OpenTelemetry in Go, you might record a metric like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Code-first instrumentation example (OpenTelemetry)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;counter&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;metric&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Must&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;meter&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;NewInt64Counter&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;request_count&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;counter&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Add&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;attribute&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;service.id&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;attribute&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;endpoint&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;/api/v1/resource&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;attribute&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Int&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;status_code&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In this snippet, the keys &lt;code&gt;&amp;quot;service.id&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;endpoint&amp;quot;&lt;/code&gt;, and &lt;code&gt;&amp;quot;status_code&amp;quot;&lt;/code&gt; are just strings. You choose them in code, perhaps following suggested naming conventions. This is straightforward, but all the knowledge about these fields is implicit. The code is the only place where the “schema” exists. There’s no formal description of what &lt;code&gt;&amp;quot;service.id&amp;quot;&lt;/code&gt; means or what values &lt;code&gt;&amp;quot;status_code&amp;quot;&lt;/code&gt; can take, beyond maybe documentation or tribal knowledge. Common problems with the code-first approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Discoverability:&lt;/strong&gt; Consumers of telemetry (engineers writing queries or dashboards) have to guess or search what attributes exist and what they mean. With many services, each might use slightly different keys for the same concept (e.g., &lt;code&gt;&amp;quot;userId&amp;quot;&lt;/code&gt; vs &lt;code&gt;&amp;quot;user.id&amp;quot;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistency:&lt;/strong&gt; Nothing enforces consistency. Developers may mis-name a field or use it differently than others. OpenTelemetry offers &lt;em&gt;semantic conventions&lt;/em&gt;, standard names for common attributes, but these are encouraged, not enforced.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lack of semantic info:&lt;/strong&gt; The backend treats most data as opaque strings. It might not know that &lt;code&gt;&amp;quot;status_code&amp;quot;&lt;/code&gt; is an integer HTTP status, or that &lt;code&gt;&amp;quot;endpoint&amp;quot;&lt;/code&gt; is a URL path. Units and types are often lost; everything becomes a string label.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Change management:&lt;/strong&gt; Evolving telemetry is risky. Rename an attribute in code and any dashboard or alert that depends on the old name breaks. Without a schema there’s no easy way to notify or automatically migrate consumers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a code-first world, adding a new attribute (say a shard ID) to a metric is just another key-value in the code. Easy for the developer, but that new field’s existence and meaning still has to reach everyone downstream, usually through documentation or code review.&lt;/p&gt;
&lt;h2 id="what-is-schema-first-telemetry"&gt;What is Schema-First Telemetry?&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;schema-first approach&lt;/strong&gt; means you define the structure and metadata of telemetry &lt;strong&gt;before&lt;/strong&gt; writing instrumentation code, much like designing a schema for a database or an API. Yuri Shkuro (Meta) describes using Thrift IDL files to define telemetry data types (metrics, log events, and so on) with rich annotations for semantics. The schema becomes the single source of truth for what telemetry your app can emit.&lt;/p&gt;
&lt;p&gt;Key aspects of schema-first telemetry:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IDL-based Schema Definition:&lt;/strong&gt; You write an IDL (Interface Definition Language) file describing your telemetry data. For example, a metric “RequestCounter” might be defined as a struct with fields for &lt;code&gt;service_id&lt;/code&gt;, &lt;code&gt;endpoint&lt;/code&gt;, &lt;code&gt;status_code&lt;/code&gt;, etc., each with a type and description.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rich Metadata Annotations:&lt;/strong&gt; The schema can include annotations for things like human-readable descriptions, units, acceptable values, privacy levels, etc. For instance, you might annotate that &lt;code&gt;status_code&lt;/code&gt; is an HTTP status and an integer, or tag fields with &lt;code&gt;@DisplayName&lt;/code&gt; and &lt;code&gt;@Description&lt;/code&gt; in the IDL. This metadata travels with the schema.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code Generation:&lt;/strong&gt; A tool generates language-specific code (for Go, it could generate Go structs or helper functions) from the schema. Developers use this generated code instead of manually writing key strings. This ensures they only emit data that conforms to the schema.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compile-time checks:&lt;/strong&gt; Because telemetry fields are now struct fields or typed constants, many errors (typos in attribute names, wrong types) become compile-time errors. Try to record a string into a field defined as an int and the code won’t compile, which is a real win for robustness.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schema Registry &amp;amp; Versioning:&lt;/strong&gt; The schemas are versioned and stored in a registry (or metadata store). Both producers and consumers (backends, query tools) can retrieve the schema to know how to parse and interpret telemetry data. When updating a schema, you might bump a version or use a compatibility check in CI.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="example-defining-and-using-a-telemetry-schema"&gt;Example: Defining and Using a Telemetry Schema&lt;/h3&gt;
&lt;p&gt;Let’s illustrate with a simplified example inspired by Meta’s approach. Suppose we want a metric for counting requests. In schema-first style, we define it in an IDL (here using pseudo-Go syntax for clarity):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Schema (could be in Thrift, Proto, etc.)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ServiceID&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#75715e"&gt;// alias type for service identifier&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;StatusCode&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int32&lt;/span&gt; &lt;span style="color:#75715e"&gt;// alias type for HTTP status code&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RequestCounter&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;ServiceID&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;service_id&lt;/span&gt; &lt;span style="color:#75715e"&gt;// e.g., &amp;#34;orderservice&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;endpoint&lt;/span&gt; &lt;span style="color:#75715e"&gt;// e.g., &amp;#34;/api/v1/submit&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;StatusCode&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;status_code&lt;/span&gt; &lt;span style="color:#75715e"&gt;// e.g., 200&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// (We can add more fields later, like shard_id)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This schema says: a &lt;code&gt;RequestCounter&lt;/code&gt; metric event has three fields with specified types. We could add metadata annotations too, such as descriptions or units (not shown here for brevity).&lt;/p&gt;
&lt;p&gt;From this schema, a code generator would produce a Go struct and helpers. In Go, the generated code might look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Generated code (simplified)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RequestCounter&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ServiceID&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Endpoint&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;StatusCode&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int32&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... plus maybe helper methods or validators&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, to emit telemetry in our application, we use this struct with the OpenTelemetry API:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Using schema-first generated struct in instrumentation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;rc&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RequestCounter&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ServiceID&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;orderservice&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Endpoint&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/api/v1/submit&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;StatusCode&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// The telemetry SDK could provide a helper to record struct data:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;telemetry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Record&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;rc&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Under the hood, &lt;code&gt;telemetry.Record&lt;/code&gt; could iterate over the struct’s fields and convert them to OpenTelemetry attributes, using a mapping of field names to attribute keys. For example, &lt;code&gt;ServiceID&lt;/code&gt; -&amp;gt; &lt;code&gt;&amp;quot;service.id&amp;quot;&lt;/code&gt;, &lt;code&gt;Endpoint&lt;/code&gt; -&amp;gt; &lt;code&gt;&amp;quot;endpoint&amp;quot;&lt;/code&gt;, &lt;code&gt;StatusCode&lt;/code&gt; -&amp;gt; &lt;code&gt;&amp;quot;http.status_code&amp;quot;&lt;/code&gt;, based on the schema definition. The result is similar telemetry data, but produced in a schema-driven way rather than ad-hoc in code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adding a new field&lt;/strong&gt; in schema-first starts with the schema. Say we want to add &lt;code&gt;shard_id&lt;/code&gt; to the &lt;code&gt;RequestCounter&lt;/code&gt;. We add it to the IDL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ShardID&lt;/span&gt; &lt;span style="color:#75715e"&gt;// add shard identifier for sharded services&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This change is made in the schema (with perhaps a description annotation for clarity). The codegen then updates the Go struct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RequestCounter&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ServiceID&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Endpoint&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;StatusCode&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int32&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ShardID&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#75715e"&gt;// new field&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now the application code can start populating &lt;code&gt;ShardID&lt;/code&gt; when recording the metric:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;rc&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RequestCounter&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ServiceID&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;orderservice&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Endpoint&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/api/v1/submit&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;StatusCode&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ShardID&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;shard-42&amp;#34;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// new dimension&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;telemetry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Record&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;rc&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// now includes shard_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From the developer’s side, adding the field was a small change: one extra line in the IDL, plus using the new field in code. From the system’s side, a lot more happens behind the scenes to keep it seamless and safe. That is the telemetry &lt;strong&gt;lifecycle&lt;/strong&gt; in a schema-first model.&lt;/p&gt;
&lt;h2 id="telemetry-lifecycle-in-a-schema-first-model"&gt;Telemetry Lifecycle in a Schema-First Model&lt;/h2&gt;
&lt;p&gt;In a schema-first system like the one at Meta, there is an end-to-end flow that ensures schema changes and data emission stay in sync:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Schema Definition/Change:&lt;/strong&gt; A developer edits the schema file (IDL) to add or modify telemetry fields (for example, add &lt;code&gt;shard_id&lt;/code&gt; to &lt;code&gt;RequestCounter&lt;/code&gt;). This is done alongside the code that will emit the data. Both changes can go in a single pull request.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI Validation:&lt;/strong&gt; A continuous integration step checks the schema change for backward compatibility (no forbidden changes like renaming a field outright). It may also generate the new code and run tests. If the schema change would break consumers (e.g., deleting a field or changing its type in a non-backwards-compatible way), the CI rejects it. This ensures stability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schema Registry Update:&lt;/strong&gt; When the change is approved and merged, an &lt;strong&gt;actualization service&lt;/strong&gt; updates the central &lt;strong&gt;metadata store&lt;/strong&gt; or schema registry with the new schema version. Now the organization’s observability platform “knows” about the new &lt;code&gt;shard_id&lt;/code&gt; field for &lt;code&gt;RequestCounter&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Telemetry Emission:&lt;/strong&gt; The application runs with the updated code. The OpenTelemetry SDK (or a custom telemetry SDK) serializes the telemetry. In Meta’s case, they even serialize it as a &lt;strong&gt;binary Thrift payload&lt;/strong&gt; on the wire for efficiency. Importantly, each telemetry data point can carry a reference to the schema version used.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backend Ingestion:&lt;/strong&gt; The telemetry backend receives the data. Seeing the schema reference (e.g., a schema URL or version), it can parse the data using the correct schema. Because the backend was informed of the schema update, it already understands the new field by the time data arrives. This avoids the usual problem of “surprise” fields nobody knows about.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query &amp;amp; Analysis:&lt;/strong&gt; When you go to query this data (maybe in a monitoring UI or via a query language), the tools can pull the schema metadata from the registry. This means UIs can present human-friendly names or descriptions for fields, enforce correct units or types in queries, and even suggest joins across telemetry streams if they share common schema elements. The rich annotations we discussed (like descriptions, units, etc.) are available to make exploration easier.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="opentelemetry-today-schemas-and-semantic-conventions"&gt;OpenTelemetry Today: Schemas and Semantic Conventions&lt;/h2&gt;
&lt;p&gt;How does this compare with &lt;strong&gt;OpenTelemetry (OTel)&lt;/strong&gt;, the open-source observability framework many Go developers use? OTel wasn’t originally schema-first, but it recognized some of these issues and introduced a &lt;em&gt;schema concept&lt;/em&gt; in version 1.8 of the spec.&lt;/p&gt;
&lt;p&gt;OTel provides &lt;strong&gt;Semantic Conventions&lt;/strong&gt;, a catalog of standard attribute names for common scenarios (HTTP, database, messaging, and so on). They’re defined in YAML files and published as part of the spec. In Go they’re available as constants (for example, &lt;code&gt;semconv.HTTPMethodKey&lt;/code&gt; for the &lt;code&gt;&amp;quot;http.method&amp;quot;&lt;/code&gt; attribute). Semantic conventions add some structure and consistency: if everyone follows them, all services use &lt;code&gt;&amp;quot;http.method&amp;quot;&lt;/code&gt; to mean the same thing. But as Shkuro et al. point out, these are “only conventions”. Nothing prevents a developer from deviating or misusing them, and there is no automatic validation that your values make sense (e.g., that an &lt;code&gt;&amp;quot;http.status_code&amp;quot;&lt;/code&gt; attribute is a number and not a string saying &amp;ldquo;OK&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;To help conventions evolve, OpenTelemetry also introduced &lt;strong&gt;Telemetry Schemas&lt;/strong&gt; identified by a Schema URL. An instrumentation library can embed a schema URL like &lt;code&gt;https://opentelemetry.io/schemas/1.9.0&lt;/code&gt; in the data it emits, signaling “I’m using version 1.9.0 of the conventions.” If a backend sees data from an older schema version, it can in theory automatically translate attributes to the newer schema (for example, if a key was renamed between OTel 1.8.0 and 1.9.0). So OTel schemas enable version negotiation and transformation, a bit like a migration script for telemetry data, which is useful for handling gradual changes in conventions without breaking dashboards.&lt;/p&gt;
&lt;p&gt;But OTel’s Schema URL mechanism is still about evolving &lt;em&gt;conventions&lt;/em&gt;. It doesn’t give you a per-application custom schema with rich types, and it doesn’t formally describe the data’s shape beyond listing name changes between versions. So it helps with compatibility, but it doesn’t reach the full schema-first vision of strongly typed telemetry with all semantics captured.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Comparing Approaches:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Schema Definition:&lt;/em&gt; Meta’s approach defines exact fields and types for each telemetry signal (e.g., the fields in &lt;code&gt;RequestCounter&lt;/code&gt;). OTel defines common fields in a looser way (a list of recommended keys for things like HTTP, etc., which you may or may not use).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Enforcement:&lt;/em&gt; In schema-first, your code won’t compile if you try to emit something that’s not in the schema or of the wrong type. In OTel, you can always add any attribute key on a span or metric, with no compile-time checks. Errors or inconsistencies might only surface in the backend, or not until someone queries and finds nonsense values.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Evolution:&lt;/em&gt; Schema-first supports safe evolution through versioned schemas and automated compatibility checks. OTel’s schema URLs let backends adapt to renamed fields, but they don’t solve deeper changes (splitting a field in two, changing data types) and don’t prevent problems if producers and consumers aren’t in lockstep.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Metadata Richness:&lt;/em&gt; In schema-first, metadata like unit (e.g. milliseconds vs seconds for a latency metric) can be captured in the schema. In OTel’s code-first model, if you use a semantic convention the unit might be suggested by convention (e.g., &lt;code&gt;&amp;quot;ms&amp;quot;&lt;/code&gt; for milliseconds), but it’s not enforced. It’s up to the developer to remember and the backend to interpret. Many existing telemetry systems treat all labels as opaque strings with no unit or type info.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OpenTelemetry is moving toward more schema awareness (schema URLs, generated semantic-convention libraries), but it is still largely a code-first ecosystem. The schema-first approach, as championed by Meta, goes further: it treats telemetry schemas as first-class artifacts in your development workflow.&lt;/p&gt;
&lt;h2 id="evolving-the-ecosystem-schema-first-telemetry-elsewhere"&gt;Evolving the Ecosystem: Schema-First Telemetry Elsewhere&lt;/h2&gt;
&lt;p&gt;Meta isn’t alone in seeing the need for better telemetry metadata. Other organizations and open-source projects are pushing in the same direction:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Elastic Common Schema (ECS):&lt;/strong&gt; Elastic created ECS as a unified schema for logs and metrics (primarily for Elasticsearch ingestion). It defines a common set of field names and types across many domains (cloud, host, network, http, and so on) so disparate sources can be correlated. For example, ECS says use &lt;code&gt;source.ip&lt;/code&gt; for an IP address rather than every app inventing its own field. In 2023, Elastic announced it is working with OpenTelemetry to merge ECS with OTel’s semantic conventions, aiming for one common open schema. This is still about standardizing field naming, not a compiled per-app schema, but it shows the industry’s appetite for schema convergence. If ECS and OTel unify, a Go developer might one day rely on a single set of well-known field names for all telemetry.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CloudEvents schema references:&lt;/strong&gt; The CloudEvents standard (for describing event payloads in a common way) includes a &lt;code&gt;dataschema&lt;/code&gt; attribute, a URI pointing to the schema of the event’s data. This is conceptually similar to OTel’s schema URL: it tells consumers where to find the schema to interpret the event. It doesn’t enforce the schema on the producer side, but it encourages formal schemas for event data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenTelemetry Weaver (Project OTel Weaver):&lt;/strong&gt; This is a proof-of-concept CLI tool from the OpenTelemetry community exploring schema-first ideas (&lt;a href="https://github.com/f5/otel-weaver#:~:text=,not%20ready%20for%20production%20use"&gt;GitHub - f5/otel-weaver: OTel Weaver a schema-first approach to OpenTelemetry (status: POC)&lt;/a&gt;) (&lt;a href="https://github.com/f5/otel-weaver#:~:text=OTel%20Weaver%20is%20a%20CLI,tool%20that%20enables%20users%20to"&gt;GitHub - f5/otel-weaver: OTel Weaver a schema-first approach to OpenTelemetry (status: POC)&lt;/a&gt;). OTel Weaver can generate a client SDK from a telemetry schema and allows searching and resolving of schema definitions. It’s inspired by the vision laid out in an OpenTelemetry discussion (OTEP 0243) about &lt;strong&gt;Application Telemetry Schema&lt;/strong&gt; and &lt;strong&gt;Component Telemetry Schema&lt;/strong&gt;. The goal is to let developers define custom schemas (for their specific application’s telemetry) on top of the global conventions, and have tools to generate code and validate telemetry against those schemas. As of now, this is in early stages (not production-ready), but it shows that the open-source community is actively working on enabling schema-first workflows in the future.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Datadog Reference Tables:&lt;/strong&gt; Some observability vendors tackle the metadata problem by letting users enrich telemetry after the fact. Datadog, for instance, has &lt;em&gt;Reference Tables&lt;/em&gt; that let you join telemetry data with external data (CMDB info, business metadata). It isn’t schema-first, but it comes from the same pain point: needing more context and meaning in telemetry. Reference Tables enrich a posteriori (add metadata after data is collected), whereas schema-first bakes it in a priori. At Meta they had experience with an a posteriori metadata store and found it can drift out of sync if not tightly coupled, which is why they moved to schema-first for critical systems.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chronosphere’s approach:&lt;/strong&gt; Chronosphere (an observability platform) emphasizes standardizing telemetry metadata and naming across an organization. In a blog post on OTel attribute naming, they note that &lt;em&gt;“metadata plays a key role in making observability data meaningful”&lt;/em&gt; and advocate for clear conventions and company-wide standards. This isn’t a full schema-first system, but the recommended practice of defining a naming-standard document and running workshops to align teams is essentially a manual schema-governance process. The goals are the same: consistency, discoverability, and interoperability of telemetry.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s broad recognition that telemetry needs stronger contracts. Whether through formal schemas, unified conventions, or metadata enrichment, the industry is converging on telemetry that is more self-describing and can evolve without breaking consumers. Schema-first is one of the leading techniques to get there.&lt;/p&gt;
&lt;h2 id="go-example-schema-first-telemetry-in-practice"&gt;Go Example: Schema-First Telemetry in Practice&lt;/h2&gt;
&lt;p&gt;Back to the Go developer’s perspective. How might you apply schema-first ideas in Go &lt;em&gt;today&lt;/em&gt; with OpenTelemetry? The full tooling isn’t here yet, but you can adopt some patterns:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Define Structs for Telemetry Data:&lt;/strong&gt; Instead of sprinkling string keys throughout your code, define Go structs to represent the telemetry events or metrics you intend to emit. This gives you a single place to see all fields and their types.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Define a struct as a pseudo-schema for a log or metric event&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderSubmittedEvent&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;OrderID&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#75715e"&gt;// business identifier&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;float64&lt;/span&gt; &lt;span style="color:#75715e"&gt;// value of the order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;CustomerID&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#75715e"&gt;// customer identifier&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Region&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#75715e"&gt;// region of the request&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Add comments or struct tags to document the fields. This struct is a mini-schema. It doesn’t enforce anything on its own, but it’s a start.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use Constructor Functions to Enforce Required Fields or Types:&lt;/strong&gt; You can provide functions to create these events, which can apply validation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NewOrderSubmittedEvent&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;orderID&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;float64&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;customerID&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;region&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;OrderSubmittedEvent&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Here you might enforce some constraints or default values&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderSubmittedEvent&lt;/span&gt;{&lt;span style="color:#a6e22e"&gt;orderID&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;customerID&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;region&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Conversion to OTel Attributes:&lt;/strong&gt; Implement a method to convert your struct to OTel attributes. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;go.opentelemetry.io/otel/attribute&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;e&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderSubmittedEvent&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;Attributes&lt;/span&gt;() []&lt;span style="color:#a6e22e"&gt;attribute&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;KeyValue&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; []&lt;span style="color:#a6e22e"&gt;attribute&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;KeyValue&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;attribute&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;order.id&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;e&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;OrderID&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;attribute&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Float64&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;order.value&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;e&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;attribute&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;customer.id&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;e&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CustomerID&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;attribute&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;region&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;e&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Region&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here we decide on the actual keys (like &lt;code&gt;&amp;quot;order.id&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;customer.id&amp;quot;&lt;/code&gt;). Ideally, follow existing semantic conventions or your organization’s schema guidelines for naming (e.g., if “region” should be &lt;code&gt;cloud.region&lt;/code&gt; per OTel conventions, use that). This method is essentially encoding the schema knowledge.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Emit Telemetry Using the Struct:&lt;/strong&gt; Now use the struct in your instrumentation code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;meter&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;global&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Meter&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;myapp/orders&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ordersCounter&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;metric&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Must&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;meter&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;NewInt64Counter&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;orders_submitted_total&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// When an order is submitted:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;evt&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NewOrderSubmittedEvent&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;orderID&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;customerID&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;region&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ordersCounter&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Add&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;evt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Attributes&lt;/span&gt;()&lt;span style="color:#f92672"&gt;...&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This ensures every time you record &lt;code&gt;orders_submitted_total&lt;/code&gt;, you attach the full set of attributes as defined in the struct. If you forget a field, the compiler will complain if it’s not set (since the struct requires you to provide all fields in &lt;code&gt;NewOrderSubmittedEvent&lt;/code&gt;). If you ever rename a field in the struct, you’ll update it in one place (the &lt;code&gt;Attributes()&lt;/code&gt; method), rather than hunting down every occurrence of the string literal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Optional, schema registration:&lt;/strong&gt; If you want to go one step further, you could maintain a simple registry or metadata map in your code that describes these events. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TelemetrySchema&lt;/span&gt; = &lt;span style="color:#66d9ef"&gt;map&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;]&lt;span style="color:#66d9ef"&gt;map&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;]&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;OrderSubmittedEvent&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;order.id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;ID of the order&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;order.value&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Value of the order in USD&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;customer.id&amp;#34;&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;&amp;#34;ID of the customer who placed the order&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;region&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Geographical region of the request&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This could expose the schema programmatically (say, an HTTP endpoint that returns the schema JSON, or a log line at startup). It’s a rudimentary schema registry. Consumers (or your team’s documentation) could pull it to know what telemetry to expect. It’s not as robust as a real schema registry, but it builds the habit of treating telemetry fields as a contract.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In a true schema-first setup, much of this would be automated: an IDL, codegen, and so on. But even without full tool support, these Go patterns get you closer to the benefits, with type safety, clarity, and a self-documenting approach.&lt;/p&gt;
&lt;h2 id="benefits-why-schema-first-telemetry-matters"&gt;Benefits: Why Schema-First Telemetry Matters&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;For developers&lt;/strong&gt;, schema-first means a better experience when instrumenting code. You get auto-complete for telemetry fields (they’re struct fields or constants) and compile-time feedback when you misuse them. It cuts the cognitive load of remembering “what keys should I use here?” because the schema lives in one place. As Meta’s engineers found, a type-safe API is “more ergonomic and robust” than generic key-value pairs. It’s like the difference between raw SQL strings and an ORM with defined models: the latter catches errors earlier and is easier to navigate in an IDE.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For telemetry consumers&lt;/strong&gt; (SREs, analysts, and so on), the data comes with a map. It’s more discoverable: you can list all metrics and their dimensions from the schema registry without reading the source. It’s more interpretable: units and descriptions are right there, so you don’t have to guess whether a duration is in seconds or milliseconds, or what “xyz_flag=true” really means. Streams correlate more reliably because shared dimensions are clearly identified. If logs and metrics both carry a &lt;code&gt;customer.id&lt;/code&gt; field per schema, you can join on it with confidence; in a code-first world where one used &lt;code&gt;user&lt;/code&gt; and the other &lt;code&gt;customerId&lt;/code&gt;, you might not even realize they refer to the same thing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For platform engineers and architects&lt;/strong&gt;, schema-first enables better governance and tooling. You can enforce policies centrally, like “every metric must have a unit annotation” or “PII must be marked in the schema and handled accordingly.” It also opens the door to optimizations: if a field is an enum with a limited set of values, you can encode it more efficiently than a free-form string. Meta reported performance gains from serializing telemetry as a struct (no repeated keys per record) and using binary encoding. The schema also allows automated compatibility layers, similar to OTel’s schema transforms but potentially more powerful, because you know the data types, not just the names.&lt;/p&gt;
&lt;p&gt;Schema-first isn’t free. It adds an upfront step, writing or updating the schema, that code-first avoids. The Meta paper acknowledges this friction but calls it “a worthy trade-off” given the benefits. Tooling (schema wizards, templates) can soften the overhead, and once a schema exists, using it is straightforward, especially when it’s wired into your build (Meta integrates schema codegen into their Buck build).&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Schema-first application telemetry brings strong typing and rich metadata to observability. For Go developers it means moving from ad-hoc strings in code to defined structs and schemas that describe your metrics, logs, and traces. Meta’s implementation shows the payoff: compile-time validation, easier multi-signal correlation, and automatic enforcement of consistency. OpenTelemetry today gives you semantic conventions and schema versioning, which solve part of the problem, and a fuller schema-first model is coming with community efforts like OTel Weaver and the unification of industry schemas.&lt;/p&gt;
&lt;p&gt;Even an informal schema-first mindset can improve the telemetry your Go services emit. Start by defining and documenting your telemetry fields in one place, whether an IDL, a Go struct, or a markdown file in your repo. Use code generation or helper libraries so everyone shares the same definitions. Over time this can grow into a more automated schema-first toolchain as the ecosystem matures.&lt;/p&gt;
&lt;p&gt;Telemetry with a schema is like code with types: safer, clearer, and easier to change. As observability keeps growing in importance for cloud-native systems, schema-first techniques may well become a standard part of the Go developer’s toolkit for building reliable, introspectable services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sources:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2206.11380"&gt;Yuri Shkuro et al., &lt;em&gt;“Positional Paper: Schema-First Application Telemetry”&lt;/em&gt; Meta (2022)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.shkuro.com/talks/2022-10-27-schema-first-application-telemetry/"&gt;Yuri Shkuro, &lt;em&gt;&amp;ldquo;Schema-first Application Telemetry&amp;rdquo;&lt;/em&gt;, SRECon 2022&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OpenTelemetry Project, &lt;em&gt;&lt;a href="https://opentelemetry.io/docs/concepts/semantic-conventions/"&gt;Semantic Conventions&lt;/a&gt; and &lt;a href="https://opentelemetry.io/docs/specs/otel/schemas/"&gt;Telemetry Schema&lt;/a&gt;&lt;/em&gt; in spec and OTEPs.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.elastic.co/blog/ecs-elastic-common-schema-otel-opentelemetry-announcement"&gt;Elastic Blog, &lt;em&gt;“Elastic Common Schema contributes to OpenTelemetry”&lt;/em&gt; Ken Exner (2023)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chronosphere.io/learn/top-3-opentelemetry-attribute-naming-best-practices/"&gt;Chronosphere Blog, &lt;em&gt;“Top 3 OpenTelemetry Attribute Naming Best Practices”&lt;/em&gt; (2024)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.datadoghq.com/blog/reference-tables/"&gt;Datadog Blog, &lt;em&gt;“Enrich Your Telemetry with Custom Metadata (Reference Tables)”&lt;/em&gt; (2022)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/open-telemetry/weaver"&gt;OpenTelemetry Weaver&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Build nice terminal UI with Bubble Tea</title><link>https://www.prskavec.net/post/bubbletea/</link><pubDate>Thu, 04 Jul 2024 12:54:08 +0200</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/bubbletea/</guid><description>Building a nice terminal UI in Go with Bubble Tea</description><content:encoded>&lt;p&gt;If you want to build text-based user interfaces in Go, &lt;a href="https://github.com/charmbracelet/bubbletea"&gt;Bubble Tea&lt;/a&gt; is a good option. It makes building TUI (Text User Interface) applications easier, and the results can look good and stay interactive.&lt;/p&gt;
&lt;h2 id="what-is-bubble-tea"&gt;What is Bubble Tea?&lt;/h2&gt;
&lt;p&gt;Bubble Tea is a Go library from &lt;a href="https://charm.sh/"&gt;Charm_&lt;/a&gt;, based on The Elm Architecture, which keeps state and UI updates predictable. It works well for dashboards, command-line tools, and games.&lt;/p&gt;
&lt;h2 id="key-features"&gt;Key Features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Elm-inspired architecture: manage application state in a structured, predictable way.&lt;/li&gt;
&lt;li&gt;Rendering: build interactive interfaces with the Bubble Tea rendering engine.&lt;/li&gt;
&lt;li&gt;Concurrency: use Go&amp;rsquo;s concurrency model for responsive applications.&lt;/li&gt;
&lt;li&gt;Cross-platform: works across operating systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="getting-started"&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;I will use &lt;a href="https://github.com/abtris/rss-bubletea-demo"&gt;simple application that I created for demo purpose&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a simple RSS reader that fetches the latest items from a feed and shows them in the terminal. It uses Bubble Tea for the TUI, so you can navigate the news items and read the full content of each article.&lt;/p&gt;
&lt;p&gt;Main app run model and give TUI data. Project have all components related to TUI in &lt;code&gt;tui&lt;/code&gt; package.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;github.com/abtris/rss-bubbletea/tui&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tea&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;github.com/charmbracelet/bubbletea&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;github.com/mmcdole/gofeed&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;file&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Open&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;data/podcast.xml&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;file&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fp&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;gofeed&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewParser&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;feed&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Parse&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;file&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fatal&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;parse feed failed&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;model&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tui&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewModel&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;feed&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fatal&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;create model failed&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; len(&lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Getenv&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;DEBUG&amp;#34;&lt;/span&gt;)) &amp;gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;f&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tea&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;LogToFile&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;debug.log&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;debug&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;fatal:&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Exit&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;f&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;p&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tea&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewProgram&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;model&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;tea&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WithAltScreen&lt;/span&gt;(), &lt;span style="color:#a6e22e"&gt;tea&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WithMouseCellMotion&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;p&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Start&lt;/span&gt;(); &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fatal&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;start failed: &amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;model &lt;code&gt;tui/model.go&lt;/code&gt; using &lt;a href="https://github.com/charmbracelet/bubbles/tree/master/list"&gt;list component&lt;/a&gt; from Bubbles component library.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tui&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;md&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;github.com/JohannesKaufmann/html-to-markdown&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;github.com/charmbracelet/bubbles/list&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;github.com/mmcdole/gofeed&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;model&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;list&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Model&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;choice&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;content&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;detail&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;quitting&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;width&lt;/span&gt; = &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;height&lt;/span&gt; = &lt;span style="color:#ae81ff"&gt;40&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;title&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;RSS Reader&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NewModel&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;data&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gofeed&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Feed&lt;/span&gt;) (&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;model&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;items&lt;/span&gt; []&lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Item&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;converter&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;md&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewConverter&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;rssItem&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;range&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Items&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;markdown&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;converter&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ConvertString&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;rssItem&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Description&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Convert to markdown&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;item&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;title&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;rssItem&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Title&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;desc&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Published at &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rssItem&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Published&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;\n\n&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;markdown&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;items&lt;/span&gt; = append(&lt;span style="color:#a6e22e"&gt;items&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;l&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;New&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;items&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewDefaultDelegate&lt;/span&gt;(), &lt;span style="color:#a6e22e"&gt;width&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;height&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;l&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Title&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;title&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;model&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;l&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;choice&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;detail&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;interaction with interface is covered by &lt;code&gt;tui/update.go&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tui&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tea&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;github.com/charmbracelet/bubbletea&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;model&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;Update&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;msg&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tea&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Msg&lt;/span&gt;) (&lt;span style="color:#a6e22e"&gt;tea&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Model&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;tea&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Cmd&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;switch&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;msg&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;msg&lt;/span&gt;.(&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tea&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WindowSizeMsg&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetWidth&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;msg&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Width&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tea&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;KeyMsg&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;switch&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keypress&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;msg&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(); &lt;span style="color:#a6e22e"&gt;keypress&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;q&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;ctrl+c&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;quitting&lt;/span&gt; = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;tea&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Quit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;enter&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;ok&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SelectedItem&lt;/span&gt;().(&lt;span style="color:#a6e22e"&gt;item&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ok&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;detail&lt;/span&gt; = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;choice&lt;/span&gt; = string(&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;title&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;content&lt;/span&gt; = string(&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;desc&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;b&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;detail&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;choice&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;content&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;p&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;detail&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;changeIndex&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Index&lt;/span&gt;() &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;changeIndex&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;changeIndex&lt;/span&gt; = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Select&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;changeIndex&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;ok&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SelectedItem&lt;/span&gt;().(&lt;span style="color:#a6e22e"&gt;item&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ok&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;choice&lt;/span&gt; = string(&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;title&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;content&lt;/span&gt; = string(&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;desc&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;n&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;detail&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;changeIndex&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Index&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;maxLength&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; len(&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Items&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;changeIndex&lt;/span&gt; &amp;gt; (&lt;span style="color:#a6e22e"&gt;maxLength&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;changeIndex&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;maxLength&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Select&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;changeIndex&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;ok&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SelectedItem&lt;/span&gt;().(&lt;span style="color:#a6e22e"&gt;item&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ok&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;choice&lt;/span&gt; = string(&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;title&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;content&lt;/span&gt; = string(&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;desc&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cmd&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tea&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Cmd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;cmd&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Update&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;msg&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;cmd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;and view &lt;code&gt;tui/view.go&lt;/code&gt; take care about rendering. I&amp;rsquo;m using &lt;a href="https://github.com/charmbracelet/glamour"&gt;glamour&lt;/a&gt; for rendering markdown content.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tui&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;github.com/charmbracelet/glamour&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;model&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;View&lt;/span&gt;() &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;s&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; len(&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;choice&lt;/span&gt;) &amp;gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;s&lt;/span&gt; &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;# &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;title&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;s&lt;/span&gt; &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;\n## &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;choice&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;s&lt;/span&gt; &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;\n\n&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;s&lt;/span&gt; &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;renderer&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;glamour&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewTermRenderer&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;glamour&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WithAutoStyle&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;glamour&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WithWordWrap&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;width&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;out&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;renderer&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Render&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;s&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;out&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;View&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Whole demo is recorded using &lt;a href="https://github.com/charmbracelet/vhs"&gt;VHS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.prskavec.net/post/demo.gif"
alt=""
loading="lazy"
decoding="async"&gt;
&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Bubble Tea is a solid library for building terminal UIs in Go. Whether you&amp;rsquo;re writing a small command-line tool or a more involved interactive app, it gives you what you need without much ceremony.&lt;/p&gt;
&lt;p&gt;I had talk about Bubble Tea on &lt;a href="https://www.youtube.com/watch?v=nuLZPGTvs0Y"&gt;Go meetup #15&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Exploring Terramate - A Solution to Terraform Orchestration Challenges</title><link>https://www.prskavec.net/post/exploring-terramate/</link><pubDate>Sun, 10 Mar 2024 12:22:10 +0100</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/exploring-terramate/</guid><description>Terramate complements Terraform with structure and tooling for managing multiple Terraform stacks, environments, and configurations. It targets the pain points of large-scale Terraform: dependency management, stack management, and environment differentiation.</description><content:encoded>&lt;p&gt;Terraform by HashiCorp has become a leading tool for defining and provisioning infrastructure as code. Its declarative configuration language lets developers and operations teams manage infrastructure precisely. But as projects grow in size and complexity, you run into orchestration, state management, and configuration spread across many environments. Terramate is built to help with exactly those problems.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.prskavec.net/post/exploring-terramate/./featured.jpeg"
alt=""
loading="lazy"
decoding="async"&gt;
&lt;/p&gt;
&lt;h2 id="what-terramate-does"&gt;What Terramate does&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://terramate.io/docs/cli/introduction#what-is-terramate-cli"&gt;Terramate&lt;/a&gt; complements Terraform with extra structure and tooling for managing multiple stacks, environments, and configurations. It targets the pain points of large-scale Terraform deployments: dependency management, stack management, and environment differentiation.&lt;/p&gt;
&lt;h2 id="key-features"&gt;Key features&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stack management:&lt;/strong&gt; Terramate organizes Terraform configurations into stacks, which makes complex infrastructure easier to manage. The hierarchy lets teams handle parts of their infrastructure in a more modular way.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Orchestration:&lt;/strong&gt; Terramate can define and manage dependencies between stacks, so components deploy in the right order, a common problem in multi-stack projects. You can define and reuse data across stacks with variables and metadata, and integrate Terramate into CI/CD pipelines to automate testing and deployment. It supports Terraform, OpenTofu, Terragrunt, Kubernetes, Pulumi, AWS CloudFormation, AWS Cloud Development Kit (CDK), Azure Resource Manager (ARM), Bicep, and others.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Code generation:&lt;/strong&gt; Generate code from templates and modules to cut duplication and keep configuration consistent across your infrastructure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Git integration:&lt;/strong&gt; Detects and manages stacks with changes in a branch, commit, or pull request.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="is-it-the-right-tool-for-you"&gt;Is it the right tool for you?&lt;/h2&gt;
&lt;p&gt;Whether Terramate fits depends on your situation: how complex your infrastructure is, the specific problems you have, and how your team likes to work. It&amp;rsquo;s worth weighing it against other tools in the Terraform space, like Terragrunt, before deciding.&lt;/p&gt;
&lt;p&gt;My use case is focus make our infrastructure in Azure Cloud automated. Our infrastructure team give us recommendation to make modules smallest as possible to work with their pipelines. It&amp;rsquo;s nice idea but it&amp;rsquo;s very hard to do it with complex infrastructure that have tens of modules. So I decided to use Terramate for my project. Split modules into stacks and use features for ordering to make graph how I want run them to be sure that order is right.&lt;/p&gt;
&lt;p&gt;I really like generating code. I make templates for backend and providers, that can works for everyone. But I can setup templates specific for project. We generate common variables, injected data from others team etc. That simplify many parts that was duplicated on many places.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Terramate helps if you&amp;rsquo;re orchestrating and managing large Terraform projects. Stack management, environment handling, and dependency management take some of the pain out of infrastructure as code. If you&amp;rsquo;re running into these problems, it&amp;rsquo;s worth a look.&lt;/p&gt;
&lt;p&gt;I will about more details how we using Terramate after our project will be released to public.&lt;/p&gt;</content:encoded></item><item><title>OpenTelemetry and Go Gin Framework</title><link>https://www.prskavec.net/post/otel-gin-go/</link><pubDate>Sat, 21 Oct 2023 09:06:54 +0100</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/otel-gin-go/</guid><description>How to make W3C TraceContext work for your application in the Go Gin Framework</description><content:encoded>&lt;p&gt;On &lt;a href="https://opentelemetry.io/"&gt;OpenTelemetry&lt;/a&gt; website, you can read the definition what is OpenTelemetry:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;OpenTelemetry is a collection of APIs, SDKs, and tools. Use it to instrument, generate, collect, and export telemetry data (metrics, logs, and traces) to help you analyze your software&amp;rsquo;s performance and behavior.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is not vendor free way to instrument your code. In Go, we don&amp;rsquo;t have in Oct 2023 full support. Traces, metrics, logs, are not in the stable version. You can still start using it, at least for traces and metrics.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m working on a project in which I need to make traces from Python to my Go API, and we want all stored in logs and traces. I started with &lt;a href="https://gin-gonic.com/"&gt;Gin framework&lt;/a&gt;, which is my default simple framework for APIs. There are many good alternatives, but I like to use this one.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s great if you Google &lt;code&gt;otel gin&lt;/code&gt; you get &lt;a href="https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"&gt;otelgin&lt;/a&gt; library for instrument your code.&lt;/p&gt;
&lt;p&gt;You find good &lt;a href="https://github.com/open-telemetry/opentelemetry-go-contrib/blob/instrumentation/github.com/gin-gonic/gin/otelgin/example/v0.45.0/instrumentation/github.com/gin-gonic/gin/otelgin/example/server.go"&gt;example&lt;/a&gt; where you see how to use it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Use&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;otelgin&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Middleware&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;my-server&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You add a new line to your code with &lt;code&gt;otelgin&lt;/code&gt; middleware, and you are done! Or not?&lt;/p&gt;
&lt;h2 id="context-propagation"&gt;Context Propagation&lt;/h2&gt;
&lt;p&gt;If you need to connect traces across services, you need setup propagation, and you find in every example of your tracer.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;otel&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetTextMapPropagator&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;propagation&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewCompositeTextMapPropagator&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;propagation&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TraceContext&lt;/span&gt;{}, &lt;span style="color:#a6e22e"&gt;propagation&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Baggage&lt;/span&gt;{}))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I will focus on &lt;code&gt;propagation.TraceContext{}&lt;/code&gt;. &lt;a href="https://www.w3.org/TR/trace-context/"&gt;TraceContext&lt;/a&gt; is W3C spec on how to pass information about TraceId, SpanId across services in Header &lt;code&gt;traceparent&lt;/code&gt;. All work for you if you call another system. But it doesn&amp;rsquo;t work for me.&lt;/p&gt;
&lt;p&gt;I tested with this command:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl -H &amp;#34;traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01&amp;#34; http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I can&amp;rsquo;t find my correct traceId in my logs.&lt;/p&gt;
&lt;p&gt;You need to configure &lt;code&gt;otelgin&lt;/code&gt; middleware with the propagator too. Not just tracer.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;otelginOption&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;otelgin&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WithPropagators&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;propagation&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TraceContext&lt;/span&gt;{})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Use&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;otelgin&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Middleware&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;my-server&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;otelginOption&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After that, all start working.&lt;/p&gt;
&lt;h2 id="how-to-debug-it"&gt;How to debug it?&lt;/h2&gt;
&lt;p&gt;I use two ways to debug.&lt;/p&gt;
&lt;p&gt;First, I&amp;rsquo;m using &lt;a href="https://github.com/CtrlSpice/otel-desktop-viewer"&gt;otel-desktorp-viewer&lt;/a&gt; to debug traces locally without running an observability server.&lt;/p&gt;
&lt;p&gt;Second, I&amp;rsquo;m using &lt;code&gt;log/slog&lt;/code&gt; library for logging (available in Go 1.21+). I make simple logging middleware for Gin that records traceId and spanId.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GinSlogMiddleware&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;logger&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Logger&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;gin&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HandlerFunc&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gin&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Context&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;start&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Now&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;UTC&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;path&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;URL&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;query&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;URL&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;RawQuery&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Next&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Context&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;end&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Now&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;UTC&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;latency&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;end&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sub&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;start&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Group&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Int&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Writer&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Status&lt;/span&gt;()),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;method&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Method&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;path&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;query&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;query&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;ip&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ClientIP&lt;/span&gt;()),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;user-agent&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;UserAgent&lt;/span&gt;()),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Duration&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;latency&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;latency&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; len(&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Errors&lt;/span&gt;) &amp;gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;e&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;range&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Errors&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Errors&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;logger&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ErrorContext&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;e&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;logger&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;InfoContext&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;path&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can see that the key is logging with Context, which contains information about traces. Context handler will log information about traces.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ContextHandler&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Handler&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;h&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ContextHandler&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;Handle&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;context&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Context&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Record&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddAttrs&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;h&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;addTraceFromContext&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;)&lt;span style="color:#f92672"&gt;...&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;h&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Handler&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Handle&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;h&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ContextHandler&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;addTraceFromContext&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;context&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Context&lt;/span&gt;) (&lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; []&lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Attr&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;span&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;trace&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SpanContextFromContext&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;traceID&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;span&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TraceID&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;spanID&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;span&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SpanID&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;traceGroup&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Group&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;trace&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;traceID&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;spanGroup&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Group&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;span&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;spanID&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; = append(&lt;span style="color:#a6e22e"&gt;as&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;traceGroup&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; = append(&lt;span style="color:#a6e22e"&gt;as&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;spanGroup&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And my logger init looks as this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;InitLog&lt;/span&gt;() &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Logger&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;jsonHandler&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewJSONHandler&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Stdout&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ctxHandler&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ContextHandler&lt;/span&gt;{&lt;span style="color:#a6e22e"&gt;jsonHandler&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;slog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;New&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctxHandler&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can make a custom JSONHandler if you need to override the logging format, for example, for support &lt;a href="https://www.elastic.co/guide/en/ecs/current/index.html"&gt;Elastic ECS&lt;/a&gt; if your destination is Elastic stack.&lt;/p&gt;
&lt;p&gt;Resources and libraries supporting &lt;code&gt;log/slog&lt;/code&gt; aren&amp;rsquo;t always ready, but I expected that will resolved in the future.&lt;/p&gt;</content:encoded></item><item><title>Watch out for basic HTTP client settings in Go</title><link>https://www.prskavec.net/post/watch_out_for_basic_http_client_settings_in_go/</link><pubDate>Wed, 25 Jan 2023 13:55:01 +0100</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/watch_out_for_basic_http_client_settings_in_go/</guid><description>Most programming languages don't have basic settings for HTTP made to run in production.</description><content:encoded>&lt;p&gt;Many articles have been written about this problem, but I keep seeing this problem coming back over and over again. Most programming languages don&amp;rsquo;t have basic settings for HTTP made to run in production. We&amp;rsquo;ll demonstrate this with the example of Go, but other languages are often similar, sometimes better, sometimes worse.&lt;/p&gt;
&lt;p&gt;If you take a standard library, and you want to make a request, it will come out something like this.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;url&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:3000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;httpClient&lt;/span&gt; = &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Client&lt;/span&gt;{}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;httpClient&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All the HTTP client settings are given by &lt;a href="https://go.dev/src/net/http/transport.go"&gt;default transport&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DefaultTransport&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RoundTripper&lt;/span&gt; = &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Transport&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Proxy&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;ProxyFromEnvironment&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;DialContext&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;defaultTransportDialContext&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;net&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Dialer&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Timeout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;30&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;KeepAlive&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;30&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ForceAttemptHTTP2&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;MaxIdleConns&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;IdleConnTimeout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;90&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;TLSHandshakeTimeout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ExpectContinueTimeout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Your HTTP client will behave according to that basic setup. All the timeouts are defined, and you can&amp;rsquo;t simply hit that client later.&lt;/p&gt;
&lt;p&gt;If you want to tweak it, if you need to touch the base timeout, you can set it like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;url&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:3000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;httpClient&lt;/span&gt; = &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Client&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Timeout&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;httpClient&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will override the 30s timeout to 10s which is about the maximum value I would accept for a synchronous POST request, although it&amp;rsquo;s well beyond what I&amp;rsquo;m willing to expect as a user.&lt;/p&gt;
&lt;p&gt;The way to keep things under control is to have a custom client where we overload the transport and use custom settings.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;HTTPClientSettings&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Connect&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Duration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ConnKeepAlive&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Duration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ExpectContinue&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Duration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;IdleConn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Duration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;MaxAllIdleConns&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;MaxHostIdleConns&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ResponseHeader&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Duration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;TLSHandshake&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Duration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NewHTTPClientWithSettings&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;httpSettings&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;HTTPClientSettings&lt;/span&gt;) (&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Client&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;client&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Client&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tr&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Transport&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ResponseHeaderTimeout&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;httpSettings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ResponseHeader&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Proxy&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ProxyFromEnvironment&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;DialContext&lt;/span&gt;: (&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;net&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Dialer&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;KeepAlive&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;httpSettings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ConnKeepAlive&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;DualStack&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Timeout&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;httpSettings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Connect&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }).&lt;span style="color:#a6e22e"&gt;DialContext&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;MaxIdleConns&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;httpSettings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;MaxAllIdleConns&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;IdleConnTimeout&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;httpSettings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;IdleConn&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;TLSHandshakeTimeout&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;httpSettings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TLSHandshake&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;MaxIdleConnsPerHost&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;httpSettings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;MaxHostIdleConns&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ExpectContinueTimeout&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;httpSettings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ExpectContinue&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// So client makes HTTP/2 requests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http2&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ConfigureTransport&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;tr&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;client&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we have custom settings, then the usage is similar, just directly specifying the individual parameters.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;url&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:3000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;httpClient&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NewHTTPClientWithSettings&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;HTTPClientSettings&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Connect&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ExpectContinue&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;IdleConn&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;90&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ConnKeepAlive&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;30&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;MaxAllIdleConns&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;MaxHostIdleConns&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ResponseHeader&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;TLSHandshake&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Got an error creating custom HTTP client:&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;httpClient&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a good solution, but it still needs to solve everything. Sometimes we would like a different timeout or deadline for a specific case (PUT, POST), and we want to keep the HTTP client the same. This can be achieved with &lt;code&gt;context.WithTimeout&lt;/code&gt; or &lt;code&gt;context.WithCancel&lt;/code&gt; context. The usage is then seen in the following example.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;url&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:3000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewRequest&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;MethodGet&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fatal&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;cancel&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;context&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WithCancel&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;context&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Background&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AfterFunc&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() { &lt;span style="color:#a6e22e"&gt;cancel&lt;/span&gt;() })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;httpClient&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NewHTTPClientWithSettings&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;HTTPClientSettings&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Connect&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ExpectContinue&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;IdleConn&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;90&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ConnKeepAlive&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;30&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;MaxAllIdleConns&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;MaxHostIdleConns&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ResponseHeader&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;TLSHandshake&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Got an error creating custom HTTP client:&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fatal&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;httpClient&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Do&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WithContext&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Still, it can happen that you are not on the client but on the server side, and you can&amp;rsquo;t solve it entirely well. That&amp;rsquo;s why in Go 1.20, they added &lt;a href="https://pkg.go.dev/net/http@go1.20rc2#ResponseController"&gt;&lt;code&gt;ResponseController&lt;/code&gt;&lt;/a&gt; where you get access to these methods:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Flush&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;FlushError&lt;/span&gt;() &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt; &lt;span style="color:#75715e"&gt;// alternative Flush returning an error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Hijack&lt;/span&gt;() (&lt;span style="color:#a6e22e"&gt;net&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Conn&lt;/span&gt;, &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;bufio&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ReadWriter&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;SetReadDeadline&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;deadline&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Time&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;SetWriteDeadline&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;deadline&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Time&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here you can, for example, extend the request deadline if you need it. Find the complete &lt;a href="https://github.com/golang/go/issues/16100"&gt;discussion of timeout handling in Handler on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So remember to set up HTTP clients for production, test performance, measure latency and adjust your settings accordingly. It&amp;rsquo;s good to know your limits and adjust accordingly. Please keep it to the basic settings in production. It can cause annoying errors that are hard to detect because they often look like random errors.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in this, come and talk about Go and release 1.20 at &lt;a href="https://www.meetup.com/prague-golang-meetup/events/291042846/"&gt;the next Prague Go Meetup on February 21&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Pipelines as Code</title><link>https://www.prskavec.net/post/pipelines_as_code/</link><pubDate>Mon, 28 Nov 2022 14:08:54 +0100</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/pipelines_as_code/</guid><description>Using Dagger.io Go SDK for Pipelines implementation.</description><content:encoded>&lt;p&gt;&amp;ldquo;Pipelines as code&amp;rdquo; is not exactly a new concept, I heard about it a few years ago with the launch of &lt;a href="https://tekton.dev/"&gt;Tecton&lt;/a&gt;. I wrote a design document at that time to create a new CI that would accept various rules (Jenkins, CircleCI, TravisCI and Github Actions) and you wouldn&amp;rsquo;t have to learn new syntax. Unfortunately, the project never materialized, so I didn&amp;rsquo;t pursue this much longer.&lt;/p&gt;
&lt;p&gt;Solomon Hykes left Docker a few years ago and started &lt;a href="https://dagger.io/"&gt;Dagger.io&lt;/a&gt;, where they started adopting &lt;a href="https://cuelang.org/"&gt;Cue lang&lt;/a&gt; and working on an interesting new project. When I got access to early access I wasn&amp;rsquo;t too excited about it, because writing pipelines in Cue is not something I would recommend to developers. After experiencing how hard it was to push Grafana dashboards in &lt;a href="https://jsonnet.org/"&gt;Jsonnet&lt;/a&gt;, I&amp;rsquo;m cautious.&lt;/p&gt;
&lt;p&gt;A few weeks ago, they launched a technical preview of &lt;a href="https://docs.dagger.io/sdk/go/"&gt;Dagger Go SDK&lt;/a&gt; and when I tried out the &lt;a href="https://youtu.be/GgMskf-znh4"&gt;basic demo&lt;/a&gt; based on the video, I thought it was great. I decided to test on my project &lt;code&gt;ga-badge&lt;/code&gt; that generates badge for Github Actions. Here&amp;rsquo;s the relevant &lt;a href="https://github.com/abtris/ga-badge/pull/49/files"&gt;PR&lt;/a&gt; with the changes, showing how simple the change is. Then here&amp;rsquo;s a log of when it&amp;rsquo;s dropped. I use &lt;a href="https://magefile.org/"&gt;Mage&lt;/a&gt; for dropping as recommended by the Dagger folks for the multi repository. I&amp;rsquo;m sure there are other ways to do it, but it seemed fine to me at first.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://asciinema.org/a/4DwhBANFV53kW7QgsJRMsUZ5E"&gt;&lt;img src="https://asciinema.org/a/4DwhBANFV53kW7QgsJRMsUZ5E.svg"
alt="asciicast"
loading="lazy"
decoding="async"&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll be talking about the Dagger Go SDK at &lt;a href="https://www.meetup.com/prague-golang-meetup/events/289247920/"&gt;Go Meetup #9&lt;/a&gt; on November 30, 2022, and if you&amp;rsquo;re interested, stop by the discussion.&lt;/p&gt;</content:encoded></item><item><title>How to make the perfect Grafana dashboard?</title><link>https://www.prskavec.net/post/how-to-make-perfect-grafana-dashboard/</link><pubDate>Fri, 30 Oct 2020 12:09:14 +0100</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/how-to-make-perfect-grafana-dashboard/</guid><description>You may think that your dashboard is perfect but the next incident can completely break this perception.</description><content:encoded>&lt;p&gt;First, there is no such thing as a perfect dashboard. You may think that your dashboard is perfect but the next incident can completely break this perception. This is normal, everything is imperfect in this world. We have to work with that. Improving dashboards is an iterative process. You have to make it a part of the culture in the same way as post mortems or other best practices for running services.&lt;/p&gt;
&lt;h2 id="how-to-start"&gt;How to start&lt;/h2&gt;
&lt;p&gt;I always recommend starting with simple things. First, I’d like to recommend reading Grafana best practices articles&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. There are lists of things that will help you formulate the overall high-level strategy. Don’t fill all the things at once - start slow. You can always add more metrics as you go.&lt;/p&gt;
&lt;p&gt;It’s good to start with the first dashboard and basic things, such as absent metrics (to detect if your metrics are even working) and up/down metrics (are all instances of database healthy?).&lt;/p&gt;
&lt;p&gt;Generally, it’s good not to forget about observability rules. I prefer The Four Golden Signals&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. You can also use the USE method&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt; or RED method&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Finally, add important business metrics that are relevant to your service (number of new registrations, outgoing emails, created teams or projects, etc.).&lt;/p&gt;
&lt;h2 id="what-to-do-next"&gt;What to do next&lt;/h2&gt;
&lt;p&gt;Maintaining dashboards is not an easy task. Creating hundreds of dashboards is simple but maintaining them is a nightmare. How to prevent this? I recommend thinking about structure and using the drill-down strategy. Use tools, such as &lt;a href="https://jsonnet.org/"&gt;Jsonnet&lt;/a&gt;, to generate consistent dashboard over teams, environments and services. You will be able to reuse common parts, such as metrics for load balancers or databases used across the company.&lt;/p&gt;
&lt;p&gt;What is a drill-down strategy? It means that you have a central dashboard with overview as a single point of contact for your oncall engineers and then there are more detailed dashboards linked from the central dashboard.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.prskavec.net/post/dashboard-drilldown.jpg"
alt=""
loading="lazy"
decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;In this dashboard, they see the most important things. You need work to minimize the noise there and focus only on important signals.&lt;/p&gt;
&lt;p&gt;You have to keep in mind that nobody is constantly looking at the dashboard (except the guys working in &lt;a href="https://en.wikipedia.org/wiki/Network_operations_center"&gt;NOC&lt;/a&gt;). You have to think about it and make the visualization and error signaling significant to the person at 2am in the morning, after the pager woke them up. If a signal is visible, the operator can click on a graph and to get to another dashboard that gives more insight into the problem. This can be repeated for more levels of depth. Based on my experience, it is only useful to stick to a maximum of 2 to 4 levels of depth.&lt;/p&gt;
&lt;p&gt;Dashboards should be using variables and templates. You can let the user switch between different variable values to display the relevant data for a data region, availability domain or a specific application.&lt;/p&gt;
&lt;p&gt;Make templates for reusable components to make your setup &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;DRY&lt;/a&gt; and easy to maintain, such as a common header for all your dashboards.&lt;/p&gt;
&lt;p&gt;Links to detailed dashboards should also use variables and templates, to minimize the amount of code. For instance, you can reuse the same dashboard type for all your applications.&lt;/p&gt;
&lt;h2 id="make-the-process-work"&gt;Make the process work!&lt;/h2&gt;
&lt;p&gt;Create a process to review the dashboards and include it in your regular operations review. You should iterate on improvements of metrics and dashboards, ideally on a weekly basis. The best way to do this review is to make it a part of your incident post-mortem process. You can use your incidents to inspire you to improve your alarms and margins for your dashboards.&lt;/p&gt;
&lt;p&gt;Alarms have always a higher priority than your dashboards and you should focus on good alarms with actionable runbooks for solving issues.&lt;/p&gt;
&lt;p&gt;Dashboards have to add more context and overview for your service. This helps with finding correlation with other events&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;. If you can, integrate the logs and traces into a single tool or add links between them to find what you need.&lt;/p&gt;
&lt;p&gt;Take regular feedback from the team and operators to make it easy to make changes in alarms and maintain the dashboards.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://grafana.com/docs/grafana/latest/best-practices/"&gt;https://grafana.com/docs/grafana/latest/best-practices/&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;&lt;a href="https://landing.google.com/sre/sre-book/chapters/monitoring-distributed-systems/#xref_monitoring_golden-signals"&gt;traffic, errors, latency, saturation&lt;/a&gt;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;&lt;a href="http://www.brendangregg.com/usemethod.html"&gt;utilization, saturation, errors&lt;/a&gt;&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;&lt;a href="https://www.slideshare.net/grafana/the-red-method-how-to-monitoring-your-microservices"&gt;rate, errors, duration&lt;/a&gt;&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;for example deployments are displayed as annotations in &lt;a href="https://grafana.com/"&gt;Grafana&lt;/a&gt;, logs - &lt;a href="https://grafana.com/oss/loki/"&gt;Loki&lt;/a&gt; and traces - &lt;a href="https://opentracing.io/"&gt;Opentracing&lt;/a&gt;/&lt;a href="https://grafana.com/oss/tempo/"&gt;Tempo&lt;/a&gt;&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title>The Future of Continuous Integration Engines</title><link>https://www.prskavec.net/post/the-future-of-continuous-integration-engines/</link><pubDate>Fri, 23 Oct 2020 20:02:34 +0200</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/the-future-of-continuous-integration-engines/</guid><description>Continuous Integration Engines (CIEs) have a long history. The term Continuous Integration (CI) has been used for the first time by Grady Booch in 1991.</description><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Thanks to Daria Grudzien for correction and comments.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Continuous Integration Engines (CIEs) have a long history. The term Continuous Integration (CI) has been used for the first time by Grady Booch in 1991 &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. First known CIE is Tinderbox &lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt; (1997). Personally I started using CruiseControl (launched in 2001) around 2003. After 2005 Java programmers switched from CruseControl to Hudson. Few years later phpUnderControl (2007) became a popular tool, and later on Hudson gave way to Jenkins (released in 2011). These days Jenkins is being replaced by JenkinsX (2017). Many other CIEs have been developed in the meantime: Teamcity (2006), Bamboo (2008), as well as other commercial versions and cloud first CIEs like Travis (2011) , CircleCI (2016) and Github Actions (2018).&lt;/p&gt;
&lt;p&gt;Nowadays we have a wide range of CIEs available on every platform, which solve a variety of problems. To help select the best one for the job, in 2019 I wrote a post &lt;a href="https://blog.apiary.io/What-you-need-from-your-CI"&gt;&amp;ldquo;What you need from your CI&amp;rdquo;&lt;/a&gt; which is still valid.&lt;/p&gt;
&lt;p&gt;Today I’d like to introduce a new question— what is the future of CIE and what we can expect as developers and customers of CIE software.&lt;/p&gt;
&lt;h2 id="modularity"&gt;Modularity&lt;/h2&gt;
&lt;p&gt;These days modularity is key. Architectural changes introduced by tools like JenkinsX (2017) and &lt;a href="https://tekton.dev/"&gt;Tekton pipelines (2018)&lt;/a&gt; help in designing new CIEs with that feature in mind. Such an approach allows developers to easily change the language describing workflows by replacing modules which translate workflow description to &lt;a href="https://tekton.dev/docs/pipelines/"&gt;Tekton CRDs&lt;/a&gt; (Custom Resource Definition). I like that approach and believe that the teams behind Tekton and JenkinsX are doing great work on increasing flexibility of workflow development.&lt;/p&gt;
&lt;h2 id="flexibility"&gt;Flexibility&lt;/h2&gt;
&lt;p&gt;Flexibility is the second consideration. I don’t think that all CIEs have to be running on Kubernetes. Good alternatives are &lt;a href="https://www.nomadproject.io/"&gt;HashiCorp Nomad&lt;/a&gt;,the simplest platform like AWS Lambda and &lt;a href="https://github.com/lambci/lambci"&gt;LambdaCI&lt;/a&gt; or simply a local computer. Portable software which can be easily run anywhere can be very useful. This still gives space to different CIEs and can be a major differentiator among them.&lt;/p&gt;
&lt;h2 id="language"&gt;Language&lt;/h2&gt;
&lt;p&gt;This approach isn’t available among the existing tooling, but it would be nice to have a de facto standard for describing workflows for CIE. Thanks to the modular every CI can support such a standard workflow description, making switching CIEs for projects would become easy. Currently Yaml format is the king - it’s used by Travis, CircleCI, Github Actions among others. But I hope that we will find a better approach like HCL (HashiCorp Configuration Language) which is more of a language than a serialization format (&lt;a href="https://noyaml.com"&gt;noyaml.com&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id="observability"&gt;Observability&lt;/h2&gt;
&lt;p&gt;It’s very hard to make any good decisions without sufficient data. Something I see quite often is a lack of good solutions for handling builds, failing tests, flaky tests etc. For applications we have &lt;a href="https://openmetrics.io"&gt;open metrics&lt;/a&gt;, Prometheus is commonly supported by many libraries and applications. An open format like this is missing in CIEs and it would be great to fill those gaps.&lt;/p&gt;
&lt;h2 id="scalability"&gt;Scalability&lt;/h2&gt;
&lt;p&gt;Speed, reliability and cost are all relevant from the perspective of scalability. In the current price model you often pay for VMs which is cons ineffective for teams collocated in one timezone. If you have a “follow the sun” model and similar resource usage across workdays — what about weekends? Many large companies have their own CIE, but often it’s run on dedicated hardware which doesn’t scale up with the number of developers or tasks in the queue.&lt;/p&gt;
&lt;p&gt;It’s possible to keep the same spending level while improving developer experience by scaling the CI up and down depending on current usage. If you discover that your usage pick lasts for 4-5 hours a day—you can scale down the number of instances running CI outside of those hours. But don’t forget that running builds in parallel needs to be fast by default.&lt;/p&gt;
&lt;h2 id="bringing-it-all-together"&gt;Bringing It All Together&lt;/h2&gt;
&lt;p&gt;There are multiple CIEs available in the market today addressing some of the considerations I listed above. Do you know of a Continuous Integration Engine which addresses all those, or do still we have gaps in the market? Is there space for someone to make a significant impact on the market?&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Continuous_integration"&gt;https://en.wikipedia.org/wiki/Continuous_integration&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;In 1994, Grady Booch used the phrase continuous integration in Object-Oriented Analysis and Design with Applications (2nd edition)&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;TinderBox by Mozilla - &lt;a href="https://oduinn.com/2014/06/04/farewell-to-tinderbox/"&gt;https://oduinn.com/2014/06/04/farewell-to-tinderbox/&lt;/a&gt;&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title>Grafana dashboards and Jsonnet</title><link>https://www.prskavec.net/post/grafana-jsonnet/</link><pubDate>Sat, 17 Oct 2020 19:57:41 +0200</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/grafana-jsonnet/</guid><description>I'm using grafana for more than year and Jsonnet helps me make dashboards easy to maintain and this is my experience with it.</description><content:encoded>&lt;p&gt;I know &lt;a href="https://grafana.com/"&gt;Grafana&lt;/a&gt; for a long time, but in my previous career, we used different tools as &lt;a href="https://www.datadoghq.com/"&gt;Datadog&lt;/a&gt; or other older tools. We switched from Datadog to Grafana last year to unite with other teams in one central place.&lt;/p&gt;
&lt;p&gt;I was looking at how others maintain their dashboards and some teams just edit and store JSON outputs from Dashboards. I heard on &lt;a href="https://monitorama.com/"&gt;Monitorama&lt;/a&gt; conference about &lt;a href="https://jsonnet.org/"&gt;Jsonnet&lt;/a&gt; a few years ago. I thought this would be a better approach than editing JSON files directly.&lt;/p&gt;
&lt;p&gt;Grafana has a library called &lt;a href="https://github.com/grafana/grafonnet-lib"&gt;grafonnet-lib&lt;/a&gt; that helps you with creating widgets in grafana via jsonnet. You can find prepared components for a dashboard, annotation, graph, singlestat, template, and more. What I find out later was that many options and parts were missing. You need to edit directly or send PRs into the library. I sent a few PRs to fix it. We found more and more missing parameters and I gave up after seeing that many PR were not merged. Today I found that the Grafana team is working on a better solution. In Grafana 7.0 they are generating &lt;a href="https://github.com/grafana/grafonnet-lib/tree/master/grafonnet-7.0"&gt;jsonnet&lt;/a&gt; from &lt;a href="https://github.com/grafana/dashboard-spec"&gt;spec&lt;/a&gt;. I hope this will be a good solution to have all parameters available and without the need to fix it by ourselves.&lt;/p&gt;
&lt;h2 id="how-we-structure-our-dashboards"&gt;How we structure our dashboards&lt;/h2&gt;
&lt;p&gt;This is list of files in our repository:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;├── Makefile
├── README.md
├── build
│ ├── dev
│ ├── prod
│ └── staging
├── dashboards
│ ├── actions.jsonnet
│ ├── backend.jsonnet
│ ├── mongo.jsonnet
│ ├── oncall.jsonnet
│ ├── redis.jsonnet
│ └── traffic.jsonnet
├── environments
│ ├── dev.libsonnet
│ ├── prod.libsonnet
│ └── staging.libsonnet
├── grafonnet (copy of library)
└── templates
├── config.libsonnet
├── header.libsonnet
├── mql.libsonnet
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The makefile has these commands to prepare dashboards and command for deploy.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ make
Usage:
make &amp;lt;target&amp;gt;
Targets:
help Display this help
clean Clean built dashboards
build Build dashboards
deploy Deploy dashboards
auth Get fresh token from grafana
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We use the same dashboards for every environment and there are configuration changes that are stored in folder &lt;code&gt;environments&lt;/code&gt;. We use one grafana instance for all environments but all settings related include&lt;code&gt; name&lt;/code&gt; and &lt;code&gt;title&lt;/code&gt; of dashboards are specific for the environment. Dashboards have similar names as &lt;code&gt;Team - Environment - Service&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;template&lt;/code&gt; folder we have &lt;code&gt;config.libsonnet&lt;/code&gt; that is the main config file reused in all dashboards. In the folder we have common parts, such as a header with the message: &lt;code&gt;# Don\'t edit here. Edit in repository, and make PR there!&lt;/code&gt;. We extend code as &lt;a href="https://jsonnet.org/learning/tutorial.html"&gt;tutorial&lt;/a&gt; with &lt;code&gt;libsonnet&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;The command to compile jsonnet into &lt;code&gt;build/*.json&lt;/code&gt; looks like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;jsonnet --ext-code-file &amp;#34;env=environments/$(patsubst %/,%, $(dir $(patsubst build/%, %, $@))).libsonnet&amp;#34; dashboards/$(basename $(notdir $@)).jsonnet &amp;gt; $@
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From the files in the &lt;code&gt;dashboards&lt;/code&gt; folder, we create json output that is split to folders by environments. We are following guidance from &lt;a href="https://grafana.com/blog/2019/05/29/grafana-labs-at-kubecon-foolproof-kubernetes-dashboards-for-sleep-deprived-on-calls/"&gt;Grafana Labs at KubeCon: Foolproof Kubernetes Dashboards for Sleep-Deprived On Calls&lt;/a&gt; that recommends Hierarchical Dashboards.&lt;/p&gt;
&lt;p&gt;Our dashboards are organized in this order just on two levels. We have the main one called &lt;code&gt;oncall&lt;/code&gt; for overview with summary for traffic, databases, etc. and if you click on that in the main dashboard that opens for you a more detailed dashboard with specific information and you can select region, availability zone/domain or application in a template.&lt;/p&gt;
&lt;p&gt;We are using a jsonnet standard library to generate the list for templates that are big and easy to maintain.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;.addTemplate(
template.new(
&amp;#39;id&amp;#39;,
&amp;#39;$datasource&amp;#39;,
&amp;#39;map($app;&amp;#39; + std.manifestJsonEx(dataMap, &amp;#39;&amp;#39;) + &amp;#39;)&amp;#39;,
hide = 2,
refresh = 1,
)
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and if we get issues with newlines in a map we can easily fix using standard library too and remove newlines:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;.addTemplate(
template.new(
&amp;#39;id&amp;#39;,
&amp;#39;$datasource&amp;#39;,
&amp;#39;map($app;&amp;#39; + std.strReplace(std.manifestJsonEx(dataMap, &amp;#39;&amp;#39;),&amp;#39;\n&amp;#39;,&amp;#39;&amp;#39;) + &amp;#39;)&amp;#39;,
hide = 2,
refresh = 1,
)
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Making these changes manually in many places would be exhausting.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;The advantage of using jsonnet is more organized code for dashboards. We try to make it dry and if we have some graph type in the dashboard more times, we create a function to generate it with parameters that are different, such as application name. All code is directly in the dashboard jsonnet file. You can move that to separate files if you need it in more places.&lt;/p&gt;
&lt;p&gt;The disadvantage of this solution is the learning curve, people don&amp;rsquo;t know jsonnet, and there isn&amp;rsquo;t much documentation on how to learn it. But from the year of experience, I see more benefits.&lt;/p&gt;</content:encoded></item><item><title>Updating README in Github personal repository</title><link>https://www.prskavec.net/post/github-personal-repo/</link><pubDate>Sat, 11 Jul 2020 08:45:17 +0200</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/github-personal-repo/</guid><description>How to automate changes in README.md in personal repository in Github.</description><content:encoded>&lt;p&gt;Github introduce new feature for repository that have special name as your nickname on Github. If you add &lt;code&gt;README.md&lt;/code&gt; there they display content in profile header.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.prskavec.net/post/github-personal-repo.jpg"
alt=""
loading="lazy"
decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;I see first on &lt;a href="https://twitter.com/robinpokorny/status/1281202047086444545"&gt;twitter&lt;/a&gt; where my friend mention this. I like it and make similar version. Later I see this &lt;a href="https://twitter.com/simonw/status/1281435464474324993"&gt;tweet&lt;/a&gt; about automate content of readme.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not big friend with python and I need something else there that I make my own version in Golang using Github Actions.&lt;/p&gt;
&lt;p&gt;First I created &lt;a href="https://github.com/abtris/abtris/blob/master/README.md.template"&gt;template for readme&lt;/a&gt; and use go to proceed template. I read RSS/Atom from my blog to update readme and I used just Golang standard library. Output is just printed to stdout.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;encoding/xml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;io/ioutil&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;net/http&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;text/template&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Item type for RSS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Item&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Title&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`xml:&amp;#34;title&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Link&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`xml:&amp;#34;link&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Desc&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`xml:&amp;#34;description&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;GUID&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`xml:&amp;#34;guid&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;PubDate&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`xml:&amp;#34;pubDate&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Channel type for RSS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Channel&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Title&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`xml:&amp;#34;title&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Link&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`xml:&amp;#34;link&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Desc&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`xml:&amp;#34;description&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Items&lt;/span&gt; []&lt;span style="color:#a6e22e"&gt;Item&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`xml:&amp;#34;item&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Rss type for RSS as root&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Rss&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Channel&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Channel&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`xml:&amp;#34;channel&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;readFeed&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;) []&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fatal&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;rss&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Rss&lt;/span&gt;{}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;decoder&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;xml&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewDecoder&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;decoder&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Decode&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;rss&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fatal&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;output&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; []&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;{}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;item&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;range&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rss&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Channel&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Items&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;output&lt;/span&gt; = append(&lt;span style="color:#a6e22e"&gt;output&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sprintf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;[%s](%s)\n&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;item&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Title&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;item&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Link&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;output&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ioutil&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ReadFile&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;README.md.template&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fatal&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;text&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; string(&lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;blogURL&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Getenv&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;BLOG_URL&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;rssItems&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;readFeed&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;blogURL&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;data&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Title&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Items&lt;/span&gt; []&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Title&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Last blog posts&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Items&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;rssItems&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;t&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;template&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;New&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;readme&amp;#34;&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Parse&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fatal&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;t&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Execute&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;os&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Stdout&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fatal&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For automating this I created &lt;a href="https://github.com/abtris/abtris/blob/master/.github/workflows/build.yml"&gt;workflow file &lt;code&gt;.github/workflows/build.yml&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Build README&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;push&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;workflow_dispatch&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;schedule&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;cron&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;0 4 * * *&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;jobs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;runs-on&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ubuntu-latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Check out repo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;actions/checkout@v2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Set up Go&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;actions/setup-go@v2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;go-version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;^1.14.3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;go version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Update README&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;env&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;BLOG_URL&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${{ secrets.BLOG_URL }}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: |-&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; go run main.go &amp;gt; README.md
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; cat README.md&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Commit and push if README changed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: |-&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; git diff
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; git config --global user.email &amp;#34;readme-bot@github.com&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; git config --global user.name &amp;#34;README-bot&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; git diff --quiet || (git add README.md &amp;amp;&amp;amp; git commit -m &amp;#34;Updated README&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; git push&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I run cron daily, you can change as you need it. I run my program &lt;code&gt;go run main.go&lt;/code&gt; and stdout is forward into file and commit into repository if diff exists. Github Actions manage access into repository and you don&amp;rsquo;t need add ssh key or something else. The blog URL is defined in repository &lt;code&gt;&amp;lt;repo&amp;gt;/settings/secrets&lt;/code&gt; with name &lt;code&gt;BLOG_URL&lt;/code&gt;, but can be directly set in this yaml file too as normal environment variable.&lt;/p&gt;</content:encoded></item><item><title>Running Terratest in CircleCI</title><link>https://www.prskavec.net/post/terratest-circleci/</link><pubDate>Wed, 06 Mar 2019 00:00:00 +0000</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/terratest-circleci/</guid><description>How using Terratest in CircleCI with docker in docker.</description><content:encoded>&lt;p&gt;Running golang test on CircleCI is easy but if you need DIND (docker in docker) that is made by &lt;code&gt;setup_remote_docker&lt;/code&gt; is all easy and you have something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;jobs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;docker&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;circleci/golang:1.13&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;TEST_RESULTS&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/tmp/test-results&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;checkout&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;setup_remote_docker&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mkdir -p $TEST_RESULTS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;restore_cache&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;keys&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;v1-pkg-cache&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;go get gotest.tools/gotestsum&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;docker pull appropriate/curl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cd test &amp;amp;&amp;amp; go mod download&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;run&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Run unit tests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; make test-unit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cp tmp/unit-report.xml /tmp/test-results/unit-report.xml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;save_cache&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;key&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;v1-pkg-cache&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;paths&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;/go/pkg&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;store_artifacts&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;path&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/tmp/test-results&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;destination&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;raw-test-output&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;store_test_results&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;path&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/tmp/test-results&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But the issue is the network that is limit for security reasons and this helper will not work in DIND mode.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http_helper&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;github.com/gruntwork-io/terratest/modules/http-helper&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// It can take a few seconds for the Docker container boot up, so retry a few times&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;maxRetries&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;timeBetweenRetries&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sprintf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:%d&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;serverPort&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Setup a TLS configuration to submit with the helper, a blank struct is acceptable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;tlsConfig&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tls&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Config&lt;/span&gt;{}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// website::tag::5::Verify that we get back a 200 OK with the expected text&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;http_helper&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HttpGetWithRetry&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;t&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;, &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;tlsConfig&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;expectedServerText&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;maxRetries&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;timeBetweenRetries&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;you need to replace &lt;code&gt;http_helper&lt;/code&gt; with docker container with curl:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;opts&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;docker&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;RunOptions&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Command&lt;/span&gt;: []&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;--retry&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;5&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;--retry-connrefused&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;-s&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;http://production_nginx:80/hello&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;OtherOptions&lt;/span&gt;: []&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;--network&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;testdockercomposelocal_teststack-network&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;tag&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;appropriate/curl&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;output&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;docker&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Run&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;t&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;tag&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;opts&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;assert&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Equal&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;t&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;expectedServerText&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;output&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The important thing is to have the same network with docker-compose that I&amp;rsquo;m using in the application and this container with curl with correct network parameter. Curl have advantage for support an exponential backoff algorithm.&lt;/p&gt;</content:encoded></item><item><title>New start in 2020</title><link>https://www.prskavec.net/post/getting-started/</link><pubDate>Wed, 20 Apr 2016 00:00:00 +0000</pubDate><author>ladislav@prskavec.net (Prskavec.Net)</author><guid>https://www.prskavec.net/post/getting-started/</guid><description>New start in 2020.</description><content:encoded>&lt;p&gt;I clean up old posts and restart this with a new look. I can focus on some best practices that I collected over a year that can help multiple people.&lt;/p&gt;
&lt;p&gt;This blog is powered by &lt;a href="https://jamstack.org"&gt;JAMStack&lt;/a&gt;. I&amp;rsquo;m using static generator &lt;a href="https://gohugo.io"&gt;Hugo&lt;/a&gt; and theme &lt;a href="https://sourcethemes.com/academic/docs/"&gt;Academic&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My workflow using Github and deploy and publish is using &lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt;. Netlify makes a preview site for every PR and that helps me review every change in site.&lt;/p&gt;</content:encoded></item></channel></rss>