Skip to content

Fix broken https link and wrap at 80 columns #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 12, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 118 additions & 102 deletions doc/source/guidelines/logging.rst
Original file line number Diff line number Diff line change
@@ -1,62 +1,63 @@
Logging Guidelines
##################

This section describes several guidelines for logging in PyAnsys
libraries. These guidelines are best practices discovered through
implementing logging services and modules within PyAnsys
libraries. Suggestions and improvements are welcome.
External resources also describe `basic <https://docs.python-guide.org/writing/logging/>`__
and `advanced <https://coralogix.com/blog/python-logging-best-practices-tips/>`__ technics.
This section describes several guidelines for logging in PyAnsys libraries.
These guidelines are best practices discovered through implementing logging
services and modules within PyAnsys libraries. Suggestions and improvements are
welcome.
External resources also describe `basic
<https://docs.python-guide.org/writing/logging/>`__ and `advanced
<https://coralogix.com/blog/python-logging-best-practices-tips/>`__ technics.


Description and usage
=====================
Logging helps to track events occurring in the application. For each of them a log record
is created. It contains a detailed set of information about the current application operation.
Whenever an information must be exposed, displayed and shared, logging is the
way to do it.
It is destinated to both the users and the application developers.
It can serve several purposes:
Logging helps to track events occurring in the application. For each of them a
log record is created. It contains a detailed set of information about the
current application operation. Whenever an information must be exposed,
displayed and shared, logging is the way to do it.
It is destinated to both the users and the application developers. It can serve
several purposes:

- extract some valuable data for the final users to know the status of their work.
- track the progress and the course of the application usage.
- provide the developer with as much information as possible if an issue happens.

The message logged can contain generic information or embed data specific
to the current session.
The message logged can contain generic information or embed data specific to the
current session.
Message content is associated to a level of severity (info, warning, error...).
Generally, this degree of significance indicates the recipient of the message.
An info message is directed to the user while a debug message is useful for
the developer itself.
An info message is directed to the user while a debug message is useful for the
developer itself.


Logging in PyAnsys Libraries
============================

The logging capabilities in PyAnsys libraries should be built upon the
`standard logging <https://docs.python.org/3/library/logging.html/>`__
Copy link
Contributor Author

@dnwillia-work dnwillia-work Jan 12, 2022

Choose a reason for hiding this comment

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

Sorry for fixing something and reformatting at the same time. This is the broken link, I've fixed it below (removed the unecessary / char at the end).

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks @dnwillia-ansys for fixing it.

library. PyAnsys libries should not to replace this library, rather provide
a standardized way to interact between the built-in :mod:`logging`
library and ``PyAnsys`` libraries.
The logging capabilities in PyAnsys libraries should be built upon the `standard
logging <https://docs.python.org/3/library/logging.html>`__ library. PyAnsys
libries should not to replace this library, rather provide a standardized way to
interact between the built-in :mod:`logging` library and ``PyAnsys`` libraries.


Logging Best Practices
----------------------

Avoid printing to the console
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A common habit while prototyping a new feature is to print message into the command line executable.
Instead of using the common ``Print()`` method, it is advised to use a ``StreamHandler`` and redirect its content.
Indeed that will allow to filter messages based on their level and apply properly the formatter.
To do so, a boolean argument can be added in the initializer of the ``Logger`` class.
This argument specifies how to handle the stream.
A common habit while prototyping a new feature is to print message into the
command line executable. Instead of using the common ``Print()`` method, it is
advised to use a ``StreamHandler`` and redirect its content. Indeed that will
allow to filter messages based on their level and apply properly the formatter.
To do so, a boolean argument can be added in the initializer of the ``Logger``
class. This argument specifies how to handle the stream.

Enable/Disable handlers
~~~~~~~~~~~~~~~~~~~~~~~
Sometimes the user might want to disable specific handlers such as a
file handler where log messages are written. If so, the existing
handler must be properly closed and removed. Otherwise the file access
might be denied later when you try to write new log content.
Sometimes the user might want to disable specific handlers such as a file
handler where log messages are written. If so, the existing handler must be
properly closed and removed. Otherwise the file access might be denied later
when you try to write new log content.

Here's one approach to closing log handlers.

Expand All @@ -70,12 +71,13 @@ Here's one approach to closing log handlers.

App Filter
~~~~~~~~~~
A filter shows all its value when the content of a message depends on some conditions.
It injects contextual information in the core of the message.
This can be useful to harmonize the message rendering when the application output is not consistent
and vary upon the data processed.
It requires the creation of class based on the logging.Filter and the implementation of
the ``filter`` method. This method will contain all the modified content send to the stream.
A filter shows all its value when the content of a message depends on some
conditions. It injects contextual information in the core of the message.
This can be useful to harmonize the message rendering when the application
output is not consistent and vary upon the data processed.
It requires the creation of class based on the logging.Filter and the
implementation of the ``filter`` method. This method will contain all the
modified content send to the stream.

