#!/usr/bin/python

# tz_simple_logger.py
#       --copyright--                   Copyright 2007 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       November 8, 2006        bar
#       November 18, 2007       bar     turn on doxygen
#       November 20, 2007       bar     comments
#       November 27, 2007       bar     insert boilerplate copyright
#       May 17, 2008            bar     email adr
#       November 19, 2008       bar     trap an exception that doesn't make a lot of sense
#       May 8, 2009             bar     write()
#       May 14, 2009            bar     close protection
#       June 3, 2009            bar     put lock around 'on' in log()
#       March 6, 2010           bar     get the schizo out of filtering
#       November 5, 2011        bar     a_zip_logger and flush param
#       --eodstamps--
##      \file
#
#
#       Simple logger to append text lines to a file.
#
#       Can snag stdout and stderr - though will only do so after at least one call to write/log.
#       The log file is opened by demand. Nothing logged, nothing opened.
#
#

import  random
import  os
import  sys
import  threading
import  zipfile


MAX_LOG_FILE_SIZE       = 20000000      # default size for a_zip_logger - only is checked when the log file is opened and closed

LOG_STDOUT_ERR          = False


class   a_logger(object) :
    """ Class to log stuff how I want. """

    def __init__(me, file_name = "", log_stdout_err = LOG_STDOUT_ERR, on = True) :
        """
            file_name  is the file to create/append-to
            on         is can be used to turn logging on and off

            The file is opened by demand.
                The first log() call forces it open.
        """


        me.to_filter    = LOG_STDOUT_ERR    if (log_stdout_err is None) else    log_stdout_err
        me.filtering    = False
        me._file_name   = file_name
        me._on          = on

        me.fo           = None

        me.stdout       = None
        me.stderr       = None

        me.lock         = threading.RLock()



    def on(me, on = None) :
        """
            Set/get whether the log actually is output.
            Returns current (replaced) value.
            Log is not flushed or closed.
        """
        me.lock.acquire()

        ov  = me._on
        if  on != None :
            me._on  = on

        me.lock.release()

        return(ov)


    def log_stdout_err(me, how = None) :
        """
            Set/get whether to filter stdout and stderr.
            Return current (replaced) setting.
        """
        me.lock.acquire()

        ov  = me.to_filter
        if  how != None :
            me.to_filter        = how
            if  me.fo           :
                fov             = me.filtering
                me.filtering    = how
                if  me.filtering and not fov :
                    me.stdout   = sys.stdout
                    me.stderr   = sys.stderr

                    sys.stdout  = me.fo
                    sys.stderr  = me.fo

                elif (not me.filtering) and fov :
                    if  sys.stdout == me.fo :
                        sys.stdout  = me.stdout
                        me.stdout   = None
                    if  sys.stderr == me.fo :
                        sys.stderr  = me.stderr
                        me.stderr   = None
                    pass

                pass
            pass

        me.lock.release()

        return(ov)


    filter_stdout_err   = log_stdout_err                # stay compatible with old, unfortunate name


    def file_name(me, file_name = None) :
        """
            Set/get the log file name.
            If the name changes, close the current log.
        """

        me.lock.acquire()

        ov  = me._file_name

        if  file_name   != None :
            if  me.fo and (file_name != ov) :
                me.close()
            pass

            me._file_name   = file_name

        me.lock.release()

        return(ov)




    def log(me, li = "", flush = False) :
        """
            Write a text line out to the log file.
            Force open the file if needed.
            And start filtering stdout and stderr if wanted.
        """

        if  me._on :

            me.lock.acquire()

            if  me._on :

                if  not me.fo  and me._file_name :
                    me.fo   = open(me._file_name, "a")

                    me.log_stdout_err(me.to_filter)

                if  me.fo :
                    print >> me.fo, li.rstrip()
                    if  flush :
                        me.flush()
                    pass

                pass

            me.lock.release()

        pass


    write   = log


    def flush(me) :
        """
            If the log file is open, flush it.
        """
        me.lock.acquire()
        if  me.fo :
            try :
                me.fo.flush()
            except IOError :                # ???? why does this happen? locked net file from aborted, earlier program run. What?
                pass
        me.lock.release()



    def close(me) :
        """
            If the log file is open, close it.
            Note: The file will be opened again if log() is called without first calling on(False).
        """
        me.lock.acquire()

        me.log_stdout_err(False)

        fo      = me.fo
        me.fo   = None
        if  fo  :

            try :
                fo.close()
            except IOError :                # uh. net drive problem?
                pass
            del(fo)                         # really flushes the output

        me.lock.release()


    pass        # a_logger


class   a_zip_logger(a_logger) :

    def check_zip(me, file_name = "", max_log_size = None) :
        me.lock.acquire()

        if  (not me.fo) and me._file_name :
            if  os.path.isfile(me._file_name) and (os.path.getsize(me._file_name) >= (me.max_log_size or MAX_LOG_FILE_SIZE)) :
                zfn         = "%s_%04u.zip" % ( os.path.splitext(me._file_name)[0], random.randint(0, 9999) )
                for i in range(10000) :
                    tfn     = "%s_%04u.zip" % ( os.path.splitext(me._file_name)[0], i )
                    if not os.path.isfile(tfn) :
                        zfn = tfn
                        break
                try         :
                    zf      = zipfile.ZipFile(zfn, "w", zipfile.ZIP_DEFLATED)
                    zf.write(me._file_name)
                    zf.close()
                    os.remove(me._file_name)
                except ( OSError, IOError ) :
                    pass
                pass
            pass

        me.lock.release()


    def close(me) :
        super(a_zip_logger, me).close()
        me.check_zip()


    def __init__(me,                     file_name = "",        log_stdout_err = False,          on = True, max_log_size = None) :
        me.max_log_size = max_log_size or MAX_LOG_FILE_SIZE
        super(a_zip_logger, me).__init__(file_name = file_name, log_stdout_err = log_stdout_err, on = on)
        me.check_zip()


    #   a_zip_logger



#
#
#       Test
#
#
if __name__ == '__main__' :

    me  = a_zip_logger("tmp.tmp", True)

    print "this should not go to the log"

    me.log("now is the time\r\n\r\n")

    me.flush()

    print "this should go to the log"
    print >> sys.stderr, "this too"

    me.on(False)
    me.close()

#
#
#
# eof

