#!/usr/bin/env python

"""A centralized abstraction layer for logging via facilities.

log = LogObj()
log.nothing = Nolog()
log.debug = File(file='debuglog.txt', flags='w', bufsize=0, longlog=1)
log.info = File(sys.stdout)
log.info.deps.append('debug')
log.error = Syslog(LOG_LOCAL5|LOG_ERR)
log.error.deps.append('info')
log.crit = File(sys.stderr, longlog=1)
log.crit.deps.append('debug')
print >> log.error, 'This will go to syslog, stdout, and debuglog.txt.'
log.debug.write('This won\'t get flushed until the line is complete, ')
log.debug.write('or the buffer is flushed.')
log.debug.flush()
log.nothing('The file object is also callable, ')
log.nothing('but since this is a Nolog object, nothing will be done here.\\n')
print >> log.crit, 'This is a call to stderr with no dependencies.'
"""

import sys, time, types, os

try:
  from syslog import *
except ImportError:
  # Simulate a syslog module if one doesn't exist (win32)
  LOG_EMERG = 0
  LOG_ALERT = 0
  LOG_CRIT = 0
  LOG_ERR = 0
  LOG_WARNING = 0
  LOG_NOTICE = 0
  LOG_INFO = 0
  LOG_DEBUG = 0

  LOG_PID = 0
  LOG_CONS = 0
  LOG_NDELAY = 0
  LOG_NOWAIT = 0
  LOG_PERROR = 0

  LOG_KERN = 0
  LOG_USER = 0
  LOG_MAIL = 0
  LOG_DAEMON = 0
  LOG_AUTH = 0
  LOG_LPR = 0
  LOG_LOCAL0 = 0
  LOG_LOCAL1 = 0
  LOG_LOCAL2 = 0
  LOG_LOCAL3 = 0
  LOG_LOCAL4 = 0
  LOG_LOCAL5 = 0
  LOG_LOCAL6 = 0
  LOG_LOCAL7 = 0
  LOG_SYSLOG = 0
  LOG_CRON = 0
  LOG_UUCP = 0
  LOG_NEWS = 0

  def openlog(logname, logopt):
    print >> sys.stderr, 'syslog.openlog(%s, %s)' % \
      (repr(logname), repr(logopt))

  def closelog():
    print >> sys.stderr, 'syslog.closelog()'

  def syslog(logopt, buf):
    print >> sys.stderr, 'syslog.syslog(%s, %s)' % \
      (repr(logopt), repr(buf))


class LogObj:
  def __repr__(self):
    return repr(self.__dict__)

  def __setattr__(self, k, v):
    """D[k] = v -> None.  Sets an attribute with a PseudoFile object."""
    if isinstance(v, PseudoFile):
      self.__dict__[k] = v
      self.__dict__[k].parentdata = self.__dict__
      self.__dict__[k].facility = k
    else:
      raise AttributeError, "Value must be a PseudoFile object"

  def __getattr__(self, k):
    """D[k] -> PseudoFile sub-classed object, Nolog if key doesn't exist"""
    if k[:2] == '__':
      return self.__dict__[k]
    else:
      if self.__dict__.has_key(k):
        return self.__dict__[k]
      else:
        return Nolog()

  def __getitem__(self, k):
    """D.k -> see __getattr__"""
    return getattr(self, k)

  def __setitem__(self, k, v):
    """D.k = v -> see __setattr__"""
    return setattr(self, k, v)

  def __len__(self):
    """len(D) -> number of keys in dictionary"""
    return len(self.__dict__.keys())

  def __contains__(self, k):
    """k in D -> see has_key"""
    return self.__dict__.has_key(k)

  def __del__(self):
    """del(D) -> None.  Clears the dictionary before deleting it."""
    self.clear()

  def keys(self):
    """D.keys() -> list of D's keys"""
    return self.__dict__.keys()

  def values(self):
    """D.values() -> list of D's values"""
    return self.__dict__.values()

  def items(self):
    """D.items() -> list of D's (key, value) pairs, as 2-tuples"""
    return self.__dict__.items()

  def has_key(self, key):
    """D.has_key(k) -> 1 if D has a key k, else 0"""
    return self.__dict__.has_key(key)

  def get(self, key, default = None):
    """D.get(k[,d]) -> D[k] if D.has_key(k), else d.  d defaults to None."""
    return self.__dict__.get(key, default)

  def clear(self):
    """D.clear() -> None.  Remove all items from D."""
    return self.__dict__.clear()

  def copy(self):
    """D.copy() -> a shallow copy of D"""
    out = self.__dict__.copy()
    for i in out.keys():
      out[i].deps = []
      out[i].aprentdata = {}
      out[i].facility = 'manual'
    return out

  def update(self, E):
    """D.update(E) -> None.  Update D from E: for k in E.keys(): D[k] = E[k]"""
    for k in E.keys():
      setattr(self, k, E[k])
    return None

