Various changes.
[python_utils.git] / logging_utils.py
index b7fd11fd8168cfbc985fa1fa60e28704b86075bd..25919a765ef2430283cb0e67572d326ca62507f0 100644 (file)
@@ -5,11 +5,13 @@
 import contextlib
 import datetime
 import enum
+import io
 import logging
 from logging.handlers import RotatingFileHandler, SysLogHandler
 import os
 import pytz
 import sys
+from typing import Iterable, Optional
 
 # This module is commonly used by others in here and should avoid
 # taking any unnecessary dependencies back on them.
@@ -93,7 +95,7 @@ cfg.add_argument(
     help='logging.info also prints to stdout.'
 )
 
-# See also: OutputMultiplexer/OutputContext
+# See also: OutputMultiplexer
 cfg.add_argument(
     '--logging_captures_prints',
     action=argparse_utils.ActionNoYes,
@@ -239,46 +241,46 @@ class OutputMultiplexer(object):
     class Destination(enum.IntEnum):
         """Bits in the destination_bitv bitvector.  Used to indicate the
         output destination."""
-        STDOUT = 0x1
-        STDERR = 0x2
-        LOG_DEBUG = 0x4          # -\
-        LOG_INFO = 0x8           #  |
-        LOG_WARNING = 0x10       #   > Should provide logger to the c'tor.
-        LOG_ERROR = 0x20         #  |
-        LOG_CRITICAL = 0x40      # _/
-        FILENAME = 0x80          # Must provide a filename to the c'tor.
-        FILEHANDLE = 0x100       # Must provide a handle to the c'tor.
-        HLOG = 0x200
+        LOG_DEBUG = 0x01         # -\
+        LOG_INFO = 0x02          #  |
+        LOG_WARNING = 0x04       #   > Should provide logger to the c'tor.
+        LOG_ERROR = 0x08         #  |
+        LOG_CRITICAL = 0x10      # _/
+        FILENAMES = 0x20         # Must provide a filename to the c'tor.
+        FILEHANDLES = 0x40       # Must provide a handle to the c'tor.
+        HLOG = 0x80
         ALL_LOG_DESTINATIONS = (
             LOG_DEBUG | LOG_INFO | LOG_WARNING | LOG_ERROR | LOG_CRITICAL
         )
-        ALL_OUTPUT_DESTINATIONS = 0x2FF
+        ALL_OUTPUT_DESTINATIONS = 0x8F
 
     def __init__(self,
                  destination_bitv: int,
                  *,
                  logger=None,
-                 filename=None,
-                 handle=None):
+                 filenames: Optional[Iterable[str]] = None,
+                 handles: Optional[Iterable[io.TextIOWrapper]] = None):
         if logger is None:
             logger = logging.getLogger(None)
         self.logger = logger
 
-        if filename is not None:
-            self.f = open(filename, "wb", buffering=0)
+        if filenames is not None:
+            self.f = [
+                open(filename, 'wb', buffering=0) for filename in filenames
+            ]
         else:
-            if self.destination_bitv & OutputMultiplexer.FILENAME:
+            if destination_bitv & OutputMultiplexer.FILENAMES:
                 raise ValueError(
-                    "Filename argument is required if bitv & FILENAME"
+                    "Filenames argument is required if bitv & FILENAMES"
                 )
             self.f = None
 
-        if handle is not None:
-            self.h = handle
+        if handles is not None:
+            self.h = [handle for handle in handles]
         else:
-            if self.destination_bitv & OutputMultiplexer.FILEHANDLE:
+            if destination_bitv & OutputMultiplexer.Destination.FILEHANDLES:
                 raise ValueError(
-                    "Handle argument is required if bitv & FILEHANDLE"
+                    "Handle argument is required if bitv & FILEHANDLES"
                 )
             self.h = None
 
@@ -288,13 +290,13 @@ class OutputMultiplexer(object):
         return self.destination_bitv
 
     def set_destination_bitv(self, destination_bitv: int):
-        if destination_bitv & self.Destination.FILENAME and self.f is None:
+        if destination_bitv & self.Destination.FILENAMES and self.f is None:
             raise ValueError(
-                "Filename argument is required if bitv & FILENAME"
+                "Filename argument is required if bitv & FILENAMES"
             )
-        if destination_bitv & self.Destination.FILEHANDLE and self.h is None:
+        if destination_bitv & self.Destination.FILEHANDLES and self.h is None:
             raise ValueError(
-                    "Handle argument is required if bitv & FILEHANDLE"
+                    "Handle argument is required if bitv & FILEHANDLES"
                 )
         self.destination_bitv = destination_bitv
 
@@ -315,25 +317,23 @@ class OutputMultiplexer(object):
             sep = " "
         if end is None:
             end = "\n"
-        if self.destination_bitv & self.Destination.STDOUT:
-            print(buf, file=sys.stdout, sep=sep, end=end)
-        if self.destination_bitv & self.Destination.STDERR:
-            print(buf, file=sys.stderr, sep=sep, end=end)
         if end == '\n':
             buf += '\n'
         if (
-                self.destination_bitv & self.Destination.FILENAME and
+                self.destination_bitv & self.Destination.FILENAMES and
                 self.f is not None
         ):
-            self.f.write(buf.encode('utf-8'))
-            self.f.flush()
+            for _ in self.f:
+                _.write(buf.encode('utf-8'))
+                _.flush()
 
         if (
-                self.destination_bitv & self.Destination.FILEHANDLE and
+                self.destination_bitv & self.Destination.FILEHANDLES and
                 self.h is not None
         ):
-            self.h.write(buf)
-            self.h.flush()
+            for _ in self.h:
+                _.write(buf)
+                _.flush()
 
         buf = strip_escape_sequences(buf)
         if self.logger is not None:
@@ -352,21 +352,22 @@ class OutputMultiplexer(object):
 
     def close(self):
         if self.f is not None:
-            self.f.close()
+            for _ in self.f:
+                _.close()
 
 
-class OutputContext(OutputMultiplexer, contextlib.ContextDecorator):
+class OutputMultiplexerContext(OutputMultiplexer, contextlib.ContextDecorator):
     def __init__(self,
                  destination_bitv: OutputMultiplexer.Destination,
                  *,
-                 logger=None,
-                 filename=None,
-                 handle=None):
+                 logger = None,
+                 filenames = None,
+                 handles = None):
         super().__init__(
             destination_bitv,
             logger=logger,
-            filename=filename,
-            handle=handle)
+            filenames=filenames,
+            handles=handles)
 
     def __enter__(self):
         return self