<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Eric Goebelbecker</title>
    <description>The latest articles on Forem by Eric Goebelbecker (@egoebelbecker).</description>
    <link>https://forem.com/egoebelbecker</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%2F106026%2F19cc4af3-9662-4114-bf4b-d54c3e9ae86e.jpg</url>
      <title>Forem: Eric Goebelbecker</title>
      <link>https://forem.com/egoebelbecker</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/egoebelbecker"/>
    <language>en</language>
    <item>
      <title>How to Merge Log Files</title>
      <dc:creator>Eric Goebelbecker</dc:creator>
      <pubDate>Tue, 24 Mar 2020 14:20:15 +0000</pubDate>
      <link>https://forem.com/scalyr/how-to-merge-log-files-22cj</link>
      <guid>https://forem.com/scalyr/how-to-merge-log-files-22cj</guid>
      <description>&lt;p&gt;You have log files from two or more applications, and you need to see them together. Viewing the data together in proper sequence will make it easier to correlate events, and listing them side-by-side in windows or tabs isn’t cutting it.&lt;/p&gt;

&lt;p&gt;You need to &lt;strong&gt;merge log files&lt;/strong&gt; by timestamps.&lt;/p&gt;

&lt;p&gt;But just merging them by timestamp isn’t the only thing you need. Many log files have entries with more than one line, and not all of those lines have timestamps on them.&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%2Flibrary.scalyr.com%2F2018%2F11%2F13003159%2FMerge_sign_in_Scalyr_colors_for_merge_log_files.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%2Flibrary.scalyr.com%2F2018%2F11%2F13003159%2FMerge_sign_in_Scalyr_colors_for_merge_log_files.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Merge Log Files by Timestamp&lt;/h2&gt;

&lt;p&gt;Let’s take a look at the simple case. We have two files from Linux's syslog daemon. One is the &lt;strong&gt;messages&lt;/strong&gt; file and the other is the &lt;strong&gt;crontab&lt;/strong&gt; log.&lt;/p&gt;

&lt;p&gt;Here are four lines from the &lt;strong&gt;messages&lt;/strong&gt; file:&lt;/p&gt;

&lt;pre&gt;Sep 4 00:00:08 ip–10–97–55–50 dhclient[2588]: XMT: Solicit on eth0, interval 120000ms.
Sep 4 00:02:08 ip–10–97–55–50 dhclient[2588]: XMT: Solicit on eth0, interval 124910ms.
Sep 4 00:04:13 ip–10–97–55–50 dhclient[2588]: XMT: Solicit on eth0, interval 109850ms.
Sep 4 00:06:03 ip–10–97–55–50 dhclient[2588]: XMT: Solicit on eth0, interval 112380ms.
&lt;/pre&gt;

&lt;p&gt;And here are five lines from&lt;strong&gt; cron&lt;/strong&gt;:&lt;/p&gt;

&lt;pre&gt;Sep 4 00:01:01 ip–10–97–55–50 CROND[18843]: (root) CMD (run-parts /etc/cron.hourly)
Sep 4 00:01:01 ip–10–97–55–50 run-parts(/etc/cron.hourly)[18843]: starting 0anacron
Sep 4 00:01:01 ip–10–97–55–50 anacron[18853]: Anacron started on 2018–09–04
Sep 4 00:01:01 ip–10–97–55–50 anacron[18853]: Jobs will be executed sequentially&amp;lt;
Sep 4 00:01:01 ip–10–97–55–50 anacron[18853]: Normal exit (0 jobs run)
&lt;/pre&gt;

&lt;p&gt;When we’re only dealing with ten lines of logs, it’s easy to see where the merge belongs. The five lines in the &lt;strong&gt;cron&lt;/strong&gt; log belong between the first and second lines of the &lt;strong&gt;messages&lt;/strong&gt; log.&lt;/p&gt;

&lt;p&gt;But with a bigger dataset, we need a tool that can merge these two files on the date and the time. The good news is that Linux has a tool for this already.&lt;/p&gt;

&lt;h3&gt;Merge Log Files With Sort&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;sort&lt;/strong&gt; command can, as its name implies, sort input. We can stream both log files into &lt;strong&gt;sort&lt;/strong&gt; and give it a hint on how to sort the two logs.&lt;/p&gt;

&lt;p&gt;Let’s give it a try.&lt;/p&gt;

&lt;pre&gt;cat messages.log cron.log |sort –key=1,2 &amp;gt; merge.log
&lt;/pre&gt;

&lt;p&gt;This creates a new file named &lt;strong&gt;merge.log.&lt;/strong&gt; Here’s what it looks like:&lt;/p&gt;

&lt;pre&gt;Sep 4 00:00:08 ip–10–97–55–50 dhclient[2588]: XMT: Solicit on eth0, interval 120000ms.
Sep 4 00:01:01 ip–10–97–55–50 CROND[18843]: (root) CMD (run-parts /etc/cron.hourly)
Sep 4 00:01:01 ip–10–97–55–50 anacron[18853]: Anacron started on 2018–09–04
Sep 4 00:01:01 ip–10–97–55–50 anacron[18853]: Jobs will be executed sequentially&amp;lt;
Sep 4 00:01:01 ip–10–97–55–50 anacron[18853]: Normal exit (0 jobs run)
Sep 4 00:01:01 ip–10–97–55–50 run-parts(/etc/cron.hourly)[18843]: starting 0anacron
Sep 4 00:02:08 ip–10–97–55–50 dhclient[2588]: XMT: Solicit on eth0, interval 124910ms.
Sep 4 00:04:13 ip–10–97–55–50 dhclient[2588]: XMT: Solicit on eth0, interval 109850ms.
Sep 4 00:06:03 ip–10–97–55–50 dhclient[2588]: XMT: Solicit on eth0, interval 112380ms.
&lt;/pre&gt;

&lt;p&gt;It worked!&lt;/p&gt;

&lt;p&gt;Let’s dissect that command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;cat messages.log cron.log |&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cat&lt;/strong&gt; concatenates files. We used it to send both logs to standard output. In this case, it sent &lt;strong&gt;messages.log&lt;/strong&gt; first and then &lt;strong&gt;cron.log.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The pipe &lt;strong&gt;|&lt;/strong&gt; is what it sounds like. It’s a pipe between two programs. It sends the contents of the two files to the next part of the command. As we’ll see below, &lt;strong&gt;sort&lt;/strong&gt; can accept a single filename on the command line. When we want to sort more than one file, we use a pipe to send the files on standard input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sort –key=2,3 &amp;gt; merge.log&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sort&lt;/strong&gt; receives the contents of two files and sorts them. Its output goes to the &lt;strong&gt;&amp;gt;&lt;/strong&gt; redirect operator, which creates the new file.&lt;/p&gt;

&lt;p&gt;The most important part of this command is &lt;strong&gt;–key=2,3&lt;/strong&gt;. We used this to tell &lt;strong&gt;sort&lt;/strong&gt; to sort its input using two fields and three of the files. For some reason, &lt;strong&gt;sort&lt;/strong&gt; starts counting fields at one instead of zero.&lt;/p&gt;

&lt;p&gt;So &lt;strong&gt;sort&lt;/strong&gt; was able to merge the two files using the day of the month and the timestamp.&lt;/p&gt;

&lt;p&gt;This is our easy case. These log files both had single line entries, and our dataset was for less than thirty days. So we don't have to worry about sorting by months.&lt;/p&gt;

&lt;p&gt;Let’s look at something that’s a little more complicated.&lt;/p&gt;

&lt;h2&gt;Merge Log Files With Multiline Entries&lt;/h2&gt;

