diff --git a/examples/main.py b/examples/basic_example.py similarity index 99% rename from examples/main.py rename to examples/basic_example.py index 62752ac..dddc4ae 100644 --- a/examples/main.py +++ b/examples/basic_example.py @@ -27,3 +27,10 @@ def main(): if __name__ == '__main__': main() + + + + + + + diff --git a/examples/logging_to_a_file_example.py b/examples/logging_to_a_file_example.py new file mode 100644 index 0000000..d2a391f --- /dev/null +++ b/examples/logging_to_a_file_example.py @@ -0,0 +1,29 @@ +from indented_logger import setup_logging, log_indent +import logging + +# Configure to log both to console and file: +# - Console: With ANSI colors and indentation +# - File: No ANSI colors, no indentation +setup_logging( + level=logging.INFO, + log_file='application.log', + include_func=True, + log_file_keep_ANSI=False, # No ANSI in file + log_file_no_indent=False # No indentation in file logs +) + +logger = logging.getLogger(__name__) + +@log_indent +def complex_operation(): + logger.info("Starting complex operation") + sub_operation() + logger.info("Complex operation completed") + +@log_indent +def sub_operation(): + logger.info("Performing sub operation step 1") + logger.info("Performing sub operation step 2") + +complex_operation() + diff --git a/fix.md b/fix.md new file mode 100644 index 0000000..696addf --- /dev/null +++ b/fix.md @@ -0,0 +1,118 @@ +It seems that the logging output is still showing the `_log_key_value` and `_log_object` function names in the debug messages. This might not be the intended outcome since these function names can clutter the log output and detract from the actual message. + +### Solution: +To avoid logging the function names in the output, we should revise the logging calls so that the `extra` dictionary only includes relevant information without showing the function names. Here’s how you can do that: + +1. **Remove the function names from the logging messages**: Instead of including the helper function names in the `extra` dictionary, focus only on the necessary context. + +Here's how you can modify your logging functions: + +```python +import logging +import re +import inspect + +def flatten_string(s): + """ + Flattens the given multi-line string into a single line. + """ + flattened = re.sub(r'\s+', ' ', s).strip() + return flattened + +def smart_indent_log(logger_object, obj, lvl, exclude=None, name=None, flatten_long_strings=True): + """ + Logs the contents of an object or dictionary with indentation based on the nesting level. + """ + if exclude is None: + exclude = [] + + caller_func = inspect.stack()[1].function # Get the caller function name + + if name: + logger_object.debug(f"{name}:", extra={"lvl": lvl}) # No longer adding callerFunc here + lvl += 1 + + _log_object(logger_object, obj, lvl, exclude, flatten_long_strings) + +def _log_object(logger_object, obj, lvl, exclude, flatten_long_strings, _visited=None): + """ + Helper function to log an object or dictionary. + """ + if _visited is None: + _visited = set() + + obj_id = id(obj) + if obj_id in _visited: + logger_object.debug("", extra={"lvl": lvl}) + return + _visited.add(obj_id) + + if hasattr(obj, "__dict__"): + dictionary = obj.__dict__ + elif isinstance(obj, dict): + dictionary = obj + else: + _log_simple_value(logger_object, obj, lvl, flatten_long_strings) + return + + for key, value in dictionary.items(): + if key in exclude: + continue + + if isinstance(value, dict): + logger_object.debug(f"{key}:", extra={"lvl": lvl}) + _log_object(logger_object, value, lvl + 1, exclude, flatten_long_strings, _visited) + elif isinstance(value, list): + logger_object.debug(f"{key}: List of length {len(value)}", extra={"lvl": lvl}) + _log_list(logger_object, value, lvl + 1, exclude, flatten_long_strings, _visited) + else: + _log_key_value(logger_object, key, value, lvl, flatten_long_strings) + +def _log_list(logger_object, lst, lvl, exclude, flatten_long_strings, _visited): + """ + Helper function to log a list. + """ + for index, item in enumerate(lst): + if isinstance(item, (dict, list)): + logger_object.debug(f"[{index}]:", extra={"lvl": lvl}) + if isinstance(item, dict): + _log_object(logger_object, item, lvl + 1, exclude, flatten_long_strings, _visited) + elif isinstance(item, list): + _log_list(logger_object, item, lvl + 1, exclude, flatten_long_strings, _visited) + else: + _log_simple_value(logger_object, item, lvl, flatten_long_strings, prefix=f"[{index}]") + +def _log_key_value(logger_object, key, value, lvl, flatten_long_strings): + """ + Helper function to log a key-value pair. + """ + if flatten_long_strings and isinstance(value, str) and len(value) > 120: + value = flatten_string(value) + logger_object.debug(f"{key}: {value}", extra={"lvl": lvl}) + +def _log_simple_value(logger_object, value, lvl, flatten_long_strings, prefix=""): + """ + Helper function to log a simple value. + """ + if flatten_long_strings and isinstance(value, str) and len(value) > 120: + value = flatten_string(value) + message = f"{prefix}: {value}" if prefix else f"{value}" + logger_object.debug(message, extra={"lvl": lvl}) +``` + +### Key Changes: +- **Removed the `callerFunc` attribute**: This change eliminates the clutter in the logs by not including the helper function names in the `extra` dictionary. +- **Streamlined `logger_object.debug` calls**: The logging now focuses on the message content without appending function names. + +### Result: +After these changes, your log output should show the data neatly without the function references. For example, you should see something like: + +``` +2024-10-24 16:57:06,152 - DEBUG - Record: +2024-10-24 16:57:06,152 - DEBUG - ready: True +2024-10-24 16:57:06,152 - DEBUG - record_id: 3 +2024-10-24 16:57:06,152 - DEBUG - record: Encard Harcaması, I.-SHOP WWW.PZZ.BY PAR g.p. +... +``` + +Let me know if this helps or if you have more adjustments in mind! \ No newline at end of file diff --git a/indented_logger/__init__.py b/indented_logger/__init__.py index 53d3c0a..01d4315 100644 --- a/indented_logger/__init__.py +++ b/indented_logger/__init__.py @@ -1,3 +1,5 @@ +# this is __init__.py + from .logging_config import setup_logging from .indent import increase_indent, decrease_indent, get_indent_level from .decorator import log_indent diff --git a/indented_logger/formatter.py b/indented_logger/formatter.py index 80bf542..bcd54ac 100644 --- a/indented_logger/formatter.py +++ b/indented_logger/formatter.py @@ -1,4 +1,7 @@ +# indented_logger/formatter.py + import logging +import re from .indent import get_indent_level class IndentFormatter(logging.Formatter): @@ -14,15 +17,18 @@ class IndentFormatter(logging.Formatter): def __init__(self, include_func=False, include_module=False, func_module_format=None, truncate_messages=False, min_func_name_col=120, indent_modules=False, - indent_packages=False, datefmt=None, indent_spaces=4, debug=False): + indent_packages=False, datefmt=None, indent_spaces=4, debug=False, + disable_colors=False, disable_indent=False): self.include_func = include_func self.include_module = include_module self.truncate_messages = truncate_messages self.min_func_name_col = min_func_name_col - self.indent_modules = indent_modules - self.indent_packages = indent_packages - self.indent_spaces = indent_spaces + self.indent_modules = indent_modules and not disable_indent + self.indent_packages = indent_packages and not disable_indent + self.indent_spaces = 0 if disable_indent else indent_spaces self.debug = debug + self.disable_colors = disable_colors + self.disable_indent = disable_indent self.func_module_format = self.build_func_module_format(func_module_format) @@ -33,7 +39,6 @@ def __init__(self, include_func=False, include_module=False, func_module_format= super().__init__(fmt=fmt, datefmt=datefmt) def build_func_module_format(self, func_module_format): - """Builds the format for function and module inclusion.""" if func_module_format is None: placeholders = [] if self.include_module: @@ -44,19 +49,31 @@ def build_func_module_format(self, func_module_format): return func_module_format def get_indent(self, record): - """Calculate the total indentation based on thread, manual, and hierarchy levels.""" + if self.disable_indent: + # No indentation logic applied + return '' thread_indent = get_indent_level() manual_indent = getattr(record, 'lvl', 0) - hierarchy_indent = (1 if self.indent_modules and record.name != '__main__' else 0) + \ - record.name.count('.') if self.indent_packages else 0 + hierarchy_indent = 0 + if self.indent_modules and record.name != '__main__': + hierarchy_indent += 1 + if self.indent_packages: + hierarchy_indent += record.name.count('.') total_indent = thread_indent + manual_indent + hierarchy_indent return ' ' * (total_indent * self.indent_spaces) + def strip_color_codes(self, text): + return re.sub(r'\033\[\d+m', '', text) + + def apply_colors(self, text, color_name='reset'): + if self.disable_colors: + return text + color = self.COLOR_MAP.get(color_name.lower(), self.COLOR_MAP['reset']) + return f"{color}{text}{self.COLOR_MAP['reset']}" + def get_colored_message(self, record, indent): - """Build the colored message with appropriate indentations.""" color_name = getattr(record, 'c', 'reset').lower() - color = self.COLOR_MAP.get(color_name, self.COLOR_MAP['reset']) message = f"{indent}{record.getMessage()}" if self.truncate_messages: @@ -64,41 +81,30 @@ def get_colored_message(self, record, indent): if len(message) > max_message_length: message = message[:max_message_length - 3] + '...' - return f"{color}{message}{self.COLOR_MAP['reset']}" + if self.disable_colors: + # Just return the plain message if colors are disabled + return message + else: + return self.apply_colors(message, color_name) def get_func_module_info(self, record): - """Format the function and module information.""" if self.func_module_format: return self.func_module_format.format(funcName=record.funcName, moduleName=record.name) return '' - def strip_color_codes(self,text): - import re - """Remove ANSI color codes from the text.""" - return re.sub(r'\033\[\d+m', '', text) - def apply_padding(self, asctime, levelname, message, func_module_info): - """Determine padding based on the length of asctime, level, and message, ignoring color codes.""" - # Strip the color codes from the message for length calculation - stripped_message = self.strip_color_codes(message) + stripped_message = self.strip_color_codes(message) if not self.disable_colors else message current_length = len(asctime) + 3 + len(levelname) + 3 + len(stripped_message) desired_column = self.min_func_name_col return ' ' * max(0, desired_column - current_length) if func_module_info else '' - # def apply_padding(self, asctime, levelname, message, func_module_info): - # """Determine padding based on the length of asctime, level, and message.""" - # current_length = len(asctime) + 3 + len(levelname) + 3 + len(message) - # desired_column = self.min_func_name_col - # - # return ' ' * max(0, desired_column - current_length) if func_module_info else '' - def format(self, record): indent = self.get_indent(record) message = self.get_colored_message(record, indent) asctime = self.formatTime(record, self.datefmt) - asctime_colored = f"{self.COLOR_MAP['cyan']}{asctime}{self.COLOR_MAP['reset']}" + asctime_colored = self.apply_colors(asctime, 'cyan') if not self.disable_colors else asctime levelname = f"{record.levelname:<8}" func_module_info = self.get_func_module_info(record) @@ -106,7 +112,6 @@ def format(self, record): padding = self.apply_padding(asctime, levelname, message, func_module_info) original_msg, original_args = record.msg, record.args - try: record.msg = message record.args = () @@ -115,7 +120,7 @@ def format(self, record): formatted_message = super().format(record) - if self.usesTime(): + if self.usesTime() and not self.disable_colors: formatted_message = formatted_message.replace(asctime, asctime_colored, 1) return formatted_message diff --git a/indented_logger/logging_config.py b/indented_logger/logging_config.py index f87532c..4b01dcc 100644 --- a/indented_logger/logging_config.py +++ b/indented_logger/logging_config.py @@ -3,12 +3,60 @@ import logging from .formatter import IndentFormatter -def setup_logging(level=logging.DEBUG, log_file=None, include_func=False, include_module=False, - func_module_format=None, truncate_messages=False, min_func_name_col=120, - indent_modules=False, indent_packages=False, indent_spaces=4, datefmt=None, - debug=False): - # Create the formatter with the new parameters - formatter = IndentFormatter( +def setup_logging(level=logging.DEBUG, + log_file=None, + include_func=False, + include_module=False, + func_module_format=None, + truncate_messages=False, + min_func_name_col=120, + indent_modules=False, + indent_packages=False, + indent_spaces=4, + datefmt=None, + debug=False, + log_file_keep_ANSI=False, + log_file_no_indent=False): + """ + Set up logging with indentation and optional file output. + + Parameters + ---------- + level : int + Logging level (e.g., `logging.INFO`, `logging.DEBUG`). + log_file : str or None + Path to a file for saving logs. If None, no file logging is performed. + include_func : bool + Include function names in the log output. + include_module : bool + Include module names in the log output. + func_module_format : str or None + Format string for {funcName} and {moduleName}. + truncate_messages : bool + If True, truncate long messages to a fixed length. + min_func_name_col : int + The column at which function/module names should start. + indent_modules : bool + If True, automatically indent logs based on whether they come from a module other than __main__. + indent_packages : bool + If True, automatically indent logs based on the depth of the package hierarchy. + indent_spaces : int + Number of spaces per indentation level. + datefmt : str or None + Date format for the log timestamps. + debug : bool + If True, enable debug mode in the formatter. + log_file_keep_ANSI : bool + If False, remove ANSI color codes from file logs. Defaults to False. + log_file_no_indent : bool + If True, no indentation logic is applied to file logs. Defaults to False. + + Returns + ------- + None + """ + # Console formatter with colors and indentation + console_formatter = IndentFormatter( include_func=include_func, include_module=include_module, func_module_format=func_module_format, @@ -21,19 +69,36 @@ def setup_logging(level=logging.DEBUG, log_file=None, include_func=False, includ debug=debug ) - # Get the root logger + # File formatter with optional removal of ANSI and indentation + # We'll add two new parameters to IndentFormatter for internal logic: + # - disable_colors (bool): If True, strip ANSI codes + # - disable_indent (bool): If True, no indentation logic applied + file_formatter = IndentFormatter( + include_func=include_func, + include_module=include_module, + func_module_format=func_module_format, + truncate_messages=truncate_messages, + min_func_name_col=min_func_name_col, + indent_modules=(False if log_file_no_indent else indent_modules), + indent_packages=(False if log_file_no_indent else indent_packages), + indent_spaces=(0 if log_file_no_indent else indent_spaces), + datefmt=datefmt, + debug=debug, + disable_colors=(not log_file_keep_ANSI), + disable_indent=log_file_no_indent + ) + logger = logging.getLogger() logger.setLevel(level) - # Add handlers only if they haven't been added yet - if not any(isinstance(handler, logging.StreamHandler) for handler in logger.handlers): - # Console handler + # Add console handler if none present + if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers): console_handler = logging.StreamHandler() - console_handler.setFormatter(formatter) + console_handler.setFormatter(console_formatter) logger.addHandler(console_handler) - # File handler (if a log_file is specified) - if log_file and not any(isinstance(handler, logging.FileHandler) for handler in logger.handlers): - file_handler = logging.FileHandler(log_file) - file_handler.setFormatter(formatter) + # Add file handler if log_file specified and not already present + if log_file and not any(isinstance(h, logging.FileHandler) for h in logger.handlers): + file_handler = logging.FileHandler(log_file, mode='a', encoding='utf-8') + file_handler.setFormatter(file_formatter) logger.addHandler(file_handler) diff --git a/indented_logger/smart_logger.py b/indented_logger/smart_logger.py index f28c700..c482deb 100644 --- a/indented_logger/smart_logger.py +++ b/indented_logger/smart_logger.py @@ -1,3 +1,4 @@ +# smart_logger.py import re def flatten_string(s): diff --git a/setup.py b/setup.py index 8ddd06d..9368e6d 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='indented_logger', # Package name - version='0.3.1', # Version of your package + version='0.3.2', # Version of your package author='Enes Kuzucu', # Your name description='A module to use common logger module with indentation support ', # Short description diff --git a/untitled folder/2.png b/untitled folder/2.png new file mode 100644 index 0000000..417ee25 Binary files /dev/null and b/untitled folder/2.png differ diff --git a/untitled folder/3.png b/untitled folder/3.png new file mode 100644 index 0000000..1afc12f Binary files /dev/null and b/untitled folder/3.png differ diff --git a/untitled folder/image (2).png b/untitled folder/image (2).png new file mode 100644 index 0000000..9d739f8 Binary files /dev/null and b/untitled folder/image (2).png differ