通用日志函数封装:如何为第三方库函数实现通用日志包装器?
Great question! You’ve already got a solid start with your shutil.copytree wrapper—creating a generic solution will eliminate repetitive logging code and make your codebase cleaner. Let’s walk through how to build a reusable logging wrapper that works for both exception-throwing functions (like most shutil methods) and non-exception-throwing ones (some subprocess utilities that return error codes, for example).
Generic Logging Wrapper with Decorators
Decorators are perfect for this use case—they let you wrap any function with logging logic without modifying the original code. Here’s a flexible implementation:
import functools import logging def log_function_call(logger, action_desc, success_desc=None, success_check=None): """ A generic decorator to log function calls, successes, and failures. Args: logger: The logging instance to use (e.g., your class's self.debug/self.error source) action_desc: A string describing the action, formatted with *args/**kwargs values success_desc: Optional string for success messages (defaults to a generic "done" message) success_check: Optional function to determine if the function succeeded (for non-exceptional failures) """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): # Log the starting action logger.debug(f":- {action_desc.format(*args, **kwargs)}") try: result = func(*args, **kwargs) except Exception as e: # Log exceptions and return False (or re-raise if you prefer callers to handle it) logger.error(f":- Failed to {action_desc.format(*args, **kwargs)}; Error: {str(e)}") return False else: # Handle non-exceptional failures if a success check is provided if success_check is not None and not success_check(result): logger.error(f":- Failed to {action_desc.format(*args, **kwargs)}; Result: {str(result)}") return False # Log success if success_desc: logger.debug(f":- {success_desc.format(*args, **kwargs)}") else: logger.debug(f":- Done with {action_desc.format(*args, **kwargs)}") return result return wrapper return decorator
How to Use It for shutil.copytree
Since shutil.copytree throws exceptions on failure, you can use the decorator without a success_check:
import shutil class YourFileHandler: def __init__(self): # Assuming your logger is configured here (maps to self.debug/self.error) self.logger = logging.getLogger(__name__) # Wrap shutil.copytree as an instance method self.copytree = log_function_call( logger=self.logger, action_desc="Copying tree from \"{0}\" to \"{1}\"", success_desc="Successfully copied tree from \"{0}\" to \"{1}\"" )(shutil.copytree) # Usage in your code: handler = YourFileHandler() handler.copytree("/path/to/source", "/path/to/dest")
Handling Non-Exception-Throwing Functions
For functions that don’t throw exceptions but return a status code or boolean (like subprocess.call), use the success_check parameter to define what counts as a failure:
import subprocess def check_subprocess_success(returncode): # Return True if the command succeeded (standard 0 return code) return returncode == 0 class YourCommandRunner: def __init__(self): self.logger = logging.getLogger(__name__) self.run_command = log_function_call( logger=self.logger, action_desc="Running command: {0}", success_desc="Command {0} completed successfully", success_check=check_subprocess_success )(subprocess.call) # Usage: runner = YourCommandRunner() runner.run_command(["echo", "Hello World"]) # Logs success runner.run_command(["invalid-command"]) # Logs failure
Key Benefits of This Approach
- No Repetition: Define your logging logic once and apply it to any function
- Flexibility: Customize messages and success criteria per function
- Preserves Original Behavior: Uses
functools.wrapsto keep the original function’s name, docstring, and signature intact - Centralized Error Handling: All logging and failure logic lives in one place, making it easy to update later
Your original copytree implementation works great, but this generic wrapper scales seamlessly to all the shutil, subprocess, and other library functions you need to log.
内容的提问来源于stack exchange,提问作者als9xd

