nodakai
nodakai

Reputation: 8001

Python: capturing both of sys.stdout and sys.stderr as a log file

Roughly speaking, I want to port this to pure Python:

#!/bin/bash

{
    python test.py
} &> /tmp/test.log

This didn't work for some unknown reasons:

import os.path, sys
import tempfile

with open(os.path.join(tempfile.gettempdir(), "test.log"), "a") as fp:
    sys.stdout = sys.stderr = fp
    raise Exception("I'm dying")

The resulting test.log was empty (and I didn't see anything on my console,) when I tested it with Python 2.6.6, Python 2.7.8 and Python 3.4.2 on CentOS x86_64.

But Ideally I'd like a solution for Python 2.6.

(For now, it's tolerable to clutter the log file with intermixed output from stdout and stderr or multithreading, as long as any data won't simply disappear into a blackhole.)

Show me a concise and portable solution which is confirmed to work with an exception stack trace on sys.stderr. (Preferably something other than os.dup2)

Upvotes: 0

Views: 2164

Answers (3)

Uri Shalit
Uri Shalit

Reputation: 2308

You can use a method like this one:

import traceback
import sys
from contextlib import contextmanager


@contextmanager
def output_to_file(filepath, write_mode='w'):
    stdout_orig = None
    stderr_orig = None

    stdout_orig = sys.stdout
    stderr_orig = sys.stderr

    f = open(filepath, write_mode)

    sys.stdout = f
    sys.stderr = f

    try:
        yield
    except:
        info = sys.exc_info()
        f.write('\n'.join(traceback.format_exception(*info)))

    f.close()

    sys.stdout = stdout_orig
    sys.stderr = stderr_orig

And the the usage is:

with output_to_file('test.log'):
    print('hello')
    raise Exception('I am dying')

And the cat test.log produces:

hello
Traceback (most recent call last):

  File "<ipython-input-3-a3b702c7b741>", line 20, in outputi_to_file
    yield

  File "<ipython-input-4-f879d82580b2>", line 3, in <module>
    raise Exception('I am dying')

Exception: I am dying

Upvotes: 2

xrisk
xrisk

Reputation: 3898

Remember that file objects are closed after with blocks :)

Use simply this:

sys.stdout = sys.stderr = open("test.log","w")
raise Exception("Dead")

Content of test.log after exit:

Traceback (most recent call last):
  File "test.py", line 5, in <module>
    raise Exception("Dead")
Exception: Dead

Upvotes: 2

Jim Dennis
Jim Dennis

Reputation: 17500

This works for me:

#!/usr/bin/env python
from __future__ import print_function
import os, os.path, sys, tempfile

old_out = os.dup(sys.stdout.fileno())
old_err = os.dup(sys.stderr.fileno())
with open(os.path.join(tempfile.gettempdir(), "test.log"), "a") as fp:
    fd = fp.fileno()
    os.dup2(fd, sys.stdout.fileno())
    os.dup2(fd, sys.stderr.fileno())
    print("Testing")
    print('testing errs', file=sys.stderr)
    raise Exception("I'm dying")

The future is just for cleaner handling of Python2 or Python3 with the same example. (I've also changed the raise statement to instantiate an exception, strings as exceptions have been deprecated for a long time and they're not properly supported under Python3).

The old_* values are just if we wanted to restore our original stdout and/or stderr after using our redirected file.

Upvotes: 1

Related Questions