# (c) Copyright 2009-2010, 2015. CodeWeavers, Inc.
"""Handles logging and error reporting."""
import os
import sys
import time
try:
import thread
except ImportError:
# pylint: disable=F0401
import _thread as thread
# internal variables
# pylint: disable=C0103
_all_on = None
_channels = {}
_log_file = None
_start = None
#####
#
# Logging API
#
#####
def log_to(out, append=True):
"""Sets up logging to the specified file object, name, or handle.
If append is true, then the log is appended to.
"""
# pylint: disable=W0603
global _log_file
if sys.version_info >= (3,):
if append:
mode = 'at'
else:
mode = 'wt'
if hasattr(out, 'write'):
# file-like object
_log_file = out
else:
# file handle or filename
_log_file = open(out, mode, 1, 'utf8', 'backslashreplace')
else:
if append:
mode = 'ab'
else:
mode = 'wb'
if hasattr(out, 'write'):
# file-like object
_log_file = out
elif isinstance(out, int):
# file handle
_log_file = os.fdopen(out, mode, 0)
else:
# filename
_log_file = open(out, mode, 0)
def get_file():
"""Returns the file the traces are sent to if any, and None otherwise."""
return _log_file
def get_filename():
"""Returns the name of the file that receives the logs.
If logging goes to a file descriptor, then "&n" is returned where 'n' is
the file descriptor. If logging is turned off, then None is returned.
Occasionally exceptions were thrown here causing all kinds of
weird hangs and breakages. Since logging is non-critical,
we now catch all exceptions during writing.
"""
if not _log_file:
return None
try:
name = _log_file.name
if name == '<fdopen>':
name = '&%s' % _log_file.fileno()
except AttributeError:
name = repr(_log_file)
return name
def is_on(channel=None):
"""Returns False if logging is turned off globally or for the specified
channel. Otherwise returns True.
The channels correspond to what is put in $CX_DEBUGMSG.
"""
if _log_file is None:
return False
if channel is None:
return _all_on is not False
if channel in _channels:
return _channels[channel]
return _all_on is True
def init_logging():
"""Sets up logging based on the CX_LOG and CX_DEBUGMSG variables.
This is called automatically when cxlog is first imported.
"""
if 'CX_LOG' in os.environ:
if os.environ['CX_LOG'] == '-':
log_to(sys.stderr)
else:
try:
log_to(os.environ['CX_LOG'])
except IOError:
ioerror = sys.exc_info()[1]
warn("could not open log output file: " + unicode(ioerror))
elif 'CX_DEBUGMSG' in os.environ:
log_to(sys.stderr)
if 'CX_DEBUGMSG' in os.environ:
# pylint: disable=W0603
global _all_on
for channel in os.environ['CX_DEBUGMSG'].split(','):
if channel == '+all':
_all_on = True
elif channel == '-all':
_all_on = False
elif channel.startswith('-'):
_channels[channel.lstrip('-')] = False
else:
_channels[channel.lstrip('+')] = True
if is_on('timestamp'):
global _start
_start = time.time()
def _tid_prefix():
if is_on('tid'):
return '%s:' % thread.get_ident()
return ''
def _timestamp_prefix():
if _start is None:
return ''
return '%.3f:' % (time.time() - _start)
try:
unicode_type = unicode
except NameError:
unicode_type = str
def _to_str(string, always_repr):
if always_repr:
result = repr(string)
elif isinstance(string, str):
return string
elif isinstance(string, unicode_type):
return string.encode('utf8')
else:
result = repr(string)
if isinstance(result, str):
return result
return "<object of type %s could not be converted to a string>" % _to_str(type(string), True)
def to_str(string):
"""Format an arbitrary object for printing.
The result is guaranteed to be a string, and this function will never cause
an exception. If it's a string or unicode object, the contents are used
literally."""
try:
return _to_str(string, False)
except: # pylint: disable=W0702
return "<exception raised while converting an object to string>"
def debug_str(string):
"""Format an arbitrary object for debugging purposes.
The result is guaranteed to be a string, and this function will never cause
an exception. If possible, this is a python representation of the object,
or something in <angle brackets>."""
try:
return _to_str(string, True)
except: # pylint: disable=W0702
return "<exception raised while converting an object to string>"
def log(message):
"""Prints a log message.
What is printed is 'fmt % args' and a linefeed is automatically added.
"""
if _log_file and _all_on is not False:
_log_file.write('%s%s%s\n' % (_timestamp_prefix(), _tid_prefix(), to_str(message)))
def log_(channel, message):
"""Same as cxlog(), but the message is only logged if logging is turned on
for the specified channel.
"""
if is_on(channel):
_log_file.write("%s%s%s:%s\n" % (_timestamp_prefix(), _tid_prefix(), to_str(channel), to_str(message)))
#####
#
# Error and warning reporting interface
#
#####
def name0():
"""Returns the current executable basename."""
return os.path.basename(sys.argv[0])
def warn(message):
"""Issues a warning to the user on stderr.
A linefeed is automatically appended to the message.
"""
msg = name0() + ":warning: " + to_str(message) + "\n"
sys.stderr.write(msg)
if _log_file and _log_file != sys.stderr:
_log_file.write(msg)
def err(message):
"""Issues an error to the user on stderr.
A linefeed is automatically appended to the message.
"""
msg = name0() + ":error: " + to_str(message) + "\n"
sys.stderr.write(msg)
if _log_file and _log_file != sys.stderr:
_log_file.write(msg)
init_logging()