<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Context Manager on SpaceShaman</title>
    <link>https://spaceshaman.github.io/pl/tags/context-manager/</link>
    <description>Recent content in Context Manager on SpaceShaman</description>
    <generator>Hugo</generator>
    <language>pl-PL</language>
    <copyright>SpaceShaman</copyright>
    <lastBuildDate>Thu, 27 Aug 2026 06:06:09 +0000</lastBuildDate>
    <atom:link href="https://spaceshaman.github.io/pl/tags/context-manager/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Jak tworzyć logi i nie zaśmiecać nimi kodu</title>
      <link>https://spaceshaman.github.io/pl/posts/how-to-add-logging-without-cluttering-your-code/</link>
      <pubDate>Thu, 27 Aug 2026 06:06:09 +0000</pubDate>
      <guid>https://spaceshaman.github.io/pl/posts/how-to-add-logging-without-cluttering-your-code/</guid>
      <description>Jak wykorzystać menedżery kontekstu i dekoratory, aby tworzyć szczegółowe logi bez zaśmiecania logiki biznesowej.</description>
      <content:encoded><![CDATA[<p>W jednym z moich projektów w Pythonie musiałem tworzyć dość szczegółowe logi opisujące przebieg działania programu. Kod projektu to w dużej mierze <em>glue code</em>, który korzysta z wielu zewnętrznych usług i automatyzuje procesy wykonywane wcześniej ręcznie. Nie mam nad nimi pełnej kontroli, a moja wiedza o ich działaniu jest bardzo ograniczona. Ponieważ różne procesy często korzystają z tych samych usług, opakowałem je w klasy. Przykładowy kod implementujący jedną z automatyzacji wygląda mniej więcej tak:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">clients.invoices</span> <span class="kn">import</span> <span class="n">InvoiceClient</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">clients.orders</span> <span class="kn">import</span> <span class="n">OrderClient</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">clients.partner</span> <span class="kn">import</span> <span class="n">PartnerClient</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">export_order</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">order_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">orders</span><span class="p">:</span> <span class="n">OrderClient</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">invoices</span><span class="p">:</span> <span class="n">InvoiceClient</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">partner</span><span class="p">:</span> <span class="n">PartnerClient</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">order</span> <span class="o">=</span> <span class="n">orders</span><span class="o">.</span><span class="n">get_order</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">attachments</span> <span class="o">=</span> <span class="n">orders</span><span class="o">.</span><span class="n">get_attachments</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">order</span><span class="o">.</span><span class="n">requires_invoice</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">invoice</span> <span class="o">=</span> <span class="n">invoices</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">order_id</span><span class="o">=</span><span class="n">order_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">positions</span><span class="o">=</span><span class="n">order</span><span class="o">.</span><span class="n">costs</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">payment_term</span><span class="o">=</span><span class="mi">14</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">attachments</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">invoice</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">partner</span><span class="o">.</span><span class="n">send_order</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">attachments</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">orders</span><span class="o">.</span><span class="n">set_status</span><span class="p">(</span><span class="n">order_id</span><span class="p">,</span> <span class="s2">&#34;exported&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Początkowo logi dodawałem wprost do kodu, np. tak:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">export_order</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">order_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">orders</span><span class="p">:</span> <span class="n">OrderClient</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">invoices</span><span class="p">:</span> <span class="n">InvoiceClient</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">partner</span><span class="p">:</span> <span class="n">PartnerClient</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Exporting order </span><span class="si">{</span><span class="n">order_id</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">order</span> <span class="o">=</span> <span class="n">orders</span><span class="o">.</span><span class="n">get_order</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Got order </span><span class="si">{</span><span class="n">order_id</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">order</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Getting attachments for order </span><span class="si">{</span><span class="n">order_id</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">attachments</span> <span class="o">=</span> <span class="n">orders</span><span class="o">.</span><span class="n">get_attachments</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">order</span><span class="o">.</span><span class="n">requires_invoice</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Creating invoice for order </span><span class="si">{</span><span class="n">order_id</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">invoice</span> <span class="o">=</span> <span class="n">invoices</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">order_id</span><span class="o">=</span><span class="n">order_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">positions</span><span class="o">=</span><span class="n">order</span><span class="o">.</span><span class="n">costs</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">payment_term</span><span class="o">=</span><span class="mi">14</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Created invoice for order </span><span class="si">{</span><span class="n">order_id</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">invoice</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">attachments</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">invoice</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Sending order </span><span class="si">{</span><span class="n">order_id</span><span class="si">}</span><span class="s2"> to partner&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">partner</span><span class="o">.</span><span class="n">send_order</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">attachments</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Setting status of order </span><span class="si">{</span><span class="n">order_id</span><span class="si">}</span><span class="s2"> to &#39;exported&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">orders</span><span class="o">.</span><span class="n">set_status</span><span class="p">(</span><span class="n">order_id</span><span class="p">,</span> <span class="s2">&#34;exported&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Nietrudno zauważyć, że kod został mocno zaśmiecony. Jeżeli dodamy do tego dziesiątki podobnych funkcji w całym projekcie, stanie się on nieczytelny i trudny w utrzymaniu.</p>
<p>Pierwszym krokiem do uporządkowania kodu było wyciągnięcie powtarzalnego schematu do osobnego menedżera kontekstu (<em>context managera</em>). Każdy etap procesu wyglądał przecież podobnie: zapisz informację o jego rozpoczęciu, wykonaj operację, a następnie zapisz informację o sukcesie albo błędzie.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Iterator</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">contextmanager</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">log_step</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="kc">None</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Starting: </span><span class="si">{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">exception</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Error during: </span><span class="si">{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">raise</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Finished: </span><span class="si">{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Wszystko przed <code>yield</code> wykonuje się przy wejściu do bloku <code>with</code>. Jeżeli jego zawartość zakończy się poprawnie, wykonana zostanie gałąź <code>else</code>. Jeżeli pojawi się wyjątek, menedżer kontekstu zapisze informację o błędzie i ponownie go zgłosi, dzięki czemu logowanie nie zmieni zachowania aplikacji.</p>
<p>Kod automatyzacji mogłem teraz zapisać w ten sposób:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">export_order</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">order_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">orders</span><span class="p">:</span> <span class="n">OrderClient</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">invoices</span><span class="p">:</span> <span class="n">InvoiceClient</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">partner</span><span class="p">:</span> <span class="n">PartnerClient</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">log_step</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Getting order </span><span class="si">{</span><span class="n">order_id</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">order</span> <span class="o">=</span> <span class="n">orders</span><span class="o">.</span><span class="n">get_order</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">attachments</span> <span class="o">=</span> <span class="n">orders</span><span class="o">.</span><span class="n">get_attachments</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">order</span><span class="o">.</span><span class="n">requires_invoice</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">log_step</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Creating invoice for order </span><span class="si">{</span><span class="n">order_id</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="n">invoice</span> <span class="o">=</span> <span class="n">invoices</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">order_id</span><span class="o">=</span><span class="n">order_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">positions</span><span class="o">=</span><span class="n">order</span><span class="o">.</span><span class="n">costs</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">payment_term</span><span class="o">=</span><span class="mi">14</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">attachments</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">invoice</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">log_step</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Sending order </span><span class="si">{</span><span class="n">order_id</span><span class="si">}</span><span class="s2"> to partner&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">partner</span><span class="o">.</span><span class="n">send_order</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">attachments</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">log_step</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Setting status of order </span><span class="si">{</span><span class="n">order_id</span><span class="si">}</span><span class="s2"> to &#39;exported&#39;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">orders</span><span class="o">.</span><span class="n">set_status</span><span class="p">(</span><span class="n">order_id</span><span class="p">,</span> <span class="s2">&#34;exported&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>W ten sposób przy każdym etapie nadal otrzymuję wpis informujący o jego rozpoczęciu, zakończeniu albo wystąpieniu błędu, ale logika biznesowa nie jest już poprzeplatana parami niemal identycznych wywołań <code>logger.info</code>. Menedżer kontekstu pilnuje również, aby wszystkie zdarzenia były zapisywane w jednakowym formacie.</p>
<p>Kodu jest wprawdzie trochę mniej, ale rezultat wciąż nie wygląda najlepiej. W bardziej skomplikowanych procesach kolejne bloki <code>with</code> tworzą wiele dodatkowych wcięć, co może być uciążliwe i mocno pogarszać czytelność.</p>
<p>Na tym etapie wpadłem na pomysł, aby wykorzystać dekoratory do logowania i oznaczać nimi metody klas odpowiedzialnych za komunikację z zewnętrznymi usługami. Dekorator nie musiał implementować całego mechanizmu od nowa — wystarczyło, aby uruchamiał funkcję wewnątrz przygotowanego wcześniej menedżera kontekstu:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">wraps</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">ParamSpec</span><span class="p">,</span> <span class="n">TypeVar</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">P</span> <span class="o">=</span> <span class="n">ParamSpec</span><span class="p">(</span><span class="s2">&#34;P&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">R</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;R&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">log_function</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="n">R</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="n">R</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="nd">@wraps</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">R</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">with</span> <span class="n">log_step</span><span class="p">(</span><span class="n">message</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">wrapper</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">decorator</span>
</span></span></code></pre></div><p>Taki dekorator działa również w przypadku metod instancji. Opakowana funkcja nadal jest deskryptorem, dlatego Python automatycznie przekaże instancję jako pierwszy argument <code>self</code>. Z kolei <code>@wraps</code> zachowa nazwę, dokumentację i pozostałe metadane oryginalnej metody.</p>
<p>Mogłem więc przenieść odpowiedzialność za logowanie do klas klientów:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">OrderClient</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@log_function</span><span class="p">(</span><span class="s2">&#34;Getting order&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">get_order</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">order_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Order</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_api</span><span class="o">.</span><span class="n">get_order</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@log_function</span><span class="p">(</span><span class="s2">&#34;Getting order attachments&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">get_attachments</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">order_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">Attachment</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_api</span><span class="o">.</span><span class="n">get_attachments</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@log_function</span><span class="p">(</span><span class="s2">&#34;Setting order status&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">set_status</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">order_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">status</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_api</span><span class="o">.</span><span class="n">set_status</span><span class="p">(</span><span class="n">order_id</span><span class="p">,</span> <span class="n">status</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">InvoiceClient</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@log_function</span><span class="p">(</span><span class="s2">&#34;Creating invoice&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">create</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">order_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">positions</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Cost</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="n">payment_term</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Invoice</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_api</span><span class="o">.</span><span class="n">create_invoice</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">order_id</span><span class="o">=</span><span class="n">order_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">positions</span><span class="o">=</span><span class="n">positions</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">payment_term</span><span class="o">=</span><span class="n">payment_term</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">PartnerClient</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@log_function</span><span class="p">(</span><span class="s2">&#34;Sending order to partner&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">send_order</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">order</span><span class="p">:</span> <span class="n">Order</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">attachments</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Attachment</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_api</span><span class="o">.</span><span class="n">send_order</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">attachments</span><span class="p">)</span>
</span></span></code></pre></div><p>Dzięki temu kod procesu wrócił do swojej pierwotnej postaci:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">export_order</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">order_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">orders</span><span class="p">:</span> <span class="n">OrderClient</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">invoices</span><span class="p">:</span> <span class="n">InvoiceClient</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">partner</span><span class="p">:</span> <span class="n">PartnerClient</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">order</span> <span class="o">=</span> <span class="n">orders</span><span class="o">.</span><span class="n">get_order</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">attachments</span> <span class="o">=</span> <span class="n">orders</span><span class="o">.</span><span class="n">get_attachments</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">order</span><span class="o">.</span><span class="n">requires_invoice</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">invoice</span> <span class="o">=</span> <span class="n">invoices</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">order_id</span><span class="o">=</span><span class="n">order_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">positions</span><span class="o">=</span><span class="n">order</span><span class="o">.</span><span class="n">costs</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">payment_term</span><span class="o">=</span><span class="mi">14</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">attachments</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">invoice</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">partner</span><span class="o">.</span><span class="n">send_order</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">attachments</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">orders</span><span class="o">.</span><span class="n">set_status</span><span class="p">(</span><span class="n">order_id</span><span class="p">,</span> <span class="s2">&#34;exported&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Logowanie znalazło się na granicy komunikacji z zewnętrznymi systemami, a funkcja opisująca proces ponownie pokazuje przede wszystkim jego logikę. Każde wywołanie udekorowanej metody nadal generuje wpis informujący o rozpoczęciu, zakończeniu albo wystąpieniu błędu — niezależnie od tego, w ilu automatyzacjach dany klient jest wykorzystywany.</p>
<p>To rozwiązanie ma jednak istotne ograniczenie: komunikat przekazany do dekoratora jest statyczny. Dla każdego zlecenia powstanie więc dokładnie taki sam wpis: <code>Getting order</code>. Podczas definiowania klasy nie mogę użyć zapisu <code>f&quot;Getting order {order_id}&quot;</code>, ponieważ wartość <code>order_id</code> pojawi się dopiero podczas wywołania metody.</p>
<p>Chciałem zachować prostą składnię dekoratora, a jednocześnie umożliwić odwoływanie się w komunikacie do argumentów funkcji:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">OrderClient</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@log_function</span><span class="p">(</span><span class="s2">&#34;Getting order </span><span class="si">{order_id}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">get_order</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">order_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Order</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_api</span><span class="o">.</span><span class="n">get_order</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span></code></pre></div><p>Samo wywołanie <code>message.format(**kwargs)</code> nie wystarczy. Metodę można przecież wywołać zarówno jako <code>get_order(order_id=&quot;123&quot;)</code>, jak i <code>get_order(&quot;123&quot;)</code>. W drugim przypadku słownik <code>kwargs</code> będzie pusty, natomiast <code>args</code> będzie zawierać instancję klienta oraz identyfikator zlecenia.</p>
<p>Python udostępnia do tego odpowiednie narzędzia w module <a href="https://docs.python.org/3/library/inspect.html#introspecting-callables-with-the-signature-object"><code>inspect</code></a>. Funkcja <code>signature()</code> odczytuje sygnaturę dekorowanej funkcji, a <a href="https://docs.python.org/3/library/inspect.html#inspect.Signature.bind"><code>Signature.bind()</code></a> przypisuje otrzymane argumenty pozycyjne i nazwane do nazw jej parametrów. Wynikiem jest obiekt <code>BoundArguments</code>, który zawiera gotowe mapowanie, na przykład <code>{&quot;self&quot;: client, &quot;order_id&quot;: &quot;123&quot;}</code>.</p>
<p>Rozszerzona wersja dekoratora wygląda następująco:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">wraps</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">inspect</span> <span class="kn">import</span> <span class="n">signature</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">ParamSpec</span><span class="p">,</span> <span class="n">TypeVar</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">P</span> <span class="o">=</span> <span class="n">ParamSpec</span><span class="p">(</span><span class="s2">&#34;P&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">R</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;R&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">log_function</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">message_template</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">[[</span><span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="n">R</span><span class="p">]],</span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="n">R</span><span class="p">]]:</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="n">R</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">[</span><span class="n">P</span><span class="p">,</span> <span class="n">R</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="n">func_signature</span> <span class="o">=</span> <span class="n">signature</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nd">@wraps</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">P</span><span class="o">.</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">R</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">bound_args</span> <span class="o">=</span> <span class="n">func_signature</span><span class="o">.</span><span class="n">bind</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">bound_args</span><span class="o">.</span><span class="n">apply_defaults</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="n">log_context</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">name</span><span class="p">:</span> <span class="n">value</span>
</span></span><span class="line"><span class="cl">                <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">bound_args</span><span class="o">.</span><span class="n">arguments</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">{</span><span class="s2">&#34;self&#34;</span><span class="p">,</span> <span class="s2">&#34;cls&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="n">message</span> <span class="o">=</span> <span class="n">message_template</span><span class="o">.</span><span class="n">format_map</span><span class="p">(</span><span class="n">log_context</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">with</span> <span class="n">log_step</span><span class="p">(</span><span class="n">message</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">wrapper</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">decorator</span>
</span></span></code></pre></div><p>Sygnatura jest odczytywana tylko raz — podczas dekorowania funkcji. Przy każdym jej wywołaniu <code>bind()</code> tworzy mapowanie przekazanych wartości, a <code>apply_defaults()</code> uzupełnia je wartościami domyślnymi pominiętych argumentów. Usuwam z niego <code>self</code> i <code>cls</code>, aby reprezentacja całej instancji nie trafiła przypadkowo do komunikatu. Na koniec <code>format_map()</code> podstawia wybrane wartości do szablonu.</p>
<p>Teraz dekoratory mogą korzystać z argumentów metod niezależnie od sposobu ich przekazania:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">OrderClient</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@log_function</span><span class="p">(</span><span class="s2">&#34;Getting order </span><span class="si">{order_id}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">get_order</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">order_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Order</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_api</span><span class="o">.</span><span class="n">get_order</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@log_function</span><span class="p">(</span><span class="s2">&#34;Getting attachments for order </span><span class="si">{order_id}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">get_attachments</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">order_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">Attachment</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_api</span><span class="o">.</span><span class="n">get_attachments</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@log_function</span><span class="p">(</span><span class="s2">&#34;Setting order </span><span class="si">{order_id}</span><span class="s2"> status to </span><span class="si">{status}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">set_status</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">order_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">status</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_api</span><span class="o">.</span><span class="n">set_status</span><span class="p">(</span><span class="n">order_id</span><span class="p">,</span> <span class="n">status</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">InvoiceClient</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@log_function</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Creating invoice for order </span><span class="si">{order_id}</span><span class="s2"> &#34;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;with a </span><span class="si">{payment_term}</span><span class="s2">-day payment term&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">create</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">order_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">positions</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Cost</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="n">payment_term</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Invoice</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_api</span><span class="o">.</span><span class="n">create_invoice</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">order_id</span><span class="o">=</span><span class="n">order_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">positions</span><span class="o">=</span><span class="n">positions</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">payment_term</span><span class="o">=</span><span class="n">payment_term</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">PartnerClient</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@log_function</span><span class="p">(</span><span class="s2">&#34;Sending order </span><span class="si">{order.id}</span><span class="s2"> to partner&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">send_order</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">order</span><span class="p">:</span> <span class="n">Order</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">attachments</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Attachment</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_api</span><span class="o">.</span><span class="n">send_order</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">attachments</span><span class="p">)</span>
</span></span></code></pre></div><p><a href="https://docs.python.org/3/library/string.html#format-string-syntax">Składnia formatowania</a> pozwala odwoływać się nie tylko bezpośrednio do argumentów, lecz także do ich atrybutów. Dzięki temu <code>{order.id}</code> pobierze z obiektu <code>Order</code> wyłącznie jego identyfikator. Przykładowe logi stają się znacznie bardziej użyteczne:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Starting: Getting order 123
</span></span><span class="line"><span class="cl">Finished: Getting order 123
</span></span><span class="line"><span class="cl">Starting: Creating invoice for order 123 with a 14-day payment term
</span></span><span class="line"><span class="cl">Finished: Creating invoice for order 123 with a 14-day payment term
</span></span></code></pre></div><p>Nie zdecydowałem się na automatyczne dopisywanie wszystkich argumentów. Lista kosztów, załączniki albo cały obiekt zlecenia mogłyby generować ogromne wpisy, a co gorsza, ujawnić dane osobowe, tokeny lub hasła. Szablon wymusza świadomy wybór informacji potrzebnych do zdiagnozowania problemu — zwykle są to identyfikator zlecenia, nazwa operacji i nowy status. Trzeba również pamiętać, że literówka w nazwie pola szablonu spowoduje zgłoszenie wyjątku <code>KeyError</code>, dlatego takie dekoratory warto objąć testami.</p>
<p>Ostatecznie niewielka abstrakcja pozwoliła mi zachować szczegółowe i spójne logi bez zasłaniania nimi właściwej logiki programu. Nie rozwiązuje to każdego problemu związanego z logowaniem, ale w kodzie integrującym wiele zewnętrznych usług sprawdziło się bardzo dobrze.</p>
<p>A jak wygląda logowanie w Twoich projektach? Wolisz jawne wywołania loggera, dekoratory czy inne rozwiązania? Gdzie stawiasz granicę między przydatnym kontekstem a nadmiarem informacji w logach?</p>
<p><em>Artykuł możesz również przeczytać po angielsku na <a href="https://coderlegion.com/25521/how-to-add-logging-without-cluttering-your-code">CoderLegion</a>.</em></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
