Q:

Garbage collector of singleton with daemon thread

I've implemented a logger as a singleton, where all messages go into a queue, and a daemon thread collects those messages from the queue, and prints them.
The reason I used a daemon thread is so I don't have to explicitly close the logger once I'm done with it (or the application exits).
I expected that once the logger is deleted (by the garbage collector) when the application closes, then the __del__ method would run and cleanup after. I was surprised that wasn't the case.
When I changed the thread to be non-daemon, it worked just fine (obviously I had to make some other changes so that the application would exit). I wonder if there's something I'm doing wrong, or that's just a bad practice overall.
Attached here is the code: (I would suggest that everything after the __del__ function is not interesting).
import os
import sys
import time
import Queue
import weakref
import datetime
import threading

class Logger(object):
    """
    Logger class implemented with a queue of messages, and supports only a single instace.
    This instance can be acquired by using the "GetLogger" method.
    """

    __instance = None

    @classmethod
    def GetLogger(cls, fpath, source_name=None):

        if cls.__instance is None:

            return Logger(fpath, source_name=source_name)

        else:
          if source_name is None:
              cls.__instance().log('%s@%s: %s\n' % (cls.current_date(), cls.current_time(), "Using existing Logger instance"), "REUSAGE")
          else:
              cls.__instance().log('%s@%s - %-17s: %s\n' % (cls.current_date(), cls.current_time(), source_name, "Using existing Logger instance"), "REUSAGE")

        return cls.__instance()

    def __init__(self, fpath, start_time = time.time(), source_name=None):

        if self.__instance is not None:

            raise ValueError("Singleton object already exists")

        self.__instance = weakref.ref(self)
        self.__start_time = start_time
        self.__queue = Queue.Queue()
        self.__listener = threading.Thread(target=self._listen)
        self.__listener.daemon = True
        self.__listener.start()
        if not os.path.exists(os.path.dirname(fpath)):
            os.makedirs(os.path.dirname(fpath))
        self.__f = open(fpath, 'a')
        if source_name is None:
            self.log('%s@%s: %s\n' % (self.current_date(), self.current_time(), "Created a new Logger instance"), "CREATION")
        else:
            self.log('%s@%s - %-17s: %s\n' % (self.current_date(), self.current_time(), source_name, "Created a new Logger instance"), "CREATION")

    @staticmethod
    def current_date():
        return str(datetime.datetime.now().date().isoformat())

    @staticmethod
    def current_time():
        return str(datetime.datetime.now().time().isoformat())

    def _listen(self):

        while True:

            msg = self.__queue.get()
            if msg is None:
                break

            self.__print_message(msg)

    def __print_message(self, msg_tup): # msg_tup = (message, level, stdout)
        msg_time = time.time()
        if msg_tup[2]:
            try:
                print(("|%013.6f|%-8s>>>%s" % (msg_time - self.__start_time, msg_tup[1], msg_tup[0]))),
                sys.stdout.flush()
            except:
                pass
        try:
            self.__f.write("|%013.6f|%-8s>>>%s" % (msg_time - self.__start_time, msg_tup[1], msg_tup[0]))
            self.__f.flush()
        except:
            pass

    def log(self, msg, level, to_stdout=True):
        self.__queue.put((msg, level, to_stdout))

    def close(self):
        self.__queue.put(None)
        self.__instance = None

    def __del__(self):

        while not self.__queue.empty():
            msg = self.__queue.get()
            if msg is not None:
                self.__print_message(msg)
        print("Dead...")
        self.__f.close()
        self.close()

    def info(self, msg, source_name=None, to_stdout=True):
        if source_name is None:
            self.log('%s@%s: %s\n' % (self.current_date(), self.current_time(), msg), "INFO", to_stdout=to_stdout)
        else:
            self.log('%s@%s - %-17s: %s\n' % (self.current_date(), self.current_time(), source_name, msg), "INFO", to_stdout=to_stdout)

    def debug(self, msg, source_name=None, to_stdout=True):
        if source_name is None:
            self.log('%s@%s: %s\n' %
                               (self.current_date(), self.current_time(), msg), "DEBUG", to_stdout=to_stdout)
        else:
            self.log('%s@%s - %-17s: %s\n' % (self.current_date(), self.current_time(), source_name, msg), "DEBUG", to_stdout=to_stdout)

    def trace(self, msg, sdource_name=None, to_stdout=True):
        if source_name is None:
            self.log('%s@%s: %s\n' %
                               (self.current_date(), self.current_time(), msg), "TRACE", to_stdout=to_stdout)
        else:
            self.log('%s@%s - %-17s: %s\n' % (self.current_date(), self.current_time(), source_name, msg), "TRACE", to_stdout=to_stdout)

    def warn(self, msg, source_name=None, to_stdout=True):
        if source_name is None:
            self.log('%s@%s: %s\n' %
                               (self.current_date(), self.current_time(), msg), "WARN", to_stdout=to_stdout)
        else:
            self.log('%s@%s - %-17s: %s\n' % (self.current_date(), self.current_time(), source_name, msg), "WARN", to_stdout=to_stdout)

    def error(self, msg, source_name=None, to_stdout=True):
        if source_name is None:
            self.log('%s@%s: %s\n' %
                               (self.current_date(), self.current_time(), msg), "ERROR", to_stdout=to_stdout)
        else:
            self.log('%s@%s - %-17s: %s\n' % (self.current_date(), self.current_time(), source_name, msg), "ERROR", to_stdout=to_stdout)

    def critical(self, msg, source_name=None, to_stdout=True):
        if source_name is None:
            self.log('%s@%s: %s\n' %
                               (self.current_date(), self.current_time(), msg), "CRITICAL", to_stdout=to_stdout)
        else:
            self.log('%s@%s - %-17s: %s\n' % (self.current_date(), self.current_time(), source_name, msg), "CRITICAL", to_stdout=to_stdout)

A:

The GC will never reclaim your Logger instance so long as any "live" variable in the program still contains a reference to it. The self argument of the listen(self):... method is one such variable, and that's the top-level method in which the daemon thread is running.
def _listen(self):
    while True:
        msg = self.__queue.get()
        if msg is None:
            break
        self.__print_message(msg)

The GC can not reclaim the Logger instance until the daemon thread returns from _listen(). There's only one way for that to happen:
def close(self):
    self.__queue.put(None)

If you "close" your logger, then the daemon thread eventually will get None from the queue, it will return from the _listen() call, and the daemon thread will end. But, you said

The reason I used a daemon thread is so I don't have to explicitly close the logger.

If you don't close the logger, then the daemon thread will never end, the self arg in _listen(self) will never go out of scope, and the Logger instance will never be reclaimed.