&lt;p&gt;Here are a couple of Java application logs that we would like to merge.&lt;/p&gt;

&lt;p&gt;Here’s the first:&lt;/p&gt;

&lt;pre&gt;2018-09-06 15:20:40,980 [INFO] Heimdall main:26 [main] 

Fix Engine is starting.


2018-09-06 15:20:45,639 [ERROR] AcceptorFactory createSessionSettings:92 [main] 

Session settings: [default]
SocketAcceptPort=7000
ConnectionType=acceptor
ValidateUserDefinedFields=N
ValidateLengthAndChecksum=N
ValidateFieldsOutOfOrder=N


2018-09-06 15:20:50,645 [ERROR] AcceptorFactory getSessionSettings:123 [main]

Second Session settings: [default]
SocketAcceptPort=7000
ConnectionType=acceptor
ValidateUserDefinedFields=N
ValidateLengthAndChecksum=N
ValidateFieldsOutOfOrder=N


2018-09-06 15:21:45,653 [INFO] ThreadedSocketAcceptor startSessionTimer:291 [main] SessionTimer started
2018-09-06 15:21:47,711 [INFO] NetworkingOptions logOption:119 [main] Socket option: SocketTcpNoDelay=true
2018-09-06 15:21:59,919 [INFO] SendMessageToSolace addSession:51 [QF/J Session dispatcher: FIX.4.2:FOOU-&amp;gt;TEST02] Adding session: FIX.4.2:FOOU-&amp;gt;TEST02
2018-09-06 15:22:59,920 [INFO] MessageClient openTopic:422 [QF/J Session dispatcher: FIX.4.2:FOOU-&amp;gt;TEST02]
Opening FOO/DEV/AMER/FixEngine/Admin/*/TEST02
2018-09-06 15:23:59,937 [ERROR] ConsumerNodeStatusHandler setStateUp:186 [QF/J Session dispatcher: FIX.4.2:FOOU-&amp;gt;TEST02] Setting State up: TEST02
2018-09-06 15:24:03,962 [INFO] MessageClient openTopic:422 [stateHeartbeat]

Opening FOO/DEV/AMER/State/Admin/Events
2018-09-06 15:25:00,536 [INFO] incoming messageReceived:146 [NioProcessor-2] FIX.4.2:FOOU-&amp;gt;TEST02: 8=FIX.4.29=6235=149=TEST0256=FOOU34=252=20180906-15:21:00.528112=TEST10=198
&lt;/pre&gt;

&lt;p&gt;This log has a lot of whitespace and entries that span multiple lines.&lt;/p&gt;

&lt;p&gt;Here’s the other:&lt;/p&gt;

&lt;pre&gt;2018-09-06 15:20:43:031 [INFO] com.foobar.atr.rest.controller.SessionStatusCache addSessionStatus():28 [lettuce-nioEventLoop-10-5] Adding session: TEST02 at 1536243961031
2018-09-06 15:20:46:031 [INFO] com.foobar.atr.rest.controller.SessionStatusCache addSessionStatus():28 [lettuce-nioEventLoop-13-4] Adding session: TEST02 at 1536243961031
2018-09-06 15:23:15:032 [INFO] com.foobar.atr.rest.controller.SessionStatusCache addSessionStatus():28 [lettuce-nioEventLoop-7-5] Adding session: TEST02 at 1536243961032
2018-09-06 15:24:35:257 [INFO] com.foobar.atr.rest.controller.StatusController getSessionStatus():67 [http-nio-8010-exec-4] Received request a fix session, senderCompId:RBSG2
2018-09-06 15:27:30:691 [INFO] com.foobar.atr.rest.controller.SessionStatusCache addSessionStatus():28 [lettuce-nioEventLoop-10-5] Adding session: PLOP02 at 1536244050691
&lt;/pre&gt;

&lt;p&gt;This log is more uniform, with entries that only span a single line.&lt;/p&gt;

&lt;p&gt;When we merge these two files, we want the multiline log message to remain together. So, &lt;strong&gt;sort's&lt;/strong&gt; numeric sorting won’t work. We need a tool that's capable of associating the lines without timestamps with the last line that has one.&lt;/p&gt;

&lt;p&gt;Unfortunately, no command line tool does this. We’re going to have to write some code.&lt;/p&gt;

&lt;h3&gt;A Merging Algorithm&lt;/h3&gt;

&lt;p&gt;Here’s an algorithm for merging log files that have multiline entries.&lt;/p&gt;

&lt;p&gt;First, we need to preprocess the log files.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scan the log file line by line until we reach the end.&lt;/li&gt;
&lt;li&gt;If a line has a timestamp, save it and print the last saved line to a new file.&lt;/li&gt;
&lt;li&gt;If a line has no timestamp, append it to the saved line, after replacing the new line with a special character&lt;/li&gt;
&lt;li&gt;Continue with step #1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We could do this in memory, but what happens when we’re dealing with huge log files? We’ll save the preprocessed log entries to disk so that this tool will work on huge log files.&lt;/p&gt;

&lt;p&gt;After we perform this on both files, we have a new one that is full of single line entries. We’ll use the &lt;strong&gt;sort&lt;/strong&gt; command to sort it for us, rather than reinventing the wheel. Then, we’ll replace the special characters with new lines, and we have a merged log file.&lt;/p&gt;

&lt;p&gt;And we’re done!&lt;/p&gt;

&lt;p&gt;Let's do it.&lt;/p&gt;

&lt;h3&gt;Merge Log Files With Python&lt;/h3&gt;

&lt;p&gt;We’ll use python. It’s available on all systems, and it’s easy to write a cross-platform tool that manipulates text files. I wrote the code for this article with version 2.7.14. You can find the entire script here on &lt;a href="https://github.com/egoebelbecker/mergelogs" rel="noopener noreferrer"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, we need to process our input files.&lt;/p&gt;

&lt;pre&gt;parser = argparse.ArgumentParser(description="Process input and output file names")
parser.add_argument("-f", "--files", help="list of input files", required=True, nargs='+')
parser.add_argument("-o", "--output", help="output file", required=True, type=argparse.FileType('w'))
args = parser.parse_args()

line_regex = re.compile("^[^0-90-90-90-9\-0-90-9\-0-90-9]")

with open("tmp.log", "w") as out_file:
    for filename in args.files:
        lastline = ""
        with open(filename, "r") as in_file:
            for line in in_file:
                if line_regex.search(line):
                    lastline = lastline.rstrip('\n')
                    lastline += '\1'
                    lastline += line
                else:
                    out_file.write(lastline)
                    lastline = line
&lt;/pre&gt;

&lt;p&gt;We'll start by processing command line arguments. This script accepts two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;-f&lt;/strong&gt; is a comma-separated list of input files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;-o&lt;/strong&gt; is the name of the file to write the output to&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Argparse&lt;/strong&gt; gives us a list from the arguments passed to &lt;strong&gt;-f &lt;/strong&gt;and opens the output file for us, as we’ll see below.&lt;/p&gt;

&lt;h3&gt;Python Regular Expressions&lt;/h3&gt;

&lt;p&gt;Then we'll create a regular expression. Let’s take a close look at it since this is what you’ll need to change if your logs are formatted differently.&lt;/p&gt;

&lt;p&gt;Here’s the whole expression:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;^[^0-90-90-90-9\-0-90-9\-0-90-9]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The expression starts with a caret &lt;strong&gt;^&lt;/strong&gt;. This means the beginning of a line.&lt;/p&gt;

&lt;p&gt;But then we have this: &lt;strong&gt;[^ ]&lt;/strong&gt; with some characters in the middle. Square brackets with a caret at the beginning mean &lt;strong&gt;not.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So the expression means "if this is not at the beginning of the line."&lt;/p&gt;

&lt;p&gt;The pattern we're matching is inside the brackets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;0–90–90–90–9\-0–90–9\-0–90–9&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each &lt;strong&gt;0–9&lt;/strong&gt; corresponds to a numeral. Each &lt;strong&gt;\- &lt;/strong&gt;is a dash. So it could be read like this: &lt;strong&gt;NNNN-NN-NN.&lt;/strong&gt; It’s a pattern for the date we see at the beginning of each log entry.&lt;/p&gt;

&lt;p&gt;So in English, the expression means “if the line does not begin with a date.”&lt;/p&gt;

&lt;p&gt;If you need to process logs with a different format, you'll need to change this. There's a guide to python regular expressions &lt;a href="https://docs.python.org/3.4/library/re.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Sorting the Results&lt;/h3&gt;

&lt;p&gt;Now, we'll start the real work.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open a temporary file.&lt;/li&gt;
&lt;li&gt;Open the first log file.&lt;/li&gt;
&lt;li&gt;Join lines with no timestamp to their predecessors, as described above.&lt;/li&gt;
&lt;li&gt;Repeat this for each file passed on the command line.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the third step, we'll chop the newline &lt;strong&gt;'\n' &lt;/strong&gt;from the end of the last line we saved. Then we'll add an &lt;strong&gt;SOH ('\1') &lt;/strong&gt;character and concatenate the lines. (I could've done this in one line, but I spelled it out to make it clear.)&lt;/p&gt;

&lt;p&gt;We're replacing newlines &lt;strong&gt;'\n' &lt;/strong&gt;with the &lt;strong&gt;SOH&lt;/strong&gt; character instead of &lt;strong&gt;NULLs ('\0') &lt;/strong&gt;because nulls would confuse python's string processing libraries and we'd lose data.&lt;/p&gt;

&lt;p&gt;Finally, the result of this code is a file named &lt;strong&gt;tmp.log&lt;/strong&gt; that contains the log files preprocessed to be one line per entry.&lt;/p&gt;

&lt;p&gt;Let’s finish the job.&lt;/p&gt;

&lt;pre&gt;sorted_logs = check_output(["/usr/bin/sort", "--key=1,2", "tmp.log"])

os.remove("tmp.log")

lines = sorted_logs.split('\n')
for line in lines:
    newline = line.replace('\1', '\n')
    args.output.write(newline + "\n")
&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Check_output&lt;/strong&gt; executes an external command and captures the output.&lt;/p&gt;

&lt;p&gt;So we'll use it to run &lt;strong&gt;sort&lt;/strong&gt; on our temporary file and return the results to us as a string. Then, we'll remove the temporary file.&lt;/p&gt;

&lt;p&gt;We wouldn’t want to capture the result in memory with a large file, but to keep this post short, I cheated. An alternative is to send the output of &lt;strong&gt;sort&lt;/strong&gt; to a file with the &lt;strong&gt;-o&lt;/strong&gt; option and then open that file and remove the special characters.&lt;/p&gt;

&lt;p&gt;Next, we'll split the output on the new lines into an array. Then we'll process that array and undo the special characters. We'll write each line to the file opened for us by &lt;strong&gt;argparse.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’re done!&lt;/p&gt;

&lt;p&gt;Let's run this script on two files:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;./mergelogs.py -f foo.log bar.log -o output.log&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And we'll see this.&lt;/p&gt;

&lt;pre&gt;2018-09-06 15:20:40,980 [INFO] Heimdall main:26 [main] 

Fix Engine is starting.


2018-09-06 15:20:43:031 [INFO] com.foobar.atr.rest.controller.SessionStatusCache addSessionStatus():28 [lettuce-nioEventLoop-10-5] Adding session: TEST02 at 1536243961031
2018-09-06 15:20:45,639 [ERROR] AcceptorFactory createSessionSettings:92 [main] 

Session settings: [default]
SocketAcceptPort=7000
ConnectionType=acceptor
ValidateUserDefinedFields=N
ValidateLengthAndChecksum=N
ValidateFieldsOutOfOrder=N


2018-09-06 15:20:46:031 [INFO] com.foobar.atr.rest.controller.SessionStatusCache addSessionStatus():28 [lettuce-nioEventLoop-13-4] Adding session: TEST02 at 1536243961031
2018-09-06 15:20:50,645 [ERROR] AcceptorFactory getSessionSettings:123 [main]

Second Session settings: [default]
SocketAcceptPort=7000
ConnectionType=acceptor
ValidateUserDefinedFields=N
ValidateLengthAndChecksum=N
ValidateFieldsOutOfOrder=N


2018-09-06 15:21:45,653 [INFO] ThreadedSocketAcceptor startSessionTimer:291 [main] SessionTimer started
2018-09-06 15:21:47,711 [INFO] NetworkingOptions logOption:119 [main] Socket option: SocketTcpNoDelay=true
2018-09-06 15:21:59,919 [INFO] SendMessageToSolace addSession:51 [QF/J Session dispatcher: FIX.4.2:FOOU-&amp;gt;TEST02] Adding session: FIX.4.2:FOOU-&amp;gt;TEST02
2018-09-06 15:22:59,920 [INFO] MessageClient openTopic:422 [QF/J Session dispatcher: FIX.4.2:FOOU-&amp;gt;TEST02]

Opening FOO/DEV/AMER/FixEngine/Admin/*/TEST02
2018-09-06 15:23:15:032 [INFO] com.foobar.atr.rest.controller.SessionStatusCache addSessionStatus():28 [lettuce-nioEventLoop-7-5] Adding session: TEST02 at 1536243961032
2018-09-06 15:23:59,937 [ERROR] ConsumerNodeStatusHandler setStateUp:186 [QF/J Session dispatcher: FIX.4.2:FOOU-&amp;gt;TEST02] Setting State up: TEST02
2018-09-06 15:24:03,962 [INFO] MessageClient openTopic:422 [stateHeartbeat]

