0

This is my sample code

class StreamToLogger(object):
    def __init__(self, logger, log_level=logging.INFO):
        self.logger = logger
        self.log_level = log_level
        self.linebuf = ''

    def write(self, buf):
        for line in buf.rstrip().splitlines():
            self.logger.log(self.log_level, line.rstrip())



logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s:%(levelname)s:%(name)s:%(message)s',
                    filename="logger.log",
                    filemode='w'
                    )

stdout_logger = logging.getLogger('STDOUT')
sl = StreamToLogger(stdout_logger, logging.INFO)
sys.stdout = sl

stderr_logger = logging.getLogger('STDERR')
sl = StreamToLogger(stderr_logger, logging.ERROR)
sys.stderr = sl

suite = suite()
tests = unittest.TextTestRunner(descriptions=True, verbosity=2).run(suite)

As TextTestRunner by default uses stream as stderr. I have used the stderr to write the output in the log file and it's working. Can we use stderr multiple times in same script?

2 Answers2

1

I did a new analyze of your code. The problem there is

  1. you define two streams for stdout and stderr. TextTextRunner uses only one stream: sys.stderr per default.
  2. TextTestRunner uses sys.stderr per default and this ist not your sys.stderr. That seems strange, see later. So your code works as not having defined any other output stream.

The situation changes when you call TextTextRunner() not with its defaults but if you pass explicitly your redefined sys.stderr. Now the output will be redirected to your file:

# does not at all what you want (writes output to stderr):
tests = unittest.TextTestRunner( descriptions=True, verbosity=2 ).run(suite)
# does not everything what you want (writes output to file only):
tests = unittest.TextTestRunner\
                           ( stream=sys.stderr, descriptions=True, verbosity=2 ).run(suite) 

One requirement is satisfied (write to a file) but the other one not. This is because only one of your defined streams is used (the one which you passed to TextTestRunner via stream). For the second requirement (write to console as well) there must be a way to write from one stream (the redirected stderr) into two output devices. This is in my opinion only possible if you change the your StreamToLogger.write() method in an appropriate way:

def write( self, buf ) :
    my_console_stream_instance.write(buf)
    my_file_stream_instance.write(buf)

where my_console_stream_instance is the stream which writes into the console and my_file_stream_instanceis the stream which writes into the file. So please look at the following example which should do what you want:

class StreamToLogger( object ):
    def __init__( self ):
        self.terminal = sys.stderr
        self.log = open( "logger.log", "w" )

    def write( self, buf ):
        self.terminal.write(buf) # write to stderr
        self.log.write(buf) # write to file

sys.stderr = StreamToLogger()

suite = suite()
tests = unittest.TextTestRunner\
                         ( stream=sys.stderr, descriptions=True, verbosity=2 ).run( suite )

Now there are really two outputs: one to the screen and one into the file.

But why do you need to pass sys.stderr to the stream parameter of TextTestRunner? At the first glance it seems to be the same. But in The Python Language Reference in the chapter function definitions is the explanation:

Default parameter values are evaluated when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that the same “pre-computed” value is used for each call. This is especially important to understand when a default parameter is a mutable object.

The default sys.stderr of the stream parameter in TextTestRunner is evaluated before the execution of the stderr redefinition. This is why we must pass our redefinded sys.stderr anyhow. A little proof is: redefine sys.stderr before you import unittest: the code works well without passing the redefined sys.stderr.

Humbalan
  • 677
  • 3
  • 14
  • Thanks Humbalan! For long and understandable answer. This code & logic worked for my case. – shivba gadakh Jul 07 '16 at 06:38
  • I'm glad to be able to help. Thinking about your question was also a benefit for me, since the reasons for the "default parameter behaviour" were not really clear for me, now they are. – Humbalan Jul 07 '16 at 07:55
0

For me it is not really clear, what you would like to do. Writing all the console outputs of the logging module to a file and to the console as well? If so do that:

# Create a logging object
logger = logging.getLogger ( 'STDOUT+FILE' )
logger.setLevel ( logging.DEBUG )

# Create a file handler and set level to INFO (or other value)
file_ch = logging.FileHandler( 'logger.log', 'w' )
file_ch.setLevel ( logging.DEBUG )
# ... and add it to the logging object
logger.addHandler( file_ch )

# Create a stream  handler and set level to INFO
console_ch = logging.StreamHandler( )
console_ch.setLevel ( logging.DEBUG )
# ... and add it to the logging object
logger.addHandler( console_ch )

# Do some logging
logger.debug( 'Logging Test' )
logger.debug( '------------' )
logger.info( 'this is an info log' )
logger.warn( 'this is a warn log' )
logger.debug( 'this is a debug log' )
logger.error( 'this is an error log' )
logger.critical( 'this is a critical log' )

Content of myLogFile.log is now

Logging Test
------------ 
this is an info log 
this is a warn log 
this is a debug log 
this is a critical log

The same text appears on the console

More info here

Humbalan
  • 677
  • 3
  • 14
  • I want to write the output getting from `tests = unittest.TextTestRunner(descriptions=True, verbosity=2).run(suite) ` to logger file and console. Currenltly it's only writing in logger file but not on console. – shivba gadakh Jul 05 '16 at 09:53
  • The answer of Amith Koujalgi in [this post](http://stackoverflow.com/questions/14906764/how-to-redirect-stdout-to-both-file-and-console-with-scripting) is the solution. Just add a `stream=sys.stdout` in the TextTestRunner() call and the mentioned class `Logger`. – Humbalan Jul 05 '16 at 11:29
  • that solution not working for me. I think there is some conflict between testrunnner's `stderr` and `sys.stderr = sl` which is in my sample code (refer question) – shivba gadakh Jul 05 '16 at 13:34
  • Did you try it with stdout as mentioned in my last comment 2 hours ago? – Humbalan Jul 05 '16 at 13:37