<?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: Damian</title>
    <description>The latest articles on Forem by Damian (@kampikd).</description>
    <link>https://forem.com/kampikd</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%2F374868%2F90269b2a-02ab-4d50-a8a5-3c5dae9931c1.jpg</url>
      <title>Forem: Damian</title>
      <link>https://forem.com/kampikd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kampikd"/>
    <language>en</language>
    <item>
      <title>Adding contextual data to Python logging</title>
      <dc:creator>Damian</dc:creator>
      <pubDate>Mon, 26 Apr 2021 22:15:10 +0000</pubDate>
      <link>https://forem.com/kampikd/adding-contextual-data-to-python-logging-3h53</link>
      <guid>https://forem.com/kampikd/adding-contextual-data-to-python-logging-3h53</guid>
      <description>&lt;p&gt;Recently I faced an exciting challenge. I needed to create a robust logging system integrated with an external API. One of the requirements was to have specific contextual data logged whenever it was available. The main issue was that a lot of it was spread across the whole application, nested within different functions.&lt;/p&gt;

&lt;p&gt;The most trivial approach of passing down necessary objects through the functions would bloat the code unnecessarily. I started to think about how to leverage context managers to collect the data. After several iterations, together with the team, I came up with an elegant solution, which gave us extra fluff we could use in the app. Let me show you the code and explain the reasoning behind it. Ready? Let’s move on!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up logging context handler
&lt;/h2&gt;

&lt;p&gt;First, we will create a data store for our context manager. It needs to have three capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we need to be able to add more contextual data to it&lt;/li&gt;
&lt;li&gt;it should return a specific attribute value by its name&lt;/li&gt;
&lt;li&gt;it should be able to remove contextual data when no longer needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve this, we will create a stack with dictionaries as their elements. The dictionaries will contain a set of contextual data to be used in logging. Let’s see how it looks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoggingContextHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;([{}])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;new_context_vars&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;old_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;new_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;old_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;new_context_vars&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appendleft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s take a deeper look at the &lt;code&gt;add&lt;/code&gt; method because that’s where the magic happens.&lt;/p&gt;

&lt;p&gt;Whenever we add new data to the context, &lt;code&gt;add&lt;/code&gt; clones the dictionary from the top of the stack and updates it with newly added content. This way, we won’t need to iterate over the whole stack to get an attribute hidden deep within it.&lt;/p&gt;

&lt;p&gt;I mentioned a context manager a couple of times, but where is it? Let me fix that right away!&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating context manager
&lt;/h2&gt;

&lt;p&gt;There’s nothing fancy, just a simple function. Whatever you use as the argument will be pushed to the handler defined above. After the successful execution of the wrapped code, the manager will drop the data from the context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;logging_context_handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LoggingContextHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;contextmanager&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;logging_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;logging_context_handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;yield&lt;/span&gt;

    &lt;span class="n"&gt;logging_context_handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adjusting log records with filters
&lt;/h2&gt;

&lt;p&gt;Now that we have the data source and a way to manage it, we can add the data to the log records. The recommended way is to use filters for that, so let’s create one and attach it to a logging handler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContextFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ContextFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging_context_handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"store"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging_context_handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"client"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging_context_handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"item"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;


&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;context_filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ContextFilter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context_filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Such a filter will add attributes from logging context to each log record. Of course, we can go crazy with what we’d like to do with that. But for the sake of simplicity, let’s build a formatter that displays our newly added attributes in a logger of our choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a formatter
&lt;/h2&gt;

&lt;p&gt;We don’t need to build anything robust. For our purposes, a simple &lt;code&gt;setFormatter&lt;/code&gt; function call would be enough.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;format_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"[%(store)s | %(client)s | %(item)s]: %(message)s"&lt;/span&gt;
&lt;span class="n"&gt;stdout_formatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Formatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;format_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;stdout_handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StreamHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;stdout_handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout_formatter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout_handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let’s see how it all works
&lt;/h2&gt;

&lt;p&gt;With the setup done, we can now check how it’s all used. Let me show you a simple app as an example.&lt;/p&gt;

