Skip to content

Conversation

@NikoOinonen
Copy link
Collaborator

Fixes #214

This changes all print statements in the Python code to use the built-in logging module instead. The logging events are divided into different levels, see https://docs.python.org/3/library/logging.html#levels, so that the user can control the amount and format of the output from ppafm. I set the default level at INFO and only left high level info at that level so that more detailed output is at DEBUG level which is not printed by default. You can see in the code if you agree with my judgement and we can adjust.

There is still some output coming from the C++ code which just goes to stdout, but that's for another time. I also didn't touch the dev module and some of the CLI scripts that are not part of the ppafm pip package.

Controlling the logging output

In the code the logging level is controlled by using differently named methods for each level in the logger objects that I defined for each module. For example logger.info("This is important information"), or logger.debug("This is debug information").

The output at runtime can be controlled two different ways:

  1. Programmatically in scripts using the configure_logging function in the new logging_utils module
  2. Using the following new environment variables:
  • PPAFM_LOG_LEVEL sets the log level. All message more severe than the set level are printed. For example PPAFM_LOG_LEVEL=DEBUG to print everything.
  • PPAFM_LOG_PATH sets the file where the log is printed. If not specified (default), then the log goes to stdout. For example PPAFM_LOG_PATH=my_ppafm_run.log.
  • PPAFM_LOG_FORMAT sets the format of printed log messages. There are several keywords you can use, see: https://docs.python.org/3/library/logging.html#logrecord-attributes. By default only the plain message is printed for info or "WARNING/ERROR: message" for more severe, or if the log level is at DEBUG then more detailed message with timestamp and the originating module name is printed. For example PPAFM_LOG_FORMAT="%(asctime)s - %(message)s".
  • PPAFM_LOG_PERFORMANCE controls whether timings for benchmarking are logged. Mostly relevant for the OpenCL code for now. Setting any value to the variable enables the log, for example PPAFM_LOG_PERFORMANCE=1.

We could add arguments to the CLI scripts as well, but I left it out for now since we are intending to overhaul that system anyways.

Copy link
Collaborator

@ondrejkrejci ondrejkrejci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huge work @NikoOinonen ! I have many comments there. It seems to me, that we are slightly inconsistent with what is debug, warning and info. Still after some comments, I am more for approval, as we can change those later.

if self.verbose > 1:
print("updateView time [s]", time.perf_counter() - t0)
logger.debug(f"FFViewer.updateView {ic} {iz} {data.shape}")
perf_logger.info(f"updateView time [s] {time.perf_counter() - t0}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the perf_logger here? is it about performance - and that is instead of verbose > 1 ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I previously had the performance benchmark prints at higher verbose level, but now it's in a separate system that can be enabled as needed. Basically performance logger prints lines with info on the amount of time taken for various tasks, for example:

[2025-11-04 09:40:57,436 - ppafm.perf.AFMulator] evalAFM [s]: 0.05937764700001935

xyzs = self.parent.xyzs - lvec[0]
atomstring = io.primcoords2Xsf(self.parent.Zs, xyzs.T, lvec)
io.saveXSF(fileName, data, lvec, head=atomstring, verbose=0)
io.saveXSF(fileName, data, lvec, head=atomstring)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it the information about verbosity send through somehow?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verbosity level is now handled globally by setting the logger logging level, so there is no need to pass it manually everywhere.

from . import cpp_utils
from .logging_utils import get_logger

logger = get_logger("GridUtils")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the file/routine type has to be passed to the logger function?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just a label that allows differentiating between messages coming from different modules if needed. The name is used in the log format with %(name)s. For example setting PPAFM_LOG_FORMAT="%(name) - %(message)s" will print lines like:

ppafm.HighLevel - Saving Lennard-Jones force field to xsf

The ppafm. will be prepended to the name because the logger is hierarchically derived from a logger with the name ppafm.

if verbose > 0:
print(" zTips : ", zTips)
logger.info(">>BEGIN: relaxedScan3D_omp()")
logger.debug(f" zTips : {zTips}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand why, but it is just funny, that one, of the verbose > 0 is info and other is debug.


if level is None:
try:
level = os.environ["PPAFM_LOG_LEVEL"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we set this how? During a pip installation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are normal environment variables that you can set as you run your calculation, either for the whole session with export PPAFM_LOG_LEVEL=DEBUG or just for the one run by prepending, e.g.:

PPAFM_LOG_LEVEL=DEBUG ./run.sh

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add this to our WIKI.

looper.startLoop(molecule, atomMap, bondMap, lvecMap, AFMRef)
ErrConv = 0.1
print("# ------ To Loop ")
logger.debug("# ------ To Loop ")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like info to me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it.

if bCopy:
FE = self.downloadFF(FE)
if bFinish:
if bFinish or perf_log_enabled():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering, if bFinish is still necessary in the function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's maybe not that useful, but it's doing a different thing. You could possibly want to wait for the computation to finish even when not logging the performance.

print(" LOCAL_MEM_SIZE = ", device.get_info( cl.device_info.LOCAL_MEM_SIZE ) / 4, " float32")
print(" MAX_CONSTANT_BUFFER_SIZE = ", device.get_info( cl.device_info.MAX_CONSTANT_BUFFER_SIZE ) / 4, " float32")
print(" MAX_WORK_GROUP_SIZE = ", device.get_info( cl.device_info.MAX_WORK_GROUP_SIZE ))
logger.info("---------------------------------------------------------------")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, Ha, I would have many things from here, such as speed and mem sizes as debug.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't actually use this function anywhere in the code at the moment. It's more of a developer testing thing for figuring out what capability the device has. I think all of the things here should be at the same log level, whether it's info or debug.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe somebody more skilled with this like @yakutovicha should have look at this. I do not see there any damage, but to be honest, I am not fully understanding what is going on there.

Copy link
Collaborator

@ondrejkrejci ondrejkrejci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, Just we should put to the WIKI how to set the PPAFM_LOG_LEVEL=DEBUG and what are the possible levels.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace print statements with logging tools

3 participants