class PseudoFile:
  def __init__(self):
    """D = PseudoFile() -> file-like object.  Logging to this does nothing."""
    self.deps = []
    self.buffer = ''
    self.iid = 'PseudoFile'
    self.parentdata = {}
    self.facility = 'manual'

  
  def __del__(self):
    """del(D) -> None.  Severs dependencies and closes the file."""
    self.deps = []
    self.parentdata = {}
    self.close()

  def __call__(self, message):
    """D() -> None.  Logs a message."""
    self.write(message)

  def flush(self):
    """D.flush() -> None.  Flushes the line buffer."""
    pass

  def write(self, message, iidlist=''):
    """D.write(message) -> None.  Writes data to the line buffer."""
    if iidlist == '':
      iidlist = []
    if self.iid not in iidlist:
      iidlist.append(self.iid)
      lines = message.split('\n')
      if len(lines) == 1:
        self.buffer += lines[0]
      else:
        for i in lines[:-1]:
          self.buffer += i
          self.flush()
        if lines[-1] != '':
          self.buffer += lines[-1]

    for i in self.deps:
      if i in self.parentdata.keys():
        self.parentdata[i].write(message, iidlist)

  def writelines(self, mlist):
    """D.writelines(mlist) -> None.  Calls D.write() over a list."""
    for message in mlist:
      self.write(message)

  def close(self):
    """D.close() -> None.  Flushes the line buffer"""
    self.flush()

  def tell(self):
    """D.tell() -> 0.  Always returns 0."""
    return 0

  def truncate(self, size=0):
    """D.truncate(size) -> None.  Does nothing."""
    pass

class Nolog(PseudoFile):
  def __init__(self):
    """D = Nolog() -> file-like object.  Logging to this does nothing."""
    self.deps = []
    self.buffer = ''
    self.iid = 'Nolog'
    self.parentdata = {}
    self.facility = 'manual'

class Syslog(PseudoFile):
  logopt = 0

  def __init__(self, logopt=-1):
    """D = Syslog(logopt) -> file-like object.  Logs to Syslog.

    Keyword arguments:
    logopt -- Syslog data inherited from syslog.  Default: LOG_LOCAL5|LOG_ERR

    """
    self.counter = 0
    if logopt == -1:
      logopt = (LOG_LOCAL5|LOG_ERR)
    self.filename = '.'.join(os.path.basename(sys.argv[0]).split('.')[:-1])
    if not self.filename:
      self.filename = 'console'
    self.deps = []
    self.buffer = ''
    self.logopt = logopt
    self.iid = ('Syslog', logopt)
    self.parentdata = {}
    self.facility = 'manual'

  def flush(self):
    """D.flush() -> None.  Flushes the line buffer."""
    if self.buffer:
      buffer = self.buffer
      self.buffer = ''
      toout = []
      while buffer:
        toout.append(buffer[:256])
        buffer = buffer[256:]
      for buffer in toout:
        self.counter += 1
        openlog(self.filename, self.logopt)
        syslog(self.logopt, '%08d %s: %s' % (self.counter, self.facility, \
          buffer))
        closelog()

class File(PseudoFile):
  openfile = sys.stderr

  def __init__(self, file=sys.stderr, flags='a', bufsize=1, longlog=0):
    """D = File(file, flags, bufsize, longlog) -> file-like object.
      Logs to a file or file-like object.

    Keyword arguments:
    file -- File to log to.  Can be a string filename or a file-like object.
    flags -- Flags to open the file with if string passed as file argument.
    bufsize -- Buffer size if string passed as file argument.  0 to disable
      buffering, 1 for line buffering, any other number to specify buffer
      size.  This applies to the file only.  This object itself will always
      behave as if it is line buffered.
    longlog -- 1 for a verbose line format (full date/filename/facility),
      0 for a short line format (short date, default).

    """
    self.filename = '.'.join(os.path.basename(sys.argv[0]).split('.')[:-1])
    if not self.filename:
      self.filename = 'console'
    self.deps = []
    self.buffer = ''
    self.iid = ('File', file)
    self.parentdata = {}
    self.facility = 'manual'
    self.longlog = longlog
    if type(file) == types.FileType:
      self.openfile = file
    else:
      self.openfile = open(file, flags, bufsize)

  def __del__(self):
    """del(D) -> None.  Severs dependencies and closes the file."""
    self.openfile.close()
    PseudoFile.__del__(self)

  def flush(self):
    """D.flush() -> None.  Flushes the line buffer."""
    if self.buffer:
      if self.longlog:
        print >> self.openfile, \
          "%s (%s/%s): %s" % \
          (time.ctime(), \
          self.filename, self.facility, self.buffer)
      else:
        itime = time.localtime()
        print >> self.openfile, "%02d:%02d:%02d %s" % \
          (itime[3], itime[4], itime[5], self.buffer)
      self.buffer = ''


def test():
  """test() -> None.  Tests logger capability."""
  log = LogObj()
  log.nothing = Nolog()
  log.debug = File(file='debuglog.txt', flags='w', bufsize=0, longlog=1)
  log.info = File(sys.stdout)
  log.info.deps.append('debug')
  log.error = Syslog(LOG_LOCAL5 | LOG_ERR)
  log.error.deps.append('info')
  log.crit = File(sys.stderr, longlog=1)
  log.crit.deps.append('debug')
  print >> log.error, 'This will go to syslog, stdout, and debuglog.txt.'
  log.debug.write('This won\'t get flushed until the line is complete, ')
  log.debug.write('or the buffer is flushed.')
  log.debug.flush()
  log.nothing('The file object is also callable, ')
  log.nothing('but since this is a Nolog object, nothing will be done here.\n')
  print >> log.crit, 'This is a call to stderr with no dependencies.'

if __name__ == "__main__":
  test()
