0

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)

4
  • Possible duplicate of Redirect stdout to a file in Python? Commented Feb 4, 2016 at 6:27
  • @Rishav That article surely gives me some general insights but I'm looking for a specific solution to my specific problem. Commented Feb 4, 2016 at 6:36
  • I works if instead of using with you just assign the file to fp Commented Feb 4, 2016 at 6:52
  • @eli You're right, so the stack trace is printed outside of with... Commented Feb 4, 2016 at 6:59

3 Answers 3

2

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 
Sign up to request clarification or add additional context in comments.

3 Comments

Hmmmm I confirmed there was data in test.log
@nodakai what do you mean?
So far it seems like a valid solution to my problem but I'd like to check if Python can ensure any data won't be lost.
2

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 

5 Comments

In what sort of environment did you confirm with output_to_file('test.log'): raise Exception("I'm dying") worked? test.log was empy in my environment.
You are right. didn't test for exceptions. Fixed it. See it now
Your approach is interesting but there's a problem with it; usually, when a thrown exception is not caught, the return code of the script is set to 1 rather than 0, indicating an abnormal completion of the script. But under your try...catch..., it won't happen. This can make a difference in shell scripting.
So I got this variant, but it assumes an uncaught exception from the wrapped code block terminates the entire script. I think it kills half of the advantages of your approach. def output_to_file(filepath, write_mode='w'): stdout_orig = sys.stdout stderr_orig = sys.stderr with open(filepath, write_mode) as f: sys.stdout = f sys.stderr = f try: yield except: info = sys.exc_info() f.write('\n'.join(traceback.format_exception(*info))) sys.exit(1) finally: sys.stdout = stdout_orig sys.stderr = stderr_orig
Another option is to reraise the exception (or use only finally depending on your need), and add a try except in your main function, doing one sys.exit for all your code. Keeping this function "clean"
1

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.

1 Comment

Thanks, os.dup2 doesn't actually look like a bad solution

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.