&lt;p&gt;Imagine that we have a grocery store, and we want to track to whom we sell the goods through logging. We have two clients, Jim and Tim, each with his shopping list. Let’s build a code that will allow us to sell goods to them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;logs&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;logs.logging_context&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging_context&lt;/span&gt;

&lt;span class="n"&gt;clients&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Jim"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"potatoes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tomatoes"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"Tim"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"bread"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"eggs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"milk"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sell_goods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shopping_list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;shopping_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;logging_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sold 1 item."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;logging_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Hannah's Grocery Store"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shopping_list&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;logging_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;sell_goods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shopping_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the only argument passed to &lt;code&gt;sell_goods&lt;/code&gt; function is a shopping list since there’s no need to add anything else.&lt;/p&gt;

&lt;p&gt;After you run the script, it will produce such an output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Hannah's Grocery Store | Jim | potatoes]: Sold 1 item.
[Hannah's Grocery Store | Jim | tomatoes]: Sold 1 item.
[Hannah's Grocery Store | Tim | bread]: Sold 1 item.
[Hannah's Grocery Store | Tim | eggs]: Sold 1 item.
[Hannah's Grocery Store | Tim | milk]: Sold 1 item.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, all data stored in context get displayed in logs.&lt;/p&gt;

&lt;p&gt;We can take this even further. Let’s say that the &lt;code&gt;sell_goods&lt;/code&gt; function also checks if an item is available and throws an error when there’ none. If we logged the exception, we could then see that next clients didn’t buy eggs, because the store ran out of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beware of the base logger!
&lt;/h2&gt;

&lt;p&gt;One critical thing to mention is that you should create a separate logger for your app.&lt;/p&gt;

&lt;p&gt;Once, I mistakenly added the handlers to the base logger, and then I saw external libraries throwing errors at me. After a short investigation, it turned out that they were all trying to add extra attributes to log records without having access to them. Save yourself from frustration, and don’t do this, please.&lt;/p&gt;




&lt;p&gt;I hope that you had a good read and have another neat solution to add to your toolbox!&lt;/p&gt;

&lt;p&gt;If you’d like to download the project to check its capabilities immediately, here’s a link to the repository: &lt;a href="https://github.com/kampikd/logging-context-python"&gt;https://github.com/kampikd/logging-context-python&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to pull the code and play around with it!&lt;/p&gt;

&lt;p&gt;Many thanks to Piotr Kotnis for plenty of thought-provoking ideas 💙. Without his help and insights, this solution would not be as refined as it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/howto/logging-cookbook.html"&gt;https://docs.python.org/3/howto/logging-cookbook.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>logging</category>
    </item>
    <item>
      <title>What is Test Driven Development and why it is so awesome</title>
      <dc:creator>Damian</dc:creator>
      <pubDate>Sat, 13 Jun 2020 19:46:42 +0000</pubDate>
      <link>https://forem.com/kampikd/what-is-test-driven-development-and-why-it-is-so-awesome-53n4</link>
      <guid>https://forem.com/kampikd/what-is-test-driven-development-and-why-it-is-so-awesome-53n4</guid>
      <description>&lt;p&gt;&lt;strong&gt;At the beginning of my career as a Ruby developer, I used to work on a project that had barely any tests.&lt;/strong&gt; This, combined with poor code quality, made the development of any new feature similar to a walk through a minefield — you never knew if your change is going to crash the app, even in a different place. Add my lack of knowledge at the time and a lot of stress, and you get a nightmare of experience for any beginner programmer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honestly, it was a miserable time.&lt;/strong&gt; The project left such a vivid memory that it serves as an ongoing warning to avoid similar ones at all costs.&lt;/p&gt;

&lt;p&gt;It’s been a while, and, luckily, I had a chance to learn proper practices, among them — Test-Driven Development. I can’t recall how many times it saved me from coding struggles in the course of my career.&lt;/p&gt;

&lt;p&gt;Hence, knowing both sides of the coin, &lt;em&gt;&lt;strong&gt;I want others to be able to kick-start their programming journey instead of making it a struggle.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let me explain what’s the general idea behind TDD and give you a couple of reasons to give it a shot in your software development process. So, without further ado, let’s get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Test-Driven Development?
&lt;/h2&gt;

&lt;p&gt;Test-Driven Development (or TDD) is a technique of software development in which you turn the requirements for a specific piece of code to a set of tests. Then, you write the code that should meet the requirements and make the tests pass. When you’re satisfied with the outcome, pick a new feature, and start all over. This perpetual process will result in a test suite created along with your main codebase.&lt;/p&gt;

&lt;p&gt;Sounds easy, but why should you add an extra layer of code to maintain? There are plenty of reasons to do so:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. It makes you think through your code better
&lt;/h2&gt;

&lt;p&gt;When you plan a feature, it’s worth giving extra attention to good tests. There are plenty of things to consider when creating a new feature, among them performance, readability, etc. Still, no matter how your code is going to perform, you need to have it working correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tests put your focus on the expected results.&lt;/strong&gt; After you prepare them, you can work on providing a piece of code that will meet the requirements.&lt;/p&gt;

&lt;p&gt;Once you have it figured out, you can focus on code augmentation in a way that will let you achieve the same outcome, but in a better way. And if you ever get lost, there is a checkpoint you can always roll back to and start anew with a working piece of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. You can reliably introduce changes to the code
&lt;/h2&gt;

&lt;p&gt;In the early days of the project, it’s tempting to forfeit automated tests. I mean — why would you if you can grasp the whole concept? But what if the project grows and it becomes harder to remember every single bit of it?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think of writing tests as an investment&lt;/strong&gt; — you need to sacrifice some time now to save a lot of it in the future. If you forget about the tests at the very beginning, you will waste time and nerves while manually checking if everything works fine. And the bigger the app is, the worse this problem will become. On the other hand, a good test suite gives you the ability to check if the new changes are compatible with the rest of the codebase.&lt;/p&gt;

&lt;p&gt;Moreover, it will save you plenty of time. Instead of clicking through the whole app you will run the test suite and wait for the results. If there’s an error, simply fix the code and you’re good to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Well-written tests can serve as documentation
&lt;/h2&gt;

&lt;p&gt;Your code might behave in many different ways depending on the conditions. Good tests dispel all doubts by providing a clear description of the code’s behavior.&lt;/p&gt;

&lt;p&gt;If you ever forget how a class works, you can come back to the test suite and check it. Moreover, it helps in the onboarding of new team members by being a single source of truth about the code.&lt;/p&gt;

&lt;p&gt;I find this especially prominent when using Behavior-Driven Development frameworks like RSpec. To me, such tests have much more expressive syntax than regular ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I like the verbosity of tests created with BDD frameworks.&lt;/strong&gt; It resembles natural language closer, hence making it easier to read even if you don’t know the programming language well. Still, either choice is fine, as long as you care for the quality of the tests. Just remember that better tests equal better documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. They can be easily automated
&lt;/h2&gt;

&lt;p&gt;You can easily set up automated tools that help you keep good code quality by running tests. You can do it on various levels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One common practice is to set up Continuous Integration (CI) tools.&lt;/strong&gt; CI is a development practice in which developers frequently integrate their code into a shared repository. It intends to make the app as up-to-date as possible. Such an approach requires quality checks. Running tests on a dedicated server is one way to accomplish that. There are plenty of ready-made solutions in the market like TravisCI and CircleCI. You can also set up Jenkins on your server to run the tests for you.&lt;/p&gt;

&lt;p&gt;And if that’s not enough, there’s plenty of tools you can run on your machine to monitor changes in your files and run appropriate tests. A couple of examples would be Guard for Ruby or Jest tests, where you append &lt;code&gt;— watch&lt;/code&gt; flag to &lt;code&gt;npm run test&lt;/code&gt; command.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;I hope that I managed to show you the major benefits of using TDD.&lt;/strong&gt; From my observations, it isn’t only another way to make your code better. Rather, it has become an industry standard and it happened for a reason. If you’re striving to make yourself a reliable developer, this is a way to go. Also, if you’re working on a project alone and have nobody else to check your code, or maybe have a worse day, &lt;em&gt;&lt;strong&gt;automated tests can save you a lot of fuss by catching things you would have missed otherwise.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tdd</category>
      <category>beginners</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
