<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Preslav Mihaylov</title>
    <description>The latest articles on DEV Community by Preslav Mihaylov (@pmihaylov).</description>
    <link>https://dev.to/pmihaylov</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%2F50013%2Ffced6635-1ffc-46aa-a6c2-1898c2a047ca.jpeg</url>
      <title>DEV Community: Preslav Mihaylov</title>
      <link>https://dev.to/pmihaylov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pmihaylov"/>
    <language>en</language>
    <item>
      <title>Lessons learnt from publishing my programming course</title>
      <dc:creator>Preslav Mihaylov</dc:creator>
      <pubDate>Sat, 05 Feb 2022 16:08:04 +0000</pubDate>
      <link>https://dev.to/pmihaylov/lessons-learnt-from-publishing-my-programming-course-c4m</link>
      <guid>https://dev.to/pmihaylov/lessons-learnt-from-publishing-my-programming-course-c4m</guid>
      <description>&lt;p&gt;I’ve been working on my course — &lt;a href="https://www.udemy.com/course/learn-programming-with-go/?referralCode=305C0004BD6402F88FC3"&gt;Learn Programming with Go, One Game at a Time&lt;/a&gt;, for nearly a year &amp;amp; made many mistakes along the way.&lt;/p&gt;

&lt;p&gt;Nevertheless, I finally published it &amp;amp; earned $2,158.25 in a month:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WLt__1Qs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/464/0%2AwkSTkLWaqR_5jTtz" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WLt__1Qs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/464/0%2AwkSTkLWaqR_5jTtz" alt="" width="464" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s what I’ve learnt along the way 👇&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting started is scariest
&lt;/h3&gt;

&lt;p&gt;One of the scariest things of making my course was getting started.&lt;/p&gt;

&lt;p&gt;I’ve been teaching programming to 2000+ students since 2015 in bootcamps &amp;amp; universities, but I never felt ready to make my own course.&lt;/p&gt;

&lt;p&gt;I realised that I never will be &amp;amp; took the leap. Best decision ever!&lt;/p&gt;

&lt;h3&gt;
  
  
  It’s harder than it looks
&lt;/h3&gt;

&lt;p&gt;Initially, I thought designing the curriculum &amp;amp; recording the videos is the bulk of the effort.&lt;/p&gt;

&lt;p&gt;There was so much more — choosing the right platform, video editing, marketing, designing the logo, etc…&lt;/p&gt;

&lt;p&gt;I thought that making the course would take me ~3 months in total. I ended up spending more than a year working on it.&lt;/p&gt;

&lt;p&gt;But most of that time was lost because I had no clue what I had to do &amp;amp; missed many opportunities to move efforts in parallel.&lt;/p&gt;

&lt;p&gt;For example, I first spent 3 months recording all the videos &amp;amp; just then started to look for a video editor to edit all of them. If I do this again, I’d rather do both of those in parallel — record one week’s worth of content &amp;amp; send it over for editing.&lt;/p&gt;

&lt;p&gt;What’s more, I started thinking about the course poster only after everything else was ready. Little did I know that would also take more than a month because the illustrator I hired had too many orders in their queue.&lt;/p&gt;

&lt;p&gt;If I had known all this &amp;amp; optimised my workflows, I expect I would have been able to finish everything in 5–6 months.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choosing the best platform
&lt;/h3&gt;

&lt;p&gt;In terms of platform, I thought about building my own at first, but quickly realised that’s infeasible, unless I have a year or so to spare.&lt;/p&gt;

&lt;p&gt;Hence, I decided to use one of the existing teaching platforms. The ones I was considering were &lt;a href="https://teachable.com/"&gt;Teachable&lt;/a&gt;, &lt;a href="https://www.slip.so/"&gt;Slip&lt;/a&gt; and &lt;a href="https://www.udemy.com/"&gt;Udemy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first two looked good but I thought they were more suited for creators with established audiences.&lt;/p&gt;

&lt;p&gt;I chose Udemy because I could keep most of the profits from my channels but they also offered free marketing on my behalf for a bigger commission, which was fair.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfalls to watch out for when recording
&lt;/h3&gt;

&lt;p&gt;Recording the videos seemed trivial but there were some pitfalls to watch out for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maximise your IDE/Browser/etc font size.&lt;/li&gt;
&lt;li&gt;Record at max possible quality.&lt;/li&gt;
&lt;li&gt;Don’t record \w low energy. Hard to fix later.&lt;/li&gt;
&lt;li&gt;Have the video editor in mind. Don’t make them guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  To video edit or not
&lt;/h3&gt;

&lt;p&gt;At first I thought about doing it myself.&lt;/p&gt;

&lt;p&gt;And I did… for a single lecture, which took me more than 20 hours.&lt;/p&gt;

&lt;p&gt;Totally not worth it. Hire a good video editor who’ll do it much faster at a reasonable price.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.fiverr.com/"&gt;Fiverr&lt;/a&gt; is a great place to find one.&lt;/p&gt;

&lt;h3&gt;
  
  
  The beautiful course poster
&lt;/h3&gt;

&lt;p&gt;Fiverr is also the place I found an amazing illustrator for the course poster.&lt;/p&gt;

&lt;p&gt;Some might consider this micro optimisation, but for me, it was totally worth it. I don’t care if it brings more engagement or not, I just love it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EtD99YkN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/960/0%2AleAc74DRn5DCVPw7" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EtD99YkN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/960/0%2AleAc74DRn5DCVPw7" alt="" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The dreadful marketing…
&lt;/h3&gt;

&lt;p&gt;In terms of marketing, I shared the course \w my inner circle first — friends &amp;amp; family who gave me good initial reviews.&lt;/p&gt;

&lt;p&gt;Next, I shared it via Linkedin to my professional circle.&lt;/p&gt;

&lt;p&gt;But what worked best was &lt;a href="https://www.reddit.com/r/golang/comments/rz0bjz/learn_programming_with_go_one_game_at_a_time/"&gt;sharing it&lt;/a&gt; to the Golang community on reddit.&lt;/p&gt;

&lt;p&gt;What didn’t work was using twitter promotions. &lt;a href="https://twitter.com/PreslavMihaylov/status/1479460688028487682"&gt;My tweet&lt;/a&gt; got good engagement, but not many actually bought the course.&lt;/p&gt;

&lt;p&gt;I spent around $115 for a single day, which lead to 3 people buying the course.&lt;/p&gt;

&lt;p&gt;I’m probably just a marketing newbie ¯\_(ツ)_/¯&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Finally, no matter how optimised my setup is, the most important thing for me is delivering very high-quality content.&lt;/p&gt;

&lt;p&gt;I’d rather grow to be a niche instructor with courses students adore vs. pushing my way through with growth hacks &amp;amp; delivering sugarcoated money-grabbers.&lt;/p&gt;

&lt;p&gt;If you wanna learn more about my journey, follow &lt;a href="https://twitter.com/PreslavMihaylov"&gt;@PreslavMihaylov&lt;/a&gt; on Twitter.&lt;/p&gt;

</description>
      <category>course</category>
      <category>lessonslearned</category>
      <category>go</category>
      <category>coding</category>
    </item>
    <item>
      <title>How to set up Vim for Go Development</title>
      <dc:creator>Preslav Mihaylov</dc:creator>
      <pubDate>Sun, 11 Apr 2021 08:35:41 +0000</pubDate>
      <link>https://dev.to/pmihaylov/how-to-setup-vim-for-go-development-14e7</link>
      <guid>https://dev.to/pmihaylov/how-to-setup-vim-for-go-development-14e7</guid>
      <description>&lt;p&gt;One of the major reasons I love programming in Go is that I can do it in any IDE or editor I want, including my favourite &lt;a href="https://pmihaylov.com/category/boost-your-vim/" rel="noreferrer noopener"&gt;Vim editor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Unlike Java, for example, which is highly coupled to IntelliJ or Eclipse, Go is much more lightweight and the tools you use with it can be integrated with any editor.&lt;/p&gt;

&lt;p&gt;In this guide, I will help you setup your Vim as a fully-fledged Go IDE, including the plugins you need to install but also setting up the important options &amp;amp; mappings which you'll use daily.&lt;/p&gt;

&lt;h2&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;You'll need to have &lt;a href="https://golang.org/doc/install" rel="noopener noreferrer"&gt;Go installed&lt;/a&gt; &amp;amp; your Go environment setup, namely your &lt;strong&gt;$GOPATH&lt;/strong&gt; and related subdirectories - &lt;strong&gt;/bin /pkg /src&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You'll also need to use &lt;a href="https://github.com/neovim/neovim" rel="noopener noreferrer"&gt;nvim&lt;/a&gt; instead of vim or have your vim updated to version 8+.&lt;/p&gt;

&lt;h2&gt;The Main Vim plugin for Go&lt;/h2&gt;

&lt;p&gt;One of the only plugins you need for Go is called &lt;a href="https://github.com/fatih/vim-go" rel="noopener noreferrer"&gt;vim-go&lt;/a&gt;. It aggregates all the tools you need to get started with Go development.&lt;/p&gt;

&lt;p&gt;To install it via &lt;a href="https://github.com/tpope/vim-pathogen" rel="noopener noreferrer"&gt;pathogen&lt;/a&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;git clone https://github.com/tpope/vim-pathogen ~/.vim/bundle&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://github.com/fatih/vim-go#install" rel="noopener noreferrer"&gt;Other installation options&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After you've installed it, you can customise it using these options which are highly opinionated, but work best for me:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;" disable all linters as that is taken care of by coc.nvim
let g:go_diagnostics_enabled = 0
let g:go_metalinter_enabled = []

" don't jump to errors after metalinter is invoked
let g:go_jump_to_error = 0

" run go imports on file save
let g:go_fmt_command = "goimports"

" automatically highlight variable your cursor is on
let g:go_auto_sameids = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For syntax highlighting, use these options:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;let g:go_highlight_types = 1
let g:go_highlight_fields = 1
let g:go_highlight_functions = 1
let g:go_highlight_function_calls = 1
let g:go_highlight_operators = 1
let g:go_highlight_extra_types = 1
let g:go_highlight_build_constraints = 1
let g:go_highlight_generate_tags = 1&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Finally, install all the required go tools by opening vim and running:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;:GoInstallBinaries&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;coc.nvim - Autocompletion &amp;amp; linting&lt;/h2&gt;

&lt;p&gt;In the past, I've relied on &lt;a href="https://github.com/ycm-core/YouCompleteMe" rel="noreferrer noopener"&gt;YouCompleteMe&lt;/a&gt; for auto-completion or &lt;a href="https://github.com/fatih/vim-go" rel="noopener noreferrer"&gt;vim-go&lt;/a&gt;'s native integration with vim for auto-completion &amp;amp; &lt;a href="https://github.com/dense-analysis/ale" rel="noreferrer noopener"&gt;ALE&lt;/a&gt; for linting/static code analysis.&lt;/p&gt;

&lt;p&gt;Nowadays, I prefer using &lt;a href="https://github.com/neoclide/coc.nvim" rel="noreferrer noopener"&gt;coc.nvim&lt;/a&gt; and its plugins to do all that as I've found it to be more reliable than all of those tools.&lt;/p&gt;

&lt;p&gt;For installation instructions, follow the &lt;a href="https://github.com/neoclide/coc.nvim#quick-start" rel="noreferrer noopener"&gt;Quick Start&lt;/a&gt; guide.&lt;/p&gt;

&lt;p&gt;After you've installed it, run this command to install the necessary coc extension for Go:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;vim -c 'CocInstall -sync coc-go coc-html coc-css coc-json|q'&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The html/css/json extensions are optional but who doesn't deal with those nowadays. You'd probably need them at some point.&lt;/p&gt;

&lt;p&gt;Afterwards, open vim &amp;amp; type &lt;strong&gt;:CocConfig&lt;/strong&gt; to open coc's configuration file. These are my opinionated settings, stripped from the non-go related settings:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
  "suggest.noselect": false,
  "diagnostic.errorSign": "✘",
  "diagnostic.warningSign": "!",
  "diagnostic.infoSign": "?",
  "diagnostic.checkCurrentLine": true,
  "coc.preferences.formatOnSaveFiletypes": [
    "javascript",
    "html",
    "json",
    "css",
    "scss",
    "go"
  ],
  "coc.preferences.hoverTarget": "float",
  "languageserver": {
    "golang": {
      "command": "gopls",
      "rootPatterns": ["go.mod"],
      "filetypes": ["go"]
    }
  },
  "go.goplsOptions": {
    "staticcheck": true
  }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For all auxiliary coc settings, add &lt;a href="https://github.com/preslavmihaylov/dotfiles/blob/master/vim/coc.vim" rel="noopener noreferrer"&gt;these settings&lt;/a&gt; to your .vimrc.&lt;/p&gt;

&lt;h2&gt;Mappings For The Most Useful Commands&lt;/h2&gt;

&lt;p&gt;Having great tools is not enough. You need to know how to use them. Hence, it is best to customize some of the mappings which vim-go and coc provide you so that you're more effective when dealing with Go code.&lt;/p&gt;

&lt;p&gt;Here are the mappings I use most often and the way I've configured them:&lt;/p&gt;

&lt;h3&gt;Manage unit tests in the current file&lt;/h3&gt;

&lt;p&gt;I've mapped running all the tests in the current file to &amp;lt;leader&amp;gt;-t which translates to \ + t on my Mac &amp;amp; Linux.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;autocmd BufEnter *.go nmap &amp;lt;leader&amp;gt;t  &amp;lt;Plug&amp;gt;(go-test)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I've also mapped \ + tt to run the current test function only, instead of running all of them:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;autocmd BufEnter *.go nmap &amp;lt;leader&amp;gt;tt &amp;lt;Plug&amp;gt;(go-test-func)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Finally, I use \ + c to toggle the coverage profile for the current file I'm in:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;autocmd BufEnter *.go nmap &amp;lt;leader&amp;gt;c  &amp;lt;Plug&amp;gt;(go-coverage-toggle)&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Inspect a Go Codebase&lt;/h3&gt;

&lt;p&gt;Show the function signature for a given routine with \ + i:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;autocmd BufEnter *.go nmap &amp;lt;leader&amp;gt;i  &amp;lt;Plug&amp;gt;(go-info)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Show the interfaces a type implements with \ + ii:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;autocmd BufEnter *.go nmap &amp;lt;leader&amp;gt;ii  &amp;lt;Plug&amp;gt;(go-implements)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Describe the definition of a given type with \ + ci:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;autocmd BufEnter *.go nmap &amp;lt;leader&amp;gt;ci  &amp;lt;Plug&amp;gt;(go-describe)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;See the callers of a given function with \ + cc:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;autocmd BufEnter *.go nmap &amp;lt;leader&amp;gt;cc  &amp;lt;Plug&amp;gt;(go-callers)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Find all references of a given type/function in the codebase with \ + cr:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;nmap &amp;lt;leader&amp;gt;cr &amp;lt;Plug&amp;gt;(coc-references)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Go to definition/Go back with Ctrl+d and Ctrl+a:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;nmap &amp;lt;C-a&amp;gt; &amp;lt;C-o&amp;gt;
nmap &amp;lt;C-d&amp;gt; &amp;lt;Plug&amp;gt;(coc-definition)&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Refactor Go Code&lt;/h3&gt;

&lt;p&gt;Not many options here, but there's renaming the symbol your cursor is on with \ + r:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;nmap &amp;lt;leader&amp;gt;r &amp;lt;Plug&amp;gt;(coc-rename)&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Occasional Maintenance of Tooling&lt;/h2&gt;

&lt;p&gt;To update all Go tools, run this from vim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;:GoUpdateBinaries&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And updating all coc plugins:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;:CocUpdate&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To upgrade vim-go or coc.nvim, delete the folders from &lt;strong&gt;~/.vim/bundle&lt;/strong&gt; and git clone the repos again.&lt;/p&gt;

&lt;h2&gt;Setup The Cool gruvbox Theme (Optional)&lt;/h2&gt;

&lt;p&gt;Install &lt;a href="https://github.com/morhetz/gruvbox" rel="noreferrer noopener"&gt;gruvbox&lt;/a&gt; using pathogen:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;git clone https://github.com/morhetz/gruvbox ~/.vim/bundle&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Enable it in your &lt;strong&gt;.vimrc&lt;/strong&gt; with some opinionated extra options:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;colorscheme gruvbox
autocmd ColorScheme * highlight CocErrorFloat guifg=#ffffff
autocmd ColorScheme * highlight CocInfoFloat guifg=#ffffff
autocmd ColorScheme * highlight CocWarningFloat guifg=#ffffff
autocmd ColorScheme * highlight SignColumn guibg=#adadad&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Want to see how it looks?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpmihaylov.com%2Fwp-content%2Fuploads%2F2021%2F03%2Fgruvbox-1024x492.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpmihaylov.com%2Fwp-content%2Fuploads%2F2021%2F03%2Fgruvbox-1024x492.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Setup tmux For Terminal Multiplexing (Optional)&lt;/h2&gt;

