<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: shelby spees (she/her)</title>
    <description>The latest articles on Forem by shelby spees (she/her) (@shelbyspees).</description>
    <link>https://forem.com/shelbyspees</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F86319%2Fc796cc0a-d0c9-44a9-9bc8-cf3f74c1da95.jpeg</url>
      <title>Forem: shelby spees (she/her)</title>
      <link>https://forem.com/shelbyspees</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/shelbyspees"/>
    <language>en</language>
    <item>
      <title>How to set up Twitter cards for your Hugo site</title>
      <dc:creator>shelby spees (she/her)</dc:creator>
      <pubDate>Mon, 14 Dec 2020 05:59:21 +0000</pubDate>
      <link>https://forem.com/shelbyspees/how-to-set-up-twitter-cards-for-your-hugo-site-2od8</link>
      <guid>https://forem.com/shelbyspees/how-to-set-up-twitter-cards-for-your-hugo-site-2od8</guid>
      <description>&lt;p&gt;My &lt;a href="https://gohugo.io"&gt;Hugo&lt;/a&gt; site is basically for vanity. So of course when I linked to one of my blog posts on Twitter today and the unfurl was super ugly, I had to go and fix it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N0MvDcgE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/bSr2n2T.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N0MvDcgE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/bSr2n2T.png" alt="Screenshot of a tweet from Shelby that says &amp;quot;Yuuuup&amp;quot; and links to her blog post &amp;quot;On Tech Management.&amp;quot; The image displayed in the Twitter card is an awkwardly-cropped version of Shelby's Nova ears logo from her website."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Grody.&lt;/p&gt;

&lt;p&gt;So how to fix? First we need to connect the dots on how this unfurl happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are social cards?
&lt;/h2&gt;

&lt;p&gt;An occupational hazard of doing devrel and technical marketing is that you learn that companies do a lot to optimize how their links look on social media.&lt;/p&gt;

&lt;p&gt;For any particular image post, you need to create multiple sizes so the image shows up correctly on each platform: Twitter vs. Facebook vs. LinkedIn vs. Instagram, etc. The best aspect ratio for Instagram is a square, but these other platforms couldn't agree on a standard aspect ratio or image size so the burden is on you to adjust for each one. Thankfully, there are &lt;a href="https://sproutsocial.com/insights/social-media-image-sizes-guide/"&gt;helpful articles from SEO people&lt;/a&gt; that list all of the varying sizes.&lt;/p&gt;

&lt;p&gt;Size is important for image quality, sure, but you can't just throw in a giant .png and call it a day. Aspect ratio is critical when the image contains text that you don't want to get cut off, like event details. Brands also care about less critical things like spacing and alignment, because bad visual flow can reflect poorly on the company. Priorities differ--image margins are less important when I'm Joe Schmoe Developer and more important when I'm Microsoft or Coca-Cola, but it's good to be armed with such knowledge.&lt;/p&gt;

&lt;p&gt;If you think making different image sizes for each platform sounds like a pain in the ass, you'd be correct. Thankfully, tools like &lt;a href="https://www.canva.com/"&gt;Canva&lt;/a&gt; and &lt;a href="https://spark.adobe.com/"&gt;Adobe Spark&lt;/a&gt; make this easier. Visual designers can create templates that marketers can fill in with content and export in the various sizes. Developers can attach the exported image assets to the original source website, or marketers can manually add the images to social posts on each platform. My company creates social posts the second way, uploading the image to &lt;a href="https://www.hubspot.com/"&gt;Hubspot&lt;/a&gt; because we often want to customize the image and post content separate from the link. What I'm trying to do for my blog is the first way--having an image live in my website's files so that social platforms (specifically Twitter, in this case) can grab it for the unfurl every time my link is shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Twitter cards
&lt;/h2&gt;

&lt;p&gt;So for any link dropped into a tweet, Twitter will look at content of that page and try to grab relevant information to display in the unfurl--called a &lt;a href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards"&gt;Twitter card&lt;/a&gt;. Twitter also has a handy &lt;a href="https://cards-dev.twitter.com/validator"&gt;Card Validator&lt;/a&gt; tool for checking how your link unfurl will look before you hit "publish" on a post. Here's what it looks like for Google.com:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UBbWydCR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/DLy21nM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UBbWydCR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/DLy21nM.png" alt="Twitter card validator preview for Google.com, showing the link unfurl with the Google logo decorated for the holidays"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The validator also gives you a handy log from the JavaScript that's grabbing the page metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO:  Page fetched successfully
INFO:  17 metatags were found
INFO:  twitter:card = summary_large_image tag found
INFO:  Card loaded successfully
WARN:  this card is redirected to https://www.google.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at the preview for my website:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WdAxLKQh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/t6nAf5L.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WdAxLKQh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/t6nAf5L.png" alt="Twitter card validator preview for shelbyspees.com, showing the link unfurl with the awkward Nova ears logo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I get mostly the same info, although without the redirect warning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO:  Page fetched successfully
INFO:  15 metatags were found
INFO:  twitter:card = summary_large_image tag found
INFO:  Card loaded successfully
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So where is it getting this &lt;code&gt;twitter:card = summary_large_image&lt;/code&gt; tag it says that it found? The previous line gives us a hint: &lt;code&gt;metatags&lt;/code&gt;. Let's open up Chrome devtools.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W9g5XtUG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/r8YpjsQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W9g5XtUG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/r8YpjsQ.png" alt="shelbyspees.com with Chrome devtools open, highlighting the Twitter meta tags"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I highlighted these four &lt;code&gt;meta&lt;/code&gt; tags because they correspond to the info displayed in the Twitter card preview:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:card"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"summary_large_image"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://shelbyspees.com/nova-ears.png"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"shelby spees"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"shelby spees: developer advocate, dog mom, etc."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first one says &lt;a href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary-card-with-large-image"&gt;&lt;code&gt;summary_large_image&lt;/code&gt;&lt;/a&gt; and it seems to be pointing to &lt;code&gt;nova-ears.png&lt;/code&gt;, which is the image on my website's my home page. But like, where did all this get defined? I don't remember specifying anything for Twitter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hugo Twitter cards
&lt;/h2&gt;

&lt;p&gt;Oh wait nvm, &lt;a href="https://github.com/shelbyspees/website/commit/6aad7d3cbea17ccbf618c652f0560fb33b1af0f0"&gt;apparently&lt;/a&gt; I thought I knew what I was doing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_ypnIUkK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/mxFV3vl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_ypnIUkK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/mxFV3vl.png" alt="screenshot of commit on GitHub titled 'Add Google Analytics and Twitter card info'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well sometime between September 22nd and December 8th, I forgot that I had done that. I guess at the time I got frustrated and gave up? Then last week I made a &lt;a href="https://dev.to/2020/12/08/i-was-on-screaming-in-the-cloud/"&gt;post about being on Screaming in the Cloud&lt;/a&gt; and was reminded by my RSS feed tweeting about it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HOy7GpDt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/7T93twV.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HOy7GpDt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/7T93twV.png" alt="twitter thread. first tweet is a link to Shelby's blog post 'I was on Screaming in the Cloud.' second tweet is Shelby saying, 'ugh I really need to fix that social image'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's when I finally bothered to look into it (evidently not for the first time) and (re)learned: &lt;a href="https://gohugo.io/templates/internal/#twitter-cards"&gt;Hugo supports Twitter cards out of the box!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The docs say that the &lt;code&gt;_internal/twitter_cards.html&lt;/code&gt; template grabs the image path from my site's config file, in the &lt;code&gt;images&lt;/code&gt; list under &lt;code&gt;params&lt;/code&gt;, which is what I set in the commit I shared above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[params]&lt;/span&gt;
  &lt;span class="py"&gt;defaultTheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"light"&lt;/span&gt;
  &lt;span class="py"&gt;homeSubtitle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"developer advocate, dog mom, etc."&lt;/span&gt;
  &lt;span class="py"&gt;mainSections&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;["posts"]&lt;/span&gt;
  &lt;span class="py"&gt;showReadMore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="py"&gt;gitUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/shelbyspees/website/commit/"&lt;/span&gt;
  &lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"shelby spees: developer advocate, dog mom, etc."&lt;/span&gt;
  &lt;span class="py"&gt;images&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;["/nova-ears.png"]&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="err"&gt;this&lt;/span&gt; &lt;span class="err"&gt;one&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since &lt;code&gt;nova-ears.png&lt;/code&gt; is square, it ends up looking super wonky in the &lt;code&gt;2:1&lt;/code&gt; ratio of the Twitter card. I'm going to have to replace it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hugo layouts
&lt;/h2&gt;

&lt;p&gt;To be honest though, I still felt the need to connect the dots a bit more. Where in &lt;a href="https://github.com/shelbyspees/hugo-theme-hello-friend-ng"&gt;my theme&lt;/a&gt; is this getting used? (Note: I keep my theme as a git submodule separate from the main content of my website.)&lt;/p&gt;

