Skip to content

Commit

Permalink
add save to a file support
Browse files Browse the repository at this point in the history
  • Loading branch information
karaposu committed Dec 6, 2024
1 parent 40d6be3 commit 1ec436c
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 46 deletions.
7 changes: 7 additions & 0 deletions examples/main.py → examples/basic_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ def main():

if __name__ == '__main__':
main()







29 changes: 29 additions & 0 deletions examples/logging_to_a_file_example.py
Original file line number Diff line number Diff line change
@@ -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()

118 changes: 118 additions & 0 deletions fix.md
Original file line number Diff line number Diff line change
@@ -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("<Recursion detected>", 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!
2 changes: 2 additions & 0 deletions indented_logger/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
65 changes: 35 additions & 30 deletions indented_logger/formatter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# indented_logger/formatter.py

import logging
import re
from .indent import get_indent_level

class IndentFormatter(logging.Formatter):
Expand All @@ -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)

Expand All @@ -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:
Expand All @@ -44,69 +49,69 @@ 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:
max_message_length = 50
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)

padding = self.apply_padding(asctime, levelname, message, func_module_info)

original_msg, original_args = record.msg, record.args

try:
record.msg = message
record.args = ()
Expand All @@ -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
Expand Down
Loading

0 comments on commit 1ec436c

Please sign in to comment.