&lt;p&gt;If you want to manage multiple terminal tabs in the same window, use &lt;a href="https://github.com/tmux/tmux/wiki" rel="noopener noreferrer"&gt;tmux&lt;/a&gt; (See my &lt;a href="https://pmihaylov.com/tmux-terminal-multiplexer/" rel="noopener noreferrer"&gt;how to guide&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fpmihaylov.com%2Fwp-content%2Fuploads%2F2018%2F05%2FSelection_018-1024x576.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fpmihaylov.com%2Fwp-content%2Fuploads%2F2018%2F05%2FSelection_018-1024x576.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is one of the most useful tools I have setup because it allows me to do my code editing in vim and manage all sorts of other terminal-related tasks in separate tabs all inside the same window.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;And that's everything I use for my daily Go programming. &lt;/p&gt;

&lt;p&gt;This setup has worked extremely well for me and have in mind that I'm also dealing with &lt;a href="https://eng.uber.com/go-monorepo-bazel/" rel="noopener noreferrer"&gt;Uber's huge Go Monorepo&lt;/a&gt;, where I've been able to integrate my vim environment seamlessly (with some extra settings which you probably won't need.&lt;/p&gt;

&lt;p&gt;If you want to see my entire vim development environment, check out all my &lt;a href="https://github.com/preslavmihaylov/dotfiles" rel="noreferrer noopener"&gt;dotfiles&lt;/a&gt; and &lt;a href="https://github.com/preslavmihaylov/default-setups" rel="noreferrer noopener"&gt;default-setups&lt;/a&gt; repos, which include my full setup, including external programs, the terminal, vim, tmux, colorschemes, etc.&lt;/p&gt;

&lt;p&gt;Happy Gophing!&lt;/p&gt;

&lt;p&gt;Check out some of my other Go series:&lt;br&gt;&lt;a href="https://pmihaylov.com/series-integrating-go-with-elk/" rel="noreferrer noopener"&gt;Integrating your Go Service with ELK&lt;/a&gt;&lt;br&gt;&lt;a href="https://pmihaylov.com/grpc-with-go-crash-course/" rel="noreferrer noopener"&gt;gRPC With Go Crash Course&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>vim</category>
      <category>ide</category>
    </item>
    <item>
      <title>Thread-Safety in Go - an Overlooked Concern</title>
      <dc:creator>Preslav Mihaylov</dc:creator>
      <pubDate>Tue, 29 Dec 2020 18:04:02 +0000</pubDate>
      <link>https://dev.to/pmihaylov/thread-safety-in-go-an-overlooked-concern-hil</link>
      <guid>https://dev.to/pmihaylov/thread-safety-in-go-an-overlooked-concern-hil</guid>
      <description>&lt;p&gt;When you first start tinkering with concurrency in any language, what's the most sought after goal is extracting the maximum output from the hardware you have available.&lt;/p&gt;

&lt;p&gt;That's why we use it and study it in the first place - to achieve optimal parallelism &amp;amp; get some significant speed-up for our apps out of the box.&lt;/p&gt;

&lt;p&gt;However, a not so glamorous part of studying the subject is understanding how to write thread-safe code. The techniques and principles which will enable you to keep your application well-behaved, even after scaling it to dozens of threads.&lt;/p&gt;

&lt;p&gt;Even though this is an important thing to consider while writing concurrent code, it is often overlooked in most learning resources about concurrency. This problem is even more relevant in the Go community due to two common misunderstandings I will cover in the article.&lt;/p&gt;

&lt;h2&gt;What does thread-safety mean anyways?&lt;/h2&gt;

&lt;p&gt;Let's first explore what thread-safety is exactly. One reasonable way to define the term is to think of it in terms of correctness.&lt;/p&gt;

&lt;p&gt;If a component conforms to its specification, it is correct. Say you have an Interval struct which defines two limits - lower and higher. The important invariant instances of the struct have to maintain is &lt;strong&gt;lower &amp;lt; higher&lt;/strong&gt;. If at any point, this invariant is not true, then this class is incorrect.&lt;/p&gt;

&lt;p&gt;A thread-safe class, on the other hand is able to sustain its correctness even after being bombarded by a dozen of threads, accessing it concurrently.&lt;/p&gt;

&lt;p&gt;Now, when designing thread-safe components, there are also other concerns one has to consider. One such concern is achieving reasonable performance - if a component performs worse in a multi-threaded environment, instead of a single-threaded one, it's obviously not a great solution.&lt;/p&gt;

&lt;p&gt;However, for the scope of this article, we will just worry about making a component thread-safe - i.e. correct in a multi-threaded environment.&lt;/p&gt;

&lt;h2&gt;Why should you care about thread-safety?&lt;/h2&gt;

&lt;p&gt;Most people understand that thread-safety is an issue. When exploring the topic of concurrency in any language, they've probably seen at least a single thread-safety issue.&lt;/p&gt;

&lt;p&gt;Consider this example:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;When I run this code on my laptop, I get as result 9303. The second time, it was a different number. When you run it, it might even be correct.&lt;/p&gt;

&lt;p&gt;This is an example of non-thread-safe code. The expected behaviour for this routine is to print 10000 when I increment the counter that many times, albeit concurrently. &lt;/p&gt;

&lt;p&gt;Instead, when I ran it the first time I got 9303. The second time was a different number which was still incorrect. Try running it on your end and see what's your lucky number.&lt;/p&gt;

&lt;p&gt;Given this example, most people acknowledge that writing thread-safe code is important as otherwise, you are subject to sporadic bugs which would be very hard to trace, debug and reproduce.&lt;/p&gt;

&lt;p&gt;Even so, the concern of writing thread-safe code is still quite overlooked by many due to two main reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They think they don't need to worry about it because they don't directly spawn threads in their applications&lt;/li&gt;
&lt;li&gt;They think they don't need to worry because they use channels and there is no memory sharing when using them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unfortunately both of these assumptions are incorrect for most people. Let's see why.&lt;/p&gt;

&lt;h3&gt;You are already writing multi-threaded applications&lt;/h3&gt;

&lt;p&gt;Let's first cover the subtle fact that you are already writing multi-threaded applications even if you haven't spawned a single goroutine in it.&lt;/p&gt;

&lt;p&gt;Have you ever created an http handler using Go's standard http package?&lt;/p&gt;

&lt;p&gt;Congratulations, you are already writing multi-threaded software!&lt;/p&gt;

&lt;p&gt;Although you aren't spawning goroutines in your codebase directly, you are using a framework which spawns one for every incoming http request. &lt;/p&gt;

&lt;p&gt;This means that there are already multiple threads, executing your code. Hence, it needs to be thread-safe to avoid subtle bugs, which would be quite hard to find.&lt;/p&gt;

&lt;p&gt;By the way, this is applicable to any multi-threaded framework or library you might use. Even if you're simply spawning a timer to execute some background task, you're already dealing with a multi-threaded application.&lt;/p&gt;

&lt;h3&gt;Go channels are great, but not always applicable&lt;/h3&gt;

&lt;p&gt;There's this notorious technique, applied in Go, of not communicating by sharing memory but sharing memory by communicating.&lt;/p&gt;

&lt;p&gt;You can achieve this via Go's channel primitive. It's a data structure which enables one to achieve thread-safe code in a multi-threaded environment. It's nice because you don't need any synchronisation whatsoever as long as you use it to share data across threads.&lt;/p&gt;

&lt;p&gt;It, undoubtedly, is a great solution for avoiding thread-safety issues when writing concurrent code. However, it is not applicable in all situation.&lt;/p&gt;

&lt;h3&gt;Let's explore an example&lt;/h3&gt;

&lt;p&gt;Take this simple web chat implementation:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This looks like a perfectly reasonable application, where no goroutines are spawned. When you test it out via a rest client, it appears to work great!&lt;/p&gt;

&lt;p&gt;However, if you deploy this code into production and enough users start using it, you will start receiving a flood of bug reports which you can hardly reproduce.&lt;/p&gt;

&lt;p&gt;What's more, changing this code to work with channels in quite impractical as the resulting code will be hard to understand and reason about. A much more practical solution is sticking to the good old synchronisation primitives, implemented in the &lt;a href="https://golang.org/pkg/sync/" rel="noreferrer noopener"&gt;sync&lt;/a&gt; package.&lt;/p&gt;

&lt;p&gt;And this is where the classic, well-known practices for achieving thread-safety are applicable in Go, despite the advent of channels.&lt;/p&gt;

&lt;p&gt;Are you curious if you have any non-thread-safe classes in your own web application?&lt;/p&gt;

&lt;p&gt;One common source of thread-safety bugs in web applications is having a struct field of type slice or map whose state you change in any function. If that struct instance is shared across multiple http requests and you don't have any synchronisation, then that's probably a non-thread-safe component.&lt;/p&gt;

&lt;h2&gt;Some good resources on thread-safety &amp;amp; concurrency&lt;/h2&gt;

&lt;p&gt;Most existing resources cover the subject of thread-safety in a very shallow way. I know this as after consuming many of them, I've still felt that I don't understand it at all.&lt;/p&gt;

&lt;p&gt;For example, one of the only books on concurrency in go - &lt;a href="https://www.amazon.com/Concurrency-Go-Tools-Techniques-Developers/dp/1491941197"&gt;Concurrency in Go&lt;/a&gt;, dedicates a single chapter on covering the problems of thread-safety. And it doesn't even go into enough detail exploring what options we have for solving them or even demonstrating all possible sources of thread-safety violations.&lt;/p&gt;

&lt;p&gt;Once you read that chapter you would, at best, have a shallow understanding of the fact that you need to be careful when writing concurrent code. Oh, and that there is something called a lock which appears to be helpful for resolving that issue.&lt;/p&gt;

&lt;p&gt;So far, I've found a single resource on concurrency which goes into enough depth on the subject, exploring why thread-safety issues happen and how to avoid them - &lt;a href="https://pmihaylov.com/my-thoughts-on-jcip/" rel="noreferrer noopener"&gt;Java Concurrency in Practice&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to explore concurrency and thread-safety beyond this article, then I highly suggest you check out that book. &lt;a href="https://github.com/preslavmihaylov/booknotes"&gt;My book notes&lt;/a&gt; can also greatly aid you in digesting it.&lt;/p&gt;

&lt;p&gt;Oh, and I know it's a Java book, not a Go one but trust me. It's still better than all the Go books/courses on the subject.&lt;/p&gt;

&lt;p&gt;Additionally, I'm planning on releasing several additional articles on the subject, covering the subject in more detail. If you want to get a notification once I release the next article in the series, make sure to subscribe.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Thread-safety is an important concern for stable &amp;amp; reliable applications, nowadays as most of us are already writing multi-threaded applications.&lt;/p&gt;

&lt;p&gt;If you want to avoid the subtle bugs arising from thread-safety issues, you should make sure you sufficiently understand the topic.&lt;/p&gt;

&lt;p&gt;In order to bridge the gaps in your knowledge, check out &lt;a href="https://pmihaylov.com/my-thoughts-on-jcip/" rel="noreferrer noopener"&gt;Java Concurrency in Practice&lt;/a&gt;. Also subscribe for the blog for the next articles in these series on thread-safety in Go.&lt;/p&gt;

</description>
      <category>go</category>
      <category>concurrency</category>
    </item>
    <item>
      <title>Good and Bad Practices - a Limiting Perspective</title>
      <dc:creator>Preslav Mihaylov</dc:creator>
      <pubDate>Sun, 15 Nov 2020 19:32:31 +0000</pubDate>
      <link>https://dev.to/pmihaylov/good-and-bad-practices-a-limiting-perspective-cob</link>
      <guid>https://dev.to/pmihaylov/good-and-bad-practices-a-limiting-perspective-cob</guid>
      <description>&lt;p&gt;Growing up as software engineers, we typically spend the first few years of our careers studying various technologies and concepts from the programming area - what a list is, what a hash table is, what's JavaScript, what's HTTP, what is a client-server architecture...&lt;/p&gt;

&lt;p&gt;The goal of &lt;a href="https://pmihaylov.com/study-plan/"&gt;this venture&lt;/a&gt; is for one to accumulate the necessary skills &amp;amp; knowledge to land their first jobs.&lt;/p&gt;

&lt;p&gt;But apart from understanding "what" something is, over the years, the preliminary knowledge required to land your first job has expanded to also cover topics which help one not just write software that works, but also write "good code".&lt;/p&gt;

&lt;p&gt;Most aspiring software engineers nowadays are advised to read up about the SOLID principles, about what "clean code" is, they're also taught to split up their applications using the MVC "architecture" and have separate models, views and controllers.&lt;/p&gt;

&lt;h2&gt;Concepts vs. Principles&lt;/h2&gt;

&lt;p&gt;The difference between learning how a linked list works and what the S in SOLID stands for is that the former is a concept which is easier to understand as it solves a problem much more familiar to a beginner - it helps one efficiently store data in particular use-cases.&lt;/p&gt;

&lt;p&gt;The Single Responsibility Principle, on the other hand, is a principle which has been carved out in our software engineering books thanks to the deliberate efforts of generations of programmers who've accumulated years of both good and bad experience and they've distilled what they've learnt into a single-lined principle.&lt;/p&gt;

&lt;p&gt;But the simplicity of it is deceptive.&lt;/p&gt;

&lt;p&gt;Memorising a single-lined principle and a few examples of applying it it simple and anyone can do it. Any junior developer learns to cite the S in SOLID by heart before their first job interview.&lt;/p&gt;

&lt;p&gt;However, it is much more challenging to understand it.&lt;/p&gt;

&lt;p&gt;To understand and appreciate the solution, one must first understand the problem it solves.&lt;/p&gt;

&lt;p&gt;Then, you can look at the principle as a clever approach to dealing with a common problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trying to use a principle without understanding the problem it solves is like trying to use a hammer when you don't know what a nail is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But we as a society, have always tried to circumvent the long and hard road to true understanding. We're always looking for the shortcut.&lt;/p&gt;

&lt;p&gt;Hence, we are teaching what SOLID is quite well, but we're not teaching why it exists or what problem it solves nearly as well.&lt;/p&gt;

&lt;p&gt;Suddenly, you receive a hammer and everything looks like a nail you can hit.&lt;/p&gt;

&lt;h2&gt;Good and Bad Practices&lt;/h2&gt;

&lt;p&gt;Thanks to all this, we've developed the concepts of good and bad practices.&lt;/p&gt;

&lt;p&gt;We've come so far as to be disgusted from certain patterns in our codebases. Using a global variable or directly initialising a dependency in your class instead of using dependency injection is considered a sin.&lt;/p&gt;

&lt;p&gt;Why? &lt;/p&gt;

&lt;p&gt;Because it's a "bad practice". It is not "clean code".&lt;/p&gt;

&lt;p&gt;However, in programming there are no perfect choices. There are only trade-offs.&lt;/p&gt;

&lt;p&gt;Every little idiom or witty technique you apply in your codebase has a cost associated with it. Most often, that cost is in increasing the complexity of the codebase.&lt;/p&gt;

&lt;p&gt;After you do this too many times, it suddenly takes an hour to initialise a simple utility class you need for a small part of the larger task you are solving.&lt;/p&gt;

&lt;p&gt;You want to directly construct a new class? No, you can't do that. You should use a factory instead. But wait, to make things even more sophisticated, we'll make it a "builder factory" so that the code is "extensible".&lt;/p&gt;

&lt;p&gt;This vicious cycle continues to a point where you have a small project, for which you need people with PhDs in software design to understand. And I've actually seen that.&lt;/p&gt;

&lt;p&gt;I've worked on a giant project with half a million lines of code, which is easier to understand than a 10k lines project.&lt;/p&gt;

&lt;p&gt;That is the price we pay for over-engineering by blindly following the "good practices".&lt;/p&gt;

&lt;p&gt;Oh, and by the way, this problem extends beyond code. It has become prevalent in our system designs as well. &lt;/p&gt;

&lt;p&gt;For example, most projects built with a "micro services" architecture I've seen don't really need micro services. They could have just sticked to the not-so-glossy monolith and saved themselves dozens of problems, which the shiny buzzword brings with itself.&lt;/p&gt;

&lt;h2&gt;Dealing With Imperfect Principles&lt;/h2&gt;

&lt;p&gt;Although I've been painting a not-so-rosy picture of all the "good practices" in this article so far, note that there is nothing wrong with the principles themselves. They have become popular and widely adopted because they are quite effective in solving some of the problems we often face while writing code.&lt;/p&gt;

&lt;p&gt;However, by not understanding the problems they solve and the problems they introduce, it is not easy to recognise when to use them and when to avoid them.&lt;/p&gt;

&lt;p&gt;Therefore, before applying any sort of design patterns or good practices, read up about them. Don't just pick up the first vague article which either simply explains how to use them or, worse, try to sell them by outlining all the benefits while avoiding the costs.&lt;/p&gt;

&lt;p&gt;What's even better, try to figure that out yourself. Oftentimes, techniques for writing good code address the problem of making your project more maintainable.&lt;/p&gt;

&lt;p&gt;What that means is based on context. &lt;/p&gt;

&lt;p&gt;Sometimes, making your project more maintainable means writing code which is easy to test. Other times, it is about writing code which is easier to read. &lt;/p&gt;

&lt;p&gt;Some techniques contribute to more specialised use-cases. Such are, for example, making the frameworks and peripherals of your codebase more detached from the rest of the project.&lt;/p&gt;

&lt;p&gt;And note that certain techniques might aid you in some of those goals, while impeding you in others. &lt;/p&gt;

&lt;p&gt;For example, applying dependency injection might make writing tests easier, but it makes initialising and using your components harder. Is it worth the cost?&lt;/p&gt;

&lt;p&gt;It depends. Sometimes it is, other times it isn't. You are the one to decide as only you understand your project well enough.&lt;/p&gt;

&lt;h2&gt;A Case Study&lt;/h2&gt;

&lt;p&gt;Oftentimes, sticking to simpler idioms, condemned as bad practices, might actually be more beneficial in your use case.&lt;/p&gt;

&lt;p&gt;Check out Go's standard library &lt;a href="https://golang.org/pkg/net/http/"&gt;http package&lt;/a&gt; for example. In it, there is a global http client variable.&lt;/p&gt;

&lt;p&gt;Perhaps some bells start ringing right now - that makes the package harder to test, it raises the &lt;a href="https://pmihaylov.com/my-thoughts-on-jcip/"&gt;risk for thread-safety issues&lt;/a&gt;, it breaks encapsulation, etc etc.&lt;/p&gt;

&lt;p&gt;And although most of those claims might be true, there is another thing which also holds true - it makes using the package much easier for simple use-cases.&lt;/p&gt;

&lt;p&gt;Thanks to that decision, in order to make a simple GET request, all you need to do is:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;http.Get("http://example.com/")&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That's it. There are no initialisations, no configurations, no need to look into how to configure your timeouts &amp;amp; headers. You just stick to the reasonable defaults which the global variable gives you and in most cases, that works just fine.&lt;/p&gt;

&lt;p&gt;This is an example of how, for the Go team, the widely adopted wisdom of avoiding global variables didn't apply to this specific use case they were aiming for - making the http package easy to use.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In software, there are no silver bullets. Not even the globally-recognised principles for writing good code.&lt;/p&gt;

&lt;p&gt;Every pattern, principle and idiom is created with a specific problem in mind. And all of them have a certain cost you'll have to pay for applying them.&lt;/p&gt;

&lt;p&gt;In order to use them effectively, you should examine both sides of the coin.&lt;/p&gt;

&lt;p&gt;Otherwise, you will have to continue your endeavour in software engineering by blindly following certain patterns while condemning others as bad practices.&lt;/p&gt;

&lt;p&gt;At that point, decision making will no longer be about making rational choices, but more about maintaining religious beliefs.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>My thoughts on Java Concurrency in Practice</title>
      <dc:creator>Preslav Mihaylov</dc:creator>
      <pubDate>Tue, 06 Oct 2020 14:15:53 +0000</pubDate>
      <link>https://dev.to/pmihaylov/my-thoughts-on-java-concurrency-in-practice-k0c</link>
      <guid>https://dev.to/pmihaylov/my-thoughts-on-java-concurrency-in-practice-k0c</guid>
      <description>&lt;p&gt;One of my &lt;a rel="noreferrer noopener" href="https://pmihaylov.com/study-plan/"&gt;key goals this year&lt;/a&gt; was to gain a good understanding of concurrency and multithreading. I've always had a tangent understanding of it at best. The book Java Concurrency in Practice was one of my first picks.&lt;/p&gt;

&lt;p&gt;Sure, I've used multithreading concepts like promises &amp;amp; ajax in JavaScript before. I've also spawned go routines &amp;amp; used mutexes in Go. But I've never felt I'm proficient enough to state that I have a good understanding of this subject.&lt;/p&gt;

&lt;p&gt;Hence, to bridge this gap in knowledge &amp;amp; skills, I decided to invest in several concurrency related books &amp;amp; courses. My intention was to start from JCIP and then move on to some additional courses. Initially, I felt that just reading this book won't be sufficient to understand the subject thoroughly.&lt;/p&gt;

&lt;p&gt;However, after going through this book I didn't bother looking into any other concurrency course at all. It is one of the most succinct and yet thorough books on a given topic that I've ever read.&lt;/p&gt;

&lt;p&gt;Additionally, I would recommend you to go through this book even if you're not using Java at all. It will give you a very profound understanding of whatever framework/mechanism your language of choice uses to tackle concurrency.&lt;/p&gt;

&lt;p&gt;Here's why...&lt;/p&gt;

&lt;h2&gt;Completeness&lt;/h2&gt;

&lt;p&gt;Java Concurrency in Practice is very thorough as it takes you from the basics you need to understand about concurrent programs all the way to the advanced topics, where the "dessert" is.&lt;/p&gt;

&lt;p&gt;However, it is also quite succinct as it doesn't drift into unnecessary details or complexities which aren't foundational to the subject.&lt;/p&gt;

&lt;p&gt;Many books and courses fail to achieve this. &lt;/p&gt;

&lt;p&gt;Some drift too much towards the theoretical and impractical. This makes the reader impatient and eventually, repels him from the book/course altogether. Most classical computer science books are like this, making them quite unattractive to novices and professionals alike.&lt;/p&gt;

&lt;p&gt;Others skip the foundations and simply focus on showing you "how it's done in X", where X is your favourite language. This kind of content might, at best, leave you hungrier than you were at the beginning. Worst case scenario, it might fool you into believing that "you know it all" and not study the subject further.&lt;/p&gt;

&lt;p&gt;There are very few courses &amp;amp; books which achieve the golden balance between theory and practice.&lt;/p&gt;

&lt;h2&gt;Accessibility&lt;/h2&gt;

&lt;p&gt;Many books tend to be written in a very high-level way, skipping appropriate examples and using inaccessible language/terminology.&lt;/p&gt;

&lt;p&gt;These kinds of books require unnecessary additional attention in order to understand the context alone. This is quite deterring, especially when the reader realises that there are good alternative books on the subject half-way through.&lt;/p&gt;

&lt;p&gt;I avoid investing too much time in such books as reading those often contributes solely to "ego-reading" - giving you the prestige of a person who has read a book, which is considered a hard read. Understanding it is, though, is not a given.&lt;/p&gt;

&lt;p&gt;Now, I would consider Java Concurrency in Practice to be quite accessible and written in a very pedagogical way. It is full of examples to supplement the book &amp;amp; aid you in "getting it".&lt;/p&gt;

&lt;p&gt;However, I wouldn't mark it as an easy read. It took me substantial effort to go through the book. I read through each chapter at least twice, while &lt;a href="https://github.com/preslavmihaylov/booknotes"&gt;also taking notes&lt;/a&gt; along the way.&lt;/p&gt;

&lt;h2&gt;Critique&lt;/h2&gt;

&lt;h3&gt;Lack of exercises&lt;/h3&gt;

&lt;p&gt;What the book unfortunately lacks is a set of good exercises. &lt;/p&gt;

&lt;p&gt;That would have been tremendously helpful as it supplements the read-through with "getting your hands dirty".&lt;/p&gt;

&lt;p&gt;This is why I had to tinker with the concepts in the IDE myself. This was the only way to back my theoretical understanding with enough practice.&lt;/p&gt;

&lt;p&gt;Google doesn't have any good suggestions either. &lt;a href="https://www.reddit.com/r/learnprogramming/comments/9c6yo4/a_collection_of_projects_for_learning/"&gt;The top result&lt;/a&gt; which pops up after you search for "concurrency projects" suggests some quite impractical projects. Projects such as creating your own concurrent hash map or implementing an atomic variable.&lt;/p&gt;

&lt;p&gt;I find this to be bad exercise advice as all these are concurrency primitives. Knowing how to implement them yourself is useless. &lt;/p&gt;

&lt;p&gt;They are already available out of the box and the book goes into enough depth to explain how they work.&lt;/p&gt;

&lt;p&gt;What's more, these exercises won't prepare you for an actual concurrency-related problem. Just because you know the concurrency primitives doesn't mean you can use them effectively.&lt;/p&gt;

&lt;p&gt;It would have been great if there were a couple of exercises in addition to each chapter. This would aid one in putting the concepts covered to practice.&lt;/p&gt;

&lt;p&gt;Although the book is lagging on this important point, fortunately it is still very well-made in all other aspects. Additionally, the provided examples help one bridge the gap even though you'll have to do some homework afterwards.&lt;/p&gt;

&lt;h3&gt;The book doesn't cover modern tools &amp;amp; frameworks&lt;/h3&gt;

&lt;p&gt;The other critique I've often encountered is that the book doesn't cover the most modern practices in tackling concurrency. One could expect this given the book's age.&lt;/p&gt;

&lt;p&gt;However, I don't consider this to be that important. The book focuses on providing you with understanding of the fundamental concurrency concepts and problems.&lt;/p&gt;

&lt;p&gt;After you go through it, learning about the latest concurrency framework is a breeze. &lt;/p&gt;

&lt;p&gt;It is far more beneficial to understand the concepts covered in JCIP than it is to know the API of the latest framework.&lt;/p&gt;

&lt;h2&gt;How to approach this book&lt;/h2&gt;

&lt;p&gt;JCIP quite thoroughly covers the subject of concurrency and the author has also written it in an accessible way. However, this is not a "feel-good" book. It's not meant to be read from cover to cover. It is also not meant to be among the collection of 100 books you've read this year. It will definitely impede you in achieving that "paramount" goal.&lt;/p&gt;

&lt;p&gt;I'd advise readers to go through most chapters multiple times, especially the first five ones. In addition to that, I'd suggest one to take notes and even spend some time in the IDE tinkering with the concepts learnt.&lt;/p&gt;

&lt;p&gt;Have in mind that the author doesn't spend too much time explaining the APIs of the library routines they use. Don't be taken aback by this, simply keep a browser tab open, ready to look up the API documentation of the methods being used.&lt;/p&gt;

&lt;p&gt;Now, I wouldn't recommend these measures with any book as it is quite the time investment. But it's definitely worth it for this one. &lt;/p&gt;

&lt;p&gt;There are easier books &amp;amp; courses on the subject out there. &lt;/p&gt;

&lt;p&gt;But almost all of them leave you with a "there's something missing" feeling. This makes you hop from one piece of content to another. &lt;/p&gt;

&lt;p&gt;This contributes to that never ending feeling that you don't have a good enough understanding of concurrency.&lt;/p&gt;

&lt;p&gt;Java Concurrency in Practice is not like that. It will leave you fulfilled &amp;amp; confident that you understand the subject. After going through it, you'd rarely want to look to another book/course on the subject.&lt;/p&gt;

&lt;p&gt;But it does demand its share of attention and care. Don't pass on this opportunity, it is definitely worth it.&lt;/p&gt;

&lt;h2&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;Although JCIP is accessible, it still demands some backbone knowledge before starting it.&lt;/p&gt;

&lt;p&gt;Make sure that you are comfortable with an object-oriented programming language. You should also be comfortable opening the official documentation of an API over a stack overflow question.&lt;/p&gt;

&lt;p&gt;In addition to that, the book doesn't cover the very basics of multithreading.&lt;/p&gt;

&lt;p&gt;Concepts such as what a thread is, how it works or how to start one up. Make sure you spend a couple of days/weeks studying that beforehand.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Java Concurrency in Practice is one of the best books I've read on concurrency. I would recommend it to anyone, even if Java is not their language of choice.&lt;/p&gt;

&lt;p&gt;Its lack of exercises does force one to do some homework. But even so, the course makes most other content on concurrency obsolete.&lt;/p&gt;

&lt;p&gt;Due to this, JCIP does demand considerable attention, despite it being accessible, but it's definitely worth it.&lt;/p&gt;

</description>
      <category>java</category>
      <category>concurrency</category>
      <category>multithreading</category>
      <category>books</category>
    </item>
    <item>
      <title>Using Kibana to Debug Production Issues</title>
      <dc:creator>Preslav Mihaylov</dc:creator>
      <pubDate>Sun, 16 Aug 2020 16:52:01 +0000</pubDate>
      <link>https://dev.to/pmihaylov/using-kibana-to-debug-production-issues-2p4j</link>
      <guid>https://dev.to/pmihaylov/using-kibana-to-debug-production-issues-2p4j</guid>
      <description>&lt;p&gt;In the &lt;a href="https://pmihaylov.com/kibana-dashboard-tutorial/"&gt;last chapter&lt;/a&gt; of these series, we covered how to create your first Kibana dashboard for a sample web application.&lt;/p&gt;

&lt;p&gt;Although that will help you setup Kibana for your production services, what you'll be doing 99% of the time would be to actually use the dashboard to monitor &amp;amp; debug issues you observe on production.&lt;/p&gt;

&lt;p&gt;This is the final post in the series for monitoring Go applications \w Kibana. It will give you some practice using your newly created dashboard to identify problems with a sample web application. &lt;/p&gt;

&lt;p&gt;So if you've setup Kibana for the first time or you've just joined your new team &amp;amp; seen your Kibana dashboards without a clue what's going on, this post is for you!&lt;/p&gt;

&lt;h2&gt;The Starting Point&lt;/h2&gt;

&lt;p&gt;First, make sure you have &lt;strong&gt;docker&lt;/strong&gt; and &lt;strong&gt;docker-compose&lt;/strong&gt; installed.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;docker&lt;/strong&gt;, follow the installation instructions for your platform &lt;a href="https://docs.docker.com/get-docker/"&gt;here&lt;/a&gt;.&lt;br&gt;For &lt;strong&gt;docker-compose&lt;/strong&gt;, follow the installation instructions &lt;a href="https://docs.docker.com/compose/install/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Afterwards, download this exercise’s repository on the &lt;strong&gt;kibana-debugging-tutorial&lt;/strong&gt; branch:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;git clone --branch kibana-debugging-tutorial https://github.com/preslavmihaylov/tutorials&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Boot up all docker containers:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker-compose up&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Run this script to import the dashboard to Kibana from the project’s root:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;./import-kibana-dashboard.sh&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is the exact same dashboard as the one we create in &lt;a href="https://pmihaylov.com/kibana-dashboard-tutorial/"&gt;this post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, you should be good to go.&lt;/p&gt;

&lt;p&gt;Verify everything is working properly by going to &lt;a href="http://localhost:5601/app/kibana#"&gt;http://localhost:5601/app/kibana#&lt;/a&gt;, opening the &lt;strong&gt;Dashboard&lt;/strong&gt; tab and making sure you see some incoming traffic with occasional errors:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j59JwI2i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/PPou4oZIokrgp2SJ5fIefQ73ZkjWlACSK-WUjSPeNtbxeDjlMc3ghYLPUWCfKsCEogkmLGoIiU2ZIhU5BH4iYuwEjqMXQX0hYJ10A1aW9BV1ekx_dr5YcU868f8xwc4gb9Nm1KuM" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j59JwI2i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/PPou4oZIokrgp2SJ5fIefQ73ZkjWlACSK-WUjSPeNtbxeDjlMc3ghYLPUWCfKsCEogkmLGoIiU2ZIhU5BH4iYuwEjqMXQX0hYJ10A1aW9BV1ekx_dr5YcU868f8xwc4gb9Nm1KuM" alt="starting dashboard" width="880" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Your Goal&lt;/h2&gt;

&lt;p&gt;In this tutorial, you have a black box application, which has the following HTTP API:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/payments/execute
/payments/list
/payments/authhold&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In addition to these endpoints, what you'll see in the dashboard are some additional properties:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;method - the HTTP method invoked
countryISO2 - the ISO2 of a user's country (e.g. US, FR, UK)
userID - the ID of the user making the request
paymentMethod - the payment method used for making a transaction
userType - the type of user (e.g. trial, individual, business)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Inspect the panels in the dashboard to see some sample data for these properties.&lt;/p&gt;

&lt;p&gt;This API is the same as the one seen in the previous tutorials from the series. &lt;/p&gt;

&lt;p&gt;The difference is that this time, the application fails under certain hardcoded conditions.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;The application fails when &lt;strong&gt;the country is “US”&lt;/strong&gt; and&lt;br&gt;&lt;strong&gt;the endpoint is “/payments/list”&lt;/strong&gt; and &lt;strong&gt;the method is “PUT”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is just an example, of course, it doesn’t necessarily mean that the application returns an error under these exact conditions (or does it?).&lt;/p&gt;

&lt;p&gt;However, the failure conditions will be of that kind.&lt;/p&gt;

&lt;p&gt;Your task is to use the Kibana dashboard you are given, apply some filters to narrow down the errors and figure out what are the exact conditions which cause an error.&lt;/p&gt;

&lt;p&gt;There are &lt;strong&gt;5 error conditions&lt;/strong&gt; which you have to find out.&lt;/p&gt;

&lt;p&gt;Here are the hints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The application fails when ________ and ________&lt;/li&gt;
&lt;li&gt;The application fails when ________ and ________&lt;/li&gt;
&lt;li&gt;The application fails when ________&lt;/li&gt;
&lt;li&gt;The application fails when ________ and ________ and ________ and ________&lt;/li&gt;
&lt;li&gt;The application fails when ________ and ________ and ________ and ________&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The hints are ordered by the difficulty of finding the given error condition - The first condition is the easiest to find.&lt;/p&gt;

&lt;p&gt;One caveat to have in mind is that &lt;strong&gt;you won’t be 100% sure that a given error condition you think you’ve found is correct&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead, focus on finding &lt;strong&gt;the first two error conditions first&lt;/strong&gt;, write them down and then consult the walkthrough for the first two issues to verify if your assumptions are correct.&lt;/p&gt;

&lt;p&gt;Then, continue with the third and final two error conditions and check the walkthrough for each of those steps again.&lt;/p&gt;

&lt;h2&gt;The Walkthrough&lt;/h2&gt;

&lt;p&gt;Before you go through this, I urge you to attempt the exercise on your own.&lt;/p&gt;

&lt;p&gt;Use this only if you get stuck or if you want to check out some alternative ways to debug the issues.&lt;/p&gt;

&lt;p&gt;Well, without further ado, let’s get started.&lt;/p&gt;

&lt;h3&gt;Finding Issue #1&lt;/h3&gt;

&lt;p&gt;Issue #1 is:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;The application fails when ________ and ________&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;First, go to your Errors table and filter the dashboard to only show the errors:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rWy6BTFU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/atRBgwp15fQJFsJizAX9ufhC3P7YB-tWpvnBF8qDTz9oTaxkrRyhcMRk2WCHUmk4UKd5Q_6B8DFv0OQ2hH00gA2J1mzQlkU3y1SBbR8zHjaxzXz8M7cFaQ_q31PM7HlM-vJ7V2XY" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rWy6BTFU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/atRBgwp15fQJFsJizAX9ufhC3P7YB-tWpvnBF8qDTz9oTaxkrRyhcMRk2WCHUmk4UKd5Q_6B8DFv0OQ2hH00gA2J1mzQlkU3y1SBbR8zHjaxzXz8M7cFaQ_q31PM7HlM-vJ7V2XY" alt="issue #1" width="880" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will only filter the errors in the view, which will hide the irrelevant details we aren’t interested in.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;After we do this, this is an example of what your dashboard should look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CAsXWMwr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/bZxKYPxAfOx48ZdJMdCwK311zTFcx9_Ul6CquF-EF3Q4r0vSS9Nj_9DYgjwKmxogkWd0vsCkICDfHORTykgSx564cJYPfiru4UCgf8Q7XRCetj5mmFek9s-yHGGxLXeoZ9E6Qt6W" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CAsXWMwr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/bZxKYPxAfOx48ZdJMdCwK311zTFcx9_Ul6CquF-EF3Q4r0vSS9Nj_9DYgjwKmxogkWd0vsCkICDfHORTykgSx564cJYPfiru4UCgf8Q7XRCetj5mmFek9s-yHGGxLXeoZ9E6Qt6W" alt="issue #1 errors" width="880" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the applied filter at the top-left. That’s the bar which shows all filters applied to the current dashboard. This is one of the main mechanisms you’ll use when debugging production issues.&lt;/p&gt;

&lt;p&gt;Next, notice how the greatest amount of errors appears to happen on the &lt;strong&gt;/payments/authhold&lt;/strong&gt; endpoint.&lt;/p&gt;

&lt;p&gt;Apply that to the current filter as well:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CxClgd5R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/mAjF9tPWsd7-HnfoLB-n68fYiExfWpuWD5SYCy4SdQIyJJAVd08mgOUU3-isZ1hqW_K0nfeys60ILPyB040jo8s632wbu0LHml9XEiSKyW1B4dcq3iHUCeeu--rmcu-ovgm6WBMC" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CxClgd5R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/mAjF9tPWsd7-HnfoLB-n68fYiExfWpuWD5SYCy4SdQIyJJAVd08mgOUU3-isZ1hqW_K0nfeys60ILPyB040jo8s632wbu0LHml9XEiSKyW1B4dcq3iHUCeeu--rmcu-ovgm6WBMC" alt="filtering payments/authhold" width="880" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After we apply this filter, take a look at your tables:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xO25ZwPF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/CDsukU-GuLg7NvP9I7hEixqNtiBUqpMPIJm43c7orgy8oSL2W86-js3XhvbdJl0-hCfz4zRwq-OJa0qK37KqbTN9UGyGM0RXeoGrdiG9TOx8KV9k4ENRvscXMK0H0aNsTcbHiR_C" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xO25ZwPF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/CDsukU-GuLg7NvP9I7hEixqNtiBUqpMPIJm43c7orgy8oSL2W86-js3XhvbdJl0-hCfz4zRwq-OJa0qK37KqbTN9UGyGM0RXeoGrdiG9TOx8KV9k4ENRvscXMK0H0aNsTcbHiR_C" alt="filtered /payments/authhold dashboard" width="880" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It seems that the errors per value are evenly distributed across all tables &lt;strong&gt;except for the HTTP Method&lt;/strong&gt;. There is an abnormal amount of errors which occur when the &lt;strong&gt;HTTP method is POST&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Apply a filter on the POST method and let’s see the results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7JpDOZkl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/VApNgjSnc5cDd-IyThmmt1OaEx3_a0glPxLJxtJcSv0TghKsJp5TfAO_aw7YSXlTOP0x7uunLXsOSgt1VaI-WVfluvZjDpNulyU7_yXC7onuFbvVJV5CSqJm2OIwzSsM-zvtSCiT" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7JpDOZkl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/VApNgjSnc5cDd-IyThmmt1OaEx3_a0glPxLJxtJcSv0TghKsJp5TfAO_aw7YSXlTOP0x7uunLXsOSgt1VaI-WVfluvZjDpNulyU7_yXC7onuFbvVJV5CSqJm2OIwzSsM-zvtSCiT" alt="filtering POST method" width="880" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that the rest of the errors per value remain evenly distributed.&lt;/p&gt;

&lt;p&gt;This means that our first error condition is:&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;The application fails when &lt;strong&gt;the endpoint is /payments/authhold&lt;/strong&gt; and &lt;strong&gt;the HTTP method is POST&lt;/strong&gt;
&lt;/li&gt;&lt;/ol&gt;

&lt;p&gt;That’s a good start.&lt;/p&gt;

&lt;p&gt;One important thing to do before you move on, however, is to exclude the current error conditions from the rest. &lt;/p&gt;

&lt;p&gt;This will allow you to focus on the errors that are left, rather than rediscover the errors which you’ve already found.&lt;/p&gt;

&lt;p&gt;It is enough to exclude one of the error conditions (as both of them have to match).&lt;/p&gt;

&lt;p&gt;To do this, clear all current filters except for the error filter:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0N6c3gbH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/XvzJrpRmcvc-m-FqjAlTPW2aRxouVXISi7HQKYjzqtUEyZq4tF-gDxueF1Cj2tGyB8C4pQNcvvsTbv2ROGEtpqxIFUoJRGfiew7CuYo1Yev7PUg6hGIV4W1lqb7Vg6k5EL79cLZC" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0N6c3gbH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/XvzJrpRmcvc-m-FqjAlTPW2aRxouVXISi7HQKYjzqtUEyZq4tF-gDxueF1Cj2tGyB8C4pQNcvvsTbv2ROGEtpqxIFUoJRGfiew7CuYo1Yev7PUg6hGIV4W1lqb7Vg6k5EL79cLZC" alt="filters for issue #1" width="880" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, exclude the endpoint &lt;strong&gt;/payments/authhold&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Av7Z11T4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/0ZWPD6iUm85kq2GQM23L5zAu5a3Tte21MdR6z-OB3Mgt3ZkODGb7pkPooz2e_sAVcukmUkhVY7NVOC6xd-d4DmJndO0C4bWdqzi-t17EpsQqOCJU88OmY7BtWs9svQ6Zhxv-09Ba" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Av7Z11T4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/0ZWPD6iUm85kq2GQM23L5zAu5a3Tte21MdR6z-OB3Mgt3ZkODGb7pkPooz2e_sAVcukmUkhVY7NVOC6xd-d4DmJndO0C4bWdqzi-t17EpsQqOCJU88OmY7BtWs9svQ6Zhxv-09Ba" alt="exclude payments/authhold" width="880" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how your filters bar should look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YEk8eOuI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/YWKK0fkw85iimKcl6fm21FwqAYVsMZldcUEsD4NN1PBLPdEk_F6sdZLLZ4i4X2plUnShWZE8rsAB9COJLuQMVe_cDPPQFCzRnC-u1BXWYdwVldqJD6my0QHLUKm3mI2GI2zlmNsQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YEk8eOuI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/YWKK0fkw85iimKcl6fm21FwqAYVsMZldcUEsD4NN1PBLPdEk_F6sdZLLZ4i4X2plUnShWZE8rsAB9COJLuQMVe_cDPPQFCzRnC-u1BXWYdwVldqJD6my0QHLUKm3mI2GI2zlmNsQ" alt="filters after excluding payments/authhold" width="880" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, it is possible (and is the case) that there are other error conditions that aren’t the one we found for the endpoint we excluded.&lt;/p&gt;

&lt;p&gt;However, we will have to do some heuristics like this to narrow down the rest of the errors until there is no more room to “zoom in on”.&lt;/p&gt;

&lt;p&gt;At that point, we will have to relax our filters a bit until something we are yet to discover pops up.&lt;/p&gt;

&lt;p&gt;Typically, when working in a production environment, you would approach this kind of issue by finding a few problems at a time.&lt;/p&gt;

&lt;p&gt;Only after you’ve fixed the most outstanding issues, will you narrow down on the rest of the issues in your application which aren’t so pervasive.&lt;/p&gt;

&lt;p&gt;However, in this context, it would be quite shallow to only get a single error condition to discover. This is why you’ll have to discover all 5 at once.&lt;/p&gt;

&lt;p&gt;But don’t worry, I’ve made sure the error conditions are quite distinct to one another in order to enable you to more easily discover them.&lt;/p&gt;

&lt;p&gt;Let’s move on to the next issue.&lt;/p&gt;

&lt;h3&gt;Finding Issue #2&lt;/h3&gt;

&lt;p&gt;Issue #2 is:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;The application fails when ________ and ________&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;To find this one, we’ll take a very similar approach to the previous one. Take a good look at the errors distribution at a high level.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qeIjJsK3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/0dhfoLDruNog0g_5akQuc52oLVfvdsQcLFXc5JxWibA9qombuC2U2kn1HM7DspbOEk6BZ5eVl9pxOhRaZrLgEmR-zstC6-q2UemGVOAZmVDVkETJxUEKyRZjkDnRhW_bc1K6EcrA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qeIjJsK3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/0dhfoLDruNog0g_5akQuc52oLVfvdsQcLFXc5JxWibA9qombuC2U2kn1HM7DspbOEk6BZ5eVl9pxOhRaZrLgEmR-zstC6-q2UemGVOAZmVDVkETJxUEKyRZjkDnRhW_bc1K6EcrA" alt="issue #2 dashboard" width="880" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the outstanding amount of errors for the &lt;strong&gt;/payments/list&lt;/strong&gt; endpoint.&lt;/p&gt;

&lt;p&gt;Filter by that value and take a look at the dashboard again:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kTkypxWP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/PpaqEmSDKtvfi0ARkNDVNnHeriDfBh2SjaIWYx-O1naxAInK8c4ZkFqRPJKSCIt8F5kpJgMhKPhY4HVjepoh3pEAiHBwRqmeksNF0x4cYmekIfC49ewSRZDO3Ivynl67nApIROYX" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kTkypxWP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/PpaqEmSDKtvfi0ARkNDVNnHeriDfBh2SjaIWYx-O1naxAInK8c4ZkFqRPJKSCIt8F5kpJgMhKPhY4HVjepoh3pEAiHBwRqmeksNF0x4cYmekIfC49ewSRZDO3Ivynl67nApIROYX" alt="filtering /payments/list" width="880" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As previously, there is one outstanding value which catches the eye. it is the &lt;strong&gt;ES country&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Filter by that value now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ksLCP5XI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/9jJoyDRcEzW63LbRa1PkkB-KeBw5OuusHcFjUAOc0izBE4l_AFyeIhpagNvtYkMlugFfVUHaBhyFOhIAImOtnzmUvwpX3iJF3DBB_WtY6lLJ7zB_wOOfVJLaDhAnvRV9uA4S0qBV" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ksLCP5XI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/9jJoyDRcEzW63LbRa1PkkB-KeBw5OuusHcFjUAOc0izBE4l_AFyeIhpagNvtYkMlugFfVUHaBhyFOhIAImOtnzmUvwpX3iJF3DBB_WtY6lLJ7zB_wOOfVJLaDhAnvRV9uA4S0qBV" alt="filtering ES country" width="880" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you see, it looks like all other values are evenly distributed when we filter by endpoint &lt;strong&gt;/payments/list&lt;/strong&gt; and &lt;strong&gt;country ES&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This means, that the second error condition our application has is:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;The application fails when the endpoint is &lt;strong&gt;/payments/list&lt;/strong&gt; and &lt;strong&gt;the country is ES&lt;/strong&gt;
&lt;/li&gt;&lt;/ul&gt;





&lt;p&gt;Nicely done. Now remove the filters we added in this step and exclude one of the error conditions we just discovered.&lt;/p&gt;





&lt;p&gt;In this case, I would choose to exclude the &lt;strong&gt;ES country&lt;/strong&gt; as excluding another endpoint will make the resulting view way too narrow.&lt;/p&gt;





&lt;p&gt;This is what your filters bar should look like:&lt;/p&gt;





&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NwKyt6bc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/eTDWVeoWylnGpKxLH8PtTJz8ecL1xRBmeO4RT--u6Co8l7CFggJBmXQFuYJqSv3rcSqFotz8OvRvB5fp2bgQ458k_Qv5tnIBNZURPU2wL0GIqTsdvKuPAohKiUjS7Zb1g9yNlqov" alt="filters after issue #2" width="880" height="114"&gt;





&lt;p&gt;Let’s move on.&lt;/p&gt;





&lt;h3&gt;Finding Issue #3&lt;/h3&gt;





&lt;p&gt;Issue #3 is:&lt;/p&gt;





&lt;ul&gt;&lt;li&gt;The application fails when ________&lt;/li&gt;&lt;/ul&gt;





&lt;p&gt;Taking a look at the view we have now, there is nothing that immediately pops out:&lt;/p&gt;





&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fhryr-cR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/c2ny4kRecQA2YONgihQTcxfmYo-OmhLBZfBZi8IdvHTOeEoSTLm9oZ6mU2ma903c0P0edZs_zcPACl-ABrPTjsyCNlLp7dfSf9pe0NXstgfOd3m5HrAdjyTL5AlUO01s07IeVTRc" alt="issue #3 dashboard" width="880" height="446"&gt;





&lt;p&gt;So it's hard to narrow down in any direction from here.&lt;/p&gt;





&lt;p&gt;However, there is one view on our dashboard which we’ve neglected thus far.&lt;/p&gt;





&lt;p&gt;It is the detailed logs view at the bottom of the dashboard.&lt;/p&gt;





&lt;p&gt;It will be the one which will guide us in the right direction:&lt;/p&gt;





&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lLLmKp0n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/RMBpk7Ib0HtRfn_oDGeVrTo3dtPAOMngdRM4voH77X_kbVlmjQwU9o68IQinWcPV0ZduIm6IF61z_TZt9kfeEzbot7yWvGZsCVijg5cAexgDvWkkxCWXMA3vP44UePWugjCmQoG7" alt="issue #3 user panel" width="880" height="414"&gt;





&lt;p&gt;Do you notice something abnormal?&lt;/p&gt;





&lt;p&gt;The amount of errors for &lt;strong&gt;user ID 68&lt;/strong&gt; tends to be quite high in comparison with other user IDs.&lt;/p&gt;





&lt;p&gt;Perhaps there is something wrong with that user. Apply a filter on his user ID and see what the result is:&lt;/p&gt;





&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LGL0R_FB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/3hes2nM6Slot0Gm5ibq27hASfDd7gmXD26XuWSMdg4S_IRPopXDZyO0eh-r0flOVlCTFnJNsaKhliZcLlqWf-r9C0XO_aivA-RzbVuB7KqittyqEuVZ-LPM_YGuwm9shKLVyEyyQ" alt="problems for user 68" width="880" height="449"&gt;





&lt;p&gt;Notice that after we filter user ID 68, there is nothing extraordinary which pops out. Perhaps the application unconditionally fails for this user every time?&lt;/p&gt;





&lt;p&gt;To test this hypothesis remove all filters except for the user ID. This is what the filters bar should look like:&lt;/p&gt;





&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ldfcEFMO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/qzjKpTl0NobE9UyPNVJrK5j2UbRYIiz2GHW3vnvsEzGC2Oj2cgv3GtL9OBPtxX2b2DFG7odYN8ystFzFHst1fmGmIT-sCbKno2GXemK_M7PwN0Jub5dGm0SqaUCX04TzOwZ8Q4d_" alt="filtering by user 68" width="704" height="250"&gt;





&lt;p&gt;After we do this, notice that &lt;strong&gt;there isn’t a single successful request for this user&lt;/strong&gt;:&lt;/p&gt;





&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m1l9sgsW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/pxxW7UhEjxlq7NrB_13Wit1AOA5X_E_ifNFnfNW07xJNlgUM2l00n9KRc82J_l0opq9Bl3IWBWI5WRGMrh3LLEgu9SKUwxYcJHc42nw8O6Vo10qfl6Zbrc3BA5KLo9Etg6JMvaX7" alt="errors for user 68" width="880" height="373"&gt;





&lt;p&gt;This is our third error condition:&lt;/p&gt;





&lt;ul&gt;&lt;li&gt;The application fails when &lt;strong&gt;the user ID is 68&lt;/strong&gt;
&lt;/li&gt;&lt;/ul&gt;





&lt;p&gt;Now that we’ve discovered this issue, there are still two more to go.&lt;/p&gt;





&lt;p&gt;Finding them now will be significantly easier than at the beginning because we’re going to filter all other error conditions so that only those remaining will pop up.&lt;/p&gt;





&lt;p&gt;To do this, remove all filters and exclude the erroneous values from the previous issues along with the user ID we just found:&lt;/p&gt;





&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0Lg5C19t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/1_J6UyUgmYQVkvQq9cg9kZcgVHfTQ2XsNfFK5DJTtPiFDsxhlztOJmh-ws6KvP6buuWYtueBiePwCtyvSLFW2517YRXVnjBhu9wOtlPGlgtouZxJs_Yhv6UNFs_unnPYjAnd5Jca" alt="exludes after issue #3" width="880" height="102"&gt;





&lt;h3&gt;Finding Issues #4 and #5&lt;/h3&gt;





&lt;p&gt;Issues #4 and #5 are:&lt;/p&gt;





&lt;ul&gt;
&lt;li&gt;The application fails when ________ and ________ and ________ and ________&lt;/li&gt;
&lt;li&gt;The application fails when ________ and ________ and ________ and ________&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After applying all filters for the rest of the errors, you should have a very small number of errors remaining. &lt;/p&gt;

&lt;p&gt;This is normal as these two errors are the hardest to hit due to the many input values which cause them.&lt;/p&gt;

&lt;p&gt;Finding them first at the beginning is extremely difficult because of that.&lt;/p&gt;

&lt;p&gt;However, now that we’ve narrowed down our errors so much, seeing the root cause for these issues is a lot easier.&lt;/p&gt;

&lt;p&gt;Take a look at the dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vYyhGDiM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/A546YpG_0QMh94g3VyS11w4n0-tCDyVHHR95YAFHCOrJtC9H5qWWSs9RePmI4vlKjrmRwrLGYR3vpAkvz9uV624V2VoY19fjKuTwNosyzqR9tymUQ7BaI_SYPyzaC-K7TBx-P_Af" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vYyhGDiM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/A546YpG_0QMh94g3VyS11w4n0-tCDyVHHR95YAFHCOrJtC9H5qWWSs9RePmI4vlKjrmRwrLGYR3vpAkvz9uV624V2VoY19fjKuTwNosyzqR9tymUQ7BaI_SYPyzaC-K7TBx-P_Af" alt="issue #4 and #5 dashboard" width="880" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It seems that all errors are caused by the &lt;strong&gt;/payments/execute&lt;/strong&gt; endpoint. This first part of the error condition is obvious and shared by both issues.&lt;/p&gt;

&lt;p&gt;The rest of the values are not so obvious but they are all present in the current view. &lt;/p&gt;

&lt;p&gt;We simply have to find out what combination of each value will match issue #4 and, respectively, issue #5.&lt;/p&gt;

&lt;p&gt;At this point, we can simply guess until the solution pops out.&lt;/p&gt;

&lt;p&gt;Let’s start by filtering the &lt;strong&gt;GET method&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The solution to any of the issues isn’t obvious yet:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AtTGBM9h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/R1to2riWCuRBT4fkb8blD7HeASvqFYztkuhOv2KCBfq-MqTVHRqoqgiYqvUeT35NqcL7WUFdnWFBXIjx5mAVEMUU4cTZcwjp8jQN_95qTcMArKS-AxreAIkQE85OsDb9NM9hqAfr" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AtTGBM9h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/R1to2riWCuRBT4fkb8blD7HeASvqFYztkuhOv2KCBfq-MqTVHRqoqgiYqvUeT35NqcL7WUFdnWFBXIjx5mAVEMUU4cTZcwjp8jQN_95qTcMArKS-AxreAIkQE85OsDb9NM9hqAfr" alt="filtering GET method" width="880" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;Let’s continue by filtering the &lt;strong&gt;AMEX&lt;/strong&gt; &lt;strong&gt;payment method&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--61bY__an--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/BJWMzvbXhOrT3b5yVi5mcsUKONRWNMj49Q92joIKgpplp9quWPRiGSQx_WvkTidzcDejFEos28TnQLnjkjqVzxwS18Y_gf2b1BiJyXwxtC1X4b-uqQGLRgxXkZGjHAZ2SdGdN8vI" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--61bY__an--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/BJWMzvbXhOrT3b5yVi5mcsUKONRWNMj49Q92joIKgpplp9quWPRiGSQx_WvkTidzcDejFEos28TnQLnjkjqVzxwS18Y_gf2b1BiJyXwxtC1X4b-uqQGLRgxXkZGjHAZ2SdGdN8vI" alt="filtering AMEX" width="880" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It appears we’ve found one of our issues.&lt;/p&gt;

&lt;p&gt;However, there is one problem - it currently appears that the error condition consists of &lt;strong&gt;five different inputs&lt;/strong&gt;, while both issues consist of &lt;strong&gt;four inputs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, we have to find the odd one out. &lt;/p&gt;

&lt;p&gt;We can do this by removing the filter on any of the parameters on-screen. &lt;/p&gt;

&lt;p&gt;After several iterations of trial-and-error, you will figure out that when we remove the filter on the &lt;strong&gt;GET method&lt;/strong&gt;, it can be seen that the error happens on other methods as well:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZCDVwgoL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/FmcafsycUG5YUe7rCC3ZXh9ui-EoIsDNCsz8ji_olBNz_OyZJxTs1JN-DPhjn-M1cP5U9cLNCJp1xLWN6JGRW4iKbHJJdUs3mxcJWhLwxYDEU00xowEjH7jsxkEfU4iYIkucr3SH" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZCDVwgoL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/FmcafsycUG5YUe7rCC3ZXh9ui-EoIsDNCsz8ji_olBNz_OyZJxTs1JN-DPhjn-M1cP5U9cLNCJp1xLWN6JGRW4iKbHJJdUs3mxcJWhLwxYDEU00xowEjH7jsxkEfU4iYIkucr3SH" alt="result after filtering AMEX" width="880" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This leads us to the conclusion that issue #4 is:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;The application fails when &lt;strong&gt;the endpoint is /payments/execute&lt;/strong&gt; and &lt;strong&gt;the payment method is AMEX&lt;/strong&gt; and &lt;strong&gt;the country is AU&lt;/strong&gt; and &lt;strong&gt;the user type is corporate&lt;/strong&gt;
&lt;/li&gt;&lt;/ul&gt;





&lt;p&gt;Now, exclude one of those parameters, e.g. &lt;strong&gt;the AMEX payment method &lt;/strong&gt;and the final issue is evident:&lt;/p&gt;





&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MYeaj_lg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/E10fn0P7aF5-KRXqLphkk4Srw4CWLIt92aNebXaxsBmdXNUpBiNFpyXutyJbGubZnEqjZ_PYwKJcPNsNifjC3WJgYbsa2IVr7gdzrjytNURqiN8feSUIuqWnON_1ibqapVJRcklh" alt="issue #5 root cause" width="880" height="444"&gt;





&lt;p&gt;Issue #5 is:&lt;/p&gt;





&lt;ul&gt;&lt;li&gt;The application fails when &lt;strong&gt;the endpoint is /payments/execute&lt;/strong&gt; and &lt;strong&gt;the payment method is Cash&lt;/strong&gt; and &lt;strong&gt;the country is BG&lt;/strong&gt; and &lt;strong&gt;the user type is trial&lt;/strong&gt;
&lt;/li&gt;&lt;/ul&gt;





&lt;h2&gt;Conclusion&lt;/h2&gt;





&lt;p&gt;Congratulations. 👏👏👏&lt;/p&gt;





&lt;p&gt;You’ve successfully completed the tutorial. &lt;/p&gt;





&lt;p&gt;You should now have a good practice using Kibana to analyze real issues with your services.&lt;/p&gt;





&lt;p&gt;This concludes the series on &lt;strong&gt;Integrating your Go service with the ELK Stack&lt;/strong&gt;. You should now have a decent understanding of:&lt;/p&gt;





&lt;ul&gt;
&lt;li&gt;How to install &amp;amp; setup ELK&lt;/li&gt;
&lt;li&gt;How to use structured logging in your services&lt;/li&gt;
&lt;li&gt;How to create Kibana dashboards&lt;/li&gt;
&lt;li&gt;How to analyse production issues in the Kibana dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Well Done &amp;amp; happy logging. 👌&lt;/p&gt;

</description>
      <category>logging</category>
      <category>go</category>
      <category>microservices</category>
      <category>kibana</category>
    </item>
    <item>
      <title>How to create a Logging Dashboard with Kibana</title>
      <dc:creator>Preslav Mihaylov</dc:creator>
      <pubDate>Sun, 09 Aug 2020 07:30:56 +0000</pubDate>
      <link>https://dev.to/pmihaylov/how-to-create-a-logging-dashboard-with-kibana-2ml4</link>
      <guid>https://dev.to/pmihaylov/how-to-create-a-logging-dashboard-with-kibana-2ml4</guid>
      <description>&lt;p&gt;In this tutorial, I'll show you how to create a dashboard for your application's structured logs in Kibana.&lt;/p&gt;

&lt;p&gt;This is a follow-up to &lt;a href="https://pmihaylov.com/go-structured-logs/"&gt;this article&lt;/a&gt;, which covers how to instrument your Go application \w structured logging for use by Kibana (in this tutorial).&lt;/p&gt;

&lt;p&gt;We'll use Kibana v7.6 but any version you're using should work. It's just that your UI might look a bit different &amp;amp; you'll have to adjust.&lt;/p&gt;

&lt;p&gt;Let's jump straight in! We'll stick to simple panels, which suite most of the use-cases you'd need.&lt;/p&gt;

&lt;h2&gt;The Starting Point&lt;/h2&gt;

&lt;p&gt;First, make sure you have &lt;strong&gt;docker&lt;/strong&gt; and &lt;strong&gt;docker-compose&lt;/strong&gt; installed.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;docker&lt;/strong&gt;, follow the installation instructions for your platform &lt;a href="https://docs.docker.com/get-docker/"&gt;here&lt;/a&gt;.&lt;br&gt;For &lt;strong&gt;docker-compose&lt;/strong&gt;, follow the installation instructions &lt;a href="https://docs.docker.com/compose/install/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Afterwards, download this exercise’s repository on the &lt;strong&gt;kibana-dashboard-tutorial&lt;/strong&gt; branch:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;git clone --branch kibana-dashboard-tutorial https://github.com/preslavmihaylov/tutorials&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;What you already have is a Go application, which randomly produces logs for endpoints &lt;strong&gt;/payments/execute&lt;/strong&gt;, &lt;strong&gt;/payments/list&lt;/strong&gt;, &lt;strong&gt;/payments/authhold&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Starting from here, you will build a dashboard for effectively monitoring &amp;amp; analysing incoming traffic.&lt;/p&gt;

&lt;p&gt;In the following section, I will walk you through how to achieve this step by step.&lt;/p&gt;

&lt;h2&gt;The Walkthrough&lt;/h2&gt;

&lt;h3&gt;Boot up all containers&lt;/h3&gt;

&lt;p&gt;Invoke this command to bring up all containers:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker-compose up&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For all following steps, make sure you never execute docker-compose down.&lt;/p&gt;

&lt;p&gt;That command will not only stop all containers but delete them as well.&lt;/p&gt;

&lt;p&gt;If you're seeing some issues with booting up elasticsearch, here is a reference to some common issues &amp;amp; how to handle them:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/laradock/laradock/issues/1699"&gt;elasticsearch exit code 78&lt;/a&gt;&lt;br&gt;&lt;a href="https://github.com/10up/wp-local-docker/issues/6"&gt;elasticsearch exit code 137&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Setup your application’s baseline saved search&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href="http://localhost:5601/app/kibana#/home"&gt;http://localhost:5601/app/kibana#/home&lt;/a&gt; and open the &lt;strong&gt;Discover&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;First, expand the tab names in the bottom-left as they are unfamiliar at first:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--napCzMNz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/sOpKgG_GBDIzy2fvbObbUz1ae3FYa9UsWSBaTtgJ1dQm_ZErpGHm2p8KMj1Ut8VxZhZbAl_4WMmGVhnNar-jH-kZyNCnjNzm4KOXQszVxWpTl0fbe3WTbaVBuG1vKjHM9WCVgoH1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--napCzMNz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/sOpKgG_GBDIzy2fvbObbUz1ae3FYa9UsWSBaTtgJ1dQm_ZErpGHm2p8KMj1Ut8VxZhZbAl_4WMmGVhnNar-jH-kZyNCnjNzm4KOXQszVxWpTl0fbe3WTbaVBuG1vKjHM9WCVgoH1" alt="the expand button" width="450" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, find the &lt;strong&gt;Discover&lt;/strong&gt; tab in the top-left:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yJyvMqxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/0_OX3LO3-oLo6AT7SV2C3KZR6KFxHmC-ZO2Nzss_jD0MnKQw4XT1tB-wNvHJ0kDhldMfji6aGEtOY6MfuzRfzopNkL8XSmBuUYFZmg5lklWMDGIkxezE_jlh74CYw9RhZ8TnX-dX" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yJyvMqxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/0_OX3LO3-oLo6AT7SV2C3KZR6KFxHmC-ZO2Nzss_jD0MnKQw4XT1tB-wNvHJ0kDhldMfji6aGEtOY6MfuzRfzopNkL8XSmBuUYFZmg5lklWMDGIkxezE_jlh74CYw9RhZ8TnX-dX" alt="Click discover in the Kibana dashboard" width="664" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set the index pattern if you haven’t already:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6xWHJxog--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/PigYRco-kctctYwF_Pm_WvojuunJ2WgexEEPr-noiquQ1AWE9DI-TGKVJQy0N8vox8bV8uOY1pvkU4oR8yLDvbzCKeqGSNDVtxgGwRGZ-ju1SqSnrinpAZvyoIwk4WmZvc0XAVfg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6xWHJxog--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/PigYRco-kctctYwF_Pm_WvojuunJ2WgexEEPr-noiquQ1AWE9DI-TGKVJQy0N8vox8bV8uOY1pvkU4oR8yLDvbzCKeqGSNDVtxgGwRGZ-ju1SqSnrinpAZvyoIwk4WmZvc0XAVfg" alt="Create index pattern" width="880" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next step, choose &lt;strong&gt;@timestamp&lt;/strong&gt; as a time filter:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PPisK8by--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/vqREV-Zx7s7Q3oT_iPwm4gnQ7lEPt31dncYn6JDymQsgOLR8xMRgt5PuO1FuMMZhy7zKH-PB-6UL_tTf71umcRAJmjudTPOsXujY4DxKiiIOztZMWSc63sUA3wAq4x9YQ77LbM7k" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PPisK8by--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/vqREV-Zx7s7Q3oT_iPwm4gnQ7lEPt31dncYn6JDymQsgOLR8xMRgt5PuO1FuMMZhy7zKH-PB-6UL_tTf71umcRAJmjudTPOsXujY4DxKiiIOztZMWSc63sUA3wAq4x9YQ77LbM7k" alt="Configure index pattern settings" width="880" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open the &lt;strong&gt;Discover&lt;/strong&gt; tab again. This is what the initial view should look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HxiZ1gM6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/2MMlqalb-uNDR-QrvALEq3F4uYNQsKDvMCK27JW5MuwOHTNWTKD-mGPmCxAb_73DBh7rPznvJZhBvUqcu_jw9sfcuAmvjRHVlkA87HPZfRXcBQhQcwnAo6AYXKjAmH6Qcug_BUQ6" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HxiZ1gM6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/2MMlqalb-uNDR-QrvALEq3F4uYNQsKDvMCK27JW5MuwOHTNWTKD-mGPmCxAb_73DBh7rPznvJZhBvUqcu_jw9sfcuAmvjRHVlkA87HPZfRXcBQhQcwnAo6AYXKjAmH6Qcug_BUQ6" alt="The discover tab" width="880" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Currently, kibana shows all log lines from our application without any filtered fields. We can exclude all fields we don’t need:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZDcCcsQ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/Wb6ozBL95IghC5tObSqx09919GqDMrm6ecN0cvy4qj7i8vpp-MZv0z1qGpBFwVE9GQhI3jRKNPovPQa805muoW7CpPotC38vf7vNzb3bX_X3_F_BVra1Z_OIdXgrjBKRllrTqhv6" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZDcCcsQ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/Wb6ozBL95IghC5tObSqx09919GqDMrm6ecN0cvy4qj7i8vpp-MZv0z1qGpBFwVE9GQhI3jRKNPovPQa805muoW7CpPotC38vf7vNzb3bX_X3_F_BVra1Z_OIdXgrjBKRllrTqhv6" alt="Exclude unneeded fields" width="880" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, select only the fields which make sense for our application.&lt;/p&gt;

&lt;p&gt;Those are &lt;strong&gt;endpoint, method, countryISO2, userID, paymentMethod, userType, error, msg&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For each of them, click Add to add them to the selected fields in the visualised log:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JrGwKoOH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/84_InyZ2LisDaJ9IMMeBm7z8p2A9tft4emi8HZHH4B1yUtCCrSjRqwtGXtuGk2sYxXHcZocCR-akkz5a4wK0tS_-Ti8CG3TI6crkPO_tLCutu0OIapeOlrRSHns8dLmFW0eqAJ3q" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JrGwKoOH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/84_InyZ2LisDaJ9IMMeBm7z8p2A9tft4emi8HZHH4B1yUtCCrSjRqwtGXtuGk2sYxXHcZocCR-akkz5a4wK0tS_-Ti8CG3TI6crkPO_tLCutu0OIapeOlrRSHns8dLmFW0eqAJ3q" alt="Filtering the fields you need" width="698" height="1018"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the end, this is what your &lt;strong&gt;Selected fields&lt;/strong&gt; should be:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f-nOuDt1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/L78RkT2keQToPjkGI7qvtsyjD_i8-iHeSnh-UuBr2BsGkHcj3eL_SlHONYS_w5sCLsZjYwDrVSH6MbdlWkwHcahyHwPb0Tgldn6vb4hShrZNynhdqdMv4Gb5Fc68TX5yeC35hzCU" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f-nOuDt1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/L78RkT2keQToPjkGI7qvtsyjD_i8-iHeSnh-UuBr2BsGkHcj3eL_SlHONYS_w5sCLsZjYwDrVSH6MbdlWkwHcahyHwPb0Tgldn6vb4hShrZNynhdqdMv4Gb5Fc68TX5yeC35hzCU" alt="The final selected fields" width="606" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you did these steps properly, this is an example of what your logs should look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Jt1O_Ozs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/qOgyrN0CfQqK1RCXsidLgywYZsE3NC2SaH-B0Zv0OG_KuuuOKkG1Sbpi5xxM0-Ds1NN0rc0uxos56RY30PR61VZeKAKu29ON2Dj80o93oeTuvOZyrE4jj-GpuNvgcvlh8xUvZILH" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jt1O_Ozs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/qOgyrN0CfQqK1RCXsidLgywYZsE3NC2SaH-B0Zv0OG_KuuuOKkG1Sbpi5xxM0-Ds1NN0rc0uxos56RY30PR61VZeKAKu29ON2Dj80o93oeTuvOZyrE4jj-GpuNvgcvlh8xUvZILH" alt="Discover tab after filtering" width="880" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will use this saved search as a baseline view for our dashboard. &lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Save&lt;/strong&gt; at the top-left to later reuse this view for our dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JNEFZiuO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/gfsRAEnWZKcq2yW2xCVd2cd5ynVcTkvQM4xlwYfr-mGRvvrnv0cuHp8eOQcT6uE7C2-XuFIl0ZqHjvQGQvyc-qRDXJCOmhYuBI-Go9SYB8YRwofUigOjJOMMmLn6ei0qMWvgbF8M" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JNEFZiuO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/gfsRAEnWZKcq2yW2xCVd2cd5ynVcTkvQM4xlwYfr-mGRvvrnv0cuHp8eOQcT6uE7C2-XuFIl0ZqHjvQGQvyc-qRDXJCOmhYuBI-Go9SYB8YRwofUigOjJOMMmLn6ei0qMWvgbF8M" alt="Saving your current view" width="880" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is one of the most useful views as it allows one to inspect the details of the events which happen in your application.&lt;/p&gt;

&lt;h3&gt;Setting up your initial Kibana dashboard&lt;/h3&gt;

&lt;p&gt;Open the &lt;strong&gt;Dashboard&lt;/strong&gt; tab to create your first dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nr9MDT27--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/CZhCxyjk6NLXRnHFoZwUatYfdZ4BYYB3JF1PY_Ws9rSC0SlwavrsgTn8wbCv-t0Xs6LsPLJ_2YR9VHKC2k0HYPKHSv26KggKOzoYvXXwlJfM3zNmSuT0qsG_fbfzt2CTYvUI6DUa" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nr9MDT27--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/CZhCxyjk6NLXRnHFoZwUatYfdZ4BYYB3JF1PY_Ws9rSC0SlwavrsgTn8wbCv-t0Xs6LsPLJ_2YR9VHKC2k0HYPKHSv26KggKOzoYvXXwlJfM3zNmSuT0qsG_fbfzt2CTYvUI6DUa" alt="Your initial Kibana dashboard" width="552" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow the instructions on-screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7zpbhJAR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/UBxo4umfb5ZQJLrh-GeZOL37qVpfkjFbiWNs6dvd5a9pe8FxMghcxA7unUP0AB5VX7sI3OGfrLQyUpx2FoMp-24qmG6ftoQC6hUL71pmRReZy35z-g2-R1jpRqwZwk5awJX_A9wO" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7zpbhJAR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/UBxo4umfb5ZQJLrh-GeZOL37qVpfkjFbiWNs6dvd5a9pe8FxMghcxA7unUP0AB5VX7sI3OGfrLQyUpx2FoMp-24qmG6ftoQC6hUL71pmRReZy35z-g2-R1jpRqwZwk5awJX_A9wO" alt="Creating your first dashboard message" width="880" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After this, you should see an empty dashboard which doesn’t show anything:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kDNnXXpv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/nrNvqCZXWJaJd194VFJ31wIDFnlWbAl6-vUn04IJYorUrWhkwwB22l01J_o-WxSEGvS6cTxkvikTp3kHWG3-9GGjNS2u3A5PRfRwwAwrcKrysm1rsjmfOxoDbgEnAGwjOGCf_RBC" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kDNnXXpv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/nrNvqCZXWJaJd194VFJ31wIDFnlWbAl6-vUn04IJYorUrWhkwwB22l01J_o-WxSEGvS6cTxkvikTp3kHWG3-9GGjNS2u3A5PRfRwwAwrcKrysm1rsjmfOxoDbgEnAGwjOGCf_RBC" alt="Initial view of the empty Kibana dashboard" width="880" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the &lt;strong&gt;Add&lt;/strong&gt; button at the top-left to add a new visualisation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XMBy846O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/2ITOX0ufcf7k4oxMFWsrbzX6znK1WKMbVICT_ND_mrUJE8Na4qTy0QoBGoTG9zQKxiVMrnSKvWyVV76NrL4ufXVt1bd01EUvg9-Q44QJHCkpcWn3q72cchXoBoL1xzLh5DYJfdy6" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XMBy846O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/2ITOX0ufcf7k4oxMFWsrbzX6znK1WKMbVICT_ND_mrUJE8Na4qTy0QoBGoTG9zQKxiVMrnSKvWyVV76NrL4ufXVt1bd01EUvg9-Q44QJHCkpcWn3q72cchXoBoL1xzLh5DYJfdy6" alt="Add a new element" width="848" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see the name of the &lt;strong&gt;saved search&lt;/strong&gt; you created in the previous step. Choose that:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mlkMF9aS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/QRkAc6vPzbv6EVPqQKRr3O8QqlRNZyomKTV5lXkDQ4LSVkRAbkHimleDp9cuA6JyXqfKnFJoriZ_sbatRFn9Bk35rxuXUQzKpEY_E_v6LPKGJB4MALQfDeazZwV36DnzbET0_jXe" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mlkMF9aS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/QRkAc6vPzbv6EVPqQKRr3O8QqlRNZyomKTV5lXkDQ4LSVkRAbkHimleDp9cuA6JyXqfKnFJoriZ_sbatRFn9Bk35rxuXUQzKpEY_E_v6LPKGJB4MALQfDeazZwV36DnzbET0_jXe" alt="Select a panel to add to" width="880" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, resize it as you see fit and you should see the first panel in your dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Csa75Ole--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/V0KPt4qbJEEqw00wz34Q3Vy74-a6k8Hz-CsjFJHHw9STU_YvQ7ALpS8q3lXrq1xqtrlmR3uw7XQzbShAtBcDn24SX_8W8GhU-c2_EjqWvVxcDDOUj8F6OoRaWlhCpK2LOGW8q7Ut" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Csa75Ole--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/V0KPt4qbJEEqw00wz34Q3Vy74-a6k8Hz-CsjFJHHw9STU_YvQ7ALpS8q3lXrq1xqtrlmR3uw7XQzbShAtBcDn24SX_8W8GhU-c2_EjqWvVxcDDOUj8F6OoRaWlhCpK2LOGW8q7Ut" alt="Adding the saved search" width="880" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This view is great for inspecting the details of all requests &amp;amp; using it for additional filtering on a given property. &lt;/p&gt;

&lt;p&gt;However, we will need to add some more visualisations to be able to see aggregations of the data in our dashboard.&lt;/p&gt;

&lt;p&gt;Save your new dashboard with a descriptive name and proceed to the next section:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--E-haWxoc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/Ro1XT4eK8OpXt04bx9p-iW-bGnY40exiPHMuv43rMEbqEiAJOjjoTrAnINrYGZ7MqPhoJnQ5RSKVhbXjUPCd5ArAK4WounbDc2EvxYVpiV-NMR7WctmZxqXX-9UVeKVX_Zl2EwoX" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E-haWxoc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/Ro1XT4eK8OpXt04bx9p-iW-bGnY40exiPHMuv43rMEbqEiAJOjjoTrAnINrYGZ7MqPhoJnQ5RSKVhbXjUPCd5ArAK4WounbDc2EvxYVpiV-NMR7WctmZxqXX-9UVeKVX_Zl2EwoX" alt="Saving the Kibana dashboard" width="880" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Add a data table to monitor requests per endpoint&lt;/h3&gt;

&lt;p&gt;In this step, we will create a &lt;strong&gt;data table&lt;/strong&gt; which will show how many requests we get per endpoint.&lt;/p&gt;

&lt;p&gt;Click the &lt;strong&gt;Edit&lt;/strong&gt; button at the top-left to edit the contents of the dashboard and select &lt;strong&gt;Add&lt;/strong&gt; to create a new view. &lt;/p&gt;

&lt;p&gt;In the panel which pops up, create a new visualisation from the bottom-left:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3px5W3eA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/6Lm9lMxk027D2vlNr6EKco_7-YuU0trRDF4n36_RT-gzutB-JhJJmsSCO5bEe2hbL6yMq-VXnXNAwEmCQdl_dnmB4d-D4f8XMempHG3VBN3iI0w3rnnMEXo3wjf1_pbJlsjJwkG7" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3px5W3eA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/6Lm9lMxk027D2vlNr6EKco_7-YuU0trRDF4n36_RT-gzutB-JhJJmsSCO5bEe2hbL6yMq-VXnXNAwEmCQdl_dnmB4d-D4f8XMempHG3VBN3iI0w3rnnMEXo3wjf1_pbJlsjJwkG7" alt="Create a new visualization" width="838" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, create a new data table:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VT4ENdnX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/VU5aqUm8NHSNtjEauRaYk2fQuRQJufcTxDCt4ncttf3bLUTI1fixrr4vpleagwKLDnaN3rAmJAvVzohiBuCFqjsj7lhxBBcLbnfpjJdinBXlMFtCenCUwau1J9k6P63oNQHCsAv-" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VT4ENdnX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/VU5aqUm8NHSNtjEauRaYk2fQuRQJufcTxDCt4ncttf3bLUTI1fixrr4vpleagwKLDnaN3rAmJAvVzohiBuCFqjsj7lhxBBcLbnfpjJdinBXlMFtCenCUwau1J9k6P63oNQHCsAv-" alt="Picking a visualization template" width="880" height="693"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next screen, select your &lt;strong&gt;saved search&lt;/strong&gt;’s name as the data source to use.&lt;/p&gt;

&lt;p&gt;Afterwards, you should see a screen which simply shows the total count of requests in a table. This is because the table currently aggregates all requests &amp;amp; shows the total count for them.&lt;/p&gt;

&lt;p&gt;We will aggregate the data in the table by the &lt;strong&gt;endpoint&lt;/strong&gt; keys. This will show the count of each request per endpoint.&lt;/p&gt;

&lt;p&gt;To do this, add a new &lt;strong&gt;Bucket &lt;/strong&gt;which splits rows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HDA1DLCJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/Ea3DRqBlyXrF2T8AxMcpwZfaS4RHm1YjLN6FZblmzYYh33mjHtvuxFdAmv5GNvNsPvYUAN8iPwv_Gqg2YoHNsmBHJZN8IBqfs-S7eHKunWLhdD1cYUnu9ZNhyi64x-PVrsPAPCrq" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HDA1DLCJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/Ea3DRqBlyXrF2T8AxMcpwZfaS4RHm1YjLN6FZblmzYYh33mjHtvuxFdAmv5GNvNsPvYUAN8iPwv_Gqg2YoHNsmBHJZN8IBqfs-S7eHKunWLhdD1cYUnu9ZNhyi64x-PVrsPAPCrq" alt="Split rows setting" width="730" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As an aggregation, use &lt;strong&gt;Terms&lt;/strong&gt;. This is a simple aggregation which simply groups the data by the unique values for the key we’ll choose:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xW0K0Fl_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/OnjB7XL7F0DUbIpV5c4RseX8j8VTEpyLujiFsh8QZsB4DNJDesrRxmYn4FHRm5OSBhT86ninZhPu5F1AFaSPi9C7_xi_FqWQiu2rNHy3gJiDGYbLWD3mnZdphPLYCveI7lJ1j8kq" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xW0K0Fl_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/OnjB7XL7F0DUbIpV5c4RseX8j8VTEpyLujiFsh8QZsB4DNJDesrRxmYn4FHRm5OSBhT86ninZhPu5F1AFaSPi9C7_xi_FqWQiu2rNHy3gJiDGYbLWD3mnZdphPLYCveI7lJ1j8kq" alt="Aggregating by Terms" width="678" height="724"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Aggregate by the endpoint key and use the following configuration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EbVvp9bi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/vLSF0mZnVoe6r8I9YJ7AJkvmHNagHCFnfmKPPj3DcrvyAUaiF91CuH9XnHErDDK1UfhcDGEg4k0HR6uE2Z6O5p_bq5uhCFmzrC8xpD7hEpC7-nKi46YViQOebtU1sZxmsDF1znRN" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EbVvp9bi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/vLSF0mZnVoe6r8I9YJ7AJkvmHNagHCFnfmKPPj3DcrvyAUaiF91CuH9XnHErDDK1UfhcDGEg4k0HR6uE2Z6O5p_bq5uhCFmzrC8xpD7hEpC7-nKi46YViQOebtU1sZxmsDF1znRN" alt="The aggregation settings" width="646" height="1028"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Group other values in separate bucket will display all data which is too little and doesn’t fit in the table (the limit is 5 rows, which we specify) in a general &lt;strong&gt;“Other”&lt;/strong&gt; bucket.&lt;/p&gt;

&lt;p&gt;For example, if we had 7 different endpoint and five of them get 80% of the traffic, the 20% other values will be aggregated in a row called &lt;strong&gt;“Other”&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, click the Play button at the top of the window to see how your table looks. You should see the count of requests per endpoint:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4jqRxLJu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/CAJmDkpnhc23grSBSFU9A7aGT0IQPt99JbO7nHxUwFoOdw85b216h4LZaJ7kPHathqxXzsow4wdV1WRfd-_cXOSi96rg0UHI6aVX_jqp0HLUMHtRX3c7R3lnRaFT5xtPQIA_Qjbw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4jqRxLJu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/CAJmDkpnhc23grSBSFU9A7aGT0IQPt99JbO7nHxUwFoOdw85b216h4LZaJ7kPHathqxXzsow4wdV1WRfd-_cXOSi96rg0UHI6aVX_jqp0HLUMHtRX3c7R3lnRaFT5xtPQIA_Qjbw" alt="Initial view after aggregation" width="880" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This view now looks quite useful. With it, we can track how much traffic we get per endpoint. Click the &lt;strong&gt;Save&lt;/strong&gt; button at the top-left, give the new view a title (e.g. &lt;strong&gt;Calls by Endpoints&lt;/strong&gt;) and rearrange your dashboard to look aesthetically pleasing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YRpFYRVx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/bM48fmgIUWuWRSDnCtfODM_C-dWyU759Fn_0X8IJ5d6IBili-hwx_5ir0UMP1SDY33opCN9y2UiMLzpN6ZWv-LoiAijEG-MsWcJNI77py6Al5r_Nq_n3RqZN4Ov0i7nuovbk0HZo" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YRpFYRVx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/bM48fmgIUWuWRSDnCtfODM_C-dWyU759Fn_0X8IJ5d6IBili-hwx_5ir0UMP1SDY33opCN9y2UiMLzpN6ZWv-LoiAijEG-MsWcJNI77py6Al5r_Nq_n3RqZN4Ov0i7nuovbk0HZo" alt="Adding the new visualization" width="880" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s now do the same for the rest of our significant fields.&lt;/p&gt;

&lt;h3&gt;Add a data table for all significant fields&lt;/h3&gt;

&lt;p&gt;In this step, add a new table, following the same steps as the previous one, for &lt;strong&gt;method, countryISO2, userType &lt;/strong&gt;and&lt;strong&gt; paymentMethod&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There is no need to include a view for &lt;strong&gt;userID&lt;/strong&gt; as the granularity of that field is typically way too big to make a sensible dashboard. &lt;/p&gt;

&lt;p&gt;However, you can usually take that by making custom filters on some of the log lines in the saved search view.&lt;/p&gt;

&lt;p&gt;After you go through this, here is what the dashboard should look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SxL4HgUS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/0xWb_4KToEpnubYP_HCUZt_xA4SauxjOey0o5Rm5pIbp3M2_1JF62WOnYTAxcHpvMj0hskIho8t0Z309wC_Zn4Phw0iLqOCNXeFlLSAfns0yWmcamVFj07ae_l66eATgR16JPYj6" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SxL4HgUS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/0xWb_4KToEpnubYP_HCUZt_xA4SauxjOey0o5Rm5pIbp3M2_1JF62WOnYTAxcHpvMj0hskIho8t0Z309wC_Zn4Phw0iLqOCNXeFlLSAfns0yWmcamVFj07ae_l66eATgR16JPYj6" alt="Adding all data tables" width="880" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, you can see aggregations for all significant data points you are interested in. &lt;/p&gt;

&lt;p&gt;If you want to further inspect e.g. what payment methods one uses when calling the &lt;strong&gt;/payments/execute&lt;/strong&gt; endpoint, you can “zoom in” on the data by filtering for that data. &lt;/p&gt;

&lt;p&gt;Try it out and explore for yourself:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AtIygVgS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/EiwZazuFfHFDja9xHOlHFA4g6tq9QOmS-VasvNBFa8dAPKssp_HkoFRFrjxSlComlia2_hu27UjDJ-XKyU-exdy_2hHDBbQXIUYKwKQoCz3kOxrS_MTmdAUM9MuOg8jB8om43FNw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AtIygVgS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/EiwZazuFfHFDja9xHOlHFA4g6tq9QOmS-VasvNBFa8dAPKssp_HkoFRFrjxSlComlia2_hu27UjDJ-XKyU-exdy_2hHDBbQXIUYKwKQoCz3kOxrS_MTmdAUM9MuOg8jB8om43FNw" alt="Filtering by a given field" width="880" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a challenge for you, zoom in on the &lt;strong&gt;errors&lt;/strong&gt; and identify which is the endpoint which has the most errors.&lt;/p&gt;

&lt;p&gt;The scope of this exercise is creating the dashboard, but don’t worry. You will get to play with it in the following exercise.&lt;/p&gt;

&lt;p&gt;Our dashboard already looks quite informative. &lt;/p&gt;

&lt;p&gt;But there is one final touch it needs.&lt;/p&gt;

&lt;h3&gt;Add a histogram for tracking success/error rate&lt;/h3&gt;

&lt;p&gt;In this step, we will diverge from the good old data table and add a histogram to track the &lt;strong&gt;success-to-error&lt;/strong&gt; ratio over time.&lt;/p&gt;

&lt;p&gt;The rest of the views in the dashboard are typically used to find the root cause of an issue. &lt;/p&gt;

&lt;p&gt;The view we’ll create now will enable us to quickly identify if there is an issue.&lt;/p&gt;

&lt;p&gt;Create a new visualisation, but this time, select a &lt;strong&gt;Vertical Bar&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lFkfq2Me--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/_8J7Rde6OK8aEqNkJqMjBBXQVv-usxg0Rw0WNvsL6s9MNq6z88SCs6JHGAARGnpDHTu94UUbnjpFYpJY7yb2DqS1FU_Fzo8ABWsICbRHwUaRDmF7xhAK6dMr3WxVwSKhCCQs3kuf" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lFkfq2Me--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/_8J7Rde6OK8aEqNkJqMjBBXQVv-usxg0Rw0WNvsL6s9MNq6z88SCs6JHGAARGnpDHTu94UUbnjpFYpJY7yb2DqS1FU_Fzo8ABWsICbRHwUaRDmF7xhAK6dMr3WxVwSKhCCQs3kuf" alt="Adding a histogram" width="880" height="692"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the beginning, you will simply see a single bar with the total count of requests in the given time period. &lt;/p&gt;

&lt;p&gt;For starters, add an X-Axis which will create time intervals for each of the lines in the histogram. This will allow us to see the tendency of events over time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0kuuGp40--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/B19Ck1BBahMGnlJ3YHe_5tcJjdhP_KcPv7rzgW_qplBVJtX8dfD-eXiTLk439QLpBCi9tbR8Oa5E0G0x9tZh5WH0zDWn81MTWSmCda860aTJ_GY4UQqV4d8MyCYdX7ymIO5GkQ3M" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0kuuGp40--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/B19Ck1BBahMGnlJ3YHe_5tcJjdhP_KcPv7rzgW_qplBVJtX8dfD-eXiTLk439QLpBCi9tbR8Oa5E0G0x9tZh5WH0zDWn81MTWSmCda860aTJ_GY4UQqV4d8MyCYdX7ymIO5GkQ3M" alt="Choosing the X-Axis" width="692" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This time, the selected aggregation is Date histogram on the @timestamp field:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9t8UkF5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/TBUXiBsUAO0SZI2ivGataKkLHj_Q8l_oq2EOwgQ6S8QDAzwjszmlfqreRHYnpAwegSr1EU_5mbWaIX-1n8Eo80Qf5ryyvK3lyIwPNNFYTQEv-Fy6sCA4kSHatLicO8VJVm58MNje" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9t8UkF5F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/TBUXiBsUAO0SZI2ivGataKkLHj_Q8l_oq2EOwgQ6S8QDAzwjszmlfqreRHYnpAwegSr1EU_5mbWaIX-1n8Eo80Qf5ryyvK3lyIwPNNFYTQEv-Fy6sCA4kSHatLicO8VJVm58MNje" alt="X-Axis settings" width="642" height="1058"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the &lt;strong&gt;Play&lt;/strong&gt; button and see how the view changes to reflect the count of events over time:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RfDK_qVL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/qExMoMPhtNFolD4fVbY4t764bHmJiIEmcZp8GWwejRAzAua41mzIKSXmqz-jfs1nHvtTcy6nim-Tar4PCNEIEh-pU8DF1Tdbwj9jBeJ8VWWnLCzuEeD9iVG7yqIGwV2KiB6fSU8c" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RfDK_qVL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/qExMoMPhtNFolD4fVbY4t764bHmJiIEmcZp8GWwejRAzAua41mzIKSXmqz-jfs1nHvtTcy6nim-Tar4PCNEIEh-pU8DF1Tdbwj9jBeJ8VWWnLCzuEeD9iVG7yqIGwV2KiB6fSU8c" alt="Initial histogram" width="880" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is cool, but it would be useful if we could actually see how many of those requests are success and how many are errors.&lt;/p&gt;

&lt;p&gt;To do this, let’s customize the existing Y-Axis to only aggregate successful requests.&lt;/p&gt;

&lt;p&gt;First, change the Y-Axis aggregation from Count to &lt;strong&gt;Sum bucket&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then, use &lt;strong&gt;Filters&lt;/strong&gt; as the &lt;strong&gt;Bucket&lt;/strong&gt; aggregation. This will allow us to use KQL (Kibana Query Language) to make any custom filter we like.&lt;/p&gt;

&lt;p&gt;Add a filter which says select all messages which are not empty strings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cEMIdheS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/AuHwKgmQA_Wzo56ryHueplQGmutENBR-TX4mF9Xo4xxXaKNE4nrVwSg_LxpR2s9y1LfqVmk40VgWaY0W5zhmvRybutXl4IjyEXcHS8G7lAPw9wnjjS3tdli9P3rH_0n_lKhdoBCW" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cEMIdheS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/AuHwKgmQA_Wzo56ryHueplQGmutENBR-TX4mF9Xo4xxXaKNE4nrVwSg_LxpR2s9y1LfqVmk40VgWaY0W5zhmvRybutXl4IjyEXcHS8G7lAPw9wnjjS3tdli9P3rH_0n_lKhdoBCW" alt="Setting up the Y-Axis" width="626" height="822"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, add a custom label &lt;strong&gt;“Success”&lt;/strong&gt; for this Y-Axis at the bottom of the panel.&lt;/p&gt;

&lt;p&gt;Click the Play button to see the results. You should now see the number of successful requests every 30s. The default color, however, is a bit off, so change it at the top-right to green:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kW8TxYJG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/S6bwh01v6y1l4dH7l_lmm80jUmNBkuwvt6jp7LzjeoDhJW0aZmoQoRqo1uKobvJgoUHY8Y6nwaY7dxKeXXj7Dq6rHMr6wk5mdKGgp6Xp095AhJpUCxugGBGvYLPX1pMcG_CvEKIY" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kW8TxYJG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/S6bwh01v6y1l4dH7l_lmm80jUmNBkuwvt6jp7LzjeoDhJW0aZmoQoRqo1uKobvJgoUHY8Y6nwaY7dxKeXXj7Dq6rHMr6wk5mdKGgp6Xp095AhJpUCxugGBGvYLPX1pMcG_CvEKIY" alt="Choosing a histogram color" width="786" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, it looks a lot better:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EN7DSL4U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/y7CDOe4YE3xHDwRqIZIa7X7LZ-zIVanmC4Lva1kOdRVtwMtTeVYqQH4mVxFFr57agI-I7XMUfUS551BX0RZNwqNqwBo79-pW4XOBqL2kzxaz2cA8HMgkWWNnrQIjOSYuHQkWTVRs" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EN7DSL4U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/y7CDOe4YE3xHDwRqIZIa7X7LZ-zIVanmC4Lva1kOdRVtwMtTeVYqQH4mVxFFr57agI-I7XMUfUS551BX0RZNwqNqwBo79-pW4XOBqL2kzxaz2cA8HMgkWWNnrQIjOSYuHQkWTVRs" alt="Result after changing color" width="880" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We aren’t done yet. &lt;/p&gt;

&lt;p&gt;It’s now time to stack the &lt;strong&gt;errors&lt;/strong&gt; on this Y-Axis as well.&lt;/p&gt;

&lt;p&gt;Collapse the current Y-Axis settings and add a new one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T41pQIQL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/r51mHHvUWrY1v-9hVHInp8LDH_eotdD_QTuAxxjdBj_I6fQOgnhaxzS3EWev3T7un2R58KCm2l__AuaJy8lyHyZ5531N0LhpXi7Z9QCwoNJJWy70CBlM5x3tvjr5AmdUhg4DQQur" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T41pQIQL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/r51mHHvUWrY1v-9hVHInp8LDH_eotdD_QTuAxxjdBj_I6fQOgnhaxzS3EWev3T7un2R58KCm2l__AuaJy8lyHyZ5531N0LhpXi7Z9QCwoNJJWy70CBlM5x3tvjr5AmdUhg4DQQur" alt="Adding the Y-Axis metric" width="724" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The setup for this Y-Axis is simpler. &lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Sum bucket&lt;/strong&gt; as an aggregation again and &lt;strong&gt;Terms&lt;/strong&gt; as an aggregation for the bucket. Choose &lt;strong&gt;errors.keyword&lt;/strong&gt; as the field to use. &lt;/p&gt;

&lt;p&gt;Keep the rest of the settings as-is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_2vhX9Xq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/YR8U2O3MnC_7OuoKsouugdvgKOFX0t89bJqPQ64_aKyYowMK-jfJrQ1ZWEyZrgXFW9v2js-Y4sYBUKEjIHBce888TZL0irSgoIxeBr83VQkttxTVL9GLheRrsCM4wh-M6K6J-AX9" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_2vhX9Xq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/YR8U2O3MnC_7OuoKsouugdvgKOFX0t89bJqPQ64_aKyYowMK-jfJrQ1ZWEyZrgXFW9v2js-Y4sYBUKEjIHBce888TZL0irSgoIxeBr83VQkttxTVL9GLheRrsCM4wh-M6K6J-AX9" alt="Y-Axis metric setup" width="600" height="730"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a custom label &lt;strong&gt;“Errors”&lt;/strong&gt;, change the color to red and here’s what you get:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9QCXBSJO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/0veByMCfwOGbMoc8g3KQ4eAUrb7qF63y5Wycud1lJ3WpWosPVqGmcbRdyr3GGEBa0wF61O2mvpW5SQFx1vRZE4y1xpcJ-Gu87AHVpDNsS_QdgJyfE-HSBUHLzK5VSSY9BvNv7UAL" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9QCXBSJO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/0veByMCfwOGbMoc8g3KQ4eAUrb7qF63y5Wycud1lJ3WpWosPVqGmcbRdyr3GGEBa0wF61O2mvpW5SQFx1vRZE4y1xpcJ-Gu87AHVpDNsS_QdgJyfE-HSBUHLzK5VSSY9BvNv7UAL" alt="Success to error ratio" width="880" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the success-to-error ratio, aggregated per 30s.&lt;/p&gt;

&lt;p&gt;This view will now enable you to see the % of errors for all endpoints with a single glance.&lt;/p&gt;

&lt;p&gt;You could also zoom in on the data again and see the % of errors per endpoint/payment method/userType/etc.&lt;/p&gt;

&lt;p&gt;Save this view, add it to your dashboard, arrange it nicely at the top and gaze at your beautiful application dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--elzbDRCM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/ul1DY26Rc2lzZMT6um6-fKpJPiHj-8oJZ1zNSKFgh-6ry4qZgF3Q-rB1vOsb-BafHVsqXlo1n8BW-YgX6nPROxejdllPnai3cskLu_XNPpOsaMHUWI5idFsuBKw7EzTU8xUAx_vX" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--elzbDRCM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/ul1DY26Rc2lzZMT6um6-fKpJPiHj-8oJZ1zNSKFgh-6ry4qZgF3Q-rB1vOsb-BafHVsqXlo1n8BW-YgX6nPROxejdllPnai3cskLu_XNPpOsaMHUWI5idFsuBKw7EzTU8xUAx_vX" alt="Final dashboard" width="880" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Finale&lt;/h2&gt;

&lt;p&gt;Congratulations. 👏👏👏&lt;/p&gt;

&lt;p&gt;You’ve successfully completed the tutorial. &lt;/p&gt;

&lt;p&gt;You should now have a decent practice using Kibana to create basic dashboards for your application.&lt;/p&gt;

&lt;p&gt;The final step of this exercise is to add such a dashboard for your real production application, buy a 146-inch monitor, attach it to the wall in your office, open your app’s dashboard, enter full-screen and enable &lt;strong&gt;Auto-refresh&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After you do this, all your colleagues from the other teams will stare at it enviously.&lt;/p&gt;

&lt;p&gt;But unless you know how to effectively use Kibana for debugging real production issues, that’s all the value you’ll get from it - a shiny dashboard to show-off with in the office.&lt;/p&gt;

&lt;p&gt;In the following tutorial, you’ll learn how to now capitalize on this dashboard and use it to instantly discover the root cause of production issues.&lt;/p&gt;

</description>
      <category>kibana</category>
      <category>elasticsearch</category>
      <category>monitoring</category>
      <category>microservices</category>
    </item>
    <item>
      <title>todocheck v0.2.0 is live!</title>
      <dc:creator>Preslav Mihaylov</dc:creator>
      <pubDate>Sat, 01 Aug 2020 17:43:56 +0000</pubDate>
      <link>https://dev.to/pmihaylov/todocheck-v0-2-0-is-live-1he7</link>
      <guid>https://dev.to/pmihaylov/todocheck-v0-2-0-is-live-1he7</guid>
      <description>&lt;p&gt;The next release of &lt;a href="https://github.com/preslavmihaylov/todocheck"&gt;todocheck&lt;/a&gt; - the tool that helps you track &amp;amp; keep TODOs actionable is live!&lt;/p&gt;

&lt;p&gt;First time you hear about it? - &lt;a href="https://pmihaylov.com/todocheck/"&gt;check this out first&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In version 0.2.0, the main focus was extending support to new programming languages &amp;amp; issue trackers.&lt;/p&gt;

&lt;p&gt;Hence, there are now five new languages supported - R, PHP, Rust, Swift &amp;amp; Groovy.&lt;/p&gt;

&lt;p&gt;Support has been also provided for two new issue trackers - &lt;a href="https://www.pivotaltracker.com/dashboard"&gt;Pivotal Tracker&lt;/a&gt; &amp;amp; &lt;a href="https://redmine.org/"&gt;Redmine&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Additionally, one useful new feature is that todocheck now supports passing in your issue tracker authentication token via an environment variable - this will make it a lot easier to integrate the tool in your CI environment!&lt;/p&gt;

&lt;p&gt;Finally, you can now specify todocheck's output to be in JSON format. This provides the opportunity to create IDE plugins or include support for todocheck into linter aggregators.&lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://github.com/preslavmihaylov/todocheck/releases/tag/v0.2.0"&gt;full changelog here&lt;/a&gt; &amp;amp; don't forget to update your binary to the latest release!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>go</category>
      <category>todocheck</category>
      <category>linter</category>
    </item>
    <item>
      <title>Eliminate the undocumented TODOs with todocheck</title>
      <dc:creator>Preslav Mihaylov</dc:creator>
      <pubDate>Sun, 19 Jul 2020 17:47:16 +0000</pubDate>
      <link>https://dev.to/pmihaylov/eliminate-the-undocumented-todos-with-todocheck-2a16</link>
      <guid>https://dev.to/pmihaylov/eliminate-the-undocumented-todos-with-todocheck-2a16</guid>
      <description>&lt;p&gt;Yesterday, I released &lt;a href="https://github.com/preslavmihaylov/todocheck"&gt;todocheck&lt;/a&gt; - a new kind of static code analyser for annotated TODOs.&lt;/p&gt;

&lt;p&gt;Way too often, we let leftover TODOs slip into our main branch, which leaves your coworkers puzzles, looking at it a year from now. &lt;/p&gt;

&lt;p&gt;They're thinking - what did I mean by "TODO: Move this to the users package"? What is the users package? It doesn't seem to exist anymore.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/preslavmihaylov/todocheck"&gt;todocheck&lt;/a&gt; helps you fix this by forcing you to mark all your TODOs against an existing, open issue in your issue tracker.&lt;/p&gt;

&lt;p&gt;That way, if you, at some point, close the issue, thinking you're done, the CI pipeline will sparkle in red as there is an open, unaddressed TODO in your main branch.&lt;/p&gt;

&lt;p&gt;No longer can developers close a half-baked issue, rushing for the weekly sprint review to say "I'm done!".&lt;/p&gt;

&lt;h2&gt;How todocheck works&lt;/h2&gt;

&lt;p&gt;You run &lt;a href="https://github.com/preslavmihaylov/todocheck"&gt;todocheck&lt;/a&gt; as a standalone binary from the root of your project and it looks for issues with the following format:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// TODO J123: Fix this typo
func fuu() {
    ...
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In case the linked issue &lt;code&gt;J123&lt;/code&gt; is open, &lt;code&gt;todocheck&lt;/code&gt; will not report any error. In case you close the issue or it doesn't exist, todocheck will show an error:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ERROR: Issue is closed.
myproject/main.go:12: // TODO J123: Fix this typo

ERROR: Issue doesn't exist.
myproject/main.go:14: // TODO J321: A non-existent issue&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If there is an unannotated &lt;code&gt;TODO&lt;/code&gt; in your code base, todocheck will also report it as a malformed &lt;code&gt;TODO&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ERROR: Malformed todo.
myproject/main.go:16: // TODO - This is not a valid annotated todo&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Only &lt;code&gt;TODO&lt;/code&gt;s with valid, open issues are allowed to exist in the codebase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/preslavmihaylov/todocheck/blob/master/images/todocheck-demo.gif" rel="noreferrer noopener"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ilu3cQKt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://github.com/preslavmihaylov/todocheck/raw/master/images/todocheck-demo.gif" alt="todocheck demo gif" width="600" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By integrating todocheck in your development workflow &amp;amp; CI pipeline, you can ensure that there will be no half-baked issue closed with pending &lt;code&gt;TODO&lt;/code&gt;s in the codebase.&lt;/p&gt;

&lt;h2&gt;Does todocheck work with my language?&lt;/h2&gt;

&lt;p&gt;You can see the list of supported programming languages &lt;a href="https://github.com/preslavmihaylov/todocheck#supported-programming-languages"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is also support for various public &amp;amp; private issue trackers. See the list &lt;a href="https://github.com/preslavmihaylov/todocheck#supported-issue-trackers"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But your favorite language/issue tracker is not in the list?&lt;/p&gt;

&lt;p&gt;Then feel free to &lt;a href="https://github.com/preslavmihaylov/todocheck/issues/new"&gt;open an issue&lt;/a&gt; &amp;amp; describe your needs. Oftentimes, adding support for new languages/issue trackers is a matter of configuration, so you might see your tool in the above lists pretty soon. Here is an &lt;a href="https://github.com/preslavmihaylov/todocheck/issues/1"&gt;example&lt;/a&gt; of such a request.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;If you want to learn more about included features &amp;amp; configurations, consult the project's &lt;a href="https://github.com/preslavmihaylov/todocheck/blob/master/README.md"&gt;README&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Why don't you give todocheck a try right now? Download the &lt;a href="https://github.com/preslavmihaylov/todocheck/releases/latest"&gt;latest release&lt;/a&gt; &amp;amp; check out the &lt;a href="https://github.com/preslavmihaylov/todocheck#quickstart"&gt;Quickstart&lt;/a&gt; section to get started in no-time.&lt;/p&gt;

&lt;p&gt;Like the project? Show some love by ⭐-ing it on &lt;a href="https://github.com/preslavmihaylov/todocheck"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>todocheck</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>go</category>
    </item>
    <item>
      <title>A Concise Guide to the Latest Go Generics Draft Design</title>
      <dc:creator>Preslav Mihaylov</dc:creator>
      <pubDate>Thu, 18 Jun 2020 13:35:23 +0000</pubDate>
      <link>https://dev.to/pmihaylov/a-concise-guide-to-the-latest-go-generics-draft-design-30b</link>
      <guid>https://dev.to/pmihaylov/a-concise-guide-to-the-latest-go-generics-draft-design-30b</guid>
      <description>&lt;p&gt;Recently, the Go team announced an &lt;a href="https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md"&gt;updated draft design&lt;/a&gt; for their Generics in Go proposal. It goes into a lot of details about why certain decisions were made, implementation details, etc.&lt;/p&gt;

&lt;p&gt;In this article, my goal is to summarise the major upcoming changes, as the whole draft design can be a mouthful for many.&lt;/p&gt;

&lt;p&gt; I will provide some code snippets to demonstrate the major features as well as give you the chance to experiment yourself with them, thanks to the &lt;a href="https://go2goplay.golang.org/"&gt;new Go playground&lt;/a&gt; with support for generics.&lt;/p&gt;

&lt;h2&gt;Type Constraints in Generic Functions&lt;/h2&gt;

&lt;p&gt;Here's how a generic function looks like without any constraints on the type parameter:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://go2goplay.golang.org/p/76fyt-j5jGo"&gt;Try it out!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add constraints on the generic type, you can demand that it implements a given interface:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://go2goplay.golang.org/p/1PdVsq036H8"&gt;Try it out!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To specify multiple type parameters, separate them by commas:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://go2goplay.golang.org/p/dVJxy0tM1NC"&gt;Try it out!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Constraints on multiple type parameters are written the same way you write function argument types:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://go2goplay.golang.org/p/FwkEp2spInY"&gt;Try it out!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's how to specify different types for both parameters:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://go2goplay.golang.org/p/av_-VYA0-Rl"&gt;Try it out!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Type Lists &amp;amp; comparable&lt;/h2&gt;

&lt;p&gt;Instead of constraining types based on a set of methods, you can constraint them based on a set of supported types. For example, you could specify that you accept a generic type which can only be an int or a long.&lt;/p&gt;

&lt;p&gt;This allows you to e.g. leverage operators like "less than", "greater than", which are only available for basic types in Go:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://go2goplay.golang.org/p/W9c6vDaH7HJ"&gt;Try it out!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You also have an out-of-the-box constraint called &lt;strong&gt;comparable&lt;/strong&gt;, which constraints types to those which support the == and != operators.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://go2goplay.golang.org/p/bcjlPyFxf8M"&gt;Try it out!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Interfaces using these constraints - type lists and/or comparable, cannot be implemented by a struct. &lt;/p&gt;

&lt;p&gt;They can only be used for type constraint definitions.&lt;/p&gt;

&lt;h2&gt;Generic Types&lt;/h2&gt;

&lt;p&gt;Structs can be defined using a generic type. Once specified, in the type declaration, there is no need to specify the type for all the functions of the type:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://go2goplay.golang.org/p/34O9oLiHS_d"&gt;Try it out!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also do that in interfaces. This is especially useful when a type constraint depends on itself. &lt;/p&gt;

&lt;p&gt;E.g. you have a type constraint for &lt;strong&gt;T&lt;/strong&gt;, which requires an &lt;strong&gt;Equal&lt;/strong&gt; method, which accepts a &lt;strong&gt;T&lt;/strong&gt; parameter:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://go2goplay.golang.org/p/dTKRo-1Yeh3"&gt;Try it out!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In case you need to specify a type parameter, who has a state-modifying function (e.g. a setter), then you can specify a pointer type constraint:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://go2goplay.golang.org/p/oy4O_oYxRn8"&gt;Try it out!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice how this example above requires that you explicitly specify the type you'll use in the function - &lt;strong&gt;FromStrings(Settable)&lt;/strong&gt;...&lt;/p&gt;

&lt;p&gt;This is because whenever the type is not present as a function argument, the compiler cannot infer the actual type once the code is compiled. Hence, you need to explicitly specify it.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;This article was purposefully succinct and straightforward. Hopefully, it will help you quickly get up-to-date on the latest generics draft design, while you're drinking your morning coffee.&lt;/p&gt;

&lt;p&gt;However, there is a lot of rationale behind any single design choice around generics in Go. If you are interested to dive deeper into this subject, check out the &lt;a href="https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#type-parameters-draft-design"&gt;official draft design doc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, do you like the latest Go Generics draft design?&lt;/p&gt;

&lt;p&gt;Let me know in the comments section below.&lt;/p&gt;

</description>
      <category>go</category>
      <category>generics</category>
    </item>
    <item>
      <title>How to use Structured Logs in your Go Application</title>
      <dc:creator>Preslav Mihaylov</dc:creator>
      <pubDate>Sat, 13 Jun 2020 05:23:12 +0000</pubDate>
      <link>https://dev.to/pmihaylov/how-to-use-structured-logs-in-your-go-application-kd0</link>
      <guid>https://dev.to/pmihaylov/how-to-use-structured-logs-in-your-go-application-kd0</guid>
      <description>&lt;p&gt;The &lt;a href="https://www.elastic.co/elastic-stack"&gt;Elastic stack&lt;/a&gt; (also referred to as ELK) can bring a lot of value to your production services. But it is not that much of value if you don't use structured logs in your services.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://pmihaylov.com/analysing-logs-elk/"&gt;one of my latest posts&lt;/a&gt;, I wrote about what ELK is and why you should care. I also wrote a tutorial about &lt;a href="https://pmihaylov.com/go-service-with-elk/"&gt;how to integrate ELK with your Go app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this article, I will walk you through how to integrate structured logging in your Go services. We will use a sample HTTP service with a few basic endpoints and we'll use the &lt;a href="https://github.com/uber-go/zap"&gt;zap library&lt;/a&gt; to emit logs on error/success, which would also include some domain-specific info.&lt;/p&gt;



&lt;h2&gt;The Starting Point&lt;/h2&gt;

&lt;p&gt;Download this tutorial’s repository:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Currently, the repo has a &lt;strong&gt;main.go&lt;/strong&gt; file which has http handlers for three different endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/payments/execute&lt;/li&gt;
&lt;li&gt;/payments/list&lt;/li&gt;
&lt;li&gt;/payments/authhold&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The handlers themselves don’t do anything special, they simply return a &lt;strong&gt;status OK&lt;/strong&gt; when invoked.&lt;/p&gt;

&lt;p&gt;Additionally, you have a logging middleware which you’ll be working in.&lt;/p&gt;

&lt;p&gt;Currently, what it does is it wraps the standard &lt;strong&gt;http.ResponseWriter&lt;/strong&gt; into a &lt;strong&gt;statusWriter&lt;/strong&gt;, which records the returned http status code.&lt;/p&gt;

&lt;p&gt;We will use it to determine if the http response was successful or not.&lt;/p&gt;

&lt;h3&gt;Setting up the REST Client&lt;/h3&gt;

&lt;p&gt;In addition to the Go application, you have a file &lt;strong&gt;go-elk-exercise-reqs.json&lt;/strong&gt; in the branch’s root. It is an exported set of requests you can import in a REST client of your choice.&lt;/p&gt;

&lt;p&gt;The one I use, and recommend, is &lt;a href="https://insomnia.rest/"&gt;Insomnia&lt;/a&gt;. If you want to use that one, simply import the file in your insomnia workspace like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0GqlFSWC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/lNgfShNKmOTvGmAho_eTppgHsuj-wid7CRz22moC-wo7Qlv95E5vuxze8mE8NNzsR9-yNmBHvDPtjdC8kDQ4c_3y3CVsJH2fcNSknWoA6EXC_T4gPTpNoZPzc5Q_gRDBU64raY7H" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0GqlFSWC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/lNgfShNKmOTvGmAho_eTppgHsuj-wid7CRz22moC-wo7Qlv95E5vuxze8mE8NNzsR9-yNmBHvDPtjdC8kDQ4c_3y3CVsJH2fcNSknWoA6EXC_T4gPTpNoZPzc5Q_gRDBU64raY7H" alt="How to import your requests JSON file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, choose import data from file &amp;amp; import the file you’re provided:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SAARpK7L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/WRDiXp3LKRDVJJT-i2kzvFI5wVEuItfHIebaKNYUksv6wh8bbwpmt9Ee5l6yR2DuB8hryzKOPIQd7k_EaRNeGh2unE8-mUI0lpUjaPTWyfOwmQTyFQZLXdEJRydAclXol-mUlOSk" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SAARpK7L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/WRDiXp3LKRDVJJT-i2kzvFI5wVEuItfHIebaKNYUksv6wh8bbwpmt9Ee5l6yR2DuB8hryzKOPIQd7k_EaRNeGh2unE8-mUI0lpUjaPTWyfOwmQTyFQZLXdEJRydAclXol-mUlOSk" alt="How to import requests from a file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If everything was successful, you’ll get all necessary HTTP requests imported in you workspace with all necessary data already setup (e.g. headers).&lt;/p&gt;

&lt;p&gt;Alternatively, if the import doesn’t work for your rest client, here is what the requests are, so that you can add them manually:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Execute Payment:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9UB_EPLT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/54GhOM9cr2M3iLYPTqh6e8VI4L1etzVZlvMcNt_JDlxh_y7Khzh52SY3LvekpOoZMLy3KJalaviw_jRZj17IqUPxAlpAYHByintnmi1CYmPanrkDkc8KVrXCPpj9GErpGqLNd6-r" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9UB_EPLT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/54GhOM9cr2M3iLYPTqh6e8VI4L1etzVZlvMcNt_JDlxh_y7Khzh52SY3LvekpOoZMLy3KJalaviw_jRZj17IqUPxAlpAYHByintnmi1CYmPanrkDkc8KVrXCPpj9GErpGqLNd6-r" alt="payments/execute endpoint parameters"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;List Payments:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X4oA4MgD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/rsPa0k_dVQAKBdRGoahI5hBPTy3IhYXO7ezBfpYz7sgM7YDf84t8hdfz4nldcC6ANX32g04gO3sXlaeHSvlswEmAt6INAnk_lat1C0iT0PkZU5V6TJV9P5ZPNT0HNgrbNQAEBdne" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X4oA4MgD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/rsPa0k_dVQAKBdRGoahI5hBPTy3IhYXO7ezBfpYz7sgM7YDf84t8hdfz4nldcC6ANX32g04gO3sXlaeHSvlswEmAt6INAnk_lat1C0iT0PkZU5V6TJV9P5ZPNT0HNgrbNQAEBdne" alt="payments/list endpoint parameters"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auth Hold:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--S8AwTLR1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/55Q3xbTUa6ofrBZ-J0aw9ZdEnj7w8ky9kdvaUe0YPfLRL70kL6cU4G5hOEGqIGbfzrGB1XALvlZGLY4tUHFRlLiERV7dHgSuwdG9Y5RgfQ0AfeIdapa6qwNUJElJqZxQqy5DaFAP" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--S8AwTLR1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/55Q3xbTUa6ofrBZ-J0aw9ZdEnj7w8ky9kdvaUe0YPfLRL70kL6cU4G5hOEGqIGbfzrGB1XALvlZGLY4tUHFRlLiERV7dHgSuwdG9Y5RgfQ0AfeIdapa6qwNUJElJqZxQqy5DaFAP" alt="payments/authhold endpoint parameters"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;What We'll be Doing&lt;/h3&gt;

&lt;p&gt;In this tutorial, we'll complete the loggingMiddleware function by emitting structured logs on success (status code is less than 400) or error (status code is greater than or equal to 400).&lt;/p&gt;

&lt;p&gt;The structured logs should contain the following keys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;endpoint&lt;/strong&gt; - the invoked endpoint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;method&lt;/strong&gt; - the HTTP method used&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;countryISO2, userID, paymentMethod, userType&lt;/strong&gt; - domain-specific keys for this application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In case of a success, we'll write &lt;strong&gt;“Inbound request succeeded”&lt;/strong&gt; via &lt;strong&gt;logger.Info&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In case of a bad status code, we'll write &lt;strong&gt;“Inbound request failed with status [returned status code]” &lt;/strong&gt;via &lt;strong&gt;logger.Error &lt;/strong&gt;in the default&lt;strong&gt; error key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is an example log line we'll emit by the end of the tutorial:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In the following section, I will walk you through how to achieve this step by step.&lt;/p&gt;

&lt;h2&gt;The Walkthrough&lt;/h2&gt;

&lt;p&gt;In this section, we will go through the application &amp;amp; implement structured logging step by step.&lt;/p&gt;

&lt;p&gt;A noteworthy thing to notice is how we are plugging in logging as a middleware. &lt;/p&gt;

&lt;p&gt;This makes the developers writing the domain-specific logic unaware of the implementation details of how their http invocations are logged.&lt;/p&gt;

&lt;p&gt;Additionally, it allows us to reuse the logging middleware on each endpoint we want instrumented, rather than copy-pasting the code in each handler.&lt;/p&gt;

&lt;p&gt;And finally, this application is kept simple on purpose. In a real application, you might want to include this middleware as a &lt;a href="https://pmihaylov.com/shared-components-go-microservices/"&gt;Fx module&lt;/a&gt; which you include as part of your baseline Fx modules for each of your microservices.&lt;/p&gt;

&lt;p&gt;This gives all your microservices some baseline logging capabilities out of the box.&lt;/p&gt;

&lt;p&gt;Let’s get started.&lt;/p&gt;

&lt;h3&gt;Log on success/failure&lt;/h3&gt;

&lt;p&gt;We’ll start by simply emitting log messages on each successful or failed http call.&lt;/p&gt;

&lt;p&gt;First, create an empty &lt;strong&gt;zap.Field&lt;/strong&gt; array:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We’ll use this array in the later steps to populate it with the specific keys we want to emit in our logs.&lt;/p&gt;

&lt;p&gt;Next, use &lt;strong&gt;logger.Info&lt;/strong&gt; and &lt;strong&gt;logger.Error&lt;/strong&gt; in the appropriate spots with their respective success/error messages. Also include all logging fields which are currently empty, but will be filled in in the next steps.&lt;/p&gt;

&lt;p&gt;We also encapsulate the error message in a &lt;strong&gt;zap.Error&lt;/strong&gt; field. This will propagate the error message in a separate &lt;strong&gt;error&lt;/strong&gt; field and will also include the stack trace.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Test that everything works correctly by running your application using&lt;br&gt;&lt;strong&gt;go run main.go&lt;/strong&gt; and sending an http request from your rest client.&lt;/p&gt;

&lt;p&gt;If everything went well, here is a sample log line you should see on your screen:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And here’s how an error log should look like: &lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Test this by returning an &lt;strong&gt;http.StatusBadRequest&lt;/strong&gt; in any of the endpoints and invoking it.&lt;/p&gt;

&lt;p&gt;These logs will also be available in &lt;strong&gt;logs/go.log&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let’s move on with adding the rest of the keys we’ll need.&lt;/p&gt;

&lt;h3&gt;Include the endpoint and method in your logs&lt;/h3&gt;

&lt;p&gt;To include the invoked endpoint and the HTTP method in the structured logs, you will have to take them from the &lt;strong&gt;*http.Request&lt;/strong&gt; object.&lt;/p&gt;

&lt;p&gt;Add the respective fields in the &lt;strong&gt;fields&lt;/strong&gt; array we introduced previously:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Make a similar test as in the previous section and see that your logs now include the endpoint and http method:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Your error logs should also include the new keys.&lt;/p&gt;

&lt;h3&gt;Include the domain-specific fields in the logs&lt;/h3&gt;

&lt;p&gt;This step is pretty similar to the previous one and is hence, straightforward.&lt;/p&gt;

&lt;p&gt;Here, we extract the headers from the http request and add them to our logs:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;After you add this, we should be able to see the log as expected from the beginning of this exercise:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;If you got this far, well done!&lt;/p&gt;

&lt;p&gt;You should now have a good grasp of what structured logs are and how to use them in your Go services.&lt;/p&gt;

&lt;p&gt;If you want to learn more about how to leverage it, check out some of the other articles I've linked at the beginning of the post.&lt;/p&gt;

&lt;p&gt;Stay tuned for more upcoming Go/Microservice tutorials!&lt;/p&gt;

</description>
      <category>go</category>
      <category>logging</category>
      <category>elk</category>
      <category>microservices</category>
    </item>
    <item>
      <title>How to integrate your Go service with ELK</title>
      <dc:creator>Preslav Mihaylov</dc:creator>
      <pubDate>Sat, 16 May 2020 07:03:24 +0000</pubDate>
      <link>https://dev.to/pmihaylov/how-to-integrate-your-go-service-with-elk-4hm5</link>
      <guid>https://dev.to/pmihaylov/how-to-integrate-your-go-service-with-elk-4hm5</guid>
      <description>&lt;p&gt;In my &lt;a href="https://pmihaylov.com/analysing-logs-elk/"&gt;last post&lt;/a&gt;, I shared how much value the ELK stack could bring for your application in terms of the monitoring capabilities it gives you.&lt;/p&gt;

&lt;p&gt;In this post, I will walk you through how to integrate your Go application with ELK, what are the different parts of ELK, how they work and how to create a basic configuration for them.&lt;/p&gt;

&lt;p&gt;Let's jump straight in, shall we?&lt;/p&gt;

&lt;h2&gt;The Starting Point&lt;/h2&gt;

&lt;p&gt;First, make sure you have &lt;strong&gt;docker&lt;/strong&gt; and &lt;strong&gt;docker-compose&lt;/strong&gt; installed.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;docker&lt;/strong&gt;, follow the installation instructions for your platform &lt;a href="https://docs.docker.com/get-docker/"&gt;here&lt;/a&gt;.&lt;br&gt;For &lt;strong&gt;docker-compose&lt;/strong&gt;, follow the installation instructions &lt;a href="https://docs.docker.com/compose/install/"&gt;here&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Currently, the repo simply has a &lt;strong&gt;main.go&lt;/strong&gt; file which logs random log lines on certain intervals. Occasionally, it propagates error log lines.&lt;/p&gt;

&lt;p&gt;We will use this as a backbone to integrate the &lt;strong&gt;go app&lt;/strong&gt; with ELK.&lt;/p&gt;

&lt;h2&gt;Setting Up the ELK Stack&lt;/h2&gt;

&lt;p&gt;ELK stands for &lt;strong&gt;Elasticsearch&lt;/strong&gt;, &lt;strong&gt;Logstash&lt;/strong&gt; and &lt;strong&gt;Kibana&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These are three different tools, created by the &lt;a href="https://www.elastic.co/"&gt;elastic company&lt;/a&gt;, which are typically used together to enable developers &amp;amp; sys admins to monitor production systems based on application or system logs.&lt;/p&gt;

&lt;p&gt;There is one additional component the ELK stack, which is &lt;strong&gt;Beats&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Beats is a collection of small programs which are fine-tuned to gather different kinds of logging data - e.g. from files, from OS system logs, from Windows event logs, etc.&lt;/p&gt;

&lt;p&gt;Since ELBK doesn’t sound as fancy as ELK, we still refer to the stack as the ELK stack.&lt;/p&gt;

&lt;p&gt;In the following sections, we will explore each of these components separately &amp;amp; install them accordingly.&lt;/p&gt;

&lt;h3&gt;What is Elasticsearch?&lt;/h3&gt;

&lt;p&gt;Elasticsearch is a NoSQL database which is optimized for storing &lt;strong&gt;structured documents&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This database also has a REST API in order to easily interact with it.&lt;/p&gt;

&lt;p&gt;A structured document is one which structures its metadata into JSON format.&lt;/p&gt;

&lt;p&gt;This can be any JSON-serialized object. This can be used, for example, to create a user-facing search bar in your application.&lt;/p&gt;

&lt;p&gt;Instead of doing manual &amp;amp; lame string processing of search terms, you can outsource this to elasticsearch which will do a much better job than both of us.&lt;/p&gt;

&lt;p&gt;However, in our use-case, we will use Elasticsearch for storing &lt;strong&gt;structured logs&lt;/strong&gt;. A structured log is one, which organizes its contents into a JSON object.&lt;/p&gt;

&lt;p&gt;This is an example of an unstructured log:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This is an example of a structured log:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Using a structured log, you can later index into these keys and analyse bulks of your logs fast &amp;amp; seamlessly. This is exactly what the ELK stack provides.&lt;/p&gt;

&lt;p&gt;Elasticsearch, in particular, will store this log line in a way optimized for search &amp;amp; analysis later.&lt;/p&gt;

&lt;h3&gt;Setting up Elasticsearch&lt;/h3&gt;

&lt;p&gt;To install elasticsearch, first create a &lt;code&gt;docker-compose.yml&lt;/code&gt; file in the root of the repo and add this initial yaml:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Let’s break this config down.&lt;/p&gt;

&lt;p&gt;In docker-compose, we enumerate the services we want to bring up in this manner:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In this case, we only bring up a single service which is called &lt;strong&gt;elasticsearch&lt;/strong&gt;. You can choose any name you like, but this one is the most descriptive, given what it does.&lt;/p&gt;

&lt;p&gt;Let’s now interpret the elasticsearch service config line by line.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This means that we are pulling an already setup docker image with the given version.&lt;/p&gt;

&lt;p&gt;This enables us to not bother with setting up an image from scratch. Someone has already provided that out of the box, so we are reusing it.&lt;/p&gt;

&lt;p&gt;Otherwise, you will have to bother with setting up the environment, installing dependencies, configuring them, installing elasticsearch manually, etc.&lt;/p&gt;

&lt;p&gt;With this line, we are getting all this out of the box.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;With this line, we are indicating that the local directory &lt;code&gt;./config/elasticsearch.yml&lt;/code&gt; will be mounted on &lt;code&gt;/usr/share/elasticsearch/…&lt;/code&gt; in the container and it will be read-only (indicated by the &lt;code&gt;ro&lt;/code&gt; flag).&lt;/p&gt;

&lt;p&gt;The designated folder is where the elasticsearch default config directory resides.&lt;/p&gt;

&lt;p&gt;The file &lt;code&gt;./config/elasticsearch.yml&lt;/code&gt; is not created yet, but we’ll create it soon.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This part designates that we will make health checks on the container by running the command: &lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The command will run periodically, every 3s with a 3s timeout. &lt;/p&gt;

&lt;p&gt;We will do 10 retries in total and if all fail, docker marks this container as &lt;code&gt;unhealthy&lt;/code&gt; and notifies dependent containers.&lt;/p&gt;

&lt;p&gt;The final part of the configuration maps the container’s internal &lt;code&gt;9200 port&lt;/code&gt; to our real &lt;code&gt;9200 port&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This is all we need to add to &lt;code&gt;docker-compose.yml&lt;/code&gt; for elasticsearch. Finally, add this configuration in &lt;code&gt;./config/elasticsearch.yml&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;What this configuration means is that we will use &lt;code&gt;single-node discovery&lt;/code&gt;&lt;strong&gt; &lt;/strong&gt;mode.&lt;/p&gt;

&lt;p&gt;Typically, elasticsearch runs in a cluster of nodes with masters &amp;amp; slaves in them which have to be configured.&lt;/p&gt;

&lt;p&gt;To keep things simple &amp;amp; digestible for this exercise, we will use a single elasticsearch node.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;network.host&lt;/code&gt; option indicates that elasticsearch can bind to any network interface.&lt;/p&gt;

&lt;h3&gt;Starting Elasticsearch&lt;/h3&gt;

&lt;p&gt;To boot up our new container, run &lt;code&gt;docker-compose up&lt;/code&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If all went well, you should start seeing the elasticsearch logs after a while and the container should not show any error lines or anything pointing to an issue:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---LQLmhM4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/YlB34MO1fHfATM61oP4zNwWrhyqh_9LACK7qcbKWLX8mmUyhb0Et5l4TVpTtj3_yW7g57dZSO-1vjABHYHrEFWGXIX8FTQp9dlfx-AufKwUxZzvMph6rsA7OaWMFZgiv1vXtQhS7" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---LQLmhM4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/YlB34MO1fHfATM61oP4zNwWrhyqh_9LACK7qcbKWLX8mmUyhb0Et5l4TVpTtj3_yW7g57dZSO-1vjABHYHrEFWGXIX8FTQp9dlfx-AufKwUxZzvMph6rsA7OaWMFZgiv1vXtQhS7" alt="" width="880" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, have in mind that there might be some issues which might pop-up.&lt;/p&gt;

&lt;p&gt;Here is a reference to common issues on elasticsearch startup:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/laradock/laradock/issues/1699"&gt;elasticsearch exit code 78&lt;/a&gt;&lt;br&gt;&lt;a href="https://github.com/10up/wp-local-docker/issues/6"&gt;elasticsearch exit code 137&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If all went went, you can continue with the next component in our stack - &lt;strong&gt;logstash&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;What is Logstash?&lt;/h3&gt;

&lt;p&gt;Logstash is a component which stands between elasticsearch &amp;amp; applications and does several things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filtering input documents based on some criteria (e.g. ignore all &lt;strong&gt;DEBUG &lt;/strong&gt;log lines)&lt;/li&gt;
&lt;li&gt;Adding extra metadata to the input log lines (e.g. ip, location info, timestamp, etc.)&lt;/li&gt;
&lt;li&gt;Sanitizing the log lines if needed (e.g. turning unstructured logs into structured ones)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In reality, you can interact with elasticsearch directly, but logstash is typically used as a helpful intermediary to save yourself some work on the application side.&lt;/p&gt;

&lt;h3&gt;Setting up Logstash&lt;/h3&gt;

&lt;p&gt;Add another service to your &lt;strong&gt;docker-compose.yml&lt;/strong&gt; file:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This config section is simpler than the previous one. &lt;/p&gt;

&lt;p&gt;First, we use the standard logstash docker image as a baseline.&lt;/p&gt;

&lt;p&gt;As before, we are mounting the &lt;code&gt;./config/logstash.conf&lt;/code&gt; file into &lt;strong&gt;logstash&lt;/strong&gt;’s default config location.&lt;/p&gt;

&lt;p&gt;Finally, the &lt;strong&gt;depends_on&lt;/strong&gt; section indicates that this service should only boot up when the elasticsearch service is up and healthy.&lt;/p&gt;

&lt;p&gt;Otherwise, the container won’t start at all.&lt;/p&gt;

&lt;p&gt;Next up, add this logstash configuration in &lt;code&gt;./config/logstash.conf&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The logstash configuration typically has three sections - &lt;strong&gt;input&lt;/strong&gt;, &lt;strong&gt;filter&lt;/strong&gt; &amp;amp; &lt;strong&gt;output&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;input&lt;/strong&gt; section specifies where logstash accepts data from. It has a built-in plugin called &lt;strong&gt;beats&lt;/strong&gt;, which automatically integrates with beats applications, such as filebeat, which we’ll use later.&lt;/p&gt;

&lt;p&gt;Here, we indicate that we’ll accept input from beats applications on &lt;code&gt;port 5044&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;filter&lt;/code&gt; section, you can add any custom filters or mappings (e.g. transforming one input field to another in a custom format).&lt;/p&gt;

&lt;p&gt;Here, we only specify that the source structured log will come from the &lt;strong&gt;message&lt;/strong&gt; field, which is the default key where &lt;strong&gt;filebeat&lt;/strong&gt; stores the actual application log.&lt;/p&gt;

&lt;p&gt;This will enable us to index the custom application keys in our log, not only the ones plugged in by &lt;strong&gt;filebeat&lt;/strong&gt; and/or &lt;strong&gt;logstash&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The final &lt;strong&gt;output&lt;/strong&gt; section specifies where the filtered logs should be forwarded to.&lt;/p&gt;

&lt;p&gt;It can by default connect to &lt;strong&gt;elasticsearch&lt;/strong&gt; (as we’ve done here) and integrate with it via its &lt;code&gt;default port 9200&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you change &lt;strong&gt;elasticsearch&lt;/strong&gt;’s default port to something else, you will have to modify this section as well.&lt;/p&gt;

&lt;p&gt;Now run both elasticsearch and logstash and make sure they both boot up without any issues:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;If everything goes well, your output should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Dw-QC7dH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/YO80LRNEi9q-kJRQ5QfUj-F6_3Z2LhewJyHXtA9LMosz34Xok_FGStrOWqsHdbEGPSge-A_Lw_hA-sj6aUcVIYLKCZT6dU2DFxT9-UWDUdV_Bib1-aCzvr0VbMup1QfLoEAkpMJJ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Dw-QC7dH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/YO80LRNEi9q-kJRQ5QfUj-F6_3Z2LhewJyHXtA9LMosz34Xok_FGStrOWqsHdbEGPSge-A_Lw_hA-sj6aUcVIYLKCZT6dU2DFxT9-UWDUdV_Bib1-aCzvr0VbMup1QfLoEAkpMJJ" alt="" width="880" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;What is filebeat?&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Filebeat&lt;/strong&gt; is a beats application (there are more), which is responsible for monitoring changes in log files and forwarding them to logstash.&lt;/p&gt;

&lt;p&gt;Without filebeat, you would have to manage your application’s integration with &lt;strong&gt;logstash&lt;/strong&gt; yourself.&lt;/p&gt;

&lt;p&gt;The way it works is - you specify a path to your logs which it should monitor &amp;amp; it will handle propagating those logs on regular intervals to logstash.&lt;/p&gt;

&lt;h3&gt;Setting up filebeat &amp;amp; your Go application&lt;/h3&gt;

&lt;p&gt;In this section, we will add filebeat &amp;amp; the Go application as docker containers.&lt;/p&gt;

&lt;p&gt;Add this to your &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The bulk of the new sections are pretty similar to what we already did for the rest of our services.&lt;/p&gt;

&lt;p&gt;Both new services depend on &lt;strong&gt;elasticsearch&lt;/strong&gt; being healthy, both depend on their respective baseline docker images.&lt;/p&gt;

&lt;p&gt;The differences are in the &lt;code&gt;volumes&lt;/code&gt; sections.&lt;/p&gt;

&lt;p&gt;For our go app, we’ve mounted the current directory in a subdirectory of the container’s &lt;code&gt;GOPATH&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The logs, on the other hand, have been mounted in both containers. &lt;/p&gt;

&lt;p&gt;Our Go application will use that directory to output its logging info.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;filebeat&lt;/strong&gt; service will use it to monitor changes in the logging info.&lt;/p&gt;

&lt;p&gt;In addition to that, we’ve mounted the &lt;strong&gt;filebeat&lt;/strong&gt; configuration in the standard config directory on the container.&lt;/p&gt;

&lt;p&gt;For our Go app, we’ve also specified that once the container is setup, we’ll run our application via the command:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Now, add this to &lt;code&gt;config/filebeat.yml&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This configuration specifies that the service will listen for changes to all log files in &lt;code&gt;/logs&lt;/code&gt;. Additionally, It will output any new logging info to logstash via &lt;code&gt;port 5044&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We also whitelisted that port in our &lt;strong&gt;logstash&lt;/strong&gt; service config for inputs from beats applications.&lt;/p&gt;

&lt;h3&gt;What is Kibana?&lt;/h3&gt;

&lt;p&gt;The final piece of the setup is &lt;strong&gt;Kibana&lt;/strong&gt;. It is a front-end for analysing the data stored in Elasticsearch.&lt;/p&gt;

&lt;p&gt;With it, you can visualize a huge array of data via graphs, pie charts, data tables, etc, in order to monitor what’s happening in your application via its logs.&lt;/p&gt;

&lt;p&gt;You will see this in action in the final section of this exercise.&lt;/p&gt;

&lt;h3&gt;Setting up Kibana&lt;/h3&gt;

&lt;p&gt;Add this final piece of config to your &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;There is nothing new in this configuration section.&lt;/p&gt;

&lt;p&gt;It installs the baseline docker image, depends on &lt;strong&gt;elasticsearch&lt;/strong&gt;, has a health check of its own and maps its internal &lt;code&gt;port 5601&lt;/code&gt; to the machine’s real &lt;code&gt;port 5601&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In addition to this, you can, optionally, add a dependency of your golang application on &lt;code&gt;kibana&lt;/code&gt; to make sure your entire container stack boots up only after &lt;strong&gt;Kibana&lt;/strong&gt;’s server is ready:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Now, test that everything works properly by running &lt;code&gt;docker-compose up&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, you should be able to go to &lt;a href="http://localhost:5601/app/kibana"&gt;http://localhost:5601/app/kibana&lt;/a&gt; and see Kibana’s home page load.&lt;/p&gt;

&lt;p&gt;If you didn’t do that last optional step, you might have to wait awhile before Kibana boots up:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2nL4nc_s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/D4sh4ur4mJd4uYF6vGW-WZ_0mc421476ZxxcXmkEmaBxaqHtTTzdBUJMNUo_lDbEPsJDVpaxGzZmxvzRVwBVjrhy_vkinzLi1-aolBNbP0PFcX5kMWHRTTsvoP2kQ3LGRy5f72ui" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2nL4nc_s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/D4sh4ur4mJd4uYF6vGW-WZ_0mc421476ZxxcXmkEmaBxaqHtTTzdBUJMNUo_lDbEPsJDVpaxGzZmxvzRVwBVjrhy_vkinzLi1-aolBNbP0PFcX5kMWHRTTsvoP2kQ3LGRy5f72ui" alt="The Kibana landing page" width="880" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you instead get asked for defining an Index Pattern, use&lt;strong&gt; &lt;/strong&gt;&lt;code&gt;logstash-*&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qlpRi3Hq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/jd2TkhUMhbVF4MdF4vTZRzACFYMMpjKoQV5miX3WKXh6v_hOgoS8PQQEkv_cQ5GUPXAVaAtjH4upk2JZxxl8x48EDAssC8HMv31Jh1QH7i1rxEkKIKtMJZLea-gbpuAaAKNQscpO" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qlpRi3Hq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/jd2TkhUMhbVF4MdF4vTZRzACFYMMpjKoQV5miX3WKXh6v_hOgoS8PQQEkv_cQ5GUPXAVaAtjH4upk2JZxxl8x48EDAssC8HMv31Jh1QH7i1rxEkKIKtMJZLea-gbpuAaAKNQscpO" alt="selecting the index pattern in Kibana" width="880" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next step, select &lt;code&gt;@timestamp&lt;/code&gt; as a time filter:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DVz345o7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/weu-tHxwp7PAGeAfxtlNk0_7jW3ahArV5myDzqbSOHVJEm-ZRQkSg3i0XvwVbe8nvK7BadyOz1KeqJmkk_c3sSeB5w1PSTkIZ0YPX6fw821kDwzhtwKlw_XSz23yweCXJEVwFM23" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DVz345o7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/weu-tHxwp7PAGeAfxtlNk0_7jW3ahArV5myDzqbSOHVJEm-ZRQkSg3i0XvwVbe8nvK7BadyOz1KeqJmkk_c3sSeB5w1PSTkIZ0YPX6fw821kDwzhtwKlw_XSz23yweCXJEVwFM23" alt="selecting timestatmp as a time filter field" width="880" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This finishes the Kibana setup. &lt;/p&gt;

&lt;p&gt;In the final section, we will test that everything is integrated correctly by searching through our application logs via Kibana.&lt;/p&gt;

&lt;h3&gt;Search through your logs via Kibana&lt;/h3&gt;

&lt;p&gt;Open Kibana and expand your tabs first in the bottom-left corner of the site:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kIqhEvll--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/zm0q6TcgnWX0WQzhPsQS1AGb4V84E6rQnM-PiAS1xe6ZyAT1J-4EXNIJO8yKolI2qixwQGp3a9mR4Y7Xknbf4ntrbAMLiO1CV1UmyU2tofDh39qRhQAe_i3qd0LCY58vjnnUrdlJ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kIqhEvll--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/zm0q6TcgnWX0WQzhPsQS1AGb4V84E6rQnM-PiAS1xe6ZyAT1J-4EXNIJO8yKolI2qixwQGp3a9mR4Y7Xknbf4ntrbAMLiO1CV1UmyU2tofDh39qRhQAe_i3qd0LCY58vjnnUrdlJ" alt="The collapse button in Kibana" width="848" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next up, open the &lt;strong&gt;Discover&lt;/strong&gt; tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lYAvVd-Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/_LExO2XFAW6k8ONsrhdbGcEDN1ePGUgSTdUVg5u_zSujmwkWGVh0UPriHV4SHqPkjR54V2ckzqqzcu-WGSCYq64uG3dy8r1n7GBI1JCbXW3grFe04_SawU7WznTV8HDNiqDG-rPO" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lYAvVd-Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/_LExO2XFAW6k8ONsrhdbGcEDN1ePGUgSTdUVg5u_zSujmwkWGVh0UPriHV4SHqPkjR54V2ckzqqzcu-WGSCYq64uG3dy8r1n7GBI1JCbXW3grFe04_SawU7WznTV8HDNiqDG-rPO" alt="The discover tab button" width="880" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you open it, you should see all your logs here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n1wVXOQG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/v6nn7ACZ1CxNOHPBPyNU7FmJezHI_UK24BcgfeWX-otyDcjMigm8OXM5OafuQpo3AIUS0DJaBDnMwhlCH5LEAqxiOfMhBqUG3DKSjNAOUNk3lGaNSsop4Rr24-4L_j8e2tXHBbZa" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n1wVXOQG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/v6nn7ACZ1CxNOHPBPyNU7FmJezHI_UK24BcgfeWX-otyDcjMigm8OXM5OafuQpo3AIUS0DJaBDnMwhlCH5LEAqxiOfMhBqUG3DKSjNAOUNk3lGaNSsop4Rr24-4L_j8e2tXHBbZa" alt="viewing your logs in Kibana" width="880" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Filter only the application log fields on the left:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cu9yoMfS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/aNIxOiIQWuU_UXH_AGeKJtr1XYGvUunantI-SgKRkU52r0650s2wBrexCX9Atr9BmUDHEhqBbHnCprY6Ya9tc2qaO-Ovs52v57OYiUn9MbQpyxlSujYBMmEQ2dPvBy-1ToXjyiH3" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cu9yoMfS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/aNIxOiIQWuU_UXH_AGeKJtr1XYGvUunantI-SgKRkU52r0650s2wBrexCX9Atr9BmUDHEhqBbHnCprY6Ya9tc2qaO-Ovs52v57OYiUn9MbQpyxlSujYBMmEQ2dPvBy-1ToXjyiH3" alt="select the error and msg fields from your logs" width="592" height="894"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And voila, you should be able to see all your application logs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HFGQA2cr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/EptgT_xi43SLH6S0P0HXIJgGOurLTN6F9D486kDYMLEKJ-47qv6GWrbEJGdMtx5rcFppZFrhwp6H_tS-A3o_Sw6azxFVWCicbgZKvhGsdDMwEzjWVOWhHuW0y1n-Lpg-57oHgXGS" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HFGQA2cr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/EptgT_xi43SLH6S0P0HXIJgGOurLTN6F9D486kDYMLEKJ-47qv6GWrbEJGdMtx5rcFppZFrhwp6H_tS-A3o_Sw6azxFVWCicbgZKvhGsdDMwEzjWVOWhHuW0y1n-Lpg-57oHgXGS" alt="logs visible in Kibana" width="880" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt; Conclusion&lt;/h2&gt;

&lt;p&gt;This wraps up the tutorial to integrate a Go service with the ELK stack.&lt;/p&gt;

&lt;p&gt;You should now have a good understanding of how the ELK stack works &amp;amp; each of its different components. Additionally, you should now have a good grasp of how to configure your ELK setup correctly &amp;amp; wire it to your Go application.&lt;/p&gt;

&lt;p&gt;From here, you can start playing with Kibana by setting up useful dashboards based on your data. &lt;/p&gt;

&lt;p&gt;Watch out for a follow-up tutorial for doing just that!&lt;/p&gt;

</description>
      <category>go</category>
      <category>elasticsearch</category>
      <category>microservices</category>
    </item>
  </channel>
</rss>