.. code:: python

Expand Down Expand Up @@ -113,12 +115,12 @@ the ``filter`` method. This method will contain all the modified content send to

String format
~~~~~~~~~~~~~
Even if the current practice recommends using the f-string to format
most strings, when it comes to logging, the former %-formatting is
preferable. This way the string format is not evaluated at
runtime. It is deferred and evaluated only when the message is
emitted. If there is any formatting or evaluation error, these will be
reported as logging errors and will not halt code execution.
Even if the current practice recommends using the f-string to format most
strings, when it comes to logging, the former %-formatting is preferable. This
way the string format is not evaluated at runtime. It is deferred and evaluated
only when the message is emitted. If there is any formatting or evaluation
error, these will be reported as logging errors and will not halt code
execution.

.. code:: python

Expand All @@ -127,18 +129,16 @@ reported as logging errors and will not halt code execution.

Application or Service Logging
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following guidelines describe "Application" or "Service" logging
module for a PyAnsys library, where a PyAnsys library is used to
extend or expose features from an Ansys application, product, or
service that may be local or remote.

This section describes two main loggers for a PyAnsys library that
exposes or extends a service based application, the *Global logger*
and the *Instance logger*. These loggers are customized classes that wrap
:class:`logging.Logger` from :mod:`logging` module and add specific
features to it. :ref:`logging_in_pymapdl_figure` outlines the logging
approach used by PyMAPDL and the scopes of the global and local
loggers.
The following guidelines describe "Application" or "Service" logging module for
a PyAnsys library, where a PyAnsys library is used to extend or expose features
from an Ansys application, product, or service that may be local or remote.

This section describes two main loggers for a PyAnsys library that exposes or
extends a service based application, the *Global logger* and the *Instance
logger*. These loggers are customized classes that wrap :class:`logging.Logger`
from :mod:`logging` module and add specific features to it.
:ref:`logging_in_pymapdl_figure` outlines the logging approach used by PyMAPDL
and the scopes of the global and local loggers.

.. _logging_in_pymapdl_figure:

Expand Down Expand Up @@ -167,14 +167,13 @@ Following are some unit tests demonstatring how to use the code implemented abov
Example Global logger
~~~~~~~~~~~~~~~~~~~~~

There is a global logger named ``py*_global`` which is created when
importing ``ansys.product.service``
(``ansys.product.service.__init__``). This logger is recommended for
most scenarios, especially when complex modules or classes are not
involved, since it does not track instances, rather can be used
globally. If you intend to log the initialization of a library or
module, you should use this logger. To use this global logger, you
must import it at the top of your script or module:
There is a global logger named ``py*_global`` which is created when importing
``ansys.product.service`` (``ansys.product.service.__init__``). This logger is
recommended for most scenarios, especially when complex modules or classes are
not involved, since it does not track instances, rather can be used globally.
If you intend to log the initialization of a library or module, you should use
this logger. To use this global logger, you must import it at the top of your
script or module:

.. code:: python

Expand Down Expand Up @@ -216,13 +215,12 @@ you can add a file handler using:
file_path = os.path.join(os.getcwd(), 'pylibrary.log')
LOG.log_to_file(file_path)

This enables logging to that file in addition of the standard output.
If you wish to change the characteristics of this global logger from
the beginning of the execution, you must edit the file ``__init__`` in
the directory of your library.
This enables logging to that file in addition of the standard output. If you
wish to change the characteristics of this global logger from the beginning of
the execution, you must edit the file ``__init__`` in the directory of your
library.

To log using this logger, simply call the desired method as a normal
logger.
To log using this logger, simply call the desired method as a normal logger.

.. code:: python

Expand All @@ -237,13 +235,13 @@ logger.

Instance logger
~~~~~~~~~~~~~~~
Every time that the class ``_MapdlCore`` is instantiated, a logger is
created. This logger is recommended when using the ``pool`` library
or when using multiple instances of ``Mapdl``. The main feature of
this logger is that it tracks each instance and it includes its name
when logging. The name of the instances are unique. For example in
case of using the ``gRPC`` ``Mapdl`` version, its name includes the IP
and port of the correspondent instance, making unique its logger.
Every time that the class ``_MapdlCore`` is instantiated, a logger is created.
This logger is recommended when using the ``pool`` library or when using
multiple instances of ``Mapdl``. The main feature of this logger is that it
tracks each instance and it includes its name when logging. The name of the
instances are unique. For example in case of using the ``gRPC`` ``Mapdl``
version, its name includes the IP and port of the correspondent instance, making
unique its logger.


The instance loggers can be accessed in two places:
Expand All @@ -252,11 +250,11 @@ The instance loggers can be accessed in two places:
* ``LOG._instances``. This field is a ``dict`` where the key is the
name of the created logger.

