Easy Logging
py_simple.easy_logging
easy_logging is meant to simplify logging code blocks and function calls. Built on top of the logging module, it handles start, success, and error messages without repeating try/except blocks.
Configure logging in your application before using these helpers, for example with logging.basicConfig(level=logging.INFO).
clear_log_file(file_path)
Empties a log file, leaving it in place but with no contents. Args: file_path (str): Path to the log file to clear. Returns: bool: True if the file was cleared successfully, False if the file could not be found (e.g., False when the path doesn't exist). Example: === "The Py_simple Way"
from py_simple import clear_log_file
clear_log_file("app.log")
import os
if os.path.exists("app.log"):
open("app.log", "w").close()
count_log_levels(file_path)
Counts how many lines in a log file mention each standard level.
Looks for DEBUG, INFO, WARNING, ERROR, and CRITICAL as whole words, so a line is counted once for each level name it contains. A missing file returns zeros for every level.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file_path
|
str
|
Path to the log file to scan. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
dict[str, int]: Counts keyed by level name, always including DEBUG, INFO, WARNING, ERROR, and CRITICAL. |
Example
from py_simple import count_log_levels
totals = count_log_levels("app.log")
print(totals["ERROR"])
import os
import re
levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
totals = {level: 0 for level in levels}
if os.path.exists("app.log"):
with open("app.log") as log_file:
for line in log_file:
for level in levels:
if re.search(rf"{level}", line):
totals[level] += 1
find_log_lines(file_path, search_text)
Returns log file lines that contain the supplied search text.
Searches case sensitively and removes trailing newline characters from returned lines. Returns an empty list when the file does not exist or when no lines contain the search text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file_path
|
str
|
Path to the log file to search. |
required |
search_text
|
str
|
Text to look for in each log line. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
list[str]: Matching log lines without trailing newline characters. |
Example
from py_simple.easy_logging import find_log_lines
errors = find_log_lines("app.log", "ERROR")
import os
if os.path.exists("app.log"):
with open("app.log") as log_file:
errors = [
line.rstrip("\n")
for line in log_file
if "ERROR" in line
]
else:
errors = []
log_function(message_template)
Creates a decorator that logs each call to a synchronous function and method.
Formats the message using the function's argument names and values,
including default values for omitted arguments. Excludes self and
cls from the formatting context. Uses log_step to log the start,
successful completion, or exception with a traceback through the
root logger. Preserves the function's metadata and return value.
Argument binding and message formatting happen before logging starts; errors in those steps propagate without being logged by this helper. For async functions or generators, this decorator only logs creation of the coroutine or generator, not its later execution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message_template
|
str
|
Message with named str.format_map fields matching function parameters, such as "Add {a} and {b}". Format specifications such as "{price:.2f}" are supported. |
required |
Returns:
| Type | Description |
|---|---|
Callable[[Callable[P, R]], Callable[P, R]]
|
Callable[[Callable[P, R]], Callable[P, R]]: Decorator that wraps a function with logging while preserving its parameters and return type. |
Raises:
| Type | Description |
|---|---|
KeyError
|
A message field is missing from the formatting context. |
ValueError
|
The message template or a format specification is invalid. |
TypeError
|
The supplied arguments cannot be bound to the wrapped function's signature. |
Exception
|
Re-raises any Exception from the wrapped function after logging it. |
Example
import logging
from py_simple.easy_logging import log_function
logging.basicConfig(level=logging.INFO)
@log_function("Add {a} and {b}")
def add(a: int, b: int = 10) -> int:
return a + b
print(add(5)) # -> 15; logs "Add 5 and 10"
import logging
logging.basicConfig(level=logging.INFO)
def add(a: int, b: int = 10) -> int:
message = f"Add {a} and {b}"
try:
logging.info(f"Starting: {message}")
result = a + b
except Exception:
logging.exception(f"Error during: {message}")
raise
else:
logging.info(f"Finished: {message}")
return result
print(add(5)) # -> 15; logs "Add 5 and 10"
log_step(message)
Logs the start, successful completion, or failure of a code block.
Writes "Starting:" and "Finished:" messages at INFO level. If the block raises an Exception, logs "Error during:" at ERROR level with the traceback and re-raises the original exception instead of logging a successful completion. Uses the root logger and its configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
Description of the operation, such as "Calculate total". |
required |
Yields:
| Name | Type | Description |
|---|---|---|
None |
None
|
Runs the enclosed code block without providing an object
for an optional |
Raises:
| Type | Description |
|---|---|
Exception
|
Re-raises any Exception raised by the enclosed block after logging it. |
Example
import logging
from py_simple.easy_logging import log_step
logging.basicConfig(level=logging.INFO)
with log_step("Calculate total"):
total = sum([10, 20, 30])
print(total) # -> 60
import logging
logging.basicConfig(level=logging.INFO)
try:
logging.info("Starting: Calculate total")
total = sum([10, 20, 30])
except Exception:
logging.exception("Error during: Calculate total")
raise
else:
logging.info("Finished: Calculate total")
print(total) # -> 60
log_to_file(file_path, message)
Appends a message to a log file.
Creates the file if it does not exist and writes the message as one line. Returns True after the message is written successfully.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file_path
|
str
|
Path to the log file to write to. |
required |
message
|
str
|
Message to append to the log file. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True when the message is written successfully. |
Example
from py_simple import log_to_file
log_to_file("app.log", "Started import job")
with open("app.log", "a") as log_file:
log_file.write("Started import job\n")
read_recent_log_lines(file_path, line_count=10)
Reads the most recent lines from a log file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file_path
|
str
|
Path to the log file to read. |
required |
line_count
|
int
|
Number of recent lines to return.
Defaults to |
10
|
Returns:
| Type | Description |
|---|---|
list[str]
|
list[str]: The last |
Example
from py_simple import read_recent_log_lines
recent_lines = read_recent_log_lines("app.log", line_count=5)
import os
if os.path.exists("app.log"):
with open("app.log") as log_file:
recent_lines = [line.rstrip("\n") for line in log_file.readlines()[-5:]]
else:
recent_lines = []
setup_file_logger(log_file, level=logging.INFO, format_string='%(asctime)s - %(levelname)s - %(message)s', name=None)
Sets up and returns a logger configured to write to a file.
Creates parent directories for the log file if they do not exist.
Adds a logging.FileHandler formatted with format_string at the
specified logging level. If a handler for the same file path is already
attached to the logger, duplicate handlers are not added.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_file
|
str
|
Path to the log file to write to. |
required |
level
|
int or str
|
Logging level such as |
INFO
|
format_string
|
str
|
Message format for the log handler.
Defaults to |
'%(asctime)s - %(levelname)s - %(message)s'
|
name
|
str
|
Name of the logger to configure. Defaults to
|
None
|
Returns:
| Type | Description |
|---|---|
Logger
|
logging.Logger: The configured logger instance. |
Example
from py_simple import setup_file_logger
logger = setup_file_logger("app.log")
logger.info("Application started")
import logging
logger = logging.getLogger("app.log")
logger.setLevel(logging.INFO)
handler = logging.FileHandler("app.log", encoding="utf-8")
handler.setLevel(logging.INFO)
formatter = logging.Formatter(
"%(asctime)s - %(levelname)s - %(message)s"
)
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.info("Application started")
temporary_log_level(logger, level)
Changes a logger's level for a block and restores its previous level.
Restores the exact explicit level, including NOTSET, even if the block raises. Nested blocks restore the level that was active on entry. Handler levels/filters, logger filters, propagation and global logging settings still apply and are not changed. A shared logger's temporary level is visible to other users of that logger; this is not thread-local.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
logger
|
Logger
|
Logger to adjust, normally obtained with logging.getLogger(). Its explicit level will change. |
required |
level
|
int | str
|
Integer logging level or a registered level name, such as logging.DEBUG or "debug". String names are uppercased, matching setup_file_logger. Integers follow Logger.setLevel. |
required |
Yields:
| Name | Type | Description |
|---|---|---|
None |
None
|
Runs the block without providing an |
Raises:
| Type | Description |
|---|---|
TypeError
|
If logger is not a logging.Logger or level is neither an integer nor a string accepted by logging. |
ValueError
|
If a string does not name a registered logging level. |
BaseException
|
Propagates any exception from the enclosed block after restoring the previous level. |
Example
import logging
from py_simple.easy_logging import temporary_log_level
logger = logging.getLogger("example")
logger.setLevel(logging.WARNING)
with temporary_log_level(logger, "debug"):
print(logger.isEnabledFor(logging.DEBUG)) # True
print(logger.level == logging.WARNING) # True
import logging
logger = logging.getLogger("example")
logger.setLevel(logging.WARNING)
previous_level = logger.level
logger.setLevel("DEBUG")
try:
print(logger.isEnabledFor(logging.DEBUG)) # True
finally:
logger.setLevel(previous_level)
print(logger.level == logging.WARNING) # True