Opening FOO/DEV/AMER/State/Admin/Events
2018-09-06 15:24:35:257 [INFO] com.foobar.atr.rest.controller.StatusController getSessionStatus():67 [http-nio-8010-exec-4] Received request a fix session, senderCompId:RBSG2

&lt;/pre&gt;

&lt;h2&gt;Log Files, Merged&lt;/h2&gt;

&lt;p&gt;In this tutorial, we covered how to merge &lt;a href="https://www.scalyr.com/blog/common-ways-people-destroy-their-log-files/" rel="noopener noreferrer"&gt;log files&lt;/a&gt;, looking at a straightforward case and then a more complicated situation. The code for this is available on &lt;a href="https://github.com/egoebelbecker/mergelogs" rel="noopener noreferrer"&gt;Github&lt;/a&gt;, and you're free to download and modify it for your individual needs.&lt;/p&gt;

</description>
      <category>logging</category>
    </item>
    <item>
      <title>Jaeger Tracing Tutorial: Get Going From Scratch</title>
      <dc:creator>Eric Goebelbecker</dc:creator>
      <pubDate>Tue, 17 Sep 2019 14:49:12 +0000</pubDate>
      <link>https://forem.com/scalyr/jaeger-tracing-tutorial-get-going-from-scratch-4nb3</link>
      <guid>https://forem.com/scalyr/jaeger-tracing-tutorial-get-going-from-scratch-4nb3</guid>
      <description>&lt;p&gt;The Jaeger tracing system is an open-source tracing system for microservices, and it supports the &lt;a href="https://opentracing.io"&gt;OpenTracing&lt;/a&gt; standard. We talked about &lt;a href="https://scalyr.com/blog/what-is-opentracing/"&gt;OpenTracing and why it's essential&lt;/a&gt; in a previous post. So now, let's talk more about Jaeger.&lt;/p&gt;

