From 5d2da7fce71d7e7ec9632993aa693e84f1f71e96 Mon Sep 17 00:00:00 2001 From: Nicolas M Date: Sun, 24 Aug 2025 21:35:47 +0200 Subject: [PATCH 1/2] create logging page --- docs/index.rst | 1 + docs/user/logging.md | 104 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 docs/user/logging.md diff --git a/docs/index.rst b/docs/index.rst index 62f2ab2c5..0ebcec411 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -42,6 +42,7 @@ You can contribute to `pypdf on GitHub `_. user/file-size user/pdf-version-support user/pdfa-compliance + user/logging .. toctree:: diff --git a/docs/user/logging.md b/docs/user/logging.md new file mode 100644 index 000000000..d19bfa15f --- /dev/null +++ b/docs/user/logging.md @@ -0,0 +1,104 @@ +# Logging + +All log messages from `pypdf` go through Python’s standard `logging` library under the logger name `pypdf`. This gives you full control over verbosity, whether you want detailed debug information or only critical errors. + +## Filtering logs + +You can adjust the minimum log level of pypdf as follow: + +```py +import logging +from pypdf import PdfReader + + +logger = logging.getLogger("pypdf") +logger.setLevel(logging.ERROR) # <--- set the minimum level expected + +reader = PdfReader('file.pdf') +``` + +## Temporarily Reducing Log Noise + +If you only want to suppress logs during a specific operation, you can wrap that code in a context manager: + +```py +import logging +from contextlib import contextmanager + + +@contextmanager +def reduce_log_level(level=logging.ERROR): + logger = logging.getLogger("pypdf") + old_level = logger.level + logger.setLevel(level) + try: + yield + finally: + logger.setLevel(old_level) +``` + +#### Usage: + +```py +from pypdf import PdfReader, PdfWriter +from my_module import reduce_log_level # adjust path to your module + +# Standard logging level applies +reader = PdfReader("file.pdf") +writer = PdfWriter() + +page = reader.pages[0] +writer.add_page(page) + +with reduce_log_level(level=logging.ERROR): + # Example: ignore warnings when adding annotations + for page, annotation in annotations: # annotations must be defined + writer.add_annotation(page_number=page, annotation=annotation) + +with open("new_file.pdf", "wb") as fp: + writer.write(fp) +``` + +## Customizing Log Records + +If you prefer to remap log levels (e.g., turn errors into warnings), you can subclass `logging.Logger` as follow: + +```py +import logging +from pypdf import PdfReader + +class PypdfCustomLogger(logging.Logger): + def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None): + if name == 'pypdf': + level_mapping = { + logging.NOTSET: logging.NOTSET, + logging.DEBUG: logging.DEBUG, + logging.INFO: logging.DEBUG, + logging.WARNING: logging.INFO, + logging.ERROR: logging.WARNING, + logging.CRITICAL: logging.ERROR + } + level = level_mapping.get(level, logging.DEBUG) + return super().makeRecord(name, new_level, fn, lno, msg, args, exc_info, func, extra, sinfo) +``` + +#### Usage: + +```py +import logging +from my_module import PypdfCustomLogger # adjust path to your module + +logging.setLoggerClass(PypdfCustomLogger) +logging.basicConfig() + +pdf_logger = logging.getLogger('pypdf') +myapp_logger = logging.getLogger('myapp') + +# pypdf logger level is adjusted +pdf_logger.info("This will be captured as a DEBUG message.") +pdf_logger.warning("This will be captured as a INFO message.") +pdf_logger.error("This will be captured as a WARNING message.") + +# other loggers are not impacted. +myapp_logger.error("This will be captured as a ERROR message.") +``` From b6a79ef745fd07b133029ad76a8ea2ddda6445cd Mon Sep 17 00:00:00 2001 From: Nicolas M Date: Tue, 26 Aug 2025 20:22:48 +0200 Subject: [PATCH 2/2] Adjust comments and examples --- docs/user/logging.md | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/docs/user/logging.md b/docs/user/logging.md index d19bfa15f..f0422392e 100644 --- a/docs/user/logging.md +++ b/docs/user/logging.md @@ -10,11 +10,12 @@ You can adjust the minimum log level of pypdf as follow: import logging from pypdf import PdfReader - +# Get the logger for the pypdf library +# This allows you to configure its logging level independently logger = logging.getLogger("pypdf") -logger.setLevel(logging.ERROR) # <--- set the minimum level expected +logger.setLevel(logging.ERROR) -reader = PdfReader('file.pdf') +reader = PdfReader("file.pdf") ``` ## Temporarily Reducing Log Noise @@ -41,7 +42,7 @@ def reduce_log_level(level=logging.ERROR): ```py from pypdf import PdfReader, PdfWriter -from my_module import reduce_log_level # adjust path to your module +from my_module import reduce_log_level # Adjust path to your module # Standard logging level applies reader = PdfReader("file.pdf") @@ -51,10 +52,11 @@ page = reader.pages[0] writer.add_page(page) with reduce_log_level(level=logging.ERROR): - # Example: ignore warnings when adding annotations - for page, annotation in annotations: # annotations must be defined - writer.add_annotation(page_number=page, annotation=annotation) + # Adjusted level applies + # Logs lower than ERROR will be filtered-out + do_something() +# Original logging level applies with open("new_file.pdf", "wb") as fp: writer.write(fp) ``` @@ -68,37 +70,41 @@ import logging from pypdf import PdfReader class PypdfCustomLogger(logging.Logger): - def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None): - if name == 'pypdf': + def makeRecord(self, name: str, level: int, *args, **kwargs): + if name == "pypdf": level_mapping = { logging.NOTSET: logging.NOTSET, logging.DEBUG: logging.DEBUG, logging.INFO: logging.DEBUG, logging.WARNING: logging.INFO, logging.ERROR: logging.WARNING, - logging.CRITICAL: logging.ERROR + logging.CRITICAL: logging.ERROR, } - level = level_mapping.get(level, logging.DEBUG) - return super().makeRecord(name, new_level, fn, lno, msg, args, exc_info, func, extra, sinfo) + new_level = level_mapping.get(level, logging.DEBUG) + else: + new_level = level + + # Generate a record using the new level defined + return super().makeRecord(name, new_level, *args, **kwargs) ``` #### Usage: ```py import logging -from my_module import PypdfCustomLogger # adjust path to your module +from my_module import PypdfCustomLogger # Adjust path to your module logging.setLoggerClass(PypdfCustomLogger) logging.basicConfig() -pdf_logger = logging.getLogger('pypdf') -myapp_logger = logging.getLogger('myapp') +pdf_logger = logging.getLogger("pypdf") +other_logger = logging.getLogger("other_logger") # pypdf logger level is adjusted pdf_logger.info("This will be captured as a DEBUG message.") pdf_logger.warning("This will be captured as a INFO message.") pdf_logger.error("This will be captured as a WARNING message.") -# other loggers are not impacted. -myapp_logger.error("This will be captured as a ERROR message.") +# Other loggers are not impacted +other_logger.error("This will be captured as a ERROR message.") ```