&lt;p&gt;Thankfully I know Hugo enough to start in my theme's &lt;code&gt;layouts/&lt;/code&gt; folder. You would think to go to &lt;a href="https://github.com/shelbyspees/hugo-theme-hello-friend-ng/blob/main/layouts/index.html"&gt;&lt;code&gt;layouts/index.html&lt;/code&gt;&lt;/a&gt;, but Hugo templating means things are split up a bit more. There's a layer of template beyond that, under &lt;code&gt;layouts/_default/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; layouts/_default
baseof.html
list.html
single.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The one we want is &lt;code&gt;layouts/_default/baseof.html&lt;/code&gt;, which we can confirm because it starts with &lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/code&gt; and goes on to include the &lt;code&gt;head&lt;/code&gt; tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"{{ .Site.Language }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        {{ partial "head.html" . }}
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(To be honest, it's not immediately clear from the content of &lt;code&gt;layouts/index.html&lt;/code&gt; that it's using &lt;code&gt;layouts/_default/baseof.html&lt;/code&gt; under the hood. I've learned a lot of Hugo templating logic just by poking around and adding &lt;code&gt;testeststests&lt;/code&gt; and &lt;code&gt;Shelby was here&lt;/code&gt; in random spots to see where it showed up.)&lt;/p&gt;

&lt;p&gt;That &lt;code&gt;partial "head.html"&lt;/code&gt; bit is our next clue. Hugo, like other templating engines, supports the use of &lt;a href="https://gohugo.io/templates/partials/"&gt;partial templates&lt;/a&gt;, which are like modular mini-templates that you can import into other templates. Looking at &lt;code&gt;layouts/partials/head.html&lt;/code&gt;, there it is on &lt;a href="https://github.com/shelbyspees/hugo-theme-hello-friend-ng/blame/2f863e7ba67e6f12c53e394b8aee198bcaac2e6b/layouts/partials/head.html#L40"&gt;line 40&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;{{ template "_internal/twitter_cards.html" . }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome! The theme developer put the internal Hugo Twitter card template there so I don't have to! That explains how I got it working to begin with. Now to update the image so it actually looks pretty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Twitter card
&lt;/h2&gt;

&lt;p&gt;I could have used a regular full-sized landscape image, but tbh I'll take any excuse to use Canva. I did warn you about the vanity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NegPAxD3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/5xMdCfx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NegPAxD3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/5xMdCfx.png" alt="Canva app in edit mode showing the Twitter card for shelbyspees.com, which features a selfie of Shelby and Nova"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's my new favorite picture of Nova and me together &amp;lt;3&lt;/p&gt;

&lt;p&gt;At this point I just needed to download it from Canva and add it to my &lt;code&gt;static/&lt;/code&gt; directory in my repo, and then update &lt;code&gt;config.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-  images = ["/nova-ears.png"]
&lt;/span&gt;&lt;span class="gi"&gt;+  images = ["/twitter-card-large.png"]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can see it on Twitter!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_ipRpVqZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/2ivoHMc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_ipRpVqZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/2ivoHMc.png" alt="tweet from Shelby reading 'testing testing' with the new image as the social card, and a second tweet reading 'aspect ratio is a little wonky (docs say 2:1 with a minimum of 300x157px which is obviously not 2:1 lmao) but it's an improvement'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The aspect ratio isn't &lt;em&gt;quite&lt;/em&gt; right, unfortunately (Twitter's docs lied about it being 2:1). But it's a big improvement! And we still get the Nova ears logo. Hooray! 🎉&lt;/p&gt;

</description>
      <category>hugo</category>
      <category>twitter</category>
      <category>blog</category>
    </item>
    <item>
      <title>A User Journey: Setting Up the Node Beeline on Lambda</title>
      <dc:creator>shelby spees (she/her)</dc:creator>
      <pubDate>Tue, 10 Nov 2020 22:50:53 +0000</pubDate>
      <link>https://forem.com/honeycombio/a-user-journey-setting-up-the-node-beeline-on-lambda-5cl2</link>
      <guid>https://forem.com/honeycombio/a-user-journey-setting-up-the-node-beeline-on-lambda-5cl2</guid>
      <description>&lt;p&gt;&lt;span&gt;Nic Wise at&lt;/span&gt;&lt;a href="https://tend.nz/" rel="noopener noreferrer"&gt; &lt;span&gt;Tend Health&lt;/span&gt;&lt;/a&gt;&lt;span&gt; recently wrote a series of blog posts exploring how they moved away from logs and metrics, toward adopting observability with Honeycomb. In that series, he shares lessons learned as they got their NodeJS app instrumented in an AWS environment making use of CloudFront, API Gateway, Lambda, and a few other services.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EknuGLQm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/10/unnamed-2.png" class="article-body-image-wrapper"&gt;&lt;img class="alignnone size-full wp-image-7909" src="https://res.cloudinary.com/practicaldev/image/fetch/s--EknuGLQm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/10/unnamed-2.png" alt="Tend Health application architecture digraph" width="655" height="423"&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;Tend is a New Zealand-based healthcare platform launching in 2020. With the opportunity of a near-greenfield project to work with, Nic shared his experiences learning Honeycomb that you might find useful.&lt;/span&gt;&lt;/p&gt;

&lt;h2&gt;&lt;span&gt;Getting started&lt;/span&gt;&lt;/h2&gt;

&lt;p&gt;&lt;span&gt;Nic used the&lt;/span&gt;&lt;a href="https://docs.honeycomb.io/getting-data-in/javascript/beeline-nodejs/" rel="noopener noreferrer"&gt; &lt;span&gt;Node Beeline integration&lt;/span&gt;&lt;/a&gt;&lt;span&gt; and set up some middleware to get data into Honeycomb from dev, but encountered some snags with his lambda code waiting on API calls to Honeycomb’s ingest service. In order to solve that, he needed to better understand Honeycomb’s data model. He very clearly phrases his take on using events:&lt;/span&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;span&gt;You can almost think of an event as a log line - something happened and I want to know about it - but it's also a data structure, so you can add any other context you want to it.&lt;/span&gt;&lt;/blockquote&gt;

&lt;p&gt;&lt;span&gt;Once Nic updated his mental model of working with structured events, “around 90% of the metrics [he’d previously] created had no purpose anymore.” He changed his existing log lines and metrics to dump that data into the Beeline’s spans as context, deleting a bunch of code in the process.&lt;/span&gt;&lt;/p&gt;

&lt;h2&gt;&lt;span&gt;Working with traces and AWS Lambda&lt;/span&gt;&lt;/h2&gt;

&lt;p&gt;&lt;span&gt;Nic also encountered some missing spans because of how his codebase was using &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;:&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p-BXWMuE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/10/unnamed-1.png" class="article-body-image-wrapper"&gt;&lt;img class="alignnone size-full wp-image-7908" src="https://res.cloudinary.com/practicaldev/image/fetch/s--p-BXWMuE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/10/unnamed-1.png" alt="Honeycomb trace on a graphql-api service with a missing span beneath the root span" width="1000" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;It can be tricky to debug missing parts of a trace, but I can promise when you solve it you'll have learned that part of the code like the back of your hand.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;That’s the thing about instrumentation and observability tooling: the point isn’t for the tools to think for you, it’s to help you better reason about your code and your systems. Auto-instrumentation is great for getting started but the most important parts of your code are unique to your app because they’re unique to your business. That means you’ll benefit the most from some adding custom instrumentation on top of auto-instrumentation, and it’s why Honeycomb makes the most sense as a developer tool.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;Nic got advice from &lt;/span&gt;&lt;a href="https://www.honeycomb.io/blog/spread-the-love-appreciating-our-pollinators-community/" rel="noopener noreferrer"&gt;&lt;span&gt;Honeycomb’s Pollinators community Slack&lt;/span&gt;&lt;/a&gt;&lt;span&gt; on how to fix the issue with Lambda blocking on API calls. This involved dropping down into &lt;code&gt;Libhoney&lt;/code&gt;, the event handler library used by the Beeline SDK, and redirecting his events to emit to stdout. From there, Cloudwatch Logs and &lt;/span&gt;&lt;a href="https://github.com/honeycombio/agentless-integrations-for-aws" rel="noopener noreferrer"&gt;&lt;span&gt;Honeycomb’s agent Lambda integration&lt;/span&gt;&lt;/a&gt;&lt;span&gt; send his data along to Honeycomb asynchronously.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;Once he got that working, Nic encountered a scenario where he was seeing separate traces for an asynchronous call to the Twilio API, which he wanted to combine into a single trace alongside the caller. By updating his code to pass along the trace ID and parent span ID, he was able to combine them:&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aHrQfyJm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/10/unnamed.png" class="article-body-image-wrapper"&gt;&lt;img class="alignnone size-full wp-image-7907" src="https://res.cloudinary.com/practicaldev/image/fetch/s--aHrQfyJm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/10/unnamed.png" alt="Honeycomb trace showing an asynchronous call from an SMS onboarding service out to an SMS sender service. The onboarding service call takes 28 milliseconds, and about half a second later the sender service call takes about 1.2 seconds." width="1005" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;Nic’s scenario is common among teams using Lambda, but his post clearly explores that with a straightforward example.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;Read Nic’s entire series plus his follow-up post to get the full story along with code samples:&lt;/span&gt;&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;a href="https://fastchicken.co.nz/2020/10/04/honeycomb-1-the-beginning/" rel="noopener noreferrer"&gt;&lt;span&gt;Honeycomb 1 - The Beginning&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://fastchicken.co.nz/2020/10/04/honeycomb-2-what-are-all-these-new-terms/" rel="noopener noreferrer"&gt;&lt;span&gt;Honeycomb 2 - What are all these new terms?&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://fastchicken.co.nz/2020/10/04/honeycomb-3-breaking-the-api-dependency/" rel="noopener noreferrer"&gt;&lt;span&gt;Honeycomb 3 - Breaking the API dependency&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;
&lt;a href="https://fastchicken.co.nz/2020/10/12/propagating-the-honeycomb-context-between-lambda-calls/" rel="noopener noreferrer"&gt;&lt;span&gt;Propagating the Honeycomb context between Lambda calls&lt;/span&gt;&lt;/a&gt;&lt;span&gt; &lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;span&gt;Learn more about how metrics work in Honeycomb and how you can eliminate the majority of them by using structured events: download &lt;a href="https://www.honeycomb.io/getting-started-with-honeycomb-metrics/" rel="noopener noreferrer"&gt;Getting Started with Honeycomb Metrics&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Join the swarm. Get started with &lt;a href="https://ui.honeycomb.io/signup?&amp;amp;utm_source=Devto&amp;amp;utm_Devto=blog&amp;amp;utm_campaign=referral&amp;amp;utm_keyword=%7Bkeyword%7D&amp;amp;utm_content=a-user-journey-nic-wise"&gt;Honeycomb for free&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>serverless</category>
      <category>honeycomb</category>
      <category>observability</category>
    </item>
    <item>
      <title>HoneyByte: Get a Taste for Sampling</title>
      <dc:creator>shelby spees (she/her)</dc:creator>
      <pubDate>Wed, 04 Nov 2020 22:42:38 +0000</pubDate>
      <link>https://forem.com/honeycombio/honeybyte-get-a-taste-for-sampling-53ah</link>
      <guid>https://forem.com/honeycombio/honeybyte-get-a-taste-for-sampling-53ah</guid>
      <description>&lt;p&gt;Honeycomb's event-based pricing model is pretty simple: we only care about how many events you send. For teams running workloads at scale, the question becomes: are all of my events worth keeping? How can you reduce overall event volume while maintaining fidelity? This HoneyByte is all about sampling strategies you can use to lower costs without sacrificing the value of your data.&lt;/p&gt;

&lt;p&gt;We'll look at several approaches you can use, help you avoid common pitfalls, and walk through real sampling code running in production (thanks to our friends at &lt;a href="http://dev.to"&gt;dev.to&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  What is sampling?
&lt;/h2&gt;

&lt;p&gt;Sampling is a way to reduce the amount of data you send to Honeycomb without a significant reduction in the quality of your data. It’s like getting samples of food: you can taste all the important bits without getting full.&lt;/p&gt;

&lt;p&gt;Sampling requires making decisions ahead of time about what we’re going to include and what we’re going to leave out. If you’re new to the concept of sampling, you may want to start with Irving Popovetsky’s post on &lt;a href="https://www.honeycomb.io/blog/getting-at-the-good-stuff-how-to-sample-traces-in-honeycomb/" rel="noopener noreferrer"&gt;various downsampling strategies&lt;/a&gt;. For a deeper dive into why sampling is a good approach, you should start with this fantastic talk from Liz Fong-Jones:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/HFnBHA6NWUE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;You don't need to have read or watched those resources to understand the sampling concepts in this post, although I do presume enough familiarity with tracing that we can focus on implementing trace-aware custom sampling.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does sampling work with Honeycomb?
&lt;/h2&gt;

&lt;p&gt;Honeycomb does not downsample your data: we keep every event you send. However, Honeycomb can also receive your downsampled data and use each event’s sample rate to fill in the gaps and make your graphs look approximately as they would with the full data set. We keep your data, however you want to send it to us—it’s up to you.&lt;/p&gt;

&lt;p&gt;Before we cover why samplers are implemented in particular ways, it’s important to be clear on how events are sent from your apps to Honeycomb. Previously, we’ve broken down how &lt;a href="https://www.honeycomb.io/blog/lets-talk-events/" rel="noopener noreferrer"&gt;Honeycomb events&lt;/a&gt; are classified somewhat differently than how you might think about what a meaningful event means for your service. For example, with HTTP requests (which are usually represented as traces in the Honeycomb UI), each request creates one trace. Each trace (typically) is comprised of many spans. Each trace span typically represents an underlying unit of work (e.g. an additional system call that was made to fulfill the request, etc). To Honeycomb, each individual span you send is counted as one event. &lt;b&gt;1 span == 1 event.&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;An important thing to know is that your data isn’t batched at the trace level. Rather, spans are sent as individual events, and they’re only compiled into a trace by Honeycomb’s backend services once all of your data arrives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using a sample rate
&lt;/h2&gt;

&lt;p&gt;The simplest way to downsample your events is by using a blanket &lt;code&gt;sample_rate&lt;/code&gt;. The event volume you generate will be &lt;code&gt;1/sample_rate&lt;/code&gt;. In other words, a sample rate of &lt;code&gt;1&lt;/code&gt; sends &lt;code&gt;1/1,&lt;/code&gt; or 100%, of your events to Honeycomb. A sample rate of &lt;code&gt;5&lt;/code&gt; sends &lt;code&gt;1/5,&lt;/code&gt; or 20%, of your events. (Note: the sample rate must be an integer.)&lt;/p&gt;

&lt;p&gt;For clarity, the default value for an event’s &lt;code&gt;sample_rate&lt;/code&gt; is &lt;code&gt;1&lt;/code&gt;. A sample rate of 1 means that none of your data is downsampled in any of our Beelines or in Libhoney, by default.&lt;/p&gt;

&lt;p&gt;Here’s an example Libhoney configuration borrowed from the &lt;a href="https://docs.honeycomb.io/getting-data-in/ruby/beeline/#sampling-events" rel="noopener noreferrer"&gt;Ruby Beeline docs&lt;/a&gt;, using a blanket &lt;code&gt;sample_rate&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Honeycomb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Libhoney&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;writekey: &lt;/span&gt;&lt;span class="s1"&gt;'YOUR_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;dataset: &lt;/span&gt;&lt;span class="s1"&gt;'ruby'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;sample_rate: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How this plays out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Libhoney sends 1 in every &lt;code&gt;sample_rate&lt;/code&gt; events and drops the rest&lt;/li&gt;
&lt;li&gt;In this case, it sends 1 in every 5 events and drops the other 4&lt;/li&gt;
&lt;li&gt;Honeycomb receives the event, including its &lt;code&gt;sample_rate&lt;/code&gt; field&lt;/li&gt;
&lt;li&gt;Honeycomb’s backend uses the &lt;code&gt;sample_rate&lt;/code&gt; to calculate what your overall dataset would look like&lt;/li&gt;
&lt;li&gt;In this case, Honeycomb presumes that the 1 in 5 events sent is representative of the other 4 that were not&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last bullet point is important to note. What if the 1 event that was sent isn’t representative of the other 4? Dropping 80% of events across the board can be risky. It’s likely that errors or other interesting events aren’t going to be evenly distributed across your production traffic. For most scenarios, we recommend using custom sampling logic instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overriding the sample hook
&lt;/h2&gt;

&lt;p&gt;If you’re using the Ruby Beeline, you can implement custom sampling logic by overriding &lt;code&gt;config.sample_hook&lt;/code&gt; in your Honeycomb Beeline configuration—where you include your API key.&lt;/p&gt;

&lt;p&gt;Before worrying about various sample rates, let’s just look at how overriding the sample hook works with a toy example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Honeycomb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# ... (other config settings, like write_key and dataset name)&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample_hook&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"drop_me"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;config.sample_hook&lt;/code&gt; needs to return a list with two pieces of data for every event:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;should I include this? (boolean)&lt;/li&gt;
  &lt;li&gt;`sample_rate` (integer)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So in the example, if I’ve added a field called &lt;code&gt;drop_me&lt;/code&gt; to any span in my code, we’re returning &lt;code&gt;[false, 1]&lt;/code&gt; for those events. In this case, &lt;code&gt;false&lt;/code&gt; is answering the question, “should I include this event?” This type of approach can be useful for particularly noisy events with low-value data.&lt;/p&gt;

&lt;p&gt;In my &lt;code&gt;else&lt;/code&gt; clause, we’re returning &lt;code&gt;[true, 1]&lt;/code&gt;. Here, &lt;code&gt;true&lt;/code&gt; is saying, “yes, send this” and the sample rate of &lt;code&gt;1&lt;/code&gt; tells Honeycomb that this event represents only itself, there’s no need to re-calculate missing events. (This toy example presumes we care not at all about the events we’re dropping! That’s rarely the case.)&lt;/p&gt;

&lt;p&gt;The example implements our custom sampling logic directly in the &lt;code&gt;config.sample_hook&lt;/code&gt;. But typically, we would want to write more sophisticated logic to decide which types of events we’ll be sampling. Let’s look at how to do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  DEV sampler walk-through
&lt;/h2&gt;

&lt;p&gt;In previous HoneyBytes, we looked at &lt;a href="https://www.honeycomb.io/blog/honeybyte-beeline-dev-molly-struve/" rel="noopener noreferrer"&gt;how the DEV team set up&lt;/a&gt; Honeycomb and &lt;a href="https://www.honeycomb.io/blog/honeybyte-incremental-instrumentation-beyond-the-beeline/" rel="noopener noreferrer"&gt;how they gained observability by adding more context fields&lt;/a&gt;. Now, let’s look at how the DEV team implemented custom sampling in their code (recently renamed to &lt;a href="http://github.com/forem/forem" rel="noopener noreferrer"&gt;forem/forem&lt;/a&gt;! &lt;a href="https://dev.to/devteam/for-empowering-community-2k6h"&gt;Read their story.&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Starting from &lt;code&gt;config/initializers/honeycomb.rb&lt;/code&gt;, where they have their Honeycomb configuration set up, they &lt;a href="https://github.com/forem/forem/blob/a6c4f0b8844166e2d4e630e8a02ac9e111b6c523/config/initializers/honeycomb.rb#L39-L42" rel="noopener noreferrer"&gt;override &lt;code&gt;config.sample_hook&lt;/code&gt;&lt;/a&gt; like we did in the above example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Honeycomb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# ... (config stuff)&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="c1"&gt;# Sample away highly redundant events&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample_hook&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;Honeycomb&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NoiseCancellingSampler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, though, they’re calling out to a custom &lt;code&gt;sample&lt;/code&gt; method in their own &lt;a href="https://github.com/forem/forem/blob/master/app/lib/honeycomb/noise_cancelling_sampler.rb" rel="noopener noreferrer"&gt;&lt;code&gt;Honeycomb::NoiseCancellingSampler&lt;/code&gt; class&lt;/a&gt;. First I’ll share the class implementation in full, and then I’ll walk through what each chunk of code is doing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Honeycomb&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NoiseCancellingSampler&lt;/span&gt;
    &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;Honeycomb&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DeterministicSampler&lt;/span&gt;

    &lt;span class="no"&gt;NOISY_REDIS_COMMANDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"GET rails-settings-cached/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"TIME"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

    &lt;span class="no"&gt;NOISY_SQL_COMMANDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"BEGIN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"COMMIT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

    &lt;span class="no"&gt;NOISY_REDIS_PREFIXES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"INCRBY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"TTL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"GET rack:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"SET rack:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"GET views/shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# include everything by default&lt;/span&gt;
      &lt;span class="c1"&gt;# should_sample is a no-op if the rate is 1&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"redis.command"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;in?&lt;/span&gt; &lt;span class="no"&gt;NOISY_REDIS_COMMANDS&lt;/span&gt;
        &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
      &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sql.active_record.sql"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;in?&lt;/span&gt; &lt;span class="no"&gt;NOISY_SQL_COMMANDS&lt;/span&gt;
        &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
      &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"redis.command"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;.&lt;/span&gt;&lt;span class="nf"&gt;start_with?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"BRPOP"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# BRPOP is disproportionately noisy and not really interesting&lt;/span&gt;
        &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
      &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"redis.command"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;.&lt;/span&gt;&lt;span class="nf"&gt;start_with?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="no"&gt;NOISY_REDIS_PREFIXES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;should_sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"trace.trace_id"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To start, we need to extend the &lt;a href="https://github.com/honeycombio/beeline-ruby/blob/6db9d6e0f696d93212100cca5efdeefc7723afb7/lib/honeycomb/deterministic_sampler.rb" rel="noopener noreferrer"&gt;&lt;code&gt;Honeycomb::DeterministicSampler&lt;/code&gt; module&lt;/a&gt; in the &lt;a href="https://docs.honeycomb.io/getting-data-in/ruby/beeline/#customizing-sampling-logic" rel="noopener noreferrer"&gt;Ruby Beeline&lt;/a&gt;. The important thing to know about this module is that we're using it in order to call its &lt;code&gt;should_sample&lt;/code&gt; method down in line 37, which decides whether to keep or drop each event based on the sample rate provided. I’ll explain that a bit more later, when we get to that line.&lt;/p&gt;

&lt;p&gt;The DEV team had found that Redis and SQL queries were generating a lot of noisy events that weren’t very useful. So they made a few lists of noisy commands to filter by. Here’s the list for Redis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;NOISY_REDIS_COMMANDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"GET rails-settings-cached/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"TIME"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see in Honeycomb's trace view that the &lt;code&gt;TIME&lt;/code&gt; command shows up a lot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.honeycomb.io/wp-content/uploads/2020/07/Image-2020-07-14-at-7.38.38-PM.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.honeycomb.io%2Fwp-content%2Fuploads%2F2020%2F07%2FImage-2020-07-14-at-7.38.38-PM.png" alt="Honeycomb UI showing dev.to trace with the Redis TIME command highlighted, appearing several times"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TIME&lt;/code&gt; is not very interesting, so it makes sense that they would want to downsample it. It’s the same idea for the other commands they’re downsampling.&lt;/p&gt;

&lt;p&gt;Now let’s walk through the &lt;code&gt;sample&lt;/code&gt; method, copied here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# include everything by default&lt;/span&gt;
  &lt;span class="c1"&gt;# should_sample is a no-op if the rate is 1&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"redis.command"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;in?&lt;/span&gt; &lt;span class="no"&gt;NOISY_REDIS_COMMANDS&lt;/span&gt;
    &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sql.active_record.sql"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;in?&lt;/span&gt; &lt;span class="no"&gt;NOISY_SQL_COMMANDS&lt;/span&gt;
    &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"redis.command"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;.&lt;/span&gt;&lt;span class="nf"&gt;start_with?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"BRPOP"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# BRPOP is disproportionately noisy and not really interesting&lt;/span&gt;
    &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"redis.command"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;.&lt;/span&gt;&lt;span class="nf"&gt;start_with?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="no"&gt;NOISY_REDIS_PREFIXES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;should_sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"trace.trace_id"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We set the default &lt;code&gt;rate&lt;/code&gt; to 1, which means including 100% of events.&lt;/p&gt;

&lt;p&gt;From there we set the &lt;code&gt;rate&lt;/code&gt; to different values depending on the events &lt;code&gt;fields&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;send 1% of noisy Redis commands&lt;/li&gt;
  &lt;li&gt;send 1% of noisy SQL commands&lt;/li&gt;
  &lt;li&gt;send 0.1% of `BRPOP`, which is especially noisy in Redis&lt;/li&gt;
  &lt;li&gt;send 1% of Redis commands with noisy prefixes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As I explained in earlier sections, the sample hook calling the custom &lt;code&gt;NoiseCancellingSampler.sample&lt;/code&gt; method here is expecting a tuple, where the first element answers “is this sampled?” and the second element is the &lt;code&gt;sample_rate&lt;/code&gt;. In this case the tuple in our return statement is answering the first question by calling &lt;code&gt;should_sample(rate, fields["trace.trace_id"])&lt;/code&gt;. This is the &lt;code&gt;should_sample&lt;/code&gt; I mentioned earlier—it’s what we’re leaning on to make consistent sampling decisions, and it’s why the &lt;code&gt;NoiseCancelingSampler&lt;/code&gt; class extends the Beeline’s &lt;code&gt;DeterministicSampler&lt;/code&gt; module at the beginning.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;should_sample&lt;/code&gt; method expects a &lt;code&gt;rate&lt;/code&gt; and a &lt;code&gt;value&lt;/code&gt;, and returns a boolean. Note: a rate of &lt;code&gt;1&lt;/code&gt; always returns &lt;code&gt;true&lt;/code&gt; (i.e., “keep this event”). Beyond that, &lt;code&gt;should_sample&lt;/code&gt; will give a consistent return value with consistent inputs. This is why we pass in the &lt;code&gt;trace.trace_id&lt;/code&gt; field—so that we get the same result for all spans within a trace. That’s the key to trace-aware sampling!&lt;/p&gt;

&lt;p&gt;Returning the tuple completes the implementation of the DEV team’s custom &lt;code&gt;sample_hook&lt;/code&gt;. The Beeline then takes the resulting boolean value and the &lt;code&gt;rate&lt;/code&gt;, sending events with &lt;code&gt;true&lt;/code&gt; over to Honeycomb along with the corresponding sample rate, and dropping all the events with &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To step back a bit: you can implement whatever logic you want for your custom sample hook, as long as it returns that tuple: &lt;code&gt;[boolean, rate]&lt;/code&gt;. And your boolean value here is generated from calling &lt;code&gt;should_sample&lt;/code&gt;, which is made trace-aware by passing in the &lt;code&gt;trace.trace_id&lt;/code&gt;. The rest is up to you!&lt;/p&gt;

&lt;p&gt;Another nice thing about using the Beeline’s &lt;code&gt;DeterministicSampler&lt;/code&gt; is that it’s just code, which means that you can write tests for it! &lt;a href="https://github.com/forem/forem/blob/44b8d0894d00ceb78aa30a35c6bfe62536e3c90b/spec/lib/honeycomb/noise_cancelling_sampler_spec.rb" rel="noopener noreferrer"&gt;Check out the RSpec tests&lt;/a&gt; for the DEV custom sampler.&lt;/p&gt;

&lt;h2&gt;
  
  
  A template custom sample hook
&lt;/h2&gt;

&lt;p&gt;To recap, I’ve written out a complete example that you can use as a template for your own custom sampling logic. First I overwrite &lt;code&gt;config.sample_hook&lt;/code&gt; by calling out to &lt;code&gt;MyCustomSampler.sample&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Honeycomb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# ... (config stuff)&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample_hook&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;MyCustomSampler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here’s my custom module that extends the Beeline’s &lt;code&gt;DeterministicSampler&lt;/code&gt;, implements my sampling logic, and then calls &lt;code&gt;should_sample&lt;/code&gt; using the trace ID to keep things consistent, returning our expected tuple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MyCustomSampler&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;Honeycomb&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DeterministicSampler&lt;/span&gt;

  &lt;span class="c1"&gt;# keep 1% of anything with `downsample` set to true&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"downsample"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;should_sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"trace.trace_id"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing to note is that I’m setting the &lt;code&gt;downsample&lt;/code&gt; field elsewhere, in my actual application code. You don’t have to use a special field to decide what to sample on, you can sample on an event’s &lt;code&gt;name&lt;/code&gt; field or based on values of specific fields like the DEV team did in their code. Think about what’s important vs. what’s noise—you know your code best!&lt;/p&gt;

&lt;h2&gt;
  
  
  Common sampling pitfalls
&lt;/h2&gt;

&lt;p&gt;The logic we’ve described so far is great for individual events, and it makes a lot of sense for the DEV team to downsample noisy Redis or SQL commands that don’t provide a lot of information and don’t have any child spans.&lt;/p&gt;

&lt;p&gt;Honeycomb knows to re-calculate query results for individual dropped events based on the &lt;code&gt;sample_rate&lt;/code&gt; of their counterpart events that do get sent. But! Honeycomb doesn't know where those missing events would go in the trace waterfall. The dropped spans aren't there to establish the parent/child relationship needed to render the spans in the right locations. The DEV team is aware of this trade-off—they are purposely dropping leaf spans (those with no children) in order to reduce event volume. If your sampling code drops spans with children, however, your trace waterfall will show that you're missing spans in the middle. That's probably not what you want.&lt;/p&gt;

&lt;p&gt;There are a few more considerations to keep in mind when working with traces. Let’s look at what happens when our code makes sampling decisions without being trace-aware. Here we’re trying a head-based sampling approach to drop a trace at the request level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;not_interesting&lt;/span&gt;
    &lt;span class="no"&gt;Honeycomb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'downsample'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="c1"&gt;# ... (render page, etc.)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’re setting the &lt;code&gt;downsample&lt;/code&gt; field based on some condition in the scope of the &lt;code&gt;index&lt;/code&gt; method. Unfortunately it’s gone wrong because we’re trying to downsample at the request level, but what happens is that only the root span is dropped while all the child spans still get sent. Here’s what that would look like in the trace view:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.honeycomb.io/wp-content/uploads/2020/07/Screen-Shot-2020-07-22-at-2.27.53-PM.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.honeycomb.io%2Fwp-content%2Fuploads%2F2020%2F07%2FScreen-Shot-2020-07-22-at-2.27.53-PM.png" alt="Honeycomb trace view with a missing root span. Child 1 and child 2 are shown. Child 1 has a nested grandchild span."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The solution for avoiding this is to set a trace-level field as early as possible, before any child spans are started. Our sampling hook will then check the trace-level field on each event to decide whether it gets sent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;not_interesting&lt;/span&gt;
    &lt;span class="no"&gt;Honeycomb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_field_to_trace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'downsample'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# added to the whole trace&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="c1"&gt;# ... (render page, etc.)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another common pitfall occurs with tail-based sampling. With tail-based sampling, the decision to drop an entire trace is based on some value that is set in the middle of a request. If that happens, the result can lead to orphaned child spans, which get sent to Honeycomb before the entire request completes and the trace-level data is sent.&lt;/p&gt;

&lt;p&gt;Note in this screenshot, both the &lt;code&gt;root&lt;/code&gt; span and &lt;code&gt;child 2&lt;/code&gt; were dropped. &lt;code&gt;child 1&lt;/code&gt; and &lt;code&gt;grandchild&lt;/code&gt; were already sent to Honeycomb by the time the trace-level field for dropping events gets set by &lt;code&gt;child 2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.honeycomb.io/wp-content/uploads/2020/07/Image-2020-07-22-at-2.12.33-PM.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.honeycomb.io%2Fwp-content%2Fuploads%2F2020%2F07%2FImage-2020-07-22-at-2.12.33-PM.png" alt="Honeycomb trace view with a missing root span. Child 1 is shown with a nested grandchild span, but child 2 is missing."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The logic to complete the sophisticated batching required for tail-based sampling is not built into our Beelines or Libhoney. The best approach to do tail-based sampling without breaking your traces is to run your events through a proxy that can buffer full traces before downsampling them and sending them to Honeycomb. Stay tuned for future news on ways to set up these proxies.&lt;/p&gt;

&lt;h2&gt;
  
  
  How will you sample events?
&lt;/h2&gt;

&lt;p&gt;We hope this tutorial sheds light on how you can build sampling logic into your code. Check out our docs for more &lt;a href="https://docs.honeycomb.io/working-with-your-data/best-practices/sampling/" rel="noopener noreferrer"&gt;guidance on sampling&lt;/a&gt;. You can also &lt;a href="http://github.com/shelbyspees/exsampling" rel="noopener noreferrer"&gt;check out the source code&lt;/a&gt; used to generate these examples, along with more detailed explanations of how things went wrong. Finally, learn more by downloading our white paper, &lt;a href="https://www.honeycomb.io/wp-content/uploads/2019/05/the_new_rules_of_sampling_-_typeset_final.pdf" rel="noopener noreferrer"&gt;The New Rules of Sampling (direct PDF download)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Have questions? Missing spans? Reach out to our &lt;a href="https://honeycomb.io/support" rel="noopener noreferrer"&gt;support team&lt;/a&gt;, or join the &lt;a href="https://join.slack.com/t/honeycombpollinators/shared_invite/zt-fv552707-y8m40UD2_~jonb1n9r5cNg" rel="noopener noreferrer"&gt;Pollinators Slack community&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Join the swarm. Get started with &lt;a href="https://ui.honeycomb.io/signup?&amp;amp;utm_source=Devto&amp;amp;utm_Devto=blog&amp;amp;utm_campaign=referral&amp;amp;utm_keyword=%7Bkeyword%7D&amp;amp;utm_content=honeybyte-get-a-taste-for-sampling" rel="noopener noreferrer"&gt;Honeycomb for free&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>observability</category>
      <category>meta</category>
      <category>honeycomb</category>
      <category>o11y</category>
    </item>
    <item>
      <title>Interview with Honeycomb Engineer Chris Toshok: Dogfooding OpenTelemetry</title>
      <dc:creator>shelby spees (she/her)</dc:creator>
      <pubDate>Mon, 26 Oct 2020 17:27:35 +0000</pubDate>
      <link>https://forem.com/honeycombio/interview-with-honeycomb-engineer-chris-toshok-dogfooding-opentelemetry-pg1</link>
      <guid>https://forem.com/honeycombio/interview-with-honeycomb-engineer-chris-toshok-dogfooding-opentelemetry-pg1</guid>
      <description>&lt;p&gt;At Honeycomb, we talk a lot about &lt;a href="https://www.honeycomb.io/dogfooding/"&gt;eating our own dogfood&lt;/a&gt;. Since we use Honeycomb to observe Honeycomb, we have many opportunities to try out UX changes ourselves before rolling them out to all of our users.&lt;/p&gt;

&lt;p&gt;UX doesn’t stop at the UI though! Developer experience matters too, especially when getting started with observability. We often get questions about the difference between using our Beeline SDKs compared with other integrations, especially &lt;a href="https://opentelemetry.io/"&gt;OpenTelemetry&lt;/a&gt; (abbreviated "OTel"). That’s why the team decided to do some integration dogfooding by instrumenting our own code with OpenTelemetry alongside existing Beeline instrumentation.&lt;/p&gt;

&lt;p&gt;Poodle is the frontend Go service that renders visualizations in the Honeycomb UI after getting query results and traces from the backend. Engineer &lt;a href="https://twitter.com/toshok"&gt;Chris Toshok&lt;/a&gt; has been working on adding OpenTelemetry to the Poodle code and comparing it with our existing Beeline integration. I talked to Chris about his experience setting up OpenTelemetry from the perspective of a practitioner and service owner.&lt;/p&gt;

&lt;h2&gt;Interview with Chris Toshok&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What were your thoughts going into the effort?&lt;/strong&gt;&lt;br&gt;
The main concern was schema compatibility. We have existing triggers, boards, SLOs, other things that relied on the schema that the Beeline generated. If we only sent to the existing Poodle dataset, we’d have to make sure that the OTel data would end up with the same field names and values as what's already there, so it wouldn’t break the things that depend on the existing schema.&lt;/p&gt;

&lt;p&gt;Alternatively, we could double-send: the Beeline data goes to one dataset and OTel data goes to another. We ended up going this way so that we can look at what the differences are between the two schemas without breaking existing dependencies in our Dogfood team.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Was there anything that surprised you in the process?&lt;/b&gt;&lt;br&gt;
While there are lots of little differences between the APIs, the core concepts were the same. The only real sort of surprise was that the Beelines actually let you do stuff that’s kind of scary, because of how the data is stored.&lt;/p&gt;

&lt;p&gt;With OTel, as soon as set a value for a field, you can’t modify that value, only replace it. In the Beeline, it’s just a reference to a Go struct. You can just change the values at any point before the data gets sent to Honeycomb. With some fields, like the team object within a span, changing that could be dangerous. But sometimes you want to change a certain value in the middle, like if you need to sanitize values before sending them to Honeycomb.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;The configuration for OTel is a bit different from the Beeline, did you also work on getting set up with that?&lt;/b&gt;&lt;br&gt;
We basically just set up the &lt;a href="https://github.com/honeycombio/opentelemetry-exporter-go"&gt;OpenTelemetry-Honeycomb Exporter for Go&lt;/a&gt;, written by engineer Alyson van Hardenberg.&lt;/p&gt;

&lt;p&gt;In the code, I used dependency injection to create an OTel-compatible initializer that works similarly to the Beeline. Beeline data and OTel data are stored a bit differently before they’re sent out, so we need to handle both cases. I added a shim that looks very similar to the OTel API, to wrap around both OTel and the Beeline and allow us to send to Honeycomb using either or both.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;If you could give advice to a team starting today, would you recommend they use the Beeline or OTel for their Go app?&lt;/b&gt;&lt;br&gt;
Beeline has a much more specific API that maps more closely to Honeycomb’s features. OTel is doing things at a more general layer, so it might not have all the interesting application bits that we have after several years of doing Honeycomb-specific work in the Go Beeline, which is an effect of the Beeline being something we built out alongside Honeycomb itself. We developed it to answer the questions we had about the service, and to make use of Honeycomb’s features.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;What sort of differences have you found?&lt;/b&gt;&lt;br&gt;
There were a couple small, but notable differences. OTel sends JSON blobs as strings, which don’t work with the JSON unfurling feature in Honeycomb.&lt;/p&gt;

&lt;p&gt;Also, Beelines allow us to set trace-level attributes. Any span that we send can add a trace-level field. OTel doesn’t support trace-level fields added from child spans.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Have you noticed any performance changes in &lt;/b&gt;&lt;b&gt;Poodle&lt;/b&gt;&lt;b&gt; after adding OTel?&lt;/b&gt;&lt;br&gt;
No change, but I’m wishing we had time over time queries. This is something I was interested in checking on and seeing in time series form.&lt;/p&gt;

&lt;h2&gt;Observing the impact&lt;/h2&gt;

&lt;p&gt;Toshok shared with me the queries he ran to check for any impact to Poodle's performance. Here's the baseline behavior from the week before Toshok's change went out:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.honeycomb.io/wp-content/uploads/2020/09/poodle_perf1.png"&gt;&lt;img class="aligncenter size-full wp-image-7586" src="https://res.cloudinary.com/practicaldev/image/fetch/s--0KVgVUwJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/09/poodle_perf1.png" alt="Poodle performance from the week before the change went out" width="2121" height="1134"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the same query, run on the following week. Toshok's changes got rolled out on 7/21:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.honeycomb.io/wp-content/uploads/2020/09/poodle_performance2.png"&gt;&lt;img class="aligncenter size-full wp-image-7585" src="https://res.cloudinary.com/practicaldev/image/fetch/s--MPAYGqBr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/09/poodle_performance2.png" alt="Poodle performance from the week the change went out" width="2123" height="1131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It makes sense that there would be minimal impact in this case because of how Toshok implemented the change, but it's still neat to be able to query for it.&lt;/p&gt;

&lt;h2&gt;Try it yourself!&lt;/h2&gt;

&lt;p&gt;Interested in comparing OTel and Beeline? Here’s how Toshok did it. For the most part they were 1-1 changes using the new OTel-aligned shim API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- span.AddField("error", err.Error())
&lt;/span&gt;&lt;span class="gi"&gt;+ span.SetAttribute("error", err.Error())
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Occasionally Toshok needed to do some type manipulation for compatibility with OTel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- beeline.AddField(ctx, "error", err.Error())
&lt;/span&gt;&lt;span class="gi"&gt;+ span.SetAttributes(instr.AppString("error", err.Error()))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the overall shape of the instrumentation remained the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- ctx, span := beeline.StartSpan(ctx, "runQueries")
- defer span.Send()
&lt;/span&gt;&lt;span class="gi"&gt;+ ctx, span := instr.StartSpan(ctx, "runQueries")
+ defer span.End()
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're starting out from scratch, follow our &lt;a href="https://www.honeycomb.io/blog/from-0-to-insight-with-opentelemetry-in-go/"&gt;OpenTelemetry Go tutorial&lt;/a&gt; to get up and running with Honeycomb in no time! &lt;/p&gt;

&lt;p&gt;Join the swarm. Get started with &lt;a href="https://ui.honeycomb.io/signup?&amp;amp;utm_source=Devto&amp;amp;utm_Devto=blog&amp;amp;utm_campaign=referral&amp;amp;utm_keyword=%7Bkeyword%7D&amp;amp;utm_content=interview-with-honeycomb-engineer-chris-toshok-dogfooding-opentelemetry"&gt;Honeycomb for free&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>observability</category>
      <category>opentelemetry</category>
      <category>go</category>
      <category>instrumentation</category>
    </item>
    <item>
      <title>Get on the Right Track with Our Rails Integration!</title>
      <dc:creator>shelby spees (she/her)</dc:creator>
      <pubDate>Wed, 21 Oct 2020 17:42:20 +0000</pubDate>
      <link>https://forem.com/honeycombio/get-on-the-right-track-with-our-rails-integration-4e6i</link>
      <guid>https://forem.com/honeycombio/get-on-the-right-track-with-our-rails-integration-4e6i</guid>
      <description>&lt;p&gt;Thanks to awesome contributions from the community and the hard work of our integrations team, the Honeycomb Rails integration comes with lots of great features out of the box. We’ve written about Rails-specific features of the Ruby Beeline before on the Honeycomb blog:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;a href="https://www.honeycomb.io/blog/new-features-for-ruby-and-rails-applications-with-a-new-version-of-the-honeycomb-beeline-for-ruby/"&gt;New features for Ruby and Rails applications with a new version of the Honeycomb Beeline for Ruby&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://www.honeycomb.io/blog/honeybyte-beeline-dev-molly-struve/"&gt;HoneyByte: Make a Beeline Toward Observability Just Like DEV’s Molly Struve&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href="https://www.honeycomb.io/blog/honeybyte-incremental-instrumentation-beyond-the-beeline/"&gt;HoneyByte: Incremental Instrumentation Beyond the Beeline&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is an end-to-end tutorial to show you exactly the steps involved, from creating a new Honeycomb team to getting your data in and observing your app in production.&lt;/p&gt;

&lt;p&gt;Whether you’re starting a new Rails project and want to build observability in from the start, or you’re investing in better observability for your existing Rails service, this tutorial will help you get the most out of Honeycomb right away.&lt;/p&gt;

&lt;h2&gt;Create a free Honeycomb account&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="http://ui.honeycomb.io/signup" rel="noopener noreferrer"&gt;ui.honeycomb.io/signup&lt;/a&gt; to create your account. We support SSO with Google, which means no new passwords to create or remember.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uTlEfxx_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/08/image-10.png" class="article-body-image-wrapper"&gt;&lt;img class="alignnone size-full wp-image-7482" src="https://res.cloudinary.com/practicaldev/image/fetch/s--uTlEfxx_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/08/image-10.png" alt="Screenshot of Honeycomb sign up screen " width="383" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Provide your organization’s name when prompted. This is a unique ID, so you can either use your company's name, or if it's a large company you might go with &lt;code&gt;company-team&lt;/code&gt;, e.g. &lt;code&gt;huuli-research&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're prompted to name your first dataset, call it &lt;code&gt;rails-app&lt;/code&gt; or give it the same name as your app. (If you change your mind about the name later, you can just create a new dataset.) Select the Ruby integration and click &lt;b&gt;Create&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;Once you’ve created your team, you’ll automatically receive an email inviting you to the Honeycomb Pollinators Slack community. &lt;a href="https://www.honeycomb.io/blog/spread-the-love-appreciating-our-pollinators-community/" rel="noopener noreferrer"&gt;This blog post&lt;/a&gt; from Customer Advocate Jenni Boyer details why you should join, definitely check it out when you get the chance!&lt;/p&gt;

&lt;h2&gt;Get your API key&lt;/h2&gt;

&lt;p&gt;The next page will give you your Honeycomb API key (also called the write key). If you chose the Ruby integration, this page will also list out instructions for setting up the Ruby Beeline, including Rails-specific instructions. Don’t worry about them, though—I‘ll break down each step for you here.&lt;/p&gt;

&lt;p&gt;Copy your API key now, we’ll be using it in the next steps. (If you lose track of your API key, you can always copy it again from your Team Settings.)&lt;/p&gt;

&lt;h2&gt;Configure the Ruby Beeline SDK&lt;/h2&gt;

&lt;p&gt;Now you can open up your terminal and set up the Rails integration for your app. The Ruby Beeline SDK comes in the form of a gem, so we’ll add it to our Gemfile using Bundler:&lt;/p&gt;

&lt;pre class="EnlighterJSRAW"&gt;$ bundle add honeycomb-beeline
&lt;/pre&gt;

&lt;p&gt;Now we’re ready to generate the Honeycomb configuration. The Beeline has a Rails generator built-in, so we can use that with the API key you copied earlier (denoted &lt;code&gt;abc123&lt;/code&gt; here) along with your dataset’s name, &lt;code&gt;rails-app&lt;/code&gt;:&lt;/p&gt;

&lt;pre class="EnlighterJSRAW"&gt;$ rails generate honeycomb abc123 --dataset rails-app&lt;/pre&gt;

&lt;p&gt;This will create a new file: &lt;code&gt;config/initializers/honeycomb.rb&lt;/code&gt;. The generator hard-codes your API key to start, but we’ll update this file later to store your key more safely.&lt;/p&gt;

&lt;h2&gt;Generate Your First Events&lt;/h2&gt;

&lt;p&gt;You’re ready to start sending data to Honeycomb! Start the Rails server and then navigate to &lt;code&gt;localhost:3000&lt;/code&gt; in your browser, or run &lt;code&gt;curl localhost:3000&lt;/code&gt; in your terminal if you prefer. This will hit the root path and send your first event to Honeycomb. Let’s return to &lt;a href="http://ui.honeycomb.io" rel="noopener noreferrer"&gt;ui.honeycomb.io&lt;/a&gt; to watch it arrive!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.honeycomb.io/wp-content/uploads/2020/08/Image-2020-08-23-at-10.11.58-PM.png"&gt;&lt;img class="alignnone wp-image-7487 size-full" src="https://res.cloudinary.com/practicaldev/image/fetch/s--73BHK4QI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/08/Image-2020-08-23-at-10.11.58-PM.png" alt="Image of event sent to Honeycomb IU" width="3580" height="2164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The home page shows us a single data point on the &lt;b&gt;Total Requests&lt;/b&gt; graph and a single trace under &lt;b&gt;Recent Traces&lt;/b&gt;. Success!&lt;/p&gt;

&lt;p&gt;With just this one request sent from dev, there’s not enough data to make the home page very interesting. But we can still poke around a bit. Click the button on your trace under &lt;b&gt;Recent Traces&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m3iy4jXy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/08/Image-2020-08-23-at-10.14.28-PM.png" class="article-body-image-wrapper"&gt;&lt;img class="alignnone wp-image-7488 size-full" src="https://res.cloudinary.com/practicaldev/image/fetch/s--m3iy4jXy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/08/Image-2020-08-23-at-10.14.28-PM.png" alt="Screenshot of recent traces tab" width="638" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This takes us into the trace view for the request we just made:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.honeycomb.io/wp-content/uploads/2020/08/Image-2020-08-23-at-10.25.21-PM.png"&gt;&lt;img class="alignnone wp-image-7490 size-full" src="https://res.cloudinary.com/practicaldev/image/fetch/s--AQxpO6jP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/08/Image-2020-08-23-at-10.25.21-PM.png" alt="Trace view of request" width="3580" height="2154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The tracing we see here comes built in with the Ruby Beeline! Not only do we automatically get spans wrapped around top-level HTTP requests, ActiveRecord calls, controller actions, and view rendering, we also get relevant context fields attached to all of those spans. Select the root span &lt;code&gt;http_request&lt;/code&gt; and scroll through the &lt;b&gt;Fields&lt;/b&gt; list in the right-hand sidebar to see what fields are attached to that span. Then choose another span, like &lt;code&gt;process_action.action_controller&lt;/code&gt;, to see the fields that come included with that one.&lt;/p&gt;

&lt;p&gt;You may already be thinking of additional context fields to instrument various parts of your app to send to Honeycomb, but this is a good stopping point. What are the next steps to get this MVP version into production?&lt;/p&gt;

&lt;h2&gt;Get Ready to Ship&lt;/h2&gt;

&lt;p&gt;Your app is now instrumented for Honeycomb! Let’s update a couple things so you can open a PR, push your new instrumentation to prod, and start unlocking more insights.&lt;/p&gt;

&lt;p&gt;Earlier, you called the &lt;code&gt;rails generate&lt;/code&gt; command with your API key directly, creating the &lt;code&gt;config/initializers/honeycomb.rb&lt;/code&gt; file. Now’s the time to remove the hard-coded API key there and store it safely according to how your team manages secrets.&lt;/p&gt;

&lt;p&gt;For example, if you’re using the Rails &lt;code&gt;config/secrets.yml&lt;/code&gt; file to manage your secrets, you can add your API key there.&lt;/p&gt;

&lt;pre class="EnlighterJSRAW"&gt;development:
  honeycomb_write_key: 'abc123'
production:
  honeycomb_write_key: 'abc123'&lt;/pre&gt;

&lt;p&gt;Then, update the Honeycomb initializer to safely use the API key straight from the secrets file:&lt;/p&gt;

&lt;pre class="EnlighterJSRAW"&gt;- config.write_key = "abc123"
+ config.write_key = Rails.application.secrets.honeycomb_write_key&lt;/pre&gt;

&lt;p&gt;And you’re set! Open that PR 🚢 (and &lt;a href="https://www.honeycomb.io/blog/spread-the-love-appreciating-our-pollinators-community/"&gt;check out the Pollinators blog post&lt;/a&gt; I shared earlier while you're waiting for PR reviews 😉).&lt;/p&gt;

&lt;h2&gt;Observe Production Traffic&lt;/h2&gt;

&lt;p&gt;Why the rush? Honeycomb’s home page comes alive in production. We can see the Total requests, Latency, and Error rate using the Rails auto-instrumentation built into the Ruby Beeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.honeycomb.io/wp-content/uploads/2020/08/Image-2020-08-23-at-10.53.27-PM.png"&gt;&lt;img class="alignnone wp-image-7491 size-full" src="https://res.cloudinary.com/practicaldev/image/fetch/s--5R7pjR4D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/08/Image-2020-08-23-at-10.53.27-PM.png" alt="Image showing Total requests, Latency, and Error rate using the Rails auto-instrumentation built into the Ruby Beeline" width="3632" height="2296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking over to the &lt;b&gt;HTTP Status Code&lt;/b&gt; tab, we can also see the breakdown of response codes as well as the distribution of latencies for each one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.honeycomb.io/wp-content/uploads/2020/08/Screen-Shot-2020-08-23-at-10.49.35-PM.png"&gt;&lt;img class="alignnone wp-image-7492 size-full" src="https://res.cloudinary.com/practicaldev/image/fetch/s--9COoz5zf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/08/Screen-Shot-2020-08-23-at-10.49.35-PM.png" alt="Image showing breakdown of response codes as well as the distribution of latencies for each one" width="3632" height="2296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s a ton to explore here, just start clicking around. See how much you can learn from the auto-instrumentation by itself.&lt;/p&gt;

&lt;h2&gt;Add Custom Instrumentation&lt;/h2&gt;

&lt;p&gt;Now that you’ve explored Honeycomb with your app’s production traffic, it’s time to dig deeper. You have a handle on events and tracing for regular Rails HTTP requests, so let’s try adding custom spans and fields. What’s unique about your app that you’d like to learn more about?&lt;/p&gt;

&lt;p&gt;You likely have some home-rolled &lt;code&gt;lib/&lt;/code&gt; code that’s not covered by the Beeline’s auto-instrumentation. For example, imagine that your app uses the &lt;a href="https://github.com/sferik/twitter" rel="noopener noreferrer"&gt;twitter&lt;/a&gt; gem to get a user’s tweets in batches of 200 at a time:&lt;/p&gt;

&lt;pre class="EnlighterJSRAW"&gt;def get_all_tweets(user)
  all = []
  options = { count: 200, include_rts: true }
  loop do
    tweets = client.user_timeline(user, options)
    return all if tweets.empty?
    all += tweets
    options[:max_id] = tweets.last.id - 1
  end
end&lt;/pre&gt;

&lt;p&gt;Here’s an updated version with some instrumentation:&lt;/p&gt;

&lt;pre class="EnlighterJSRAW"&gt;def get_all_tweets(user)
  Honeycomb.start_span(name: 'get_all_tweets') do
    Honeycomb.add_field_to_trace('user', user)
    all = []
    options = { count: 200, include_rts: true }
    loop do
      Honeycomb.start_span(name: 'get_batch') do
        Honeycomb.add_field('options', options)
        tweets = client.user_timeline(user, options)
        return all if tweets.empty?
        all += tweets
        options[:max_id] = tweets.last.id - 1
      end
    end
  end
end&lt;/pre&gt;

&lt;p&gt;We start by wrapping the entire &lt;code&gt;get_all_tweets&lt;/code&gt; method body in a span, and then we add the &lt;code&gt;user&lt;/code&gt; field to the entire trace, since we’ll probably care about it across the entire request. We add a second span block around the body of the loop called &lt;code&gt;get_batch&lt;/code&gt;, which creates a child span for each request where we get a 200-tweet batch. Inside of &lt;code&gt;get_batch&lt;/code&gt;, we’re adding the request &lt;code&gt;options&lt;/code&gt; as a field as well, so we can see the relevant arguments when hitting Twitter’s API.&lt;/p&gt;

&lt;p&gt;(At this point I’d probably break out the loop body into a separate &lt;code&gt;get_batch&lt;/code&gt; method, which is usually about the scope you want for a span.)&lt;/p&gt;

&lt;p&gt;Our nested spans will be rendered as a trace in the Honeycomb UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tEsm2q4r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/08/Image-2020-08-23-at-9.58.51-PM.png" class="article-body-image-wrapper"&gt;&lt;img class="alignnone size-full wp-image-7493" src="https://res.cloudinary.com/practicaldev/image/fetch/s--tEsm2q4r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/08/Image-2020-08-23-at-9.58.51-PM.png" alt="Screenshot of trace in the Honeycomb UI" width="2602" height="718"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each &lt;code&gt;get_batch&lt;/code&gt; is making an API call to Twitter, so to get all of the tweets from this user we needed to make eight API calls.&lt;/p&gt;

&lt;h2&gt;Continue Your Journey!&lt;/h2&gt;

&lt;p&gt;The Ruby Beeline does a lot of the heavy lifting to get you sending rich data into Honeycomb quickly and without a lot of work. On top of that, it’s not a lot of code to start adding your own custom instrumentation beyond what the Beeline does automatically.&lt;/p&gt;

&lt;p&gt;Now that you have data in, invite your team and see what insights you can learn together! &lt;a href="https://www.honeycomb.io/blog/all-aboard-team-onboarding/"&gt;Check out this blog post&lt;/a&gt; on helpful Honeycomb features for onboarding your teammates.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>observability</category>
      <category>o11y</category>
      <category>instrumentation</category>
    </item>
    <item>
      <title>What's your favorite incident story?</title>
      <dc:creator>shelby spees (she/her)</dc:creator>
      <pubDate>Mon, 19 Oct 2020 15:51:26 +0000</pubDate>
      <link>https://forem.com/shelbyspees/what-s-your-favorite-incident-story-125</link>
      <guid>https://forem.com/shelbyspees/what-s-your-favorite-incident-story-125</guid>
      <description>&lt;p&gt;How was the issue discovered? How were you involved? What was the impact? What did you learn?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>incidentresponse</category>
      <category>ops</category>
    </item>
    <item>
      <title>Have you ever been on-call? What was it like?</title>
      <dc:creator>shelby spees (she/her)</dc:creator>
      <pubDate>Thu, 15 Oct 2020 17:39:42 +0000</pubDate>
      <link>https://forem.com/shelbyspees/have-you-ever-been-on-call-what-was-it-like-3ad6</link>
      <guid>https://forem.com/shelbyspees/have-you-ever-been-on-call-what-was-it-like-3ad6</guid>
      <description>&lt;p&gt;Would you do it again? What would you change?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>oncall</category>
      <category>ops</category>
    </item>
    <item>
      <title>Observability 101: Terminology and Concepts</title>
      <dc:creator>shelby spees (she/her)</dc:creator>
      <pubDate>Wed, 14 Oct 2020 19:01:11 +0000</pubDate>
      <link>https://forem.com/honeycombio/observability-101-terminology-and-concepts-2644</link>
      <guid>https://forem.com/honeycombio/observability-101-terminology-and-concepts-2644</guid>
      <description>&lt;p&gt;When I first started following &lt;a href="https://twitter.com/mipsytipsy" rel="noopener noreferrer"&gt;Charity&lt;/a&gt; on Twitter back in early 2019, I was quickly overwhelmed by the new words and concepts she was discussing. I liked the results she described: faster debugging, less alert fatigue, happier users. Those are all things I wanted for my team! But I was hung up on these big polysyllabic words, which stopped me from taking those first steps toward improving our own observability.&lt;/p&gt;

&lt;p&gt;This post is my attempt to help orient folks who want to learn more about observability but maybe feel overwhelmed or intimidated by the vocabulary list, like I did. My goal is to get everyone on the same page about what these words mean so that we can focus on leveraging the tools and ideas to build better software and deliver more value! 💪&lt;/p&gt;

&lt;p&gt;Welcome to Observability 101! 🤓&lt;/p&gt;

&lt;h2&gt;Observability Fundamentals&lt;/h2&gt;

&lt;p&gt;In software, &lt;b&gt;observability&lt;/b&gt; (abbreviated as “o11y”) is the ability to ask new questions of the health of your running services without deploying new instrumentation. The term comes from &lt;a href="https://en.wikipedia.org/wiki/Control_theory"&gt;control theory&lt;/a&gt;, which defines observability as the ability to understand the internal state of a system from its external outputs (&lt;a href="https://en.wikipedia.org/wiki/Observability" rel="noopener noreferrer"&gt;source&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Telemetry&lt;/b&gt; consists of those “outputs”—it’s the data generated by your system that documents its state. Telemetry gets generated because of &lt;b&gt;instrumentation&lt;/b&gt;: code or tooling that captures data about the state of your running system and stores it in various formats. Some examples of software telemetry include: metrics, logs, traces, and structured events.&lt;/p&gt;

&lt;p&gt;Modern software teams have gotten good at accounting for failures that can be caught by tests and continuous integration tooling. We use retries and autoscaling and failovers to make our systems more resilient in the wild world of production. Since we catch and respond to known variables so well, what we're left with are the unknown-unknowns. The types of issues we often see in modern production software systems are &lt;strong&gt;emergent failure modes&lt;/strong&gt;, which happen when a bunch of unlikely events line up to degrade or take down your system. They're really interesting but difficult to debug, which is why we need observability.&lt;/p&gt;

&lt;p&gt;In order to have good observability into your software services, you need both good instrumentation generating high-context telemetry data, and you need sophisticated ways of interacting with that data that enable asking novel questions—questions you couldn’t have thought of when you wrote the code. Put more simply, software observability requires good data and good tooling. Let’s start by discussing the data.&lt;/p&gt;

&lt;h2&gt;Software Telemetry&lt;/h2&gt;

&lt;p&gt;To reiterate, &lt;strong&gt;telemetry&lt;/strong&gt; is data that your system generates that tells you about the system's health. The term comes from the Greek &lt;i&gt;tele-&lt;/i&gt;, meaning "remote", and &lt;i&gt;-metry&lt;/i&gt;, meaning "measure." You probably already generate telemetry from your programs, even if you’re not paying for monitoring or logging services. In fact, even the output from a &lt;code&gt;print&lt;/code&gt; or &lt;code&gt;console.log()&lt;/code&gt; statement is a form of telemetry!&lt;/p&gt;

&lt;p&gt;Let’s discuss the most common forms of telemetry generated from production software services.&lt;/p&gt;

&lt;h3&gt;Metrics&lt;/h3&gt;

&lt;p&gt;One commonly used form of telemetry data in software is &lt;b&gt;metrics&lt;/b&gt;. A metric consists of a single numeric value tracked over time. Traditional &lt;b&gt;monitoring&lt;/b&gt; uses system-level metrics to track things like CPU, memory, and disk performance. This data is important for choosing among virtual machine instance types, with options for processor speed, RAM, and hard disk storage. But it doesn’t tell you about user experience, or how to improve the performance of your code.&lt;/p&gt;

&lt;p&gt;Modern monitoring services also provide&lt;b&gt; application performance monitoring &lt;/b&gt;(APM) features, which track application-level metrics like average page load times, requests per second, and error rates. Each metric only tracks one variable, which makes them cheap to send and store. Values are pre-aggregated at write-time, however, so you need to deploy a code change if you want to track metrics for a new intersection of data, e.g. error rates for a specific user.&lt;/p&gt;

&lt;h3&gt;Logs&lt;/h3&gt;

&lt;p&gt;&lt;b&gt;Logs&lt;/b&gt; are text strings written to the terminal or to a file (often referred to as a “flat” log file). Logs can be any arbitrary string, but programming languages and frameworks have libraries to generate logs from your running code with relevant data at different levels of specificity (e.g. &lt;code&gt;INFO&lt;/code&gt; vs. &lt;code&gt;DEBUG&lt;/code&gt; mode). There’s no standardization among programming communities about what should get included at each log level.&lt;/p&gt;

&lt;p&gt;Log aggregation services allow you to send the content of your logs and store them for later retrieval. Querying flat logs (as opposed to structured logs) is slow because of the computational complexity of indexing and parsing strings, which makes these tools impractical for debugging and investigating your production code in near-realtime.&lt;/p&gt;

&lt;h3&gt;Traces&lt;/h3&gt;

&lt;p&gt;A &lt;b&gt;trace&lt;/b&gt; is a visualization of the events in your system showing the calling relationship between parent and child events as well as timing data for each event. The individual events that form a trace are called &lt;b&gt;spans&lt;/b&gt;. Each span stores the start time, duration, and &lt;code&gt;parent_id&lt;/code&gt;. Spans without a &lt;code&gt;parent_id&lt;/code&gt; are rendered as &lt;b&gt;root spans&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;b&gt;distributed trace &lt;/b&gt;connects calling relationships among events in distributed services. For example, Service A calls Service B, which makes a database query and then hits a third-party API.&lt;/p&gt;

&lt;h3&gt;Structured Events&lt;/h3&gt;

&lt;p&gt;A &lt;b&gt;structured event&lt;/b&gt; is a data format that allows you store key-value pairs for an arbitrary number of fields or dimensions, often in JSON format. At minimum, structured events usually have a timestamp and a &lt;code&gt;name&lt;/code&gt; field. Instrumentation libraries can automatically add other relevant data like the request endpoint, the user-agent, or the database query.&lt;/p&gt;

&lt;p&gt;When we talk about &lt;b&gt;wide events&lt;/b&gt;, we mean structured events with lots of fields. You can add whatever fields you want, and because they’re key-value pairs, it’s easy to query for them later on.&lt;/p&gt;

&lt;p&gt;Events can be built into traces by pointing each event to its parent event with the &lt;code&gt;parent_id&lt;/code&gt; field.&lt;/p&gt;

&lt;h2&gt;Telemetry concepts&lt;/h2&gt;

&lt;p&gt;These are some more terms commonly used when discussing observability.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Context&lt;/b&gt; refers to the collection of additional dimensions, fields, or tags on a piece of data that tells you more about the internal state of your system. Context can include metadata, system-level metrics, and domain-specific information about the running program.&lt;/p&gt;

&lt;p&gt;The &lt;b&gt;dimensionality&lt;/b&gt; of data refers to how many fields or attributes a piece of data has attached to it. Examples of low-dimensionality data include metrics and flat logs. High-dimensionality data includes anything that can support many fields or attributes, including JSON blobs, structs, and objects.&lt;/p&gt;

&lt;p&gt;The term &lt;b&gt;cardinality&lt;/b&gt; refers to how many possible values a dimension can have. Some examples of low-cardinality data include booleans (true/false) and days of the week. High-cardinality examples are first name, social security number, UUID, or build ID.&lt;/p&gt;

&lt;p&gt;Cardinality becomes becomes especially relevant when you start considering multiple dimensions at the same time. For a REST API, for example, endpoints can number from a dozens or hundreds (not including unique IDs). If you want to look at the behavior of each endpoint across the over 43 million user agents in the world, you get what’s called a &lt;b&gt;cardinality explosion&lt;/b&gt;. Imagine trying to track the intersection of endpoint × user agent with application-level metrics—that’s billions of lines of code. Thus when you have questions that require high-cardinality data to answer, you need telemetry and tools that support it.&lt;/p&gt;

&lt;h2&gt;Instrumentation with Honeycomb&lt;/h2&gt;

&lt;p&gt;Telemetry gets generated from &lt;b&gt;instrumentation&lt;/b&gt;, which is code or tooling that gathers data from your system in real-time. Whatever observability tool you’re using, there’s some set up and configuration to do in order to generate telemetry data and then send it off to your vendor or service.&lt;/p&gt;

&lt;p&gt;For brevity and clarity, I’m limiting this section to Honeycomb-specific instrumentation.&lt;/p&gt;

&lt;h3&gt;Instrumenting code for structured data&lt;/h3&gt;

&lt;p&gt;At Honeycomb we feel that you get the best observability into your systems when you instrument your code. There are a number of ways you can do that.&lt;/p&gt;

&lt;p&gt;You can &lt;b&gt;roll your own&lt;/b&gt; structured data by generating structured logs using your language or framework’s existing logging libraries. To get your structured logs into Honeycomb, this approach requires first instrumenting your code and then deploying an agent that’s configured to send the data to Honeycomb.&lt;/p&gt;

&lt;p&gt;There are also open-source options with built-in support for structured data. &lt;b&gt;OpenTelemetry&lt;/b&gt; (abbreviated “OTel”) is an open-source, vendor-neutral integration that can be used to instrument your service to generate structured telemetry data. OpenTelemetry is available as a package in a number languages--Go, Java, .NET, JavaScript, Python, Ruby, and others--with auto-instrumentation available for many popular frameworks. Also, since OpenTelemetry is vendor-neutral, you can use it to send the same telemetry data to multiple services!&lt;/p&gt;

&lt;p&gt;Honeycomb was founded before OpenTelemetry existed, however, so we started out by writing our own instrumentation libraries—&lt;b&gt;Beelines&lt;/b&gt;—to help you get data into Honeycomb. Our Beelines are available as a package in five languages: Go, Java, JavaScript, Python, and Ruby. The Beelines have auto-instrumentation for popular libraries and frameworks, including each language’s HTTP library as well as Spring, Rails, Flask, Express.js, and others.&lt;/p&gt;

&lt;p&gt;Under the hood, the Beeline integrations use &lt;b&gt;Libhoney&lt;/b&gt;, our low-level event handler SDK in each of the languages. Libhoney does not support tracing out of the box, and it’s rare that you should need to interface with it directly. If you’re just getting started with Honeycomb, we recommend using a Beeline or OpenTelemetry. The &lt;a href="https://docs.honeycomb.io/getting-data-in/#instrumenting-for-the-first-time" rel="noopener noreferrer"&gt;Get Your Data In docs&lt;/a&gt; provide more guidance on choosing an instrumentation library.&lt;/p&gt;

&lt;h2&gt;What observability tooling needs&lt;/h2&gt;

&lt;p&gt;Observability goes beyond application-level monitoring. It goes beyond tracing. It goes beyond “&lt;a href="https://www.honeycomb.io/blog/they-arent-pillars-theyre-lenses/" rel="noopener noreferrer"&gt;three pillars&lt;/a&gt;.”&lt;/p&gt;

&lt;p&gt;Observability tooling needs to support structured events, unaggregated data blobs containing whatever key-values pairs you decide to send. It needs to empower you to flow seamlessly between broad aggregates (time series graphs) and deep context (trace visualizations) and then broad aggregates again, using your structured events’ context fields to ensure that the answers you get are relevant and informative. Observability means aggregating at query-time, not at write-time, because you never know what questions you might want to ask later on.&lt;/p&gt;

&lt;p&gt;With observability, you can deeply interrogate your code behavior in production because you have more than just isolated time series graphs and some trace visualizations. You can send whatever fields might be relevant inside your structured events, and then ask new questions that your vendor (and you!) could never anticipate.&lt;/p&gt;

&lt;h2&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
    &lt;li&gt;To learn more about the benefits of observability, read our &lt;a href="https://www.honeycomb.io/guide-achieving-observability/" rel="noopener noreferrer"&gt;Guide to Achieving Observability&lt;/a&gt;.&lt;/li&gt;
    &lt;li&gt;For a deeper explanation of the relationship between metrics and observability, read &lt;a href="https://www.honeycomb.io/getting-started-with-honeycomb-metrics/" rel="noopener noreferrer"&gt;Getting Started with Honeycomb Metrics&lt;/a&gt;.&lt;/li&gt;
    &lt;li&gt;For guidance on how to upgrade from flat logs to structured data, read &lt;a href="https://www.honeycomb.io/the-path-from-unstructured-logs-to-observability/" rel="noopener noreferrer"&gt;The Path from Unstructured Logs to Observability&lt;/a&gt;.&lt;/li&gt;
    &lt;li&gt;To learn more about how distributed tracing fits into your observability practice, read our &lt;a href="https://www.honeycomb.io/distributed-tracing/" rel="noopener noreferrer"&gt;Distributed Tracing guide&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Reach out!&lt;/h2&gt;

&lt;p&gt;Have questions? Notice something I missed? Reach out to me on &lt;a href="https://twitter.com/shelbyspees" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, or email me directly at &lt;a href="mailto:shelby@honeycomb.io" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="mailto:shelby@honeycomb.io"&gt;shelby@honeycomb.io&lt;/a&gt;. I also hold &lt;a href="https://calendly.com/shelbyspees/30min" rel="noopener noreferrer"&gt;office hours&lt;/a&gt;—schedule 30 minutes with me so I can help you reach the next level on your observability journey!&lt;/p&gt;

&lt;p&gt;Experience what Honeycomb can do for your business. Watch &lt;a href="https://www.honeycomb.io/get-a-demo?&amp;amp;utm_source=Devto&amp;amp;utm_Devto=blog&amp;amp;utm_campaign=referral&amp;amp;utm_keyword=%7Bkeyword%7D&amp;amp;utm_content=observability-101-terminology-and-concepts"&gt;this short demo&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>observability</category>
      <category>o11y</category>
      <category>telemetry</category>
      <category>instrumentation</category>
    </item>
    <item>
      <title>HoneyByte: Incremental Instrumentation Beyond the Beeline</title>
      <dc:creator>shelby spees (she/her)</dc:creator>
      <pubDate>Wed, 15 Jul 2020 03:56:40 +0000</pubDate>
      <link>https://forem.com/honeycombio/honeybyte-incremental-instrumentation-beyond-the-beeline-1a88</link>
      <guid>https://forem.com/honeycombio/honeybyte-incremental-instrumentation-beyond-the-beeline-1a88</guid>
      <description>&lt;p&gt;"It turns out," said Liz, "it was not a giant pile of work to start adding those rich instrumentation spans as you need them."&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__341241"&gt;
  
    .ltag__user__id__341241 .follow-action-button {
      background-color: #0D4D4B !important;
      color: #fdf9f3 !important;
      border-color: #0D4D4B !important;
    }
  
    &lt;a href="/lizthegrey" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wkUuU-4X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--4Ajunks7--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/341241/2e279c9e-a7b7-4378-9e69-8a6c3f7feb63.jpeg" alt="lizthegrey image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/lizthegrey"&gt;Liz Fong-Jones&lt;/a&gt;
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/lizthegrey"&gt;Principal Dev Advocate @honeycombio, SRE, Observability expert&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Liz Fong-Jones was telling dev.to’s Molly Struve about an error she encountered while trying to update her dev.to profile. When she entered &lt;code&gt;honeycomb.io&lt;/code&gt; into the Employer URL field, the app responded with an angry red box:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hwbSpFmg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/error.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hwbSpFmg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/error.png" alt='Large red box with the following error message: "1 error prohibited your profile from being saved: Employer url is not a valid URL"'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;dev.to’s URL validator is quite picky--it rejects anything not prepended with &lt;code&gt;http&lt;/code&gt; or &lt;code&gt;https&lt;/code&gt;. Most users won’t think to include that, however. Liz certainly didn’t.&lt;/p&gt;

&lt;p&gt;Faced with this error message, Liz thought, "Gee, thanks. That’s really not helpful." Then she removed her frustrated user hat and exchanged it for her engineer hat. "I was really really curious. How often is this happening?"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kHHv2UgX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/employer_url.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kHHv2UgX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/employer_url.png" alt='Employer URL text input field highlighted in red, containing the string "honeycomb.io"'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a &lt;a href="https://dev.to/honeycombio/honeybyte-make-a-beeline-toward-observability-just-like-dev-s-molly-struve-f1e"&gt;previous HoneyByte&lt;/a&gt;, we showed how Molly Struve integrated the Ruby Beeline with dev.to's Rails codebase in one step.&lt;/p&gt;

&lt;p&gt;Leaning on the Beeline integration, which already wraps the app’s requests in spans, Liz opened a pull request on GitHub with two changes to the &lt;code&gt;update&lt;/code&gt; method of the &lt;code&gt;users_controller&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uvUnkCWK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/PR_diff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uvUnkCWK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/PR_diff.png" alt="The GitHub diff view from Liz’s pull request"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, she added an &lt;code&gt;error&lt;/code&gt; field so that the user-facing error message gets added to the current span’s context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Honeycomb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then she made sure the &lt;code&gt;update method&lt;/code&gt; returned a &lt;code&gt;400 BAD REQUEST&lt;/code&gt; for any invalid user input, which is the reason for ending up in that &lt;code&gt;else&lt;/code&gt; clause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:edit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :bad_request&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once her PR got merged, Liz reproduced the error by making the same edit to her dev.to profile Employer URL, returning the same error message. Then she went into Honeycomb to validate that the new contexts fields got sent along with her request:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jhxArc-o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/image-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jhxArc-o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/image-1.png" alt="Raw data view in Honeycomb showing error context"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great, the PR was a success!&lt;/p&gt;

&lt;h2&gt;
  
  
  Catch All the Errors!
&lt;/h2&gt;

&lt;p&gt;It turns out, Liz's pull request didn't just add instrumentation for the Employer URL field. Now dev.to is sending new context for any &lt;code&gt;update&lt;/code&gt; request where a user encounters an error.&lt;/p&gt;

&lt;p&gt;Liz wanted to see what other errors might be interesting. Since she streams on Twitch, she queried for all instances of invalid Twitch URL input. What she found was surprising.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r0UlVDF6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/raw_twitch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r0UlVDF6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/raw_twitch.png" alt="Honeycomb raw data view. Each twitch_url error has a Twitter URL as its parameter."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;dev.to users were entering their Twitter URLs into the Twitch URL field! After seeing the data, the user flow jumps out: People quickly scrolling down to fill out profile fields could easily mix Twitch with Twitter.&lt;/p&gt;

&lt;p&gt;"When I was solving the 'I can't update my employer URL,'" said Liz, "I did not anticipate being able to debug Twitch URLs and any other field that gets added in the future to the users page. But now that's been done with two lines of code."&lt;/p&gt;

&lt;p&gt;There are numerous design solutions for this bit of friction, but it's likely that it never would have been discovered without Liz's change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare with Traditional Metrics
&lt;/h2&gt;

&lt;p&gt;Let’s imagine investigating this trend using time series metrics.&lt;/p&gt;

&lt;p&gt;For those who have tried to instrument your return status codes like what Liz did, you probably saw a number of &lt;code&gt;400&lt;/code&gt; errors in production and scratched your head, unable to connect the errors to the code that sent them. Invalid user input doesn’t throw an exception, so it won’t be caught by error monitoring tools. You’d never learn about users confusing the Twitch URL field with Twitter URL.&lt;/p&gt;

&lt;p&gt;Let’s say your CEO found the Twitch/Twitter issue while trying to edit their own dev.to profile and specifically asked you to find out how frequently it occurs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i9RBq3HR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i9RBq3HR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/image.png" alt='Twitch URL text input field highlighted in red, containing a Twitter URL for user "ceo"'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to track the Twitch URL issue, you need to count that error message in isolation. It’s relatively manageable to add statsd metrics for the dozen or so input fields on a dev.to profile, but you may decide to only count the &lt;code&gt;twitch_url&lt;/code&gt; if that’s all you’re being asked about.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt; &lt;span class="ss"&gt;:twitch_url&lt;/span&gt;
  &lt;span class="vg"&gt;$statsd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"error.twitch_url"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without your CEO’s specific question, you have to decide in advance to send error messages in the first place. You might count every kind of error in your model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="vg"&gt;$statsd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"error.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Rails we can take for granted that the error &lt;code&gt;keys&lt;/code&gt; are limited by the columns in the User model, but in a different context it can be risky to generate metric names dynamically. If you have unpredictable error names and create statsd counters on the fly, you’ll end up with an unbounded explosion of metrics, each with only a few occurrences. In the mean time, you’re being charged for each one. That cost adds up quickly, which results in teams that are averse to sending data.&lt;/p&gt;

&lt;p&gt;On top of that, adding counters for errors only gets you so far. Unless you’re also tracking the parameter that was considered invalid, there’s no context that’ll tell you that users are mixing up the Twitch URL input field with Twitter.&lt;/p&gt;

&lt;p&gt;If, for some reason, you actually thought to send the invalid parameter as its own metric (unbounded high cardinality be damned), you still have to do the work of matching the bad input to the &lt;code&gt;400&lt;/code&gt; errors! It’s painful to query for this in time-series graph visualizations. You’re squinting at disparate graphs, each with a single occurrence of the thing you care about. At the end of the day, you’re just seeing aggregate counts and trying to reverse-engineer the user flow.&lt;/p&gt;

&lt;p&gt;The Twitch/Twitter URL issue will likely remain invisible, even with the granular metrics you’ve added. Understandably, most teams won’t bother.&lt;/p&gt;

&lt;p&gt;Compare this with Liz's instrumentation. By adding a single field, you get rich data for a variety of contexts. No counters necessary. You gain observability into friction in your user experience that doesn't get surfaced in bug reports. This opens the door for granular A/B testing, feature flagging, and usability research.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Turn!
&lt;/h2&gt;

&lt;p&gt;If you already use Honeycomb, you probably already have a commonly-tread path in your codebase that you've instrumented, especially if you’re using one of the Beelines. Look for somewhere in your business logic where you return an error that isn’t an exception. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bad user input&lt;/li&gt;
&lt;li&gt;permissions error&lt;/li&gt;
&lt;li&gt;network error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What context are you currently sending to Honeycomb? Are there more fields you can add? Don’t be afraid to send wide events with many context fields. Honeycomb is designed to handle it!&lt;/p&gt;




&lt;p&gt;Want to learn more about observability? &lt;a href="https://www.honeycomb.io/guide-achieving-observability/?&amp;amp;utm_source=devto&amp;amp;utm_medium=blog-links&amp;amp;utm_campaign=recycle&amp;amp;utm_term=&amp;amp;utm_content=guide-to-achieiving-observability"&gt;Download the Honeycomb Guide to Achieving Observability&lt;/a&gt; today!&lt;/p&gt;

</description>
      <category>meta</category>
      <category>observability</category>
      <category>rails</category>
      <category>honeycomb</category>
    </item>
    <item>
      <title>HoneyByte: Make a Beeline Toward Observability Just Like DEV’s Molly Struve</title>
      <dc:creator>shelby spees (she/her)</dc:creator>
      <pubDate>Mon, 13 Jul 2020 19:59:01 +0000</pubDate>
      <link>https://forem.com/honeycombio/honeybyte-make-a-beeline-toward-observability-just-like-dev-s-molly-struve-f1e</link>
      <guid>https://forem.com/honeycombio/honeybyte-make-a-beeline-toward-observability-just-like-dev-s-molly-struve-f1e</guid>
      <description>&lt;p&gt;“When things broke,” Molly explained, “you’re mad scrambling—jumping from website to website to website, trying to put the pieces together.” Molly was able to use Honeycomb to fix things up: “It makes my job easier as an SRE.”&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__119473"&gt;
  
    .ltag__user__id__119473 .follow-action-button {
      background-color: #61122F !important;
      color: #FFFFFF !important;
      border-color: #61122F !important;
    }
  
    &lt;a href="/molly_struve" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xMrAIHhS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--qARnw-JH--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/119473/4fe2a414-c5d4-4cfe-b9da-8b9da90fb5e6.jpg" alt="molly_struve image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/molly_struve"&gt;Molly Struve (she/her)&lt;/a&gt;
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/molly_struve"&gt;International Speaker 🗣
Runner 🏃‍♀️ 
Always Ambitious. Never Satisfied. 
I ride 🦄's IRL&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Getting started with Honeycomb doesn’t require a lot of work: at &lt;a href="http://dev.to"&gt;dev.to&lt;/a&gt;, they used the Ruby Beeline to get it going: “I didn’t do that much,” she said. “I feel like I cheated!” We invite you along on their journey, and we'll share precisely what lines of code dev.to used to get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service Fatigue
&lt;/h2&gt;

&lt;p&gt;“When I started at DEV, there was very little in the way of monitoring, in terms of observability,” said Molly. “We had six or seven little tools, each doing one little piece.”&lt;/p&gt;

&lt;p&gt;While each tool had initially served a purpose for the team, the fact that there were so many became a pain point during incidents. It’s a familiar experience for SREs or anyone who owns code in production.&lt;/p&gt;

&lt;p&gt;“You’d look at one service, you’d see a spike, but that was all the information you'd get. So then you’d have to go to the other services and try to line up when the spike happened.”&lt;/p&gt;

&lt;p&gt;Even during normal site operations, that’s enough to fatigue most people. With the added stress of an outage it becomes painful, error-prone, or both. That’s why &lt;a href="https://github.com/thepracticaldev/dev.to/issues/4925"&gt;Molly took the initiative to consolidate DEV’s use of monitoring services&lt;/a&gt; as much as possible. Whether facing an outage or just wanting to see how the site was being used, she wanted all that information in one place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GO1sOlhm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/list.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GO1sOlhm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/list.png" alt="GitHub issue description in which Molly lists the seven monitoring tools she wants to consolidate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Molly’s initial goal was to consolidate her tooling, but what she found with Honeycomb were benefits beyond easing the immediate pain of service fatigue.&lt;/p&gt;

&lt;p&gt;“Honeycomb really gives us some great insight into how users are interacting with our platform,” she said. “It’s been super helpful not only for debugging issues, but allowing me to find places in the application where slowdowns are happening, we’re hitting the database too many times, etc.”&lt;/p&gt;

&lt;p&gt;It’s no coincidence that this was Molly’s first project after joining the DEV team. She gave a &lt;a href="https://www.usenix.org/conference/srecon19emea/presentation/struve"&gt;talk at SREcon19&lt;/a&gt; last year on the benefits of an easy-to-use, centralized monitoring system. “The ability to have all that information in one spot and then be able to really drill down is super convenient,” she said. “Having it all in one place has been super helpful.”&lt;/p&gt;

&lt;p&gt;Additionally, streamlining their tooling has simplified onboarding engineers at DEV. “They no longer have to learn five, six, seven different tools,” she explained. “When they come on, I can say 'Here’s Honeycomb, this is how you use it,' and that’s it.”&lt;/p&gt;

&lt;p&gt;“Sometimes people think when you’re an SRE that we just magically know, but we don’t,” said Molly. “Half of what we’re doing is literally just throwing in queries, hitting stuff. We look at a graph, ‘Ah there’s nothing there.’ Okay let’s look at a different one, ‘Ah there’s nothing there.’”&lt;/p&gt;

&lt;p&gt;Molly makes videos for her teammates demonstrating how she uses Honeycomb to help them learn the tool, and seeing her exploration process has helped them understand that it’s more about mindset than expertise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M3uSTco3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/request.route_.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M3uSTco3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/request.route_.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;“I think that was a big aha moment for some people,” she said. “There’s no shortcuts to what we do, it’s basically getting all the data and finding the pieces that stick out.”&lt;/p&gt;

&lt;p&gt;Honeycomb enables first-principles debugging. That's when you don’t have to be an expert in the system, you just need to know where to start digging. Demystifying debugging as a process can empower an entire team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Up and Running
&lt;/h2&gt;

&lt;p&gt;Molly described her experience adding Honeycomb’s Beeline gem for Ruby. It was simpler than she anticipated.&lt;/p&gt;

&lt;p&gt;“I didn’t do that much,” she said. “I feel like I cheated!”&lt;/p&gt;

&lt;p&gt;“I literally just took your &lt;a href="https://docs.honeycomb.io/getting-data-in/ruby/beeline/"&gt;documentation&lt;/a&gt;, slapped it into the project and it was like, Boom! there’s all of your data,” she said. “It was pretty awesome.” DEV is open-source, so you can see the exact change she made in her &lt;a href="https://github.com/thepracticaldev/dev.to/pull/4649"&gt;pull request on GitHub&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add &lt;code&gt;honeycomb-beeline&lt;/code&gt; gem to Gemfile&lt;/li&gt;
&lt;li&gt;add Honeycomb API key to secrets&lt;/li&gt;
&lt;li&gt;generate Honeycomb configuration using the Beeline’s Rails generator: &lt;code&gt;bundle exec rails generate honeycomb YOUR_API_KEY --dataset rails&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1qSocVGV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/gemfile-1536x408.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1qSocVGV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/gemfile-1536x408.png" alt="GitHub PR diff showing the honeycomb-beeline gem being added to a Gemfile"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P_4xOdxR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/config-1536x588.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P_4xOdxR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/config-1536x588.png" alt="GitHub PR diff showing a new file added with the filename config/initializers/honeycomb.rb. The file contents are Rails configuration settings."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once Molly’s PR got merged, the DEV team was sending data to Honeycomb and gaining new insights.&lt;/p&gt;

&lt;p&gt;“It was super straightforward, and honestly that got us probably 80% of the way there,” said Molly. “Any tool where you can get up and running so quickly is huge.”&lt;/p&gt;

&lt;p&gt;By using the Ruby Beeline first, Molly reduced the overhead of starting out compared to instrumenting the entire application from scratch. “If you can get value out of it before you even have to customize? That’s a pretty big deal. If you have to go into the weeds before you can get value? That’s a blocker for a lot of people.”&lt;/p&gt;

&lt;p&gt;Once she used Honeycomb with production data, Molly thought of specific areas she wanted to instrument in more detail. Observability isn’t all-or-nothing. The incremental approach is a lot less scary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Tool
&lt;/h2&gt;

&lt;p&gt;Honeycomb has several features that encourage users to dive in and start querying. The suggested queries on the home page help users get oriented.&lt;/p&gt;

&lt;p&gt;“That’s one of the first things I start most of the videos with,” Molly said, referring to the instructional videos she makes for her teammates. Looking at the home page, Molly said, “something sticks out and then I kind of dive into it.”&lt;/p&gt;

&lt;p&gt;Another feature of Honeycomb that Molly appreciates is the query builder, in particular, the autocomplete. “That helps immensely in trying to figure out what fields you want to query for.”&lt;/p&gt;

&lt;p&gt;Rather than having users type into a blank query editor, the query builder reduces friction by breaking up clauses into separate fields. There’s no bespoke query language to learn, it’s SQL but more visual and with suggestions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--o6eZSyfw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/query-builder.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o6eZSyfw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/query-builder.png" alt="Honeycomb's query builder UI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://docs.honeycomb.io/working-with-your-data/queries/"&gt;Read more about the query builder.&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;With the query builder you don’t have to memorize all the fields the app is sending to Honeycomb. This evens the playing field among team members.&lt;/p&gt;

&lt;p&gt;Molly described to her coworkers the mindset she has when investigating interesting data in Honeycomb: “Sometimes I’m not even quite sure how I’m gonna filter down into what I want, but I just start typing and I start scrolling and I start looking.”&lt;/p&gt;

&lt;p&gt;BubbleUp is another unique feature in Honeycomb that Molly finds super helpful. “Let me tell you how many times I’ve been on other platforms where all I want is to just narrow down that graph with a drag and drop, and you can’t.”&lt;/p&gt;

&lt;p&gt;Rather than trying to correlate system behavior and line up time periods across separate graphs, BubbleUp does the work of comparing the baseline to the outlier data you’ve selected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shared History
&lt;/h2&gt;

&lt;p&gt;Another Honeycomb feature Molly loves is the saved queries. “Usually I’m opening like, tab after tab after tab after tab trying to save all my queries. I go down one path and it doesn’t return anything, so then I have to go all the way back.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SwNnrP6T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/history.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SwNnrP6T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.honeycomb.io/wp-content/uploads/2020/04/history.png" alt="several of Molly's queries in the DEV team's query history"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Honeycomb automatically saves your query history and displays them in the sidebar. “Having those saved queries, you can literally just go over to your sidebar, you can easily go back, you can go down a rabbit hole and come back to your starting point in seconds. That’s one feature that’s really stood out to me in Honeycomb that I haven’t really seen anywhere else.”&lt;/p&gt;

&lt;p&gt;The shared history and query permalinks make it easy to collaborate and get a second set of eyes, or to prevent multiple people from hitting the same dead ends. “If you’re having an incident you can slap the link to people. They can see your history as well so then they can recreate what you did and possibly find issues with it, possibly take it down their own path.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Level Up Your Team
&lt;/h2&gt;

&lt;p&gt;If you're a Rails developer, you can add the Beeline gem to your app and start sending data today. Go to &lt;a href="https://ui.honeycomb.io/signup?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=trial&amp;amp;utm_content=beeline-honeybyte"&gt;honeycomb.io/signup&lt;/a&gt; to start for free.&lt;/p&gt;

</description>
      <category>meta</category>
      <category>observability</category>
      <category>rails</category>
      <category>honeycomb</category>
    </item>
    <item>
      <title>What does service ownership mean to you?</title>
      <dc:creator>shelby spees (she/her)</dc:creator>
      <pubDate>Wed, 13 May 2020 19:21:44 +0000</pubDate>
      <link>https://forem.com/shelbyspees/what-does-service-ownership-mean-to-you-5fkl</link>
      <guid>https://forem.com/shelbyspees/what-does-service-ownership-mean-to-you-5fkl</guid>
      <description>&lt;p&gt;What does it mean to have ownership of a software service?&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
  </channel>
</rss>