&lt;p&gt;Jaeger was initially published as open source by Uber Technologies and has evolved since then. The system gives you distributing tracing, root cause analysis, service dependency analysis, and more.&lt;/p&gt;

&lt;p&gt;We're going get started with Jaeger tracing by installing it and using it to examine some RESTful API calls to a single microservice. To do this, we'll need to build a small service with tracing enabled. Jaeger has tooling for Go, Java, JavaScript (Node.js,) Python, and C++. We'll use Java for this tutorial, but the concepts we cover here will apply to any supported platform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--im6ueu8i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121919/Jaeger_tracing_image_showing_Jeager_mascot_in_scalyr_colors.png" class="article-body-image-wrapper"&gt;&lt;img class="aligncenter wp-image-3011 size-full" src="https://res.cloudinary.com/practicaldev/image/fetch/s--im6ueu8i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121919/Jaeger_tracing_image_showing_Jeager_mascot_in_scalyr_colors.png" alt="Jaeger_tracing_image_showing_Jeager_mascot_in_scalyr_colors" width="600" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Installation and Setup&lt;/h2&gt;

&lt;h3&gt;Docker&lt;/h3&gt;

&lt;p&gt;The preferred way to install and run Jaeger tracing is with Docker. It's also the easiest. So if you're not running Docker yet, take a look at the installation process for your platform &lt;a href="https://docs.docker.com/install/"&gt;here&lt;/a&gt;. The Community Edition is more than adequate for this tutorial.&lt;/p&gt;

&lt;h3&gt;Install Jaeger&lt;/h3&gt;

&lt;p&gt;Jaeger is a set of distributed components for collecting, storing, and displaying trace information. But it also ships as an "all-in-one" image that runs the entire system. We'll use that to keep the install simple for this tutorial. There are instructions for getting started &lt;a href="https://www.jaegertracing.io/docs/1.9/getting-started/"&gt;here&lt;/a&gt;, but I'll cover a condensed version in this post.&lt;/p&gt;

&lt;p&gt;Docker will download the image for you when you try to start a container. I'll use a shorter command line than the one in Jaeger's instructions because we're only going to use one of the system's tracing modes.&lt;/p&gt;

&lt;pre class="lang:sh decode:true"&gt;docker run -d --name jaeger -p 16686:16686 -p 6831:6831/udp jaegertracing/all-in-one:1.9&lt;/pre&gt;

&lt;p&gt;So, when you run the container, your command should look like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xgeB3J79--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20120923/012.png" class="article-body-image-wrapper"&gt;&lt;img class="size-full wp-image-2993 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--xgeB3J79--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20120923/012.png" alt="" width="750" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the command finishes, check to see if the server is running with &lt;strong&gt;docker ps -a&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kNgYTAcj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20120948/022.png" class="article-body-image-wrapper"&gt;&lt;img class="size-full wp-image-2994 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--kNgYTAcj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20120948/022.png" alt="" width="680" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see the container name, &lt;strong&gt;jaeger&lt;/strong&gt;, with &lt;strong&gt;up&lt;/strong&gt; in the status column. You'll also see a lot of information about service ports.&lt;/p&gt;

&lt;p&gt;Now, you can connect to the Jaeger console at &lt;a href="http://localhost:16686"&gt;http://localhost:16686&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nFS5ZY0I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121019/032.png" class="article-body-image-wrapper"&gt;&lt;img class="size-full wp-image-2995 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--nFS5ZY0I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121019/032.png" alt="" width="750" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We see the Jaeger user interface. It's running!&lt;/p&gt;

&lt;h3&gt;Java Microservice&lt;/h3&gt;

&lt;p&gt;We'll use a simple Spring Boot service to create some traces. The code for the project is on &lt;a href="https://github.com/egoebelbecker/jaeger-tutorial"&gt;GitHub.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project has scripts for running the service via script or in a container. If you want to run both the service and Jaeger in containers, you'll need to know how to get them to connect over UDP using Docker networking. That's beyond the scope of this tutorial.&lt;/p&gt;

&lt;h2&gt;Jaeger and Open Tracing Concepts&lt;/h2&gt;

&lt;p&gt;Before we start our service, we can take a look at Jaeger's interface and review some basic open tracing concepts. The user interface service reports its queries so that we can see examples of a few basic traces.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Evti5nS2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121047/042.png" class="article-body-image-wrapper"&gt;&lt;img class="size-full wp-image-2996 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--Evti5nS2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121047/042.png" alt="" width="386" height="791"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the box on the left-hand side of the page labeled &lt;strong&gt;Find Traces.&lt;/strong&gt; The first control, a chooser, lists the services available for tracing. The count should show one. (If it doesn't, try refreshing the page.) Now, click the chooser and you'll see &lt;strong&gt;jaeger-query&lt;/strong&gt; listed as the only service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CNHc-Sbm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121118/052.png" class="article-body-image-wrapper"&gt;&lt;img class="aligncenter wp-image-2997 " src="https://res.cloudinary.com/practicaldev/image/fetch/s--CNHc-Sbm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121118/052.png" alt="" width="572" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A service is an application that's registered itself to Jaeger. We'll see how to register our application below.&lt;/p&gt;

&lt;p&gt;Next, with &lt;strong&gt;jaeger-query&lt;/strong&gt; selected, click the &lt;strong&gt;Find Traces&lt;/strong&gt; button on the bottom of the form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c238YpE6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121203/061.png" class="article-body-image-wrapper"&gt;&lt;img class="size-full wp-image-2998 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--c238YpE6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121203/061.png" alt="" width="750" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A list of traces will appear on the right-hand side of the screen. The traces have titles that correspond to the &lt;strong&gt;Operation&lt;/strong&gt; selector on the search form. So, select &lt;strong&gt;/api/services&lt;/strong&gt; in the &lt;strong&gt;Operation&lt;/strong&gt; box and click the &lt;strong&gt;Find&lt;/strong&gt; button again. Depending on how many times you reloaded the page, you'll see a few operations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kPKSwBvZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121245/07.png" class="article-body-image-wrapper"&gt;&lt;img class="size-full wp-image-2999 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--kPKSwBvZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121245/07.png" alt="" width="750" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now click on one of the traces.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--liON2lit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121312/08.png" class="article-body-image-wrapper"&gt;&lt;img class="size-full wp-image-3000 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--liON2lit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121312/08.png" alt="" width="750" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This trace has one operation in it. It took 0.14 ms. There's not much to look at here. But we can look at what the service sent to the Jaeger Tracing server. So click on the box in the upper right-hand side of the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uBM7pz2Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121354/09.png" class="article-body-image-wrapper"&gt;&lt;img class=" wp-image-3001 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--uBM7pz2Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121354/09.png" alt="" width="278" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Jaeger Tracing Tags&lt;/h3&gt;

&lt;p&gt;Next, let's look at the JSON.&lt;/p&gt;

