Files
py-libp2p/libp2p/utils/logging.py
Arush Kurundodi bdadec7519 ft. modernise py-libp2p (#618)
* fix pyproject.toml , add ruff

* rm lock

* make progress

* add poetry lock ignore

* fix type issues

* fix tcp type errors

* fix text example - type error - wrong args

* add setuptools to dev

* test ci

* fix docs build

* fix type issues for new_swarm & new_host

* fix types in gossipsub

* fix type issues in noise

* wip: factories

* revert factories

* fix more type issues

* more type fixes

* fix: add null checks for noise protocol initialization and key handling

* corrected argument-errors in peerId and Multiaddr in peer tests

* fix: Noice - remove redundant type casts in BaseNoiseMsgReadWriter

* fix: update test_notify.py to use SwarmFactory.create_batch_and_listen, fix type hints, and comment out ClosedStream assertions

* Fix type checks for pubsub module

Signed-off-by: sukhman <sukhmansinghsaluja@gmail.com>

* Fix type checks for pubsub module-tests

Signed-off-by: sukhman <sukhmansinghsaluja@gmail.com>

* noise: add checks for uninitialized protocol and key states in PatternXX

Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>

* pubsub: add None checks for optional fields in FloodSub and Pubsub

Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>

* Fix type hints and improve testing

Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>

* remove redundant checks

Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>

* fix build issues

* add optional to trio service

* fix types

* fix type errors

* Fix type errors

Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>

* fixed more-type checks in crypto and peer_data files

* wip: factories

* replaced union with optional

* fix: type-error in interp-utils and peerinfo

* replace pyright with pyrefly

* add pyrefly.toml

* wip: fix multiselect issues

* try typecheck

* base check

* mcache test fixes , typecheck ci update

* fix ci

* will this work

* minor fix

* use poetry

* fix wokflow

* use cache,fix err

* fix pyrefly.toml

* fix pyrefly.toml

* fix cache in ci

* deploy commit

* add main baseline

* update to v5

* improve typecheck ci (#14)

* fix typo

* remove holepunching code (#16)

* fix gossipsub typeerrors (#17)

* fix: ensure initiator user includes remote peer id in handshake (#15)

* fix ci (#19)

* typefix: custom_types | core/peerinfo/test_peer_info | io/abc | pubsub/floodsub | protocol_muxer/multiselect (#18)

* fix: Typefixes in PeerInfo  (#21)

* fix minor type issue (#22)

* fix type errors in pubsub (#24)

* fix: Minor typefixes in tests (#23)

* Fix failing tests for type-fixed test/pubsub (#8)

* move pyrefly & ruff to pyproject.toml & rm .project-template (#28)

* move the async_context file to tests/core

* move crypto test to crypto folder

* fix: some typefixes (#25)

* fix type errors

* fix type issues

* fix: update gRPC API usage in autonat_pb2_grpc.py (#31)

* md: typecheck ci

* rm comments

* clean up : from review suggestions

* use | None over Optional as per new python standards

* drop supporto for py3.9

* newsfragments

---------

Signed-off-by: sukhman <sukhmansinghsaluja@gmail.com>
Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>
Co-authored-by: acul71 <luca.pisani@birdo.net>
Co-authored-by: kaneki003 <sakshamchauhan707@gmail.com>
Co-authored-by: sukhman <sukhmansinghsaluja@gmail.com>
Co-authored-by: varun-r-mallya <varunrmallya@gmail.com>
Co-authored-by: varunrmallya <100590632+varun-r-mallya@users.noreply.github.com>
Co-authored-by: lla-dane <abhinavagarwalla6@gmail.com>
Co-authored-by: Collins <ArtemisfowlX@protonmail.com>
Co-authored-by: Abhinav Agarwalla <120122716+lla-dane@users.noreply.github.com>
Co-authored-by: guha-rahul <52607971+guha-rahul@users.noreply.github.com>
Co-authored-by: Sukhman Singh <63765293+sukhman-sukh@users.noreply.github.com>
Co-authored-by: acul71 <34693171+acul71@users.noreply.github.com>
Co-authored-by: pacrob <5199899+pacrob@users.noreply.github.com>
2025-06-09 11:39:59 -06:00

216 lines
7.0 KiB
Python

import atexit
from datetime import (
datetime,
)
import logging
import logging.handlers
import os
from pathlib import (
Path,
)
import queue
import sys
import threading
from typing import (
Any,
)
# Create a log queue
log_queue: "queue.Queue[Any]" = queue.Queue()
# Store the current listener to stop it on exit
_current_listener: logging.handlers.QueueListener | None = None
# Event to track when the listener is ready
_listener_ready = threading.Event()
# Default format for log messages
DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
def _parse_debug_modules(debug_str: str) -> dict[str, int]:
"""
Parse the LIBP2P_DEBUG environment variable to determine module-specific log levels.
Format examples:
- "DEBUG" # All modules at DEBUG level
- "libp2p.identity.identify:DEBUG" # Only identify module at DEBUG
- "identity.identify:DEBUG" # Same as above, libp2p prefix is optional
- "libp2p.identity:DEBUG,libp2p.transport:INFO" # Multiple modules
"""
module_levels: dict[str, int] = {}
# Handle empty or whitespace-only string
if not debug_str or debug_str.isspace():
return module_levels
# If it's a plain log level without any colons, apply to all
if ":" not in debug_str and debug_str.upper() in logging._nameToLevel:
return {"": getattr(logging, debug_str.upper())}
# Handle module-specific levels
for part in debug_str.split(","):
if ":" not in part:
continue
module, level = part.split(":")
level = level.upper()
if level not in logging._nameToLevel:
continue
# Handle module name
module = module.strip()
# Remove libp2p prefix if present, it will be added back when creating logger
module = module.replace("libp2p.", "")
# Convert any remaining dots to ensure proper format
module = module.replace("/", ".").strip(".")
module_levels[module] = getattr(logging, level)
return module_levels
def setup_logging() -> None:
"""
Set up logging configuration based on environment variables.
Environment Variables:
LIBP2P_DEBUG
Controls logging levels. Examples:
- "DEBUG" (all modules at DEBUG level)
- "libp2p.identity.identify:DEBUG" (only identify module at DEBUG)
- "identity.identify:DEBUG" (same as above, libp2p prefix optional)
- "libp2p.identity:DEBUG,libp2p.transport:INFO" (multiple modules)
LIBP2P_DEBUG_FILE
If set, specifies the file path for log output. When this variable is set,
logs will only be written to the specified file. If not set, logs will be
written to both a default file (in the system's temp directory) and to
stderr (console output).
The logging system uses Python's native hierarchical logging:
- Loggers are organized in a hierarchy using dots
(e.g., libp2p.identity.identify)
- Child loggers inherit their parent's level unless explicitly set
- The root libp2p logger controls the default level
"""
global _current_listener, _listener_ready
# Reset the event
_listener_ready.clear()
# Stop existing listener if any
if _current_listener is not None:
_current_listener.stop()
_current_listener = None
# Get the log level from environment variable
debug_str = os.environ.get("LIBP2P_DEBUG", "")
if not debug_str:
# If LIBP2P_DEBUG is not set, disable logging
root_logger = logging.getLogger("libp2p")
root_logger.handlers.clear()
root_logger.setLevel(logging.WARNING)
root_logger.propagate = False
_listener_ready.set() # Signal that we're done
return
# Parse module-specific levels
module_levels = _parse_debug_modules(debug_str)
# If no valid levels specified, default to WARNING
if not module_levels:
root_logger = logging.getLogger("libp2p")
root_logger.handlers.clear()
root_logger.setLevel(logging.WARNING)
root_logger.propagate = False
_listener_ready.set() # Signal that we're done
return
# Create formatter
formatter = logging.Formatter(DEFAULT_LOG_FORMAT)
# Configure handlers
handlers: list[logging.StreamHandler[Any] | logging.FileHandler] = []
# Console handler
console_handler = logging.StreamHandler(sys.stderr)
console_handler.setFormatter(formatter)
handlers.append(console_handler)
# File handler (if configured)
log_file = os.environ.get("LIBP2P_DEBUG_FILE")
if log_file:
# Ensure the directory exists
log_path = Path(log_file)
log_path.parent.mkdir(parents=True, exist_ok=True)
else:
# Default log file with timestamp and unique identifier
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
unique_id = os.urandom(4).hex() # Add a unique identifier to prevent collisions
if os.name == "nt": # Windows
log_file = f"C:\\Windows\\Temp\\py-libp2p_{timestamp}_{unique_id}.log"
else: # Unix-like
log_file = f"/tmp/py-libp2p_{timestamp}_{unique_id}.log"
# Print the log file path so users know where to find it
print(f"Logging to: {log_file}", file=sys.stderr)
try:
file_handler = logging.FileHandler(
log_file, mode="w"
) # Use 'w' mode to clear file
file_handler.setFormatter(formatter)
handlers.append(file_handler)
except Exception as e:
print(f"Error creating file handler: {e}", file=sys.stderr)
raise
# Create a QueueHandler and QueueListener
queue_handler = logging.handlers.QueueHandler(log_queue)
# Configure root logger for libp2p
root_logger = logging.getLogger("libp2p")
root_logger.handlers.clear()
root_logger.addHandler(queue_handler)
root_logger.propagate = False
# Set default level based on configuration
if "" in module_levels:
# Global level specified
root_logger.setLevel(module_levels[""])
else:
# Default to INFO for module-specific logging
root_logger.setLevel(logging.INFO)
# Configure module-specific levels
for module, level in module_levels.items():
if module: # Skip the default "" key
logger = logging.getLogger(f"libp2p.{module}")
logger.handlers.clear()
logger.addHandler(queue_handler)
logger.setLevel(level)
logger.propagate = False # Prevent message duplication
# Start the listener AFTER configuring all loggers
_current_listener = logging.handlers.QueueListener(
log_queue, *handlers, respect_handler_level=True
)
_current_listener.start()
# Signal that the listener is ready
_listener_ready.set()
# Register cleanup function
@atexit.register
def cleanup_logging() -> None:
"""Clean up logging resources on exit."""
global _current_listener
if _current_listener is not None:
_current_listener.stop()
_current_listener = None