ACL uses enums, some more tests, other stuff.
[python_utils.git] / logging_utils.py
index cf784caf3d6a1c8d557f459634763d21f0d311b4..700bfabcf9b9bbba8b28ca199253de40296a6ccc 100644 (file)
@@ -3,27 +3,29 @@
 """Utilities related to logging."""
 
 import contextlib
+import datetime
 import logging
 from logging.handlers import RotatingFileHandler, SysLogHandler
 import os
+import pytz
 import sys
 
+# This module is commonly used by others in here and should avoid
+# taking any unnecessary dependencies back on them.
 import argparse_utils
 import config
-import string_utils as su
-import thread_utils as tu
 
-parser = config.add_commandline_args(
+cfg = config.add_commandline_args(
     f'Logging ({__file__})',
     'Args related to logging')
-parser.add_argument(
+cfg.add_argument(
     '--logging_config_file',
     type=argparse_utils.valid_filename,
     default=None,
     metavar='FILENAME',
     help='Config file containing the logging setup, see: https://docs.python.org/3/howto/logging.html#logging-advanced-tutorial',
 )
-parser.add_argument(
+cfg.add_argument(
     '--logging_level',
     type=str,
     default='INFO',
@@ -31,59 +33,59 @@ parser.add_argument(
     metavar='LEVEL',
     help='The level below which to squelch log messages.',
 )
-parser.add_argument(
+cfg.add_argument(
     '--logging_format',
     type=str,
     default='%(levelname)s:%(asctime)s: %(message)s',
     help='The format for lines logged via the logger module.'
 )
-parser.add_argument(
+cfg.add_argument(
     '--logging_date_format',
     type=str,
-    default='%Y/%m/%dT%H:%M:%S%z',
+    default='%Y/%m/%dT%H:%M:%S.%f%z',
     metavar='DATEFMT',
     help='The format of any dates in --logging_format.'
 )
-parser.add_argument(
+cfg.add_argument(
     '--logging_console',
     action=argparse_utils.ActionNoYes,
     default=True,
     help='Should we log to the console (stderr)',
 )
-parser.add_argument(
+cfg.add_argument(
     '--logging_filename',
     type=str,
     default=None,
     metavar='FILENAME',
     help='The filename of the logfile to write.'
 )
-parser.add_argument(
+cfg.add_argument(
     '--logging_filename_maxsize',
     type=int,
     default=(1024*1024),
     metavar='#BYTES',
     help='The maximum size (in bytes) to write to the logging_filename.'
 )
-parser.add_argument(
+cfg.add_argument(
     '--logging_filename_count',
     type=int,
     default=2,
     metavar='COUNT',
     help='The number of logging_filename copies to keep before deleting.'
 )
-parser.add_argument(
+cfg.add_argument(
     '--logging_syslog',
     action=argparse_utils.ActionNoYes,
     default=False,
     help='Should we log to localhost\'s syslog.'
 )
-parser.add_argument(
+cfg.add_argument(
     '--logging_debug_threads',
     action=argparse_utils.ActionNoYes,
     default=False,
     help='Should we prepend pid/tid data to all log messages?'
 )
-parser.add_argument(
+cfg.add_argument(
     '--logging_info_is_print',
     action=argparse_utils.ActionNoYes,
     default=False,
@@ -96,6 +98,19 @@ class OnlyInfoFilter(logging.Filter):
         return record.levelno == logging.INFO
 
 
+class MillisecondAwareFormatter(logging.Formatter):
+    converter = datetime.datetime.fromtimestamp
+
+    def formatTime(self, record, datefmt=None):
+        ct = self.converter(record.created, pytz.timezone("US/Pacific"))
+        if datefmt:
+            s = ct.strftime(datefmt)
+        else:
+            t = ct.strftime("%Y-%m-%d %H:%M:%S")
+            s = "%s,%03d" % (t, record.msecs)
+        return s
+
+
 def initialize_logging(logger=None) -> logging.Logger:
     assert config.has_been_parsed()
     if logger is None:
@@ -129,7 +144,7 @@ def initialize_logging(logger=None) -> logging.Logger:
 #            for k, v in encoded_priorities.items():
 #                handler.encodePriority(k, v)
             handler.setFormatter(
-                logging.Formatter(
+                MillisecondAwareFormatter(
                     fmt=fmt,
                     datefmt=config.config['logging_date_format'],
                 )
@@ -145,7 +160,7 @@ def initialize_logging(logger=None) -> logging.Logger:
         )
         handler.setLevel(numeric_level)
         handler.setFormatter(
-            logging.Formatter(
+            MillisecondAwareFormatter(
                 fmt=fmt,
                 datefmt=config.config['logging_date_format'],
             )
@@ -156,7 +171,7 @@ def initialize_logging(logger=None) -> logging.Logger:
         handler = logging.StreamHandler(sys.stderr)
         handler.setLevel(numeric_level)
         handler.setFormatter(
-            logging.Formatter(
+            MillisecondAwareFormatter(
                 fmt=fmt,
                 datefmt=config.config['logging_date_format'],
             )
@@ -184,7 +199,8 @@ def get_logger(name: str = ""):
 
 def tprint(*args, **kwargs) -> None:
     if config.config['logging_debug_threads']:
-        print(f'{tu.current_thread_id()}', end="")
+        from thread_utils import current_thread_id
+        print(f'{current_thread_id()}', end="")
         print(*args, **kwargs)
     else:
         pass
@@ -243,6 +259,7 @@ class OutputSink(object):
         self.destination_bitv = destination_bitv
 
     def print(self, *args, **kwargs):
+        from string_utils import sprintf, strip_escape_sequences
         end = kwargs.pop("end", None)
         if end is not None:
             if not isinstance(end, str):
@@ -253,7 +270,7 @@ class OutputSink(object):
                 raise TypeError("sep must be None or a string")
         if kwargs:
             raise TypeError("invalid keyword arguments to print()")
-        buf = su.sprintf(*args, end="", sep=sep)
+        buf = sprintf(*args, end="", sep=sep)
         if sep is None:
             sep = " "
         if end is None:
@@ -267,7 +284,7 @@ class OutputSink(object):
         if self.destination_bitv & self.FILENAME and self.f is not None:
             self.f.write(buf.encode('utf-8'))
             self.f.flush()
-        buf = su.strip_escape_sequences(buf)
+        buf = strip_escape_sequences(buf)
         if self.logger is not None:
             if self.destination_bitv & self.LOG_DEBUG:
                 self.logger.debug(buf)