&lt;pre class="lang:js decode:true"&gt;{
  "data": [
    {
      "traceID": "3b8496f91e044c34",
      "spans": [
        {
          "traceID": "3b8496f91e044c34",
          "spanID": "3b8496f91e044c34",
          "flags": 1,
          "operationName": "/api/traces",
          "references": [],
          "startTime": 1549827709524283,
          "duration": 142,
          "tags": [
            {
              "key": "sampler.type",
              "type": "string",
              "value": "const"
            },
            {
              "key": "sampler.param",
              "type": "bool",
              "value": true
            },
            {
              "key": "span.kind",
              "type": "string",
              "value": "server"
            },
            {
              "key": "http.method",
              "type": "string",
              "value": "GET"
            },
            {
              "key": "http.url",
              "type": "string",
              "value": "/api/traces?end=1549827709522000\u0026limit=20\u0026lookback=1h\u0026maxDuration\u0026minDuration\u0026service=jaeger-query\u0026start=1549824109522000\u0026tags=%7B%22http.status_code%22%3A%22404%22%7D"
            },
            {
              "key": "component",
              "type": "string",
              "value": "net/http"
            },
            {
              "key": "http.status_code",
              "type": "int64",
              "value": 200
            }
          ],
          "logs": [],
          "processID": "p1",
          "warnings": null
        }
      ],
      "processes": {
        "p1": {
          "serviceName": "jaeger-query",
          "tags": [
            {
              "key": "client-uuid",
              "type": "string",
              "value": "6550fb460c8ee430"
            },
            {
              "key": "hostname",
              "type": "string",
              "value": "9f77a41dfd0c"
            },
            {
              "key": "ip",
              "type": "string",
              "value": "172.17.0.2"
            },
            {
              "key": "jaeger.version",
              "type": "string",
              "value": "Go-2.15.1dev"
            }
          ]
        }
      },
      "warnings": null
    }
  ],
  "total": 0,
  "limit": 0,
  "offset": 0,
  "errors": null
}&lt;/pre&gt;
There's a lot of information here. Toward the top of the JSON, you see an array of &lt;strong&gt;spans&lt;/strong&gt;. This trace only has one. A &lt;strong&gt;trace&lt;/strong&gt; consists of one or more spans. A span is, as you might guess, an interval of time that contains one or more operations. We'll take a closer look at spans when we add some code to the Java service.

Inside the span, there's an array of &lt;strong&gt;tags.&lt;/strong&gt; Tags are attributes an application adds to traces. Here are two:
&lt;pre class="lang:js decode:true"&gt;{
    "key": "http.method",
    "type": "string",
    "value": "GET"
},
{
    "key": "http.status_code",
    "type": "int64",
    "value": 200
}
&lt;/pre&gt;

&lt;p&gt;We'll see how to add these tags to our spans below. For now, let's go back to the main page and use tags to search.&lt;/p&gt;

&lt;p&gt;Now enter &lt;strong&gt;http.method=get&lt;/strong&gt; in the &lt;strong&gt;Tags&lt;/strong&gt; field and click the find button again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2v0y0XAz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121429/10.png" class="article-body-image-wrapper"&gt;&lt;img class="size-full wp-image-3002 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--2v0y0XAz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121429/10.png" alt="" width="750" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll see a list of traces. Most of the operations in the Jaeger UI are GETS, which makes sense.&lt;/p&gt;

&lt;p&gt;That's the basics of the Jaeger interface. Let's connect a service.&lt;/p&gt;

&lt;h2&gt;Tracing a Service&lt;/h2&gt;

&lt;p&gt;The Jaeger tutorial application contains a create-read-update-delete (CRUD) API for managing employee records. The records are stored in a local hashmap. We're going to add a trace with two spans to the application.&lt;/p&gt;

&lt;h3&gt;Creating a Tracer&lt;/h3&gt;

&lt;p&gt;To add tracing to an application, you need a &lt;strong&gt;Tracer.&lt;/strong&gt; We'll create one and use Spring to supply it to the microservice's service and controller classes.&lt;/p&gt;

&lt;p&gt;Here's the method for creating the tracer:&lt;/p&gt;

&lt;pre class="lang:java decode:true"&gt;@Bean
public static JaegerTracer getTracer() {
    Configuration.SamplerConfiguration samplerConfig = Configuration.SamplerConfiguration.fromEnv().withType("const").withParam(1);
    Configuration.ReporterConfiguration reporterConfig = Configuration.ReporterConfiguration.fromEnv().withLogSpans(true);
    Configuration config = new Configuration("jaeger tutorial").withSampler(samplerConfig).withReporter(reporterConfig);
    return config.getTracer();
}
&lt;/pre&gt;

&lt;p&gt;The first step is constructing configuration classes. You use them to create the &lt;strong&gt;Tracer.&lt;/strong&gt; Jaeger has an extensive set of tools for configuration. We're accepting the default settings and naming our tracer &lt;strong&gt;jaeger tutorial&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This method is in the class with the service's &lt;strong&gt;main&lt;/strong&gt; method. We're treating it like a Spring &lt;strong&gt;Bean&lt;/strong&gt; and injecting into the constructors of the controller and service classes. If you don't understand Spring dependency injection, you can assume that the controller and service methods have access to a tracer.&lt;/p&gt;

&lt;p&gt;You can learn more about Jaeger configuration &lt;a href="https://github.com/jaegertracing/jaeger-client-java/blob/master/jaeger-core/README.md"&gt;here&lt;/a&gt; and &lt;a href="https://www.jaegertracing.io/docs/1.6/client-features/"&gt;here.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Tracing a REST API Call&lt;/h3&gt;

&lt;p&gt;Let's start with adding a single span to a POST method. Here's our code for adding a new employee.&lt;/p&gt;

&lt;pre class="lang:java decode:true"&gt;@ApiOperation(value = "Create Employee ", response = ResponseEntity.class)
@RequestMapping(value = "/api/tutorial/1.0/employees", method = RequestMethod.POST)
public ResponseEntity createEmployee(@RequestBody Employee employee) {

    // Create a span
    Span span = tracer.buildSpan("create employee").start();
        
    HttpStatus status = HttpStatus.FORBIDDEN;

    log.info("Receive Request to add employee {}", employee);
    if (employeeService.addEmployee(employee)) {
        status = HttpStatus.CREATED;
            
        // Set http status code
        span.setTag("http.status_code", 201);
    } else {
        span.setTag("http.status_code", 403);
    }
        
    // Close the span
    span.finish();
    return new ResponseEntity(null, status);
}
&lt;/pre&gt;

&lt;p&gt;We create a &lt;strong&gt;Span&lt;/strong&gt; at the start of the method, using our &lt;strong&gt;Tracer&lt;/strong&gt; instance. Then we set a tag corresponding to the HTTP status code of the request. This should make out trace look a lot like the Jaeger query service. The service has a Swagger interface, so we can use it to add an employee.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n8r62DsH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121505/11.png" class="article-body-image-wrapper"&gt;&lt;img class="size-full wp-image-3003 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--n8r62DsH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121505/11.png" alt="" width="750" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill out details for an employee and then click the &lt;strong&gt;Try it out!&lt;/strong&gt; button twice. The first request will succeed. The second will fail because the service will not accept a new employee with an existing ID.&lt;/p&gt;

&lt;p&gt;Now, take a look at the Jaeger search page. Select &lt;strong&gt;jaeger tutorial&lt;/strong&gt; in the service selector and &lt;strong&gt;create employee&lt;/strong&gt; in the operation selector and click the find button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wd0WH4vw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121534/12.png" class="article-body-image-wrapper"&gt;&lt;img class="size-full wp-image-3004 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--wd0WH4vw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121534/12.png" alt="" width="750" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We see two traces, but we know one failed and one succeeded. Let's refine the search. Enter &lt;strong&gt;http.status_code=403&lt;/strong&gt; in the &lt;strong&gt;Tags&lt;/strong&gt; text box.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eHD9b0dq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121610/13.png" class="article-body-image-wrapper"&gt;&lt;img class=" wp-image-3005 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--eHD9b0dq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121610/13.png" alt="" width="661" height="732"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, click the find button again. You'll see only one trace. Tags are useful for filtering traces and looking at specific criteria.&lt;/p&gt;

