<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Eric Stein</title>
    <link>https://www.ericstein.org/</link>
    <description></description>
    <pubDate>Tue, 12 May 2026 11:45:08 +0000</pubDate>
    <item>
      <title>Logging when print is the only option</title>
      <link>https://www.ericstein.org/logging-when-print-is-the-only-option?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[(Originally published on my old github blog on  2016-02-09)&#xA;&#xA;I ran into a particularly annoying problem while working with Python&#39;s ftplib. My script, which downloads a large number of files from an FTP server overnight, was stopping. It wasn&#39;t failing, rather, just hanging. This code runs as part of a  larger script that contains logging functions. I thought I should add logging for the FTP calls.&#xA;&#xA;The docs describe a setdebug method for the FTP instance. Great. I can use that and see how the script is interacting with the server. The problem is that the FTP module uses print statements to pass this debugging info.&#xA;&#xA;if self.debugging:&#xA;    print &#39;welcome&#39;, self.sanitize(self.welcome)&#xA;&#xA;I could use redirection to capture stdout, but I want the script to use the logging I&#39;ve already set up.&#xA;&#xA;This allowed me to play with decorators and resulted in this:&#xA;&#xA;import sys&#xA;import StringIO&#xA;def logoutput(level=&#39;info&#39;):&#xA;    def wrapper(f):&#xA;        def inner(args, kwargs):&#xA;            # Save the old stdout&#xA;            oldstdout = sys.stdout&#xA;            # Set stdout to a file like object&#xA;            sys.stdout = StringIO.StringIO()&#xA;            # run the original function&#xA;            output = f(args, *kwargs)&#xA;            # write to the specific logging level&#xA;            getattr(logging, level)(sys.stdout.getvalue())&#xA;            # set stdout to what it was before &#xA;            sys.stdout = oldstdout &#xA;            return output&#xA;        return inner&#xA;    return wrapper&#xA;&#xA;And here&#39;s how it&#39;s used:&#xA;&#xA;import logging&#xA;import datetime&#xA;import ftplib&#xA;&#xA;logging.basicConfig(filename=os.path.join(&#39;logdir&#39;, &#39;downloads{}.log&#39;.format(datetime.date.today()),&#xA;    level=logging.DEBUG, format=&#39;%(asctime)s %(message)s&#39;)&#xA;&#xA;@logoutput(level=&#39;debug&#39;)&#xA;def download():&#xA;    server = ftplib.FTP(&#39;ftp.example.com&#39;)&#xA;    server.setdebuglevel(1)&#xA;    server.login()&#xA;    with open(&#39;wanted.txt&#39;, &#39;w&#39;) as outfile:&#xA;        server.retrbinary(&#39;RETR wanted.txt&#39;, oufile.write, blocksize=8192)&#xA;    server.quit()&#xA;&#xA;logging.debug(&#39;Starting the download function&#39;)&#xA;download()&#xA;&#xA;This will give you one log file per day with very nice output:&#xA;&#xA;2016-02-06 00:15:05,437 Starting the download function.&#xA;2016-02-06 00:15:05,570 cmd &#39;USER anonymous&#39;&#xA;resp &#39;331 Anonymous login ok&#39;&#xA;resp &#39;230-Anonymous access granted, restrictions apply\n \n Please read the file README.txt\n230    it was last modified on Tue Aug 15 14:29:31 2000 - 5653 days ago&#39;&#xA;2016-02-06 00:15:05,959 cmd &#39;TYPE I&#39;&#xA;resp &#39;200 Type set to I&#39;&#xA;cmd &#39;PASV&#39;&#xA;resp &#39;227 Entering Passive Mode (162,138,203,13,190,33).&#39;&#xA;cmd &#39;RETR wanted.txt&#39;&#xA;resp &#39;150 Opening BINARY mode data connection for wanted.txt (207503 bytes)&#39;&#xA;resp* &#39;226 Transfer complete&#39;&#xA;`]]&gt;</description>
      <content:encoded><![CDATA[<p>(Originally published on my old github blog on  2016-02-09)</p>

<p>I ran into a particularly annoying problem while working with Python&#39;s ftplib. My script, which downloads a large number of files from an FTP server overnight, was stopping. It wasn&#39;t failing, rather, just hanging. This code runs as part of a  larger script that contains logging functions. I thought I should add logging for the FTP calls.</p>

<p>The <a href="https://docs.python.org/2/library/ftplib.html">docs</a> describe a <code>set_debug</code> method for the FTP instance. Great. I can use that and see how the script is interacting with the server. The problem is that the FTP module uses print statements to pass this debugging info.</p>

<pre><code class="language-python">if self.debugging:
    print &#39;*welcome*&#39;, self.sanitize(self.welcome)
</code></pre>

<p>I could use redirection to capture stdout, but I want the script to use the logging I&#39;ve already set up.</p>

<p>This allowed me to play with decorators and resulted in this:</p>

<pre><code>import sys
import StringIO
def logoutput(level=&#39;info&#39;):
    def wrapper(f):
        def inner(*args, **kwargs):
            # Save the old stdout
            oldstdout = sys.stdout
            # Set stdout to a file like object
            sys.stdout = StringIO.StringIO()
            # run the original function
            output = f(*args, **kwargs)
            # write to the specific logging level
            getattr(logging, level)(sys.stdout.getvalue())
            # set stdout to what it was before 
            sys.stdout = oldstdout 
            return output
        return inner
    return wrapper
</code></pre>

<p>And here&#39;s how it&#39;s used:</p>

<pre><code>
import logging
import datetime
import ftplib

logging.basicConfig(filename=os.path.join(&#39;logdir&#39;, &#39;downloads{}.log&#39;.format(datetime.date.today()),
    level=logging.DEBUG, format=&#39;%(asctime)s %(message)s&#39;)

@logoutput(level=&#39;debug&#39;)
def download():
    server = ftplib.FTP(&#39;ftp.example.com&#39;)
    server.set_debuglevel(1)
    server.login()
    with open(&#39;wanted.txt&#39;, &#39;w&#39;) as outfile:
        server.retrbinary(&#39;RETR wanted.txt&#39;, oufile.write, blocksize=8192)
    server.quit()

logging.debug(&#39;Starting the download function&#39;)
download()
</code></pre>

<p>This will give you one log file per day with very nice output:</p>

<pre><code>2016-02-06 00:15:05,437 Starting the download function.
2016-02-06 00:15:05,570 *cmd* &#39;USER anonymous&#39;
*resp* &#39;331 Anonymous login ok&#39;
*resp* &#39;230-Anonymous access granted, restrictions apply\n \n Please read the file README.txt\n230    it was last modified on Tue Aug 15 14:29:31 2000 - 5653 days ago&#39;
2016-02-06 00:15:05,959 *cmd* &#39;TYPE I&#39;
*resp* &#39;200 Type set to I&#39;
*cmd* &#39;PASV&#39;
*resp* &#39;227 Entering Passive Mode (162,138,203,13,190,33).&#39;
*cmd* &#39;RETR wanted.txt&#39;
*resp* &#39;150 Opening BINARY mode data connection for wanted.txt (207503 bytes)&#39;
*resp* &#39;226 Transfer complete&#39;
</code></pre>
]]></content:encoded>
      <guid>https://www.ericstein.org/logging-when-print-is-the-only-option</guid>
      <pubDate>Wed, 01 Dec 2021 03:03:39 +0000</pubDate>
    </item>
  </channel>
</rss>