فهرست منبع

Implement colored logs (#377)

This PR implements:

- Colored logs without extra dependencies.
    - The log level is colored depending on message severity. The log level and caller info are displayed in bold (see the screenshots).
    - By default, colors are disabled if stderr is redirected to a file/pipe. This can be overridden via the env variable.
- Shorter log format. This allows more messages to fit into one terminal line and makes the message easier to read.
    - The date became shorter by excluding the year. Since the `09/02` notation becomes ambiguous in this case, it is changed to `Sep 02` (which, in my opinion, also improves readability).
Alexander Borzunov 4 سال پیش
والد
کامیت
457e6bc2d3
2فایلهای تغییر یافته به همراه42 افزوده شده و 9 حذف شده
  1. 1 7
      examples/albert/utils.py
  2. 41 2
      hivemind/utils/logging.py

+ 1 - 7
examples/albert/utils.py

@@ -7,7 +7,7 @@ from hivemind import choose_ip_address
 from hivemind.dht.crypto import RSASignatureValidator
 from hivemind.dht.schema import BytesWithPublicKey, SchemaValidator
 from hivemind.dht.validation import RecordValidatorBase
-from hivemind.utils.logging import get_logger
+from hivemind.utils.logging import TextStyle, get_logger
 
 logger = get_logger(__name__)
 
@@ -30,12 +30,6 @@ def make_validators(experiment_prefix: str) -> Tuple[List[RecordValidatorBase],
     return validators, signature_validator.local_public_key
 
 
-class TextStyle:
-    BOLD = "\033[1m"
-    BLUE = "\033[34m"
-    RESET = "\033[0m"
-
-
 def log_visible_maddrs(visible_maddrs: List[Multiaddr], only_p2p: bool) -> None:
     if only_p2p:
         unique_addrs = {addr["p2p"] for addr in visible_maddrs}

+ 41 - 2
hivemind/utils/logging.py

@@ -1,8 +1,33 @@
 import logging
 import os
+import sys
 
 loglevel = os.getenv("LOGLEVEL", "INFO")
 
+_env_colors = os.getenv("HIVEMIND_COLORS")
+if _env_colors is not None:
+    use_colors = _env_colors.lower() == "true"
+else:
+    use_colors = sys.stderr.isatty()
+
+
+class TextStyle:
+    """
+    ANSI escape codes. Details: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
+    """
+
+    RESET = "\033[0m"
+    BOLD = "\033[1m"
+    RED = "\033[31m"
+    BLUE = "\033[34m"
+    PURPLE = "\033[35m"
+    ORANGE = "\033[38;5;208m"  # From 8-bit palette
+
+    if not use_colors:
+        # Set the constants above to empty strings
+        _codes = locals()
+        _codes.update({_name: "" for _name in list(_codes) if _name.isupper()})
+
 
 class CustomFormatter(logging.Formatter):
     """
@@ -10,6 +35,15 @@ class CustomFormatter(logging.Formatter):
     ``logger.log(level, message, extra={"origin_created": ..., "caller": ...})``.
     """
 
+    # Details: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
+    _LEVEL_TO_COLOR = {
+        logging.DEBUG: TextStyle.PURPLE,
+        logging.INFO: TextStyle.BLUE,
+        logging.WARNING: TextStyle.ORANGE,
+        logging.ERROR: TextStyle.RED,
+        logging.CRITICAL: TextStyle.RED,
+    }
+
     def format(self, record: logging.LogRecord) -> str:
         if hasattr(record, "origin_created"):
             record.created = record.origin_created
@@ -18,6 +52,11 @@ class CustomFormatter(logging.Formatter):
         if not hasattr(record, "caller"):
             record.caller = f"{record.name}.{record.funcName}:{record.lineno}"
 
+        # Aliases for the format argument
+        record.levelcolor = self._LEVEL_TO_COLOR[record.levelno]
+        record.bold = TextStyle.BOLD
+        record.reset = TextStyle.RESET
+
         return super().format(record)
 
 
@@ -27,9 +66,9 @@ def get_logger(module_name: str) -> logging.Logger:
 
     logging.addLevelName(logging.WARNING, "WARN")
     formatter = CustomFormatter(
-        fmt="[{asctime}.{msecs:03.0f}][{levelname}][{caller}] {message}",
+        fmt="{asctime}.{msecs:03.0f} [{bold}{levelcolor}{levelname}{reset}] [{bold}{caller}{reset}] {message}",
         style="{",
-        datefmt="%Y/%m/%d %H:%M:%S",
+        datefmt="%b %d %H:%M:%S",
     )
     handler = logging.StreamHandler()
     handler.setFormatter(formatter)