&lt;h3&gt;Multiple Spans and Log Messages&lt;/h3&gt;

&lt;p&gt;Let's finish up by adding a second span to a trace, along with log messages.&lt;/p&gt;

&lt;p&gt;Here is the controller's delete method:&lt;/p&gt;

&lt;pre class="lang:java decode:true"&gt;@ApiOperation(value = "Delete Employee ", response = ResponseEntity.class)
@RequestMapping(value = "/api/tutorial/1.0/employees/{id}", method = RequestMethod.DELETE)
public ResponseEntity deleteEmployee(@PathVariable("id") String idString) {

    Span span = tracer.buildSpan("delete employee").start();

    HttpStatus status = HttpStatus.NO_CONTENT;

    try {
        int id = Integer.parseInt(idString);
        log.info("Received Request to delete employee {}", id);
        span.log(ImmutableMap.of("event", "delete-request", "value", idString));
        if (employeeService.deleteEmployee(id, span)) {
            span.log(ImmutableMap.of("event", "delete-success", "value", idString));
            span.setTag("http.status_code", 200);
            status = HttpStatus.OK;
        } else {
            span.log(ImmutableMap.of("event", "delete-fail", "value", "does not exist"));
            span.setTag("http.status_code", 204);
        }
    } catch (NumberFormatException | NoSuchElementException nfe) {
        span.log(ImmutableMap.of("event", "delete-fail", "value", idString));
        span.setTag("http.status_code", 204);
    }

    span.finish();
    return new ResponseEntity(null, status);
 }
&lt;/pre&gt;

&lt;p&gt;Like the add method, we're opening a span at the start of the method. We're also setting the status code tag based on the result of the delete request. Also, the code has log messages based on the outcome of the query.&lt;/p&gt;

&lt;p&gt;We're also passing our &lt;strong&gt;Span&lt;/strong&gt; object to the service. Let's look at why. Here is the delete method in the service:&lt;/p&gt;

&lt;pre class="lang:java decode:true"&gt;public boolean deleteEmployee(int id, Span rootSpan) {

    Span span = tracer.buildSpan("service delete employee").asChildOf(rootSpan).start();

    boolean result = false;
    if (employeeMap.containsKey(id)) {
        employeeMap.remove(id);
        result = true;
    }
    span.finish();
    return result;
}&lt;/pre&gt;

&lt;p&gt;We're creating a new span inside the method, setting it as a child of the span that was passed in.&lt;/p&gt;

&lt;p&gt;Run the service and try to delete a valid employee and then an invalid one.&lt;/p&gt;

&lt;p&gt;Now, select &lt;strong&gt;delete employee&lt;/strong&gt; in the operation control and click the find button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n8un5Vh8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121645/14.png" class="article-body-image-wrapper"&gt;&lt;img class=" wp-image-3006 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--n8un5Vh8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121645/14.png" alt="" width="497" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see two operations with two spans each.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MD6MBDCB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121719/15.png" class="article-body-image-wrapper"&gt;&lt;img class="size-full wp-image-3007 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--MD6MBDCB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121719/15.png" alt="" width="750" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inspect each trace, and you'll see a few new things. Here's the successful trace, with both spans displayed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DfSV8lmT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121746/16.png" class="article-body-image-wrapper"&gt;&lt;img class="size-full wp-image-3008 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--DfSV8lmT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121746/16.png" alt="" width="750" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see the second trace and the log messages. The failed delete has different log messages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zUsOnHyV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121814/17.png" class="article-body-image-wrapper"&gt;&lt;img class="size-full wp-image-3009 aligncenter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--zUsOnHyV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://library.scalyr.com/2019/02/20121814/17.png" alt="" width="750" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, with a few lines of code, we can see how long operations take and get an idea of why!&lt;/p&gt;

&lt;h2&gt;Jaeger Tracing for Microservices&lt;/h2&gt;

&lt;p&gt;Jaeger tracing is an open-source implementation of the OpenTracing standard. In just a few minutes we installed the system and used it to trace a REST microservice. Tracing is an essential strategy for managing your services and monitoring your users' experience, so enjoy the fruits of this new knowledge!&lt;/p&gt;

</description>
      <category>tracing</category>
      <category>programming</category>
      <category>coding</category>
    </item>
    <item>
      <title>Java Stack Trace: Understanding It and Using It to Debug</title>
      <dc:creator>Eric Goebelbecker</dc:creator>
      <pubDate>Mon, 29 Jul 2019 20:02:36 +0000</pubDate>
      <link>https://forem.com/scalyr/java-stack-trace-understanding-it-and-using-it-to-debug-5a3a</link>
      <guid>https://forem.com/scalyr/java-stack-trace-understanding-it-and-using-it-to-debug-5a3a</guid>
      <description>&lt;p&gt;Deploying your Java code to production limits your troubleshooting options. Connecting to your app in production with a debugger is usually out of the question, and you might not even be able to get console access. So even with monitoring, you’re going to end up troubleshooting many problems post-mortem. This means looking at &lt;a href="https://www.scalyr.com/blog/get-started-quickly-javascript-logging/" rel="noopener noreferrer"&gt;logs&lt;/a&gt; and, if you’re lucky, working with a Java stack trace.&lt;/p&gt;

&lt;p&gt;That’s right, I said you’re lucky if you have a stack trace. It’s like getting a compass, a map, and a first-class airplane ticket handed to you all at once! Let’s talk about what a Java stack trace is and how you can use it.&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%2Flibrary.scalyr.com%2F2019%2F05%2F24093715%2FCoffee_cups_with_scalyr_colors_signifying_java_stack_trace.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%2Flibrary.scalyr.com%2F2019%2F05%2F24093715%2FCoffee_cups_with_scalyr_colors_signifying_java_stack_trace.png" alt="Coffee_cups_with_scalyr_colors_signifying_java_stack_trace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;What's a Java Stack Trace?&lt;/h2&gt;

&lt;p&gt;A stack trace, also called a stack backtrace or even just a backtrace, is a list of stack frames. These frames represent a moment during an application’s execution. A stack frame is information about a method or function that your code called. So the Java stack trace is a list of frames that starts at the current method and extends to when the program started.&lt;/p&gt;

&lt;p&gt;Sometimes there’s confusion between a stack and the Stack. A stack is a data structure that acts as a stack of papers on your desk: it’s first-in-last-out. You add documents to the pile and take them off in the reverse order you put them there. The Stack, more accurately called the runtime or call stack, is a set of stack frames a program creates as it executes, organized in a stack data structure.&lt;/p&gt;

&lt;p&gt;Let’s look at an example.&lt;/p&gt;

&lt;h3&gt;Java Stack Trace Example&lt;/h3&gt;

&lt;p&gt;Let’s take a look at a Java program. This class calls four methods and prints a stack trace to the console from the last one.&lt;/p&gt;

&lt;pre&gt;public class StackTrace {

  public static void main(String[] args) {
    a();
  }

  static void a() {
    b();
  }

  static void b() {
    c();
  }

  static void c() {
    d();
  }

  static void d() {
    Thread.dumpStack();
  }
}&lt;/pre&gt;



&lt;p&gt;When you run the class, you’ll see something like this:&lt;/p&gt;



&lt;pre&gt;java.lang.Exception: Stack trace
at java.base/java.lang.Thread.dumpStack(Thread.java:1383)
at com.ericgoebelbecker.stacktraces.StackTrace.d(StackTrace.java:23)
at com.ericgoebelbecker.stacktraces.StackTrace.c(StackTrace.java:19)
at com.ericgoebelbecker.stacktraces.StackTrace.b(StackTrace.java:15)
at com.ericgoebelbecker.stacktraces.StackTrace.a(StackTrace.java:11)
at com.ericgoebelbecker.stacktraces.StackTrace.main(StackTrace.java:7)
&lt;/pre&gt;