These instance loggers inherit from the ``pymapdl_global`` output
handlers and logging level unless otherwise specified. The way this
logger works is very similar to the global logger. You can add a file
handler if you wish using the method ``log_to_file`` or change the log
level using :meth:`logging.Logger.setLevel`.
These instance loggers inherit from the ``pymapdl_global`` output handlers and
logging level unless otherwise specified. The way this logger works is very
similar to the global logger. You can add a file handler if you wish using the
method ``log_to_file`` or change the log level using
:meth:`logging.Logger.setLevel`.

You can use this logger like this:

Expand All @@ -274,17 +272,22 @@ You can use this logger like this:

Wrapping Other Loggers
~~~~~~~~~~~~~~~~~~~~~~
A product, due to its architecture can be made of several loggers.
The ``logging`` library features allows to work with a finite number of loggers.
The factory function logging.getLogger() helps to access each logger by its name.
In addition of this naming-mappings, a hierachy can be established to structure the loggers
parenting and their connection.


For instance, if an ANSYS product is using a pre-exsiting custom logger encapsulated inside the product itself, the <PyProject> will benefit from exposing it through the standard python tools.
It is recommended to use the standard library as much as possible. It will facilitate every contribution -both external and internal- to the <PyProject> by exposing common tools that are widely spread.
Each developer will be able to operate quickly and autonomously.
The project will take advantage of the entire set of features exposed in the standard logger and all the upcoming improvements.
A product, due to its architecture can be made of several loggers. The
``logging`` library features allows to work with a finite number of loggers. The
factory function logging.getLogger() helps to access each logger by its name. In
addition of this naming-mappings, a hierachy can be established to structure the
loggers parenting and their connection.


For instance, if an ANSYS product is using a pre-exsiting custom logger
encapsulated inside the product itself, the <PyProject> will benefit from
exposing it through the standard python tools. It is recommended to use the
standard library as much as possible. It will facilitate every contribution
-both external and internal- to the <PyProject> by exposing common tools that
are widely spread. Each developer will be able to operate quickly and
autonomously.
The project will take advantage of the entire set of features exposed in the
standard logger and all the upcoming improvements.

Create a custom log handler to catch each product message and redirect them on another logger:
==============================================================================================
Expand All @@ -298,9 +301,12 @@ AEDT product has its own internal logger called the message manager made of 3 ma
* *Project*: related to the project
* *Design*: related to the design (most specific destination of each 3 loggers.)

The message manager is not using the standard python logging module and this might be a problem later when exporting messages and data from each ANSYS product to a common tool.
In most of the cases, it is easier to work with the standard python module to extract data.
In order to overcome this limitation, the existing message manager is wrapped into a logger based on the standard python `logging <https://docs.python.org/3/library/logging.html>`__ module.
The message manager is not using the standard python logging module and this
might be a problem later when exporting messages and data from each ANSYS
product to a common tool. In most of the cases, it is easier to work with the
standard python module to extract data.
In order to overcome this limitation, the existing message manager is wrapped
into a logger based on the standard python :mod:`logging` module.


.. figure:: images/log_flow.png
Expand All @@ -311,8 +317,10 @@ In order to overcome this limitation, the existing message manager is wrapped in
**Figure 1: Loggers message passing flow.**


This wrapper implementation boils down to a custom handler. It is based on a class inherited from logging.Handler.
The initializer of this class will require the message manager to be passed as an argument in order to link the standard logging service with the ANSYS internal message manager.
This wrapper implementation boils down to a custom handler. It is based on a
class inherited from logging.Handler. The initializer of this class will require
the message manager to be passed as an argument in order to link the standard
logging service with the ANSYS internal message manager.

.. code:: python

Expand All @@ -330,9 +338,17 @@ The initializer of this class will require the message manager to be passed as a


The purpose of this class is to send log messages in AEDT logging stream.
One of the mandatory actions is to overwrite the ``emit`` function. This method operates as a proxy. It will dispatch all the log message toward the message manager.
Based on the record level, the message is sent to the appropriate log level (debug, info, error...) into the message manager to fit the level provided by the ANSYS product.
As a reminder the record is an object containing all kind of information related to the event logged.

This custom handler is used into the new logger instance (the one based on the standard library).
A good practice before to add a handler on any logger is to verify if any appropriate handler is already available in order to avoid any conflict, message duplication...
One of the mandatory actions is to overwrite the ``emit`` function. This method
operates as a proxy. It will dispatch all the log message toward the message
manager.
Based on the record level, the message is sent to the appropriate log level
(debug, info, error...) into the message manager to fit the level provided by
the ANSYS product.
As a reminder the record is an object containing all kind of information related
to the event logged.

This custom handler is used into the new logger instance (the one based on the
standard library).
A good practice before to add a handler on any logger is to verify if any
appropriate handler is already available in order to avoid any conflict, message
duplication...