&lt;p&gt;The d() method() is at the top of the stack because that’s where the app generated the trace. The main() method is at the bottom because that’s where the program started. When the program started, the Java runtime executed the main() method. Main() called a(). A() called b(),  and b() called c(), which called d(). Finally, d() called dumpStack(), which generated the output. This Java stack trace gives us a picture of what the program did, in the order that it did it.&lt;/p&gt;

&lt;p&gt;A Java stack trace is a snapshot of a moment in time. You can see where your application was and how it got there. That’s valuable insight that you can use a few different ways.&lt;/p&gt;

&lt;h2&gt;How to Use Java Stack Traces&lt;/h2&gt;

&lt;p&gt;Now that you’ve seen what Java stack traces show you, how can you use them?&lt;/p&gt;

&lt;h3&gt;Java Exceptions&lt;/h3&gt;

&lt;p&gt;Stack traces and exceptions are often associated with each other. When you see a Java application throw an exception, you usually see a stack trace logged with it. This is because of how exceptions work.&lt;/p&gt;

&lt;p&gt;When Java code throws an exception, the runtime looks up the stack for a method that has a handler that can process it. If it finds one, it passes the exception to it. If it doesn’t, the program exits. So exceptions and the call stack are linked directly. Understanding this relationship will help you figure out why your code threw an exception.&lt;/p&gt;

&lt;p&gt;Let’s change our sample code.&lt;/p&gt;

&lt;p&gt;First, modify the d() method:&lt;/p&gt;

&lt;pre&gt;static void d() {
  throw new NullPointerException("Oops!");
}
&lt;/pre&gt;

&lt;p&gt;Then, change main() and a() so main can catch an exception. You'll need to add a checked exception to a() so the code will compile.&lt;/p&gt;

&lt;pre&gt;public static void main(String[] args) 
{
  try {
    a();
  } catch (InvalidClassException ice) {
    System.err.println(ice.getMessage());
  }
}

static void a() throws InvalidClassException 
{
  b();
}&lt;/pre&gt;



&lt;p&gt;You’re deliberately catching the “wrong” exception. Run this code and watch what happens.&lt;/p&gt;



&lt;pre&gt;Exception in thread "main" java.lang.NullPointerException: Oops!
at com.ericgoebelbecker.stacktraces.StackTrace.d(StackTrace.java:29)
at com.ericgoebelbecker.stacktraces.StackTrace.c(StackTrace.java:24)
at com.ericgoebelbecker.stacktraces.StackTrace.b(StackTrace.java:20)
at com.ericgoebelbecker.stacktraces.StackTrace.a(StackTrace.java:16)
at com.ericgoebelbecker.stacktraces.StackTrace.main(StackTrace.java:9)
&lt;/pre&gt;

&lt;p&gt;The exception bubbled up the stack past main() because you were trying to catch a different exception. So the runtime threw it, terminating the application. You can still see a stack trace though, so it’s easy to determine what happened.&lt;/p&gt;

&lt;p&gt;Now, change main() to catch a NullPointerException instead. You can remove the checked exception from a() too.&lt;/p&gt;

&lt;pre&gt;public static void main(String[] args) {
  try {
    a();
  } catch (NullPointerException ice) {
    System.err.println(ice.getMessage());
  }
}

static void a() {
  b();
}
&lt;/pre&gt;

&lt;p&gt;Rerun the program.&lt;/p&gt;

&lt;pre&gt;Oops!
&lt;/pre&gt;

&lt;p&gt;We lost the stack trace! By only printing the message attached to the exception, you missed some vital context. Unless you can remember why you wrote &lt;strong&gt;Oops!&lt;/strong&gt; in that message, tracking down this problem is going to be complicated. Let’s try again.&lt;/p&gt;

&lt;pre&gt;public static void main(String[] args) {
  try {
    a();
  } catch (NullPointerException npe) {
    npe.printStackTrace();
  }
}&lt;/pre&gt;



&lt;p&gt;Rerun the application.&lt;/p&gt;



&lt;pre&gt;java.lang.NullPointerException: Oops!
at com.ericgoebelbecker.stacktraces.StackTrace.d(StackTrace.java:28)
at com.ericgoebelbecker.stacktraces.StackTrace.c(StackTrace.java:24)
at com.ericgoebelbecker.stacktraces.StackTrace.b(StackTrace.java:20)
at com.ericgoebelbecker.stacktraces.StackTrace.a(StackTrace.java:16)
at com.ericgoebelbecker.stacktraces.StackTrace.main(StackTrace.java:9)
&lt;/pre&gt;

&lt;p&gt;That’s better! We see the stack trace, and it ends at d() where the exception occurred, even though main() printed it.&lt;/p&gt;

&lt;h3&gt;Logging Java Stack Traces&lt;/h3&gt;

&lt;p&gt;What if you don’t want to print an error message to the console but to a log file instead? The good news is that most loggers, including Log4j and Logback, will write exceptions with stack traces if you call them with the right arguments.&lt;/p&gt;

&lt;p&gt;Pass in the exception object as the last argument to the message, without a formatting directive. So if you used &lt;a href="https://www.scalyr.com/blog/log4j2-configuration-detailed-guide/" rel="noopener noreferrer"&gt;Log4j&lt;/a&gt; or Logback with the sample code like this:&lt;/p&gt;

&lt;pre&gt;logger.error(“Something bad happened:”, npe);
&lt;/pre&gt;

&lt;p&gt;You would see this in your log file:&lt;/p&gt;

&lt;pre&gt;Something bad happened:
java.lang.NullPointerException: Oops!
at com.ericgoebelbecker.stacktraces.StackTrace.d(StackTrace.java:28)
at com.ericgoebelbecker.stacktraces.StackTrace.c(StackTrace.java:24)
at com.ericgoebelbecker.stacktraces.StackTrace.b(StackTrace.java:20)
at com.ericgoebelbecker.stacktraces.StackTrace.a(StackTrace.java:16)
at com.ericgoebelbecker.stacktraces.StackTrace.main(StackTrace.java:9)
&lt;/pre&gt;

&lt;p&gt;One of the best things you can do with exceptions and stack traces is to log them so you can use them to isolate a problem. If you get in the habit of printing useful log messages with details like stack traces and log indexing, then search tools, like Scalyr, become one of the most powerful tools in your troubleshooting tool bag.&lt;/p&gt;

&lt;h3&gt;The Java Debugger&lt;/h3&gt;

&lt;p&gt;Debuggers work by taking control of a program's runtime and letting you both observe and control it. To do this, it shows you the program stack and enables you to traverse it in either direction. When you’re in a debugger, you get a more complete picture of a stack frame than you do when looking at stack traces in a log message.&lt;/p&gt;

&lt;p&gt;Let’s make a small code change and then throw the sample code into a debugger.&lt;/p&gt;

&lt;p&gt;First, add a local variable to the d() method:&lt;/p&gt;

&lt;pre&gt;static void d() {
  String message = “Oops.”
  throw new NullPointerException(message);
}

&lt;/pre&gt;

&lt;p&gt;Then add a breakpoint where d() throws the exception in your debugger. I’m using IntelliJ's debugger for this image.&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%2Flibrary.scalyr.com%2F2019%2F05%2F22083305%2Fdebugger-1024x388.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%2Flibrary.scalyr.com%2F2019%2F05%2F22083305%2Fdebugger-1024x388.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can see that the string we added to d() is part of the stack frame because it’s a local variable. Debuggers operate inside the Stack and give you a detailed picture of each frame.&lt;/p&gt;

&lt;h3&gt;Forcing a Thread Dump&lt;/h3&gt;

&lt;p&gt;Thread dumps are great post-mortem tools, but they can be useful for runtime issues too. If your application stops responding or is consuming more CPU or memory than you expect, you can retrieve information about the running app with &lt;strong&gt;jstack.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Modify main() so the application will run until killed:&lt;/p&gt;

&lt;pre&gt;public static void main(String[] args) throws Exception {
  try {
      while(true) {
          Thread.sleep(1000);
      }
  } catch (NullPointerException ice)  {
      ice.printStackTrace();
  }
}
&lt;/pre&gt;

&lt;p&gt;Run the app, determine its pid, and then run jstack. On Windows, you'll need to press &lt;strong&gt;ctrl-break&lt;/strong&gt; in the DOS window you're running your code in.&lt;/p&gt;

&lt;pre&gt;$ jstack &amp;lt;pid&amp;gt;&lt;/pre&gt;

&lt;p&gt;Jstack will generate a lot of output.&lt;/p&gt;

&lt;pre&gt;2019-05-13 10:06:17
Full thread dump OpenJDK 64-Bit Server VM (12+33 mixed mode, sharing):

Threads class SMR info:
_java_thread_list=0x00007f8bb2727190, length=10, elements={
0x00007f8bb3807000, 0x00007f8bb2875000, 0x00007f8bb2878000, 0x00007f8bb4000800,
0x00007f8bb300a800, 0x00007f8bb287b800, 0x00007f8bb287f000, 0x00007f8bb28ff800,
0x00007f8bb300b800, 0x00007f8bb3805000
}

"main" #1 prio=5 os_prio=31 cpu=60.42ms elapsed=103.32s tid=0x00007f8bb3807000 nid=0x2503 waiting on condition  [0x0000700001a0e000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(java.base@12/Native Method)
    at com.ericgoebelbecker.stacktraces.StackTrace.main(StackTrace.java:9)

"Reference Handler" #2 daemon prio=10 os_prio=31 cpu=0.08ms elapsed=103.29s tid=0x00007f8bb2875000 nid=0x4603 waiting on condition  [0x0000700002123000]
   java.lang.Thread.State: RUNNABLE
    at java.lang.ref.Reference.waitForReferencePendingList(java.base@12/Native Method)
    at java.lang.ref.Reference.processPendingReferences(java.base@12/Reference.java:241)
    at java.lang.ref.Reference$ReferenceHandler.run(java.base@12/Reference.java:213)

"Finalizer" #3 daemon prio=8 os_prio=31 cpu=0.13ms elapsed=103.29s tid=0x00007f8bb2878000 nid=0x3903 in Object.wait()  [0x0000700002226000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(java.base@12/Native Method)
    - waiting on &amp;lt;0x000000070ff02770&amp;gt; (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(java.base@12/ReferenceQueue.java:155)
    - locked &amp;lt;0x000000070ff02770&amp;gt; (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(java.base@12/ReferenceQueue.java:176)
    at java.lang.ref.Finalizer$FinalizerThread.run(java.base@12/Finalizer.java:170)

"Signal Dispatcher" #4 daemon prio=9 os_prio=31 cpu=0.27ms elapsed=103.28s tid=0x00007f8bb4000800 nid=0x3e03 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=31 cpu=6.12ms elapsed=103.28s tid=0x00007f8bb300a800 nid=0x5603 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   No compile task

"C1 CompilerThread0" #7 daemon prio=9 os_prio=31 cpu=12.01ms elapsed=103.28s tid=0x00007f8bb287b800 nid=0xa803 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   No compile task

"Sweeper thread" #8 daemon prio=9 os_prio=31 cpu=0.73ms elapsed=103.28s tid=0x00007f8bb287f000 nid=0xa603 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Service Thread" #9 daemon prio=9 os_prio=31 cpu=0.04ms elapsed=103.27s tid=0x00007f8bb28ff800 nid=0xa503 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Common-Cleaner" #10 daemon prio=8 os_prio=31 cpu=0.27ms elapsed=103.27s tid=0x00007f8bb300b800 nid=0xa303 in Object.wait()  [0x000070000293b000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
    at java.lang.Object.wait(java.base@12/Native Method)
    - waiting on &amp;lt;0x000000070ff91690&amp;gt; (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(java.base@12/ReferenceQueue.java:155)
    - locked &amp;lt;0x000000070ff91690&amp;gt; (a java.lang.ref.ReferenceQueue$Lock)
    at jdk.internal.ref.CleanerImpl.run(java.base@12/CleanerImpl.java:148)
    at java.lang.Thread.run(java.base@12/Thread.java:835)
    at jdk.internal.misc.InnocuousThread.run(java.base@12/InnocuousThread.java:134)

"Attach Listener" #11 daemon prio=9 os_prio=31 cpu=0.72ms elapsed=0.10s tid=0x00007f8bb3805000 nid=0x5e03 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"VM Thread" os_prio=31 cpu=3.83ms elapsed=103.29s tid=0x00007f8bb2874800 nid=0x3703 runnable

"GC Thread#0" os_prio=31 cpu=0.13ms elapsed=103.31s tid=0x00007f8bb282b800 nid=0x3003 runnable

"G1 Main Marker" os_prio=31 cpu=0.26ms elapsed=103.31s tid=0x00007f8bb2845000 nid=0x3103 runnable

"G1 Conc#0" os_prio=31 cpu=0.04ms elapsed=103.31s tid=0x00007f8bb3810000 nid=0x3303 runnable

"G1 Refine#0" os_prio=31 cpu=0.39ms elapsed=103.31s tid=0x00007f8bb2871000 nid=0x3403 runnable

"G1 Young RemSet Sampling" os_prio=31 cpu=13.60ms elapsed=103.31s tid=0x00007f8bb2872000 nid=0x4d03 runnable
"VM Periodic Task Thread" os_prio=31 cpu=66.44ms elapsed=103.27s tid=0x00007f8bb2900800 nid=0xa403 waiting on condition

JNI global refs: 5, weak refs: 0
&lt;/pre&gt;

&lt;p&gt;My application was running 11 threads, and jstack generated a stack trace for all of them. The first thread, helpfully named main, is the one we're concerned with. You can see it sleeping on wait().&lt;/p&gt;

&lt;h2&gt;Java Stack Traces: Your Roadmap&lt;/h2&gt;

&lt;p&gt;A stack trace is more than just a picture inside your application. It's a snapshot of a moment in time that includes every step your code took to get there. There's no reason to dread seeing one in your logs because they're a gift from Java that tells you exactly what happened. Make sure you're logging them when an error crops up and send them to a tool like &lt;a href="https://www.scalyr.com/product" rel="noopener noreferrer"&gt;Scalyr&lt;/a&gt; so they're easy to find.&lt;/p&gt;

&lt;p&gt;Now that you understand what a Java stack trace is and how to use it, take a look at your code. Are you throwing away critical information about errors and &lt;a href="https://www.scalyr.com/blog/java-exceptions-log/" rel="noopener noreferrer"&gt;exceptions&lt;/a&gt; in your code? Is there a spot where a call to &lt;strong&gt;Thread.dumpstack()&lt;/strong&gt; might help you isolate a recurring bug? Perhaps it's time to run your app through the debugger a few times with some strategically-chosen breakpoints.&lt;/p&gt;

</description>
      <category>java</category>
      <category>debug</category>
      <category>programming</category>
      <category>coding</category>
    </item>
  </channel>
</rss>
