diff --git a/geos-processing/src/geos/processing/generic_processing_tools/AttributeMapping.py b/geos-processing/src/geos/processing/generic_processing_tools/AttributeMapping.py index 1c8dbf401..42841de7b 100644 --- a/geos-processing/src/geos/processing/generic_processing_tools/AttributeMapping.py +++ b/geos-processing/src/geos/processing/generic_processing_tools/AttributeMapping.py @@ -9,7 +9,7 @@ from vtkmodules.vtkCommonDataModel import vtkDataSet, vtkMultiBlockDataSet from geos.mesh.utils.arrayModifiers import transferAttributeWithElementMap from geos.mesh.utils.arrayHelpers import ( computeElementMapping, getAttributeSet, isAttributeGlobal ) -from geos.utils.Logger import ( Logger, getLogger ) +from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler, isHandlerInLogger, getLoggerHandlerType ) from geos.utils.pieceEnum import Piece __doc__ = """ @@ -111,6 +111,18 @@ def __init__( self.logger.setLevel( logging.INFO ) self.logger.propagate = False + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) + def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: """Set a specific handler for the filter logger. @@ -120,11 +132,10 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: Args: handler (logging.Handler): The handler to add. """ - if len( self.logger.handlers ) == 0: + if not isHandlerInLogger( handler, self.logger ): self.logger.addHandler( handler ) else: - self.logger.warning( "The logger already has an handler, to use yours set the argument 'speHandler'" - " to True during the filter initialization." ) + self.logger.warning( "The logger already has this handler, it has not be added." ) def getElementMap( self: Self ) -> dict[ int, npt.NDArray[ np.int64 ] ]: """Getter of the element mapping dictionary. @@ -184,14 +195,19 @@ def applyFilter( self: Self ) -> None: transferAttributeWithElementMap( self.meshFrom, self.meshTo, self.ElementMap, attributeName, self.piece, self.logger ) - # Log the output message. - self._logOutputMessage() - - return + self.logger.warning( "test" ) - def _logOutputMessage( self: Self ) -> None: - """Create and log result messages of the filter.""" - self.logger.info( f"The filter { self.logger.name } succeeded." ) + # Log the output message. self.logger.info( - f"The attributes { self.attributeNames } have been transferred from the source mesh to the final mesh with the { self.piece.value } mapping." + f"The attributes { self.attributeNames } have been transferred from the source mesh to the final mesh with a { self.piece.value } mapping.\n" ) + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) + + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() + + return diff --git a/geos-processing/src/geos/processing/generic_processing_tools/ClipToMainFrame.py b/geos-processing/src/geos/processing/generic_processing_tools/ClipToMainFrame.py index 90a534111..88ad34efa 100644 --- a/geos-processing/src/geos/processing/generic_processing_tools/ClipToMainFrame.py +++ b/geos-processing/src/geos/processing/generic_processing_tools/ClipToMainFrame.py @@ -16,7 +16,7 @@ from vtkmodules.vtkCommonTransforms import vtkLandmarkTransform from vtkmodules.vtkFiltersGeneral import vtkTransformFilter -from geos.utils.Logger import ( Logger, getLogger ) +from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler, isHandlerInLogger, getLoggerHandlerType ) from geos.mesh.utils.genericHelpers import getMultiBlockBounds __doc__ = """ @@ -212,7 +212,7 @@ def __getFramePoints( self, vpts: vtkPoints ) -> tuple[ vtkPoints, vtkPoints ]: return ( sourcePts, targetPts ) -loggerTitle: str = "Clip mesh to main frame." +loggerTitle: str = "Clip mesh to main frame" class ClipToMainFrame( vtkTransformFilter ): @@ -227,7 +227,8 @@ def __init__( self, speHandler: bool = False, **properties: str ) -> None: properties (kwargs): kwargs forwarded to vtkTransformFilter. """ super().__init__( **properties ) - # Logger. + + # Logger self.logger: Logger if not speHandler: self.logger = getLogger( loggerTitle, True ) @@ -236,8 +237,35 @@ def __init__( self, speHandler: bool = False, **properties: str ) -> None: self.logger.setLevel( logging.INFO ) self.logger.propagate = False + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) + + def Update( self ) -> None: # type: ignore[override] + """Update the filter.""" + super().Update() + + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) + + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() + def ComputeTransform( self ) -> None: """Update the transformation.""" + self.logger.info( f"Apply filter { self.logger.name }." ) + # dispatch to ClipToMainFrame depending on input type if isinstance( self.GetInput(), vtkMultiBlockDataSet ): # locate reference point @@ -265,11 +293,10 @@ def SetLoggerHandler( self, handler: logging.Handler ) -> None: Args: handler (logging.Handler): The handler to add. """ - if len( self.logger.handlers ) == 0: + if not isHandlerInLogger( handler, self.logger ): self.logger.addHandler( handler ) else: - self.logger.warning( "The logger already has an handler, to use yours set the argument 'speHandler' to True" - " during the filter initialization." ) + self.logger.warning( "The logger already has this handler, it has not be added." ) def __locate_reference_point( self, multiBlockDataSet: vtkMultiBlockDataSet ) -> int: """Locate the block to use as reference for the transformation. diff --git a/geos-processing/src/geos/processing/generic_processing_tools/CreateConstantAttributePerRegion.py b/geos-processing/src/geos/processing/generic_processing_tools/CreateConstantAttributePerRegion.py index 572f1f094..1e4d75bc0 100644 --- a/geos-processing/src/geos/processing/generic_processing_tools/CreateConstantAttributePerRegion.py +++ b/geos-processing/src/geos/processing/generic_processing_tools/CreateConstantAttributePerRegion.py @@ -12,7 +12,7 @@ from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet, vtkDataSet from geos.utils.pieceEnum import Piece -from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler ) +from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler, isHandlerInLogger, getLoggerHandlerType ) from geos.mesh.utils.arrayHelpers import ( getArrayInObject, getComponentNames, getNumberOfComponents, getVtkDataTypeInObject, isAttributeGlobal, getAttributePieceInfo, checkValidValuesInDataSet, checkValidValuesInMultiBlock ) @@ -122,11 +122,7 @@ def __init__( # Check if the new component have default values (information for the output message). self.useDefaultValue: bool = False - # Warnings counter. - self.counter: CountWarningHandler = CountWarningHandler() - self.counter.setLevel( logging.INFO ) - - # Logger. + # Logger self.logger: Logger if not speHandler: self.logger = getLogger( loggerTitle, True ) @@ -135,6 +131,18 @@ def __init__( self.logger.setLevel( logging.INFO ) self.logger.propagate = False + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) + def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: """Set a specific handler for the filter logger. @@ -144,12 +152,10 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: Args: handler (logging.Handler): The handler to add. """ - if len( self.logger.handlers ) == 0: + if not isHandlerInLogger( handler, self.logger ): self.logger.addHandler( handler ) else: - # This warning does not count for the number of warning created during the application of the filter. - self.logger.warning( "The logger already has an handler, to use yours set the argument 'speHandler' to True" - " during the filter initialization." ) + self.logger.warning( "The logger already has this handler, it has not be added." ) def applyFilter( self: Self ) -> None: """Create a constant attribute per region in the mesh. @@ -160,9 +166,6 @@ def applyFilter( self: Self ) -> None: """ self.logger.info( f"Apply filter { self.logger.name }." ) - # Add the handler to count warnings messages. - self.logger.addHandler( self.counter ) - # Check the validity of the attribute region. if self.piece == Piece.NONE: raise AttributeError( f"The attribute { self.regionName } is not in the mesh." ) @@ -342,9 +345,6 @@ def _logOutputMessage( self: Self, trueIndexes: list[ Any ] ) -> None: Args: trueIndexes (list[Any]): The list of the true region indexes use to create the attribute. """ - # The Filter succeed. - self.logger.info( f"The filter { self.logger.name } succeeded." ) - # Info about the created attribute. # The piece where the attribute is created. self.logger.info( f"The new attribute { self.newAttributeName } is created on { self.piece.value }." ) @@ -409,3 +409,14 @@ def _logOutputMessage( self: Self, trueIndexes: list[ Any ] ) -> None: self.logger.warning( messValue ) else: self.logger.info( messValue ) + + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) + + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() + + return diff --git a/geos-processing/src/geos/processing/generic_processing_tools/FillPartialArrays.py b/geos-processing/src/geos/processing/generic_processing_tools/FillPartialArrays.py index 08b3356bf..044347708 100644 --- a/geos-processing/src/geos/processing/generic_processing_tools/FillPartialArrays.py +++ b/geos-processing/src/geos/processing/generic_processing_tools/FillPartialArrays.py @@ -6,7 +6,7 @@ from typing import Union, Any from geos.utils.pieceEnum import Piece -from geos.utils.Logger import ( Logger, getLogger ) +from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler, isHandlerInLogger, getLoggerHandlerType ) from geos.mesh.utils.arrayModifiers import fillPartialAttributes from geos.mesh.utils.arrayHelpers import getAttributePieceInfo @@ -89,7 +89,7 @@ def __init__( self.multiBlockDataSet: vtkMultiBlockDataSet = multiBlockDataSet self.dictAttributesValues: dict[ str, Union[ list[ Any ], None ] ] = dictAttributesValues - # Logger. + # Logger self.logger: Logger if not speHandler: self.logger = getLogger( loggerTitle, True ) @@ -98,6 +98,18 @@ def __init__( self.logger.setLevel( logging.INFO ) self.logger.propagate = False + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) + def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: """Set a specific handler for the filter logger. @@ -107,11 +119,10 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: Args: handler (logging.Handler): The handler to add. """ - if len( self.logger.handlers ) == 0: + if not isHandlerInLogger( handler, self.logger ): self.logger.addHandler( handler ) else: - self.logger.warning( "The logger already has an handler, to use yours set the argument 'speHandler' to True" - " during the filter initialization." ) + self.logger.warning( "The logger already has this handler, it has not be added." ) def applyFilter( self: Self ) -> None: """Create a constant attribute per region in the mesh. @@ -121,8 +132,10 @@ def applyFilter( self: Self ) -> None: ValueError: Error during the filling of the attribute. """ self.logger.info( f"Apply filter { self.logger.name }." ) + piece: Piece - for attributeName in self.dictAttributesValues: + mess: str = "" + for attributeName, values in self.dictAttributesValues.items(): piece = getAttributePieceInfo( self.multiBlockDataSet, attributeName ) if piece == Piece.NONE: raise AttributeError( f"The attribute { attributeName } is not in the mesh." ) @@ -134,9 +147,22 @@ def applyFilter( self: Self ) -> None: fillPartialAttributes( self.multiBlockDataSet, attributeName, piece=piece, - listValues=self.dictAttributesValues[ attributeName ], + listValues=values, logger=self.logger ) + if values is None: + values = [ "the default value" ] + mess = f"{ mess }The attribute { attributeName } has been filled with { values }.\n" + + # Log the output message. + self.logger.info( mess ) + + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) - self.logger.info( f"The filter { self.logger.name } succeed." ) + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() return diff --git a/geos-processing/src/geos/processing/generic_processing_tools/MergeBlockEnhanced.py b/geos-processing/src/geos/processing/generic_processing_tools/MergeBlockEnhanced.py index d44208265..c311bc34a 100644 --- a/geos-processing/src/geos/processing/generic_processing_tools/MergeBlockEnhanced.py +++ b/geos-processing/src/geos/processing/generic_processing_tools/MergeBlockEnhanced.py @@ -6,7 +6,7 @@ from typing_extensions import Self -from geos.utils.Logger import ( Logger, getLogger ) +from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler, isHandlerInLogger, getLoggerHandlerType ) from geos.mesh.utils.multiblockModifiers import mergeBlocks from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet, vtkUnstructuredGrid @@ -91,6 +91,18 @@ def __init__( self.logger.setLevel( logging.INFO ) self.logger.propagate = False + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) + def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: """Set a specific handler for the filter logger. @@ -99,11 +111,10 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: Args: handler (logging.Handler): The handler to add. """ - if len( self.logger.handlers ) == 0: + if not isHandlerInLogger( handler, self.logger ): self.logger.addHandler( handler ) else: - self.logger.warning( "The logger already has an handler, to use yours set the argument 'speHandler' to True" - " during the filter initialization." ) + self.logger.warning( "The logger already has this handler, it has not be added." ) def applyFilter( self: Self ) -> None: """Merge the blocks of a multiblock dataset mesh. @@ -117,7 +128,14 @@ def applyFilter( self: Self ) -> None: outputMesh = mergeBlocks( self.inputMesh, keepPartialAttributes=True, logger=self.logger ) self.outputMesh = outputMesh - self.logger.info( f"The filter { self.logger.name } succeeded." ) + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) + + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() return diff --git a/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py b/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py index bad8002ce..c27204649 100644 --- a/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py +++ b/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py @@ -12,7 +12,7 @@ VTK_POLYHEDRON, VTK_POLYGON ) from vtkmodules.util.numpy_support import numpy_to_vtk, vtk_to_numpy -from geos.utils.Logger import ( Logger, getLogger ) +from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler, isHandlerInLogger, getLoggerHandlerType ) from geos.processing.pre_processing.CellTypeCounterEnhanced import CellTypeCounterEnhanced from geos.mesh.model.CellTypeCounts import CellTypeCounts @@ -84,6 +84,18 @@ def __init__( self, inputMesh: vtkUnstructuredGrid, speHandler: bool = False ) - self.logger.setLevel( logging.INFO ) self.logger.propagate = False + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) + def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: """Set a specific handler for the filter logger. @@ -93,11 +105,10 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: handler (logging.Handler): The handler to add. """ self.handler = handler - if len( self.logger.handlers ) == 0: + if not isHandlerInLogger( handler, self.logger ): self.logger.addHandler( handler ) else: - self.logger.warning( "The logger already has an handler, to use yours set the argument 'speHandler' to True" - " during the filter initialization." ) + self.logger.warning( "The logger already has this handler, it has not be added." ) def applyFilter( self: Self ) -> None: """Apply the filter SplitMesh. @@ -106,7 +117,8 @@ def applyFilter( self: Self ) -> None: TypeError: Errors due to objects with the wrong type. AttributeError: Errors with cell data. """ - self.logger.info( f"Applying filter { self.logger.name }." ) + self.logger.info( f"Apply filter { self.logger.name }." ) + # Count the number of cells before splitting. Then we will be able to know how many new cells and points # to allocate because each cell type is splitted in a known number of new cells and points. nbCells: int = self.inputMesh.GetNumberOfCells() @@ -170,7 +182,15 @@ def applyFilter( self: Self ) -> None: # Transfer all cell arrays self._transferCellArrays( self.outputMesh ) - self.logger.info( f"The filter { self.logger.name } succeeded." ) + + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) + + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() return @@ -186,9 +206,13 @@ def _getCellCounts( self: Self ) -> CellTypeCounts: """ cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced( self.inputMesh, self.speHandler ) - if self.speHandler and len( cellTypeCounterEnhancedFilter.logger.handlers ) == 0: + if self.speHandler and not isHandlerInLogger( self.handler, cellTypeCounterEnhancedFilter.logger ): cellTypeCounterEnhancedFilter.setLoggerHandler( self.handler ) + cellTypeCounterEnhancedFilter.applyFilter() + # Add to the warning counter the number of warning logged with the call of CelltypeCounterEnhanced filter + self.counter.addExternalWarningCount( cellTypeCounterEnhancedFilter.nbWarnings ) + return cellTypeCounterEnhancedFilter.GetCellTypeCountsObject() def _addMidPoint( self: Self, ptA: int, ptB: int ) -> int: diff --git a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py index 0e89e6c23..db9887f3a 100644 --- a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py +++ b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py @@ -16,7 +16,7 @@ from geos.mesh.utils.arrayHelpers import ( getArrayInObject, isAttributeInObject ) from geos.utils.pieceEnum import Piece -from geos.utils.Logger import ( Logger, getLogger ) +from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler, isHandlerInLogger, getLoggerHandlerType ) from geos.utils.GeosOutputsConstants import ( AttributeEnum, ComponentNameEnum, GeosMeshOutputsEnum, PostProcessingOutputsEnum ) from geos.utils.PhysicalConstants import ( DEFAULT_FRICTION_ANGLE_RAD, DEFAULT_GRAIN_BULK_MODULUS, @@ -712,6 +712,18 @@ def __init__( self.logger.setLevel( logging.INFO ) self.logger.propagate = False + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) + def applyFilter( self: Self ) -> None: """Compute the geomechanics properties and create attributes on the mesh. @@ -750,7 +762,14 @@ def applyFilter( self: Self ) -> None: logger=self.logger ) self.logger.info( "All the geomechanics properties have been added to the mesh." ) - self.logger.info( f"The filter { self.logger.name } succeeded." ) + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) + + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() return @@ -770,12 +789,10 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: Args: handler (logging.Handler): The handler to add. """ - if len( self.logger.handlers ) == 0: + if not isHandlerInLogger( handler, self.logger ): self.logger.addHandler( handler ) else: - self.logger.warning( - "The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization." - ) + self.logger.warning( "The logger already has this handler, it has not be added." ) def getOutputType( self: Self ) -> str: """Get output object type. diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockExtractor.py b/geos-processing/src/geos/processing/post_processing/GeosBlockExtractor.py index 925d23f2a..5c12bc193 100644 --- a/geos-processing/src/geos/processing/post_processing/GeosBlockExtractor.py +++ b/geos-processing/src/geos/processing/post_processing/GeosBlockExtractor.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from typing_extensions import Self -from geos.utils.Logger import ( Logger, getLogger ) +from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler, isHandlerInLogger, getLoggerHandlerType ) from geos.utils.GeosOutputsConstants import GeosDomainNameEnum from geos.mesh.utils.arrayHelpers import getCellDimension from geos.mesh.utils.multiblockHelpers import getBlockIndexFromName @@ -193,6 +193,18 @@ def __init__( self.logger.setLevel( logging.INFO ) self.logger.propagate = False + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) + def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: """Set a specific handler for the filter logger. @@ -201,12 +213,10 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: Args: handler (logging.Handler): The handler to add. """ - if len( self.logger.handlers ) == 0: + if not isHandlerInLogger( handler, self.logger ): self.logger.addHandler( handler ) else: - self.logger.warning( - "The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization." - ) + self.logger.warning( "The logger already has this handler, it has not be added." ) def applyFilter( self: Self ) -> None: """Extract the volume, the fault or the well domain of the mesh from GEOS. @@ -229,6 +239,13 @@ def applyFilter( self: Self ) -> None: domainNames.append( domain.value ) self.logger.info( f"The GEOS domain { domainNames } have been extracted." ) - self.logger.info( f"The filter { self.logger.name } succeeded." ) + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) + + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() return diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py index 61e018bf7..88afdf9a2 100644 --- a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py +++ b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py @@ -8,7 +8,7 @@ from vtkmodules.vtkCommonDataModel import vtkCompositeDataSet, vtkMultiBlockDataSet, vtkPolyData, vtkUnstructuredGrid from geos.utils.pieceEnum import Piece -from geos.utils.Logger import ( Logger, getLogger ) +from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler, isHandlerInLogger, getLoggerHandlerType ) from geos.utils.GeosOutputsConstants import ( PHASE_SEP, PhaseTypeEnum, FluidPrefixEnum, PostProcessingOutputsEnum, getRockSuffixRenaming ) @@ -95,7 +95,6 @@ def __init__( """ self.inputMesh: vtkMultiBlockDataSet = inputMesh self.convertFaultToSurface: bool = convertFaultToSurface - self.handler: None | logging.Handler = None self.outputMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet() self.phaseNameDict: dict[ str, set[ str ] ] = { @@ -112,6 +111,18 @@ def __init__( self.logger.setLevel( logging.INFO ) self.logger.propagate = False + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) + def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: """Set a specific handler for the filter logger. @@ -120,13 +131,10 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: Args: handler (logging.Handler): The handler to add. """ - if len( self.logger.handlers ) == 0: - self.handler = handler + if not isHandlerInLogger( handler, self.logger ): self.logger.addHandler( handler ) else: - self.logger.warning( - "The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization." - ) + self.logger.warning( "The logger already has this handler, it has not be added." ) def getOutput( self: Self ) -> vtkMultiBlockDataSet: """Get the mesh with the composite blocks merged.""" @@ -184,7 +192,14 @@ def applyFilter( self: Self ) -> None: else: self.outputMesh.SetBlock( newIndex, volumeMesh ) - self.logger.info( f"The filter { self.logger.name } succeeded." ) + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) + + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() return diff --git a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py index 377b9ba58..fddd0218e 100644 --- a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py +++ b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py @@ -16,7 +16,7 @@ from geos.mesh.utils.genericHelpers import ( getLocalBasisVectors, convertAttributeFromLocalToXYZForOneCell ) import geos.geomechanics.processing.geomechanicsCalculatorFunctions as fcts from geos.utils.pieceEnum import Piece -from geos.utils.Logger import ( Logger, getLogger ) +from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler, isHandlerInLogger, getLoggerHandlerType ) from geos.utils.PhysicalConstants import ( DEFAULT_FRICTION_ANGLE_RAD, DEFAULT_ROCK_COHESION ) from geos.utils.GeosOutputsConstants import ( ComponentNameEnum, GeosMeshOutputsEnum, PostProcessingOutputsEnum ) @@ -88,12 +88,16 @@ Note that the dimension of the attributes to convert must be equal or greater than 3. """ -loggerTitle: str = "Surface Geomechanics" class SurfaceGeomechanics: - def __init__( self: Self, surfacicMesh: vtkPolyData, speHandler: bool = False ) -> None: + def __init__( + self: Self, + surfacicMesh: vtkPolyData, + loggerName: str = "Surface Geomechanics", + speHandler: bool = False, + ) -> None: """Vtk filter to compute geomechanical surfacic attributes. Input and Output objects are a vtkPolydata with surfaces @@ -101,21 +105,35 @@ def __init__( self: Self, surfacicMesh: vtkPolyData, speHandler: bool = False ) Args: surfacicMesh (vtkPolyData): The input surfacic mesh. + loggerName (str, optional): Name of the filter logger. + Defaults to "Surface Geomechanics". speHandler (bool, optional): True to use a specific handler, False to use the internal handler. Defaults to False. """ # Logger self.logger: Logger if not speHandler: - self.logger = getLogger( loggerTitle, True ) + self.logger = getLogger( loggerName, True ) else: - self.logger = logging.getLogger( loggerTitle ) + self.logger = logging.getLogger( loggerName ) self.logger.setLevel( logging.INFO ) self.logger.propagate = False + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) + # Input surfacic mesh if not surfacicMesh.IsA( "vtkPolyData" ): - self.logger.error( f"Input surface is expected to be a vtkPolyData, not a {type(surfacicMesh)}." ) + self.logger.error( f"Input surface is expected to be a vtkPolyData, not a { type( surfacicMesh ) }." ) self.inputMesh: vtkPolyData = surfacicMesh # Identification of the input surface (logging purpose) self.name: Union[ str, None ] = None @@ -144,20 +162,10 @@ def SetLoggerHandler( self: Self, handler: Logger ) -> None: Args: handler (logging.Handler): The handler to add. """ - if len( self.logger.handlers ) == 0: + if not isHandlerInLogger( handler, self.logger ): self.logger.addHandler( handler ) else: - self.logger.warning( - "The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization." - ) - - def SetSurfaceName( self: Self, name: str ) -> None: - """Set a name for the input surface. For logging purpose only. - - Args: - name (str): The identifier for the surface. - """ - self.name = name + self.logger.warning( "The logger already has this handler, it has not be added." ) def SetRockCohesion( self: Self, rockCohesion: float ) -> None: """Set rock cohesion value. Defaults to 0.0 Pa. @@ -238,13 +246,7 @@ def applyFilter( self: Self ) -> None: AttributeError: Attributes must be on cell. AssertionError: Something went wrong during the shearCapacityUtilization computation. """ - msg = f"Applying filter {self.logger.name}" - if self.name is not None: - msg += f" on surface : {self.name}." - else: - msg += "." - - self.logger.info( msg ) + self.logger.info( f"Applying filter { self.logger.name }." ) self.outputMesh = vtkPolyData() self.outputMesh.ShallowCopy( self.inputMesh ) @@ -257,7 +259,14 @@ def applyFilter( self: Self ) -> None: # Compute shear capacity utilization self.computeShearCapacityUtilization() - self.logger.info( f"Filter {self.logger.name} successfully applied on surface {self.name}." ) + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) + + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() return diff --git a/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py b/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py index 3938a5978..9dd791112 100644 --- a/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py +++ b/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py @@ -9,7 +9,7 @@ from geos.mesh.model.CellTypeCounts import CellTypeCounts from geos.mesh.stats.meshQualityMetricHelpers import getAllCellTypes -from geos.utils.Logger import ( Logger, getLogger ) +from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler, isHandlerInLogger, getLoggerHandlerType ) __doc__ = """ CellTypeCounterEnhanced module is a vtk filter that computes cell type counts. @@ -68,7 +68,7 @@ def __init__( self.outTable: vtkTable = vtkTable() self._counts: CellTypeCounts = CellTypeCounts() - # Logger. + # Logger self.logger: Logger if not speHandler: self.logger = getLogger( loggerTitle, True ) @@ -77,6 +77,18 @@ def __init__( self.logger.setLevel( logging.INFO ) self.logger.propagate = False + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) + def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: """Set a specific handler for the filter logger. @@ -86,11 +98,10 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: Args: handler (logging.Handler): The handler to add. """ - if len( self.logger.handlers ) == 0: + if not isHandlerInLogger( handler, self.logger ): self.logger.addHandler( handler ) else: - self.logger.warning( "The logger already has an handler, to use yours set the argument 'speHandler'" - " to True during the filter initialization." ) + self.logger.warning( "The logger already has this handler, it has not be added." ) def applyFilter( self: Self ) -> None: """Apply CellTypeCounterEnhanced filter. @@ -121,7 +132,15 @@ def applyFilter( self: Self ) -> None: array.SetNumberOfValues( 1 ) array.SetValue( 0, self._counts.getTypeCount( cellType ) ) self.outTable.AddColumn( array ) - self.logger.info( f"The filter { self.logger.name } succeeded." ) + + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) + + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() return diff --git a/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py b/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py index 930c64c41..99855ef3d 100644 --- a/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py +++ b/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py @@ -28,7 +28,7 @@ getChildrenCellTypes ) import geos.utils.geometryFunctions as geom -from geos.utils.Logger import ( Logger, getLogger ) +from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler, isHandlerInLogger, getLoggerHandlerType ) from geos.utils.pieceEnum import Piece __doc__ = """ @@ -131,10 +131,9 @@ def __init__( # Static members that can be loaded once to save computational times self._allCellTypesExtended: tuple[ int, ...] = getAllCellTypesExtended() self._allCellTypes: tuple[ int, ...] = getAllCellTypes() - - # Logger. self.speHandler: bool = speHandler - self.handler: None | logging.Handler = None + + # Logger self.logger: Logger if not speHandler: self.logger = getLogger( loggerTitle, True ) @@ -143,6 +142,18 @@ def __init__( self.logger.setLevel( logging.INFO ) self.logger.propagate = False + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) + def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: """Set a specific handler for the filter logger. @@ -153,11 +164,10 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: handler (logging.Handler): The handler to add. """ self.handler = handler - if len( self.logger.handlers ) == 0: + if not isHandlerInLogger( handler, self.logger ): self.logger.addHandler( handler ) else: - self.logger.warning( "The logger already has an handler, to use yours set the argument 'speHandler'" - " to True during the filter initialization." ) + self.logger.warning( "The logger already has this handler, it has not be added." ) def GetQualityMetricSummary( self: Self ) -> QualityMetricSummary: """Get QualityMetricSummary object. @@ -308,7 +318,14 @@ def applyFilter( self: Self ) -> None: self._outputMesh.Modified() - self.logger.info( f"The filter { self.logger.name } succeeded." ) + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) + + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() return @@ -320,10 +337,15 @@ def _computeCellTypeCounts( self: Self ) -> None: """Compute cell type counts.""" cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced( self._outputMesh, self.speHandler ) - if self.speHandler and len( cellTypeCounterEnhancedFilter.logger.handlers ) == 0: + + if self.speHandler and not isHandlerInLogger( self.handler, cellTypeCounterEnhancedFilter.logger ): cellTypeCounterEnhancedFilter.setLoggerHandler( self.handler ) + cellTypeCounterEnhancedFilter.applyFilter() + # Add to the warning counter the number of warning logged with the call of CelltypeCounterEnhanced filter + self.counter.addExternalWarningCount( cellTypeCounterEnhancedFilter.nbWarnings ) + counts: CellTypeCounts = cellTypeCounterEnhancedFilter.GetCellTypeCountsObject() if counts is None: raise AttributeError( "CellTypeCounts is undefined" ) @@ -356,6 +378,7 @@ def _evaluateCellQuality( self: Self, metricIndex: int ) -> None: """ arrayName: str = getQualityMetricArrayName( metricIndex ) if arrayName in getAttributesFromDataSet( self._outputMesh, piece=Piece.CELLS ): + self.logger.warning( "test" ) # Metric is already computed (by default computed for all cell types if applicable ) return diff --git a/geos-pv/src/geos/pv/plugins/generic_processing/PVAttributeMapping.py b/geos-pv/src/geos/pv/plugins/generic_processing/PVAttributeMapping.py index 96c10685a..d6b07cab6 100644 --- a/geos-pv/src/geos/pv/plugins/generic_processing/PVAttributeMapping.py +++ b/geos-pv/src/geos/pv/plugins/generic_processing/PVAttributeMapping.py @@ -3,6 +3,7 @@ # SPDX-FileContributor: Raphaël Vinour, Martin Lemay, Romain Baville # ruff: noqa: E402 # disable Module level import not at top of file import sys +import logging from pathlib import Path from typing import Union from typing_extensions import Self @@ -27,6 +28,7 @@ from geos.pv.utils.details import FilterCategory from geos.utils.pieceEnum import Piece +from geos.utils.Logger import isHandlerInLogger __doc__ = f""" AttributeMapping is a paraview plugin that transfers global attributes from a source mesh to a final mesh with same point/cell coordinates. @@ -74,6 +76,7 @@ def __init__( self: Self ) -> None: self.piece: Piece = Piece.CELLS self.clearAttributeNames = True self.attributeNames: list[ str ] = [] + self.handler: logging.Handler = VTKHandler() @smproperty.intvector( name="AttributePiece", @@ -187,12 +190,11 @@ def RequestData( attributeMappingFilter: AttributeMapping = AttributeMapping( meshFrom, outData, set( self.attributeNames ), self.piece, True ) - if len( attributeMappingFilter.logger.handlers ) == 0: - attributeMappingFilter.setLoggerHandler( VTKHandler() ) + if not isHandlerInLogger( self.handler, attributeMappingFilter.logger ): + attributeMappingFilter.setLoggerHandler( self.handler ) try: attributeMappingFilter.applyFilter() - self.clearAttributeNames = True except ( ValueError, AttributeError ) as e: attributeMappingFilter.logger.error( f"The filter { attributeMappingFilter.logger.name } failed due to:\n{ e }" ) @@ -200,4 +202,6 @@ def RequestData( mess: str = f"The filter { attributeMappingFilter.logger.name } failed due to:\n{ e }" attributeMappingFilter.logger.critical( mess, exc_info=True ) + self.clearAttributeNames = True + return 1 diff --git a/geos-pv/src/geos/pv/plugins/generic_processing/PVClipToMainFrame.py b/geos-pv/src/geos/pv/plugins/generic_processing/PVClipToMainFrame.py index acfdc2917..bc939e10e 100644 --- a/geos-pv/src/geos/pv/plugins/generic_processing/PVClipToMainFrame.py +++ b/geos-pv/src/geos/pv/plugins/generic_processing/PVClipToMainFrame.py @@ -3,6 +3,7 @@ # SPDX-FileContributor: Jacques Franc # ruff: noqa: E402 # disable Module level import not at top of file import sys +import logging from pathlib import Path from paraview.util.vtkAlgorithm import VTKPythonAlgorithmBase # type: ignore[import-not-found] @@ -21,6 +22,7 @@ from geos.pv.utils.details import ( SISOFilter, FilterCategory ) from geos.processing.generic_processing_tools.ClipToMainFrame import ClipToMainFrame +from geos.utils.Logger import isHandlerInLogger __doc__ = f""" Clip the input mesh to the main frame applying the correct LandmarkTransform @@ -43,8 +45,10 @@ class PVClipToMainFrame( VTKPythonAlgorithmBase ): def __init__( self ) -> None: """Init motherclass, filter and logger.""" self._realFilter = ClipToMainFrame( speHandler=True ) - if len( self._realFilter.logger.handlers ) == 0: - self._realFilter.SetLoggerHandler( VTKHandler() ) + self.handler: logging.Handler = VTKHandler() + + if not isHandlerInLogger( self.handler, self._realFilter.logger ): + self._realFilter.SetLoggerHandler( self.handler ) def ApplyFilter( self, inputMesh: vtkMultiBlockDataSet, outputMesh: vtkMultiBlockDataSet ) -> None: """Is applying CreateConstantAttributePerRegion filter. diff --git a/geos-pv/src/geos/pv/plugins/generic_processing/PVCreateConstantAttributePerRegion.py b/geos-pv/src/geos/pv/plugins/generic_processing/PVCreateConstantAttributePerRegion.py index a809d1279..372ea2c3a 100644 --- a/geos-pv/src/geos/pv/plugins/generic_processing/PVCreateConstantAttributePerRegion.py +++ b/geos-pv/src/geos/pv/plugins/generic_processing/PVCreateConstantAttributePerRegion.py @@ -3,6 +3,7 @@ # SPDX-FileContributor: Martin Lemay, Romain Baville # ruff: noqa: E402 # disable Module level import not at top of file import sys +import logging import numpy as np from pathlib import Path @@ -26,6 +27,7 @@ from geos.processing.generic_processing_tools.CreateConstantAttributePerRegion import CreateConstantAttributePerRegion from geos.pv.utils.details import ( SISOFilter, FilterCategory ) +from geos.utils.Logger import isHandlerInLogger __doc__ = f""" PVCreateConstantAttributePerRegion is a Paraview plugin that allows to create an attribute @@ -70,6 +72,7 @@ def __init__( self: Self ) -> None: # Use the handler of paraview for the log. self.speHandler: bool = True + self.handler: logging.Handler = VTKHandler() # Settings of the attribute with the region indexes: @smproperty.stringvector( @@ -289,8 +292,8 @@ def ApplyFilter( self, inputMesh: vtkDataSet, outputMesh: vtkDataSet ) -> None: self.speHandler, ) - if len( createConstantAttributePerRegionFilter.logger.handlers ) == 0: - createConstantAttributePerRegionFilter.setLoggerHandler( VTKHandler() ) + if not isHandlerInLogger( self.handler, createConstantAttributePerRegionFilter.logger ): + createConstantAttributePerRegionFilter.setLoggerHandler( self.handler ) try: createConstantAttributePerRegionFilter.applyFilter() diff --git a/geos-pv/src/geos/pv/plugins/generic_processing/PVFillPartialArrays.py b/geos-pv/src/geos/pv/plugins/generic_processing/PVFillPartialArrays.py index 35eda1607..547945aab 100644 --- a/geos-pv/src/geos/pv/plugins/generic_processing/PVFillPartialArrays.py +++ b/geos-pv/src/geos/pv/plugins/generic_processing/PVFillPartialArrays.py @@ -3,6 +3,7 @@ # SPDX-FileContributor: Martin Lemay, Romain Baville # ruff: noqa: E402 # disable Module level import not at top of file import sys +import logging from pathlib import Path from typing import Any, Optional, Union from typing_extensions import Self @@ -21,6 +22,7 @@ update_paths() +from geos.utils.Logger import isHandlerInLogger from geos.pv.utils.details import ( SISOFilter, FilterCategory ) from geos.processing.generic_processing_tools.FillPartialArrays import FillPartialArrays @@ -49,6 +51,7 @@ def __init__( self: Self, ) -> None: """Fill a partial attribute with constant value per component.""" self.clearDictAttributesValues: bool = True self.dictAttributesValues: dict[ str, Union[ list[ Any ], None ] ] = {} + self.handler: logging.Handler = VTKHandler() @smproperty.xml( """ None: inputType="vtkMultiBlockDataSet", outputType="vtkUnstructuredGrid", ) + self.handler: logging.Handler = VTKHandler() def RequestDataObject( self: Self, @@ -115,8 +118,8 @@ def RequestData( mergeBlockEnhancedFilter: MergeBlockEnhanced = MergeBlockEnhanced( inputMesh, True ) - if len( mergeBlockEnhancedFilter.logger.handlers ) == 0: - mergeBlockEnhancedFilter.setLoggerHandler( VTKHandler() ) + if not isHandlerInLogger( self.handler, mergeBlockEnhancedFilter.logger ): + mergeBlockEnhancedFilter.setLoggerHandler( self.handler ) try: mergeBlockEnhancedFilter.applyFilter() diff --git a/geos-pv/src/geos/pv/plugins/generic_processing/PVSplitMesh.py b/geos-pv/src/geos/pv/plugins/generic_processing/PVSplitMesh.py index e511d8471..81da5b814 100644 --- a/geos-pv/src/geos/pv/plugins/generic_processing/PVSplitMesh.py +++ b/geos-pv/src/geos/pv/plugins/generic_processing/PVSplitMesh.py @@ -3,6 +3,7 @@ # SPDX-FileContributor: Martin Lemay # ruff: noqa: E402 # disable Module level import not at top of file import sys +import logging from pathlib import Path from typing_extensions import Self @@ -21,6 +22,7 @@ update_paths() from geos.processing.generic_processing_tools.SplitMesh import SplitMesh +from geos.utils.Logger import isHandlerInLogger from geos.pv.utils.details import ( SISOFilter, FilterCategory ) __doc__ = f""" @@ -43,7 +45,7 @@ class PVSplitMesh( VTKPythonAlgorithmBase ): def __init__( self: Self ) -> None: """Split mesh cells.""" - pass + self.handler: logging.Handler = VTKHandler() def ApplyFilter( self: Self, inputMesh: vtkPointSet, outputMesh: vtkPointSet ) -> None: """Apply vtk filter. @@ -53,8 +55,8 @@ def ApplyFilter( self: Self, inputMesh: vtkPointSet, outputMesh: vtkPointSet ) - outputMesh: Output mesh. """ splitMeshFilter: SplitMesh = SplitMesh( inputMesh, True ) - if len( splitMeshFilter.logger.handlers ) == 0: - splitMeshFilter.setLoggerHandler( VTKHandler() ) + if not isHandlerInLogger( self.handler, splitMeshFilter.logger ): + splitMeshFilter.setLoggerHandler( self.handler ) try: splitMeshFilter.applyFilter() diff --git a/geos-pv/src/geos/pv/plugins/post_processing/PVGeomechanicsCalculator.py b/geos-pv/src/geos/pv/plugins/post_processing/PVGeomechanicsCalculator.py index 80a1e5fe0..693a5f5f0 100644 --- a/geos-pv/src/geos/pv/plugins/post_processing/PVGeomechanicsCalculator.py +++ b/geos-pv/src/geos/pv/plugins/post_processing/PVGeomechanicsCalculator.py @@ -3,6 +3,7 @@ # SPDX-FileContributor: Martin Lemay, Romain Baville # ruff: noqa: E402 # disable Module level import not at top of file import sys +import logging import numpy as np from pathlib import Path @@ -22,6 +23,7 @@ update_paths() +from geos.utils.Logger import ( CountWarningHandler, isHandlerInLogger, getLoggerHandlerType ) from geos.utils.PhysicalConstants import ( DEFAULT_FRICTION_ANGLE_DEG, DEFAULT_GRAIN_BULK_MODULUS, DEFAULT_ROCK_COHESION, WATER_DENSITY ) from geos.mesh.utils.multiblockHelpers import ( getBlockElementIndexesFlatten, getBlockNameFromIndex ) @@ -71,6 +73,8 @@ """ +loggerTitle: str = "Geomechanics Calculator" + @SISOFilter( category=FilterCategory.GEOS_POST_PROCESSING, decoratedLabel="GEOS Geomechanics Calculator", @@ -89,6 +93,24 @@ def __init__( self: Self ) -> None: self.rockCohesion: float = DEFAULT_ROCK_COHESION self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_DEG + self.handler: logging.Handler = VTKHandler() + self.logger = logging.getLogger( loggerTitle ) + self.logger.setLevel( logging.INFO ) + self.logger.addHandler( self.handler ) + self.logger.propagate = False + + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) + @smproperty.doublevector( name="GrainBulkModulus", label="Grain bulk modulus (Pa)", @@ -239,11 +261,12 @@ def ApplyFilter( geomechanicsCalculatorFilter = GeomechanicsCalculator( outputMesh, self.computeAdvancedProperties, + loggerName="Geomechanics Calculators on the unstructured grid", speHandler=True, ) - if len( geomechanicsCalculatorFilter.logger.handlers ) == 0: - geomechanicsCalculatorFilter.setLoggerHandler( VTKHandler() ) + if not isHandlerInLogger( self.handler, geomechanicsCalculatorFilter.logger ): + geomechanicsCalculatorFilter.setLoggerHandler( self.handler ) geomechanicsCalculatorFilter.physicalConstants.grainBulkModulus = self.grainBulkModulus geomechanicsCalculatorFilter.physicalConstants.specificDensity = self.specificDensity @@ -252,6 +275,9 @@ def ApplyFilter( try: geomechanicsCalculatorFilter.applyFilter() + # Add to the warning counter the number of warning logged with the call of GeomechanicsCalculator filter (useful for the PVGeomechanicsWorkflow) + self.counter.addExternalWarningCount( geomechanicsCalculatorFilter.nbWarnings ) + outputMesh.ShallowCopy( geomechanicsCalculatorFilter.getOutput() ) except ( ValueError, AttributeError ) as e: geomechanicsCalculatorFilter.logger.error( @@ -261,6 +287,8 @@ def ApplyFilter( geomechanicsCalculatorFilter.logger.critical( mess, exc_info=True ) elif isinstance( outputMesh, vtkMultiBlockDataSet ): + self.logger.info( f"Apply plugin { self.logger.name }." ) + volumeBlockIndexes: list[ int ] = getBlockElementIndexesFlatten( outputMesh ) for blockIndex in volumeBlockIndexes: volumeBlock: vtkUnstructuredGrid = vtkUnstructuredGrid.SafeDownCast( @@ -275,8 +303,8 @@ def ApplyFilter( True, ) - if len( geomechanicsCalculatorFilter.logger.handlers ) == 0: - geomechanicsCalculatorFilter.setLoggerHandler( VTKHandler() ) + if not isHandlerInLogger( self.handler, geomechanicsCalculatorFilter.logger ): + geomechanicsCalculatorFilter.setLoggerHandler( self.handler ) geomechanicsCalculatorFilter.physicalConstants.grainBulkModulus = self.grainBulkModulus geomechanicsCalculatorFilter.physicalConstants.specificDensity = self.specificDensity @@ -285,6 +313,9 @@ def ApplyFilter( try: geomechanicsCalculatorFilter.applyFilter() + # Add to the warning counter the number of warning logged with the call of GeomechanicsCalculator filter + self.counter.addExternalWarningCount( geomechanicsCalculatorFilter.nbWarnings ) + volumeBlock.ShallowCopy( geomechanicsCalculatorFilter.getOutput() ) volumeBlock.Modified() except ( ValueError, AttributeError ) as e: @@ -294,6 +325,14 @@ def ApplyFilter( mess = f"The filter { geomechanicsCalculatorFilter.logger.name } failed due to:\n{ e }" geomechanicsCalculatorFilter.logger.critical( mess, exc_info=True ) + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) + outputMesh.Modified() + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() return diff --git a/geos-pv/src/geos/pv/plugins/post_processing/PVGeomechanicsWorkflow.py b/geos-pv/src/geos/pv/plugins/post_processing/PVGeomechanicsWorkflow.py index d0a90a314..a92d99d3e 100644 --- a/geos-pv/src/geos/pv/plugins/post_processing/PVGeomechanicsWorkflow.py +++ b/geos-pv/src/geos/pv/plugins/post_processing/PVGeomechanicsWorkflow.py @@ -16,6 +16,7 @@ update_paths() from geos.utils.Errors import VTKError +from geos.utils.Logger import ( CountWarningHandler, getLoggerHandlerType ) from geos.utils.PhysicalConstants import ( DEFAULT_FRICTION_ANGLE_DEG, DEFAULT_GRAIN_BULK_MODULUS, DEFAULT_ROCK_COHESION, WATER_DENSITY ) @@ -138,11 +139,23 @@ def __init__( self: Self ) -> None: self.rockCohesion: float = DEFAULT_ROCK_COHESION self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_DEG + self.handler: logging.Handler = VTKHandler() self.logger = logging.getLogger( loggerTitle ) self.logger.setLevel( logging.INFO ) - self.logger.addHandler( VTKHandler() ) + self.logger.addHandler( self.handler ) + self.logger.propagate = False - self.logger.info( f"Apply plugin { self.logger.name }." ) + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) @smproperty.doublevector( name="GrainBulkModulus", @@ -323,6 +336,8 @@ def RequestData( Returns: int: 1 if calculation successfully ended, 0 otherwise. """ + self.logger.info( f"Apply plugin { self.logger.name }." ) + try: self.volumeMesh = self.GetOutputData( outInfoVec, 0 ) self.faultMesh = self.GetOutputData( outInfoVec, 1 ) @@ -334,7 +349,11 @@ def RequestData( if self.extractFault: self.applyPVSurfaceGeomechanics() - self.logger.info( f"The plugin { self.logger.name } succeeded." ) + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) except ( ValueError, VTKError, AttributeError, AssertionError ) as e: self.logger.error( f"The plugin { self.logger.name } failed due to:\n{ e }" ) @@ -342,6 +361,9 @@ def RequestData( mess: str = f"The filter { self.logger.name } failed due to:\n{ e }" self.logger.critical( mess, exc_info=True ) + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() + return 1 def applyPVGeosBlockExtractAndMerge( self: Self ) -> None: @@ -349,6 +371,8 @@ def applyPVGeosBlockExtractAndMerge( self: Self ) -> None: extractAndMergeFilter: PVGeosBlockExtractAndMerge = PVGeosBlockExtractAndMerge() extractAndMergeFilter.SetInputConnection( self.GetInputConnection( 0, 0 ) ) extractAndMergeFilter.Update() + # Add to the warning counter the number of warning logged with the call of GeosBlockExtractAndMerge plugin + self.counter.addExternalWarningCount( extractAndMergeFilter.nbWarnings ) self.volumeMesh.ShallowCopy( extractAndMergeFilter.GetOutputDataObject( 0 ) ) self.volumeMesh.Modified() @@ -376,6 +400,8 @@ def applyPVGeomechanicsCalculator( self: Self ) -> None: geomechanicsCalculatorPlugin.setRockCohesion( self.rockCohesion ) geomechanicsCalculatorPlugin.setFrictionAngle( self.frictionAngle ) geomechanicsCalculatorPlugin.Update() + # Add to the warning counter the number of warning logged with the call of GeomechanicsCalculator plugin + self.counter.addExternalWarningCount( geomechanicsCalculatorPlugin.nbWarnings ) self.volumeMesh.ShallowCopy( geomechanicsCalculatorPlugin.GetOutputDataObject( 0 ) ) self.volumeMesh.Modified() @@ -390,6 +416,8 @@ def applyPVSurfaceGeomechanics( self: Self ) -> None: surfaceGeomechanicsPlugin.a01SetRockCohesion( self.rockCohesion ) surfaceGeomechanicsPlugin.a02SetFrictionAngle( self.frictionAngle ) surfaceGeomechanicsPlugin.Update() + # Add to the warning counter the number of warning logged with the call of SurfaceGeomechanics plugin + self.counter.addExternalWarningCount( surfaceGeomechanicsPlugin.nbWarnings ) self.faultMesh.ShallowCopy( surfaceGeomechanicsPlugin.GetOutputDataObject( 0 ) ) self.faultMesh.Modified() diff --git a/geos-pv/src/geos/pv/plugins/post_processing/PVGeosBlockExtractAndMerge.py b/geos-pv/src/geos/pv/plugins/post_processing/PVGeosBlockExtractAndMerge.py index ee972080f..b84c10b70 100644 --- a/geos-pv/src/geos/pv/plugins/post_processing/PVGeosBlockExtractAndMerge.py +++ b/geos-pv/src/geos/pv/plugins/post_processing/PVGeosBlockExtractAndMerge.py @@ -23,6 +23,7 @@ from geos.utils.Errors import VTKError from geos.utils.pieceEnum import Piece +from geos.utils.Logger import ( CountWarningHandler, getLoggerHandlerType ) from geos.utils.GeosOutputsConstants import ( GeosMeshOutputsEnum, GeosDomainNameEnum, getAttributeToTransferFromInitialTime ) @@ -128,12 +129,23 @@ def __init__( self: Self ) -> None: self.outputCellsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet() + self.handler: logging.Handler = VTKHandler() self.logger = logging.getLogger( loggerTitle ) self.logger.setLevel( logging.INFO ) - self.logger.addHandler( VTKHandler() ) + self.logger.addHandler( self.handler ) self.logger.propagate = False - self.logger.info( f"Apply plugin { self.logger.name }." ) + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) def RequestDataObject( self: Self, @@ -276,7 +288,7 @@ def RequestData( f"Apply the plugin { self.logger.name } for the first time step to get the initial properties." ) try: doExtractAndMerge( inputMesh, self.outputCellsT0, vtkMultiBlockDataSet(), vtkMultiBlockDataSet(), - self.extractFault, self.extractWell ) + self.extractFault, self.extractWell, self.counter ) request.Set( executive.CONTINUE_EXECUTING(), 1 ) except ( ValueError, VTKError ) as e: self.logger.error( f"The plugin { self.logger.name } failed due to:\n{ e }" ) @@ -296,7 +308,7 @@ def RequestData( try: doExtractAndMerge( inputMesh, outputCells, outputFaults, outputWells, self.extractFault, - self.extractWell ) + self.extractWell, self.counter ) # Copy attributes from the initial time step meshAttributes: set[ str ] = getAttributeSet( self.outputCellsT0, piece=Piece.CELLS ) @@ -316,11 +328,18 @@ def RequestData( # Set to -2 in case time changes on Paraview self.requestDataStep = -2 - self.logger.info( f"The plugin { self.logger.name } succeeded." ) + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) except ( ValueError, VTKError ) as e: self.logger.error( f"The plugin { self.logger.name } failed due to:\n{ e }" ) except Exception as e: mess = f"The plugin { self.logger.name } failed due to:\n{ e }" self.logger.critical( mess, exc_info=True ) + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() + return 1 diff --git a/geos-pv/src/geos/pv/plugins/post_processing/PVGeosLogReader.py b/geos-pv/src/geos/pv/plugins/post_processing/PVGeosLogReader.py index 6459db270..9d25b940a 100644 --- a/geos-pv/src/geos/pv/plugins/post_processing/PVGeosLogReader.py +++ b/geos-pv/src/geos/pv/plugins/post_processing/PVGeosLogReader.py @@ -39,6 +39,7 @@ from geos.pv.geosLogReaderUtils.GeosLogReaderWells import GeosLogReaderWells from geos.utils.enumUnits import ( Mass, MassRate, Pressure, Time, Unit, Volume, VolumetricRate, enumerationDomainUnit ) from geos.utils.UnitRepository import UnitRepository +from geos.utils.Logger import ( CountWarningHandler, getLoggerHandlerType ) from geos.pv.utils.checkboxFunction import createModifiedCallback # type: ignore[attr-defined] from geos.pv.utils.paraviewTreatments import strListToEnumerationDomainXml @@ -59,6 +60,8 @@ """ +loggerTitle: str = "Geos Log Reader" + @smproxy.reader( name="PVGeosLogReader", @@ -147,12 +150,23 @@ def __init__( self: Self ) -> None: for prop in propsSolvers: self.m_convergence.AddArray( prop ) - self.logger: logging.Logger = logging.getLogger( "Geos Log Reader" ) + self.handler: logging.Handler = VTKHandler() + self.logger = logging.getLogger( loggerTitle ) self.logger.setLevel( logging.INFO ) - if len( self.logger.handlers ) == 0: - self.logger.addHandler( VTKHandler() ) + self.logger.addHandler( self.handler ) self.logger.propagate = False - self.logger.info( f"Apply plugin { self.logger.name }." ) + + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) @smproperty.stringvector( name="DataFilepath", default_values="Enter a filepath to your data" ) @smdomain.filelist() @@ -577,8 +591,17 @@ def RequestData( array_type=VTK_DOUBLE ) # type: ignore[no-untyped-call] newAttr.SetName( column ) output.AddColumn( newAttr ) - self.logger.info( f"The plugin { self.logger.name } succeeded." ) + + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) except Exception as e: self.logger.error( f"The plugin { self.logger.name } failed.\n{ e }" ) return 0 + + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() + return 1 diff --git a/geos-pv/src/geos/pv/plugins/post_processing/PVMohrCirclePlot.py b/geos-pv/src/geos/pv/plugins/post_processing/PVMohrCirclePlot.py index eacd6ab46..00fbe5b56 100644 --- a/geos-pv/src/geos/pv/plugins/post_processing/PVMohrCirclePlot.py +++ b/geos-pv/src/geos/pv/plugins/post_processing/PVMohrCirclePlot.py @@ -32,9 +32,10 @@ from geos.geomechanics.model.MohrCircle import MohrCircle from geos.utils.pieceEnum import Piece +from geos.utils.Logger import CountWarningHandler from geos.utils.enumUnits import Pressure, enumerationDomainUnit from geos.utils.GeosOutputsConstants import ( FAILURE_ENVELOPE, GeosMeshOutputsEnum ) -from geos.utils.Logger import CustomLoggerFormatter +from geos.utils.Logger import ( getLoggerHandlerType ) from geos.utils.PhysicalConstants import ( DEFAULT_FRICTION_ANGLE_DEG, DEFAULT_FRICTION_ANGLE_RAD, DEFAULT_ROCK_COHESION ) from geos.mesh.utils.arrayHelpers import getArrayInObject @@ -90,6 +91,8 @@ * The attribute 'CellId' has to be used for the 'Selection Labels'. """ +loggerTitle: str = "Mohr Circle" + @smproxy.filter( name="PVMohrCirclePlot", label="Plot Mohr's Circles" ) @smhint.xml( f""" @@ -178,14 +181,23 @@ def __init__( self: Self ) -> None: # Request data processing step - incremented each time RequestUpdateExtent is called self.requestDataStep: int = -1 - # Logger - self.logger: logging.Logger = logging.getLogger( "MohrCircle" ) + self.handler: logging.Handler = VTKHandler() + self.logger = logging.getLogger( loggerTitle ) self.logger.setLevel( logging.INFO ) - if not self.logger.hasHandlers(): - handler = VTKHandler() - handler.setFormatter( CustomLoggerFormatter( False ) ) + self.logger.addHandler( self.handler ) + self.logger.propagate = False + + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) - self.logger.addHandler( handler ) + self.logger.addHandler( self.counter ) @smproperty.xml( """ 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) except Exception as e: self.logger.error( "Mohr circles cannot be plotted due to:" ) self.logger.error( e ) return 0 + + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() + return 1 def _createMohrCirclesAtTimeStep( diff --git a/geos-pv/src/geos/pv/plugins/post_processing/PVSurfaceGeomechanics.py b/geos-pv/src/geos/pv/plugins/post_processing/PVSurfaceGeomechanics.py index 6548b37ee..96972f6b8 100644 --- a/geos-pv/src/geos/pv/plugins/post_processing/PVSurfaceGeomechanics.py +++ b/geos-pv/src/geos/pv/plugins/post_processing/PVSurfaceGeomechanics.py @@ -3,6 +3,7 @@ # SPDX-FileContributor: Martin Lemay, Paloma Martinez # ruff: noqa: E402 # disable Module level import not at top of file import sys +import logging from pathlib import Path import numpy as np from typing_extensions import Self @@ -21,6 +22,7 @@ update_paths() from geos.utils.Errors import VTKError +from geos.utils.Logger import ( CountWarningHandler, isHandlerInLogger, getLoggerHandlerType ) from geos.utils.PhysicalConstants import ( DEFAULT_FRICTION_ANGLE_DEG, DEFAULT_ROCK_COHESION ) from geos.processing.post_processing.SurfaceGeomechanics import SurfaceGeomechanics from geos.mesh.utils.multiblockHelpers import ( getBlockElementIndexesFlatten, getBlockFromFlatIndex ) @@ -50,6 +52,8 @@ """ +loggerTitle: str = "Surface Geomechanics" + @SISOFilter( category=FilterCategory.GEOS_POST_PROCESSING, decoratedLabel="GEOS Surface Geomechanics", @@ -66,6 +70,24 @@ def __init__( self: Self ) -> None: # friction angle (°) self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_DEG + self.handler: logging.Handler = VTKHandler() + self.logger = logging.getLogger( loggerTitle ) + self.logger.setLevel( logging.INFO ) + self.logger.addHandler( self.handler ) + self.logger.propagate = False + + counter: CountWarningHandler = CountWarningHandler() + self.counter: CountWarningHandler + self.nbWarnings: int = 0 + try: + self.counter = getLoggerHandlerType( type( counter ), self.logger ) + self.counter.resetWarningCount() + except ValueError: + self.counter = counter + self.counter.setLevel( logging.INFO ) + + self.logger.addHandler( self.counter ) + @smproperty.doublevector( name="RockCohesion", label="Rock Cohesion (Pa)", @@ -115,22 +137,28 @@ def ApplyFilter( self: Self, inputMesh: vtkMultiBlockDataSet, outputMesh: vtkMul inputMesh (vtkMultiBlockDataSet): The input multiblock mesh with surfaces. outputMesh (vtkMultiBlockDataSet): The output multiblock mesh with converted attributes and SCU. """ + self.logger.info( f"Apply plugin { self.logger.name }." ) + outputMesh.ShallowCopy( inputMesh ) surfaceBlockIndexes: list[ int ] = getBlockElementIndexesFlatten( inputMesh ) for blockIndex in surfaceBlockIndexes: surfaceBlock: vtkPolyData = vtkPolyData.SafeDownCast( getBlockFromFlatIndex( outputMesh, blockIndex ) ) - sgFilter: SurfaceGeomechanics = SurfaceGeomechanics( surfaceBlock, True ) - sgFilter.SetSurfaceName( f"blockIndex {blockIndex}" ) - if len( sgFilter.logger.handlers ) == 0: - sgFilter.SetLoggerHandler( VTKHandler() ) + loggerName: str = f"Surface geomechanics for the blockIndex { blockIndex }" + sgFilter: SurfaceGeomechanics = SurfaceGeomechanics( surfaceBlock, loggerName, True ) + + if not isHandlerInLogger( self.handler, sgFilter.logger ): + sgFilter.SetLoggerHandler( self.handler ) sgFilter.SetRockCohesion( self._getRockCohesion() ) sgFilter.SetFrictionAngle( self._getFrictionAngle() ) try: sgFilter.applyFilter() + # Add to the warning counter the number of warning logged with the call of SurfaceGeomechanics filter + self.counter.addExternalWarningCount( sgFilter.nbWarnings ) + outputSurface: vtkPolyData = sgFilter.GetOutputMesh() # add attributes to output surface mesh @@ -145,7 +173,16 @@ def ApplyFilter( self: Self, inputMesh: vtkMultiBlockDataSet, outputMesh: vtkMul mess: str = f"The filter { sgFilter.logger.name } failed due to:\n{ e }" sgFilter.logger.critical( mess, exc_info=True ) + result: str = f"The filter { self.logger.name } succeeded" + if self.counter.warningCount > 0: + self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." ) + else: + self.logger.info( f"{ result }." ) + outputMesh.Modified() + self.nbWarnings = self.counter.warningCount + self.counter.resetWarningCount() + return def _getFrictionAngle( self: Self ) -> float: diff --git a/geos-pv/src/geos/pv/plugins/qc/PVCellTypeCounterEnhanced.py b/geos-pv/src/geos/pv/plugins/qc/PVCellTypeCounterEnhanced.py index ac9fa837b..44e99ced4 100644 --- a/geos-pv/src/geos/pv/plugins/qc/PVCellTypeCounterEnhanced.py +++ b/geos-pv/src/geos/pv/plugins/qc/PVCellTypeCounterEnhanced.py @@ -3,6 +3,7 @@ # SPDX-FileContributor: Martin Lemay # ruff: noqa: E402 # disable Module level import not at top of file import sys +import logging from pathlib import Path from typing_extensions import Self from typing import Optional @@ -26,6 +27,7 @@ from geos.processing.pre_processing.CellTypeCounterEnhanced import CellTypeCounterEnhanced from geos.mesh.model.CellTypeCounts import CellTypeCounts from geos.pv.utils.details import FilterCategory +from geos.utils.Logger import isHandlerInLogger __doc__ = f""" The ``Cell Type Counter Enhanced`` filter computes cell type counts. Counts can be exported into a file easily. @@ -58,6 +60,8 @@ def __init__( self: Self ) -> None: # used to concatenate results if vtkMultiBlockDataSet self._countsAll: CellTypeCounts = CellTypeCounts() + self.handler: logging.Handler = VTKHandler() + @smproperty.intvector( name="SetSaveToFile", label="Save to file", @@ -137,8 +141,8 @@ def RequestData( assert outputTable is not None, "Output pipeline is null." cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced( inputMesh, True ) - if len( cellTypeCounterEnhancedFilter.logger.handlers ) == 0: - cellTypeCounterEnhancedFilter.setLoggerHandler( VTKHandler() ) + if not isHandlerInLogger( self.handler, cellTypeCounterEnhancedFilter.logger ): + cellTypeCounterEnhancedFilter.setLoggerHandler( self.handler ) try: cellTypeCounterEnhancedFilter.applyFilter() diff --git a/geos-pv/src/geos/pv/plugins/qc/PVMeshQualityEnhanced.py b/geos-pv/src/geos/pv/plugins/qc/PVMeshQualityEnhanced.py index 90a40b990..d959b1e89 100644 --- a/geos-pv/src/geos/pv/plugins/qc/PVMeshQualityEnhanced.py +++ b/geos-pv/src/geos/pv/plugins/qc/PVMeshQualityEnhanced.py @@ -33,6 +33,7 @@ from geos.pv.utils.checkboxFunction import createModifiedCallback # type: ignore[attr-defined] from geos.pv.utils.paraviewTreatments import getArrayChoices from geos.pv.utils.details import ( SISOFilter, FilterCategory ) +from geos.utils.Logger import isHandlerInLogger __doc__ = f""" The ``Mesh Quality Enhanced`` filter computes requested mesh quality metrics on meshes. Both surfaces and volumic metrics can be computed with this plugin. @@ -58,9 +59,7 @@ """ -@SISOFilter( category=FilterCategory.QC, - decoratedLabel="Mesh Quality Enhanced", - decoratedType="vtkUnstructuredGrid" ) +@SISOFilter( category=FilterCategory.QC, decoratedLabel="Mesh Quality Enhanced", decoratedType="vtkUnstructuredGrid" ) class PVMeshQualityEnhanced( VTKPythonAlgorithmBase ): def __init__( self: Self ) -> None: @@ -69,6 +68,8 @@ def __init__( self: Self ) -> None: self._saveToFile: bool = True self._blockIndex: int = 0 + self.handler: logging.Handler = VTKHandler() + # Used to concatenate results if vtkMultiBlockDataSet self._metricsAll: list[ float ] = [] @@ -231,8 +232,9 @@ def ApplyFilter( self, inputMesh: vtkUnstructuredGrid, outputMesh: vtkUnstructur otherMetrics: set[ int ] = self._getQualityMetricsToUse( self._commonMeshQualityMetric ) meshQualityEnhancedFilter: MeshQualityEnhanced = MeshQualityEnhanced( inputMesh, True ) - if len( meshQualityEnhancedFilter.logger.handlers ) == 0: - meshQualityEnhancedFilter.setLoggerHandler( VTKHandler() ) + if not isHandlerInLogger( self.handler, meshQualityEnhancedFilter.logger ): + meshQualityEnhancedFilter.setLoggerHandler( self.handler ) + meshQualityEnhancedFilter.SetCellQualityMetrics( triangleMetrics=triangleMetrics, quadMetrics=quadMetrics, tetraMetrics=tetraMetrics, @@ -240,6 +242,7 @@ def ApplyFilter( self, inputMesh: vtkUnstructuredGrid, outputMesh: vtkUnstructur wedgeMetrics=wedgeMetrics, hexaMetrics=hexaMetrics ) meshQualityEnhancedFilter.SetOtherMeshQualityMetrics( otherMetrics ) + try: meshQualityEnhancedFilter.applyFilter() outputMesh.ShallowCopy( meshQualityEnhancedFilter.getOutput() ) diff --git a/geos-pv/src/geos/pv/utils/workflowFunctions.py b/geos-pv/src/geos/pv/utils/workflowFunctions.py index 753c981e8..1a66f1a78 100644 --- a/geos-pv/src/geos/pv/utils/workflowFunctions.py +++ b/geos-pv/src/geos/pv/utils/workflowFunctions.py @@ -2,9 +2,10 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Romain Baville # ruff: noqa: E402 # disable Module level import not at top of file - +import logging from geos.processing.post_processing.GeosBlockExtractor import GeosBlockExtractor from geos.processing.post_processing.GeosBlockMerge import GeosBlockMerge +from geos.utils.Logger import ( CountWarningHandler, isHandlerInLogger ) from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet @@ -18,6 +19,7 @@ def doExtractAndMerge( outputWells: vtkMultiBlockDataSet, extractFault: bool, extractWell: bool, + warningCounter: CountWarningHandler, ) -> None: """Apply block extraction and merge. @@ -28,30 +30,34 @@ def doExtractAndMerge( outputWells (vtkMultiBlockDataSet): Output well mesh extractFault (bool): True if SurfaceElementRegion needs to be extracted, False otherwise. extractWell (bool): True if WellElementRegion needs to be extracted, False otherwise. + warningCounter (logging.Handler): The plugin Handler to update with the number of warning log during the call of the extract and merge filters. """ # Extract blocks blockExtractor: GeosBlockExtractor = GeosBlockExtractor( mesh, extractFault=extractFault, extractWell=extractWell, speHandler=True ) - if len( blockExtractor.logger.handlers ) == 0: - blockExtractor.setLoggerHandler( VTKHandler() ) + handler: logging.Handler = VTKHandler() + if not isHandlerInLogger( handler, blockExtractor.logger ): + blockExtractor.setLoggerHandler( handler ) blockExtractor.applyFilter() + # Add to the warning counter the number of warning logged with the call of GeosBlockExtractor filter + warningCounter.addExternalWarningCount( blockExtractor.nbWarnings ) # recover output objects from GeosBlockExtractor filter and merge internal blocks volumeBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.volume - outputCells.ShallowCopy( mergeBlocksFilter( volumeBlockExtracted, False, "Volume" ) ) + outputCells.ShallowCopy( mergeBlocksFilter( volumeBlockExtracted, warningCounter, False, "Volume" ) ) outputCells.Modified() if extractFault: faultBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.fault - outputFaults.ShallowCopy( mergeBlocksFilter( faultBlockExtracted, True, "Fault" ) ) + outputFaults.ShallowCopy( mergeBlocksFilter( faultBlockExtracted, warningCounter, True, "Fault" ) ) outputFaults.Modified() if extractWell: wellBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.well - outputWells.ShallowCopy( mergeBlocksFilter( wellBlockExtracted, False, "Well" ) ) + outputWells.ShallowCopy( mergeBlocksFilter( wellBlockExtracted, warningCounter, False, "Well" ) ) outputWells.Modified() return @@ -59,6 +65,7 @@ def doExtractAndMerge( def mergeBlocksFilter( mesh: vtkMultiBlockDataSet, + warningCounter: CountWarningHandler, convertSurfaces: bool = False, domainToMerge: str = "Volume", ) -> vtkMultiBlockDataSet: @@ -66,6 +73,7 @@ def mergeBlocksFilter( Args: mesh (vtkMultiBlockDataSet): Mesh to merge. + warningCounter (logging.Handler): The plugin Handler to update with the number of warning log during the call of the extract and merge filters. convertSurfaces (bool, optional): True to convert surface from vtkUnstructuredGrid to vtkPolyData. Defaults to False. domainToMerge (str, optional): The name of the GEOS domain processed. @@ -74,11 +82,17 @@ def mergeBlocksFilter( Returns: vtkMultiBlockDataSet: Mesh composed of internal merged blocks. """ - loggerName = f"GEOS Block Merge for the domain { domainToMerge }." + loggerName = f"GEOS Block Merge for the domain { domainToMerge }" mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( mesh, convertSurfaces, True, loggerName ) - if len( mergeBlockFilter.logger.handlers ) == 0: - mergeBlockFilter.setLoggerHandler( VTKHandler() ) + handler: logging.Handler = VTKHandler() + if not isHandlerInLogger( handler, mergeBlockFilter.logger ): + mergeBlockFilter.setLoggerHandler( handler ) + mergeBlockFilter.applyFilter() + # Add to the warning counter the number of warning logged with the call of GeosBlockMerge filter + warningCounter.addExternalWarningCount( mergeBlockFilter.nbWarnings ) + mergedBlocks: vtkMultiBlockDataSet = vtkMultiBlockDataSet() mergedBlocks.ShallowCopy( mergeBlockFilter.getOutput() ) + return mergedBlocks diff --git a/geos-utils/src/geos/utils/Logger.py b/geos-utils/src/geos/utils/Logger.py index 68e35868d..fdb4dcff6 100644 --- a/geos-utils/src/geos/utils/Logger.py +++ b/geos-utils/src/geos/utils/Logger.py @@ -105,6 +105,23 @@ def __init__( self: Self ) -> None: super().__init__() self.warningCount = 0 + def resetWarningCount( self: Self, value: int = 0 ) -> None: + """Set the warning counter to a specific value. + + Args: + value (optional, int): The value to set for the warning counter. + Defaults to 0. + """ + self.warningCount = value + + def addExternalWarningCount( self: Self, externalWarningCount: int ) -> None: + """Add external warning count. + + Args: + externalWarningCount (int): An external warning count to add to the internal one. + """ + self.warningCount += externalWarningCount + def emit( self: Self, record: logging.LogRecord ) -> None: """Count all the warnings logged. @@ -115,6 +132,59 @@ def emit( self: Self, record: logging.LogRecord ) -> None: self.warningCount += 1 +def getLoggerHandlerType( handlerType: type, logger: logging.Logger ) -> logging.Handler: + """Get the logger handler with a certain type. + + Args: + handlerType (type): The type of the handler of interest. + logger (logging.Logger): The logger to check. + + Returns: + logging.Handler: The first logger handler with the correct type. + + Raises: + ValueError: The logger has no handler with the wanted type. + """ + listLoggerHandlers: list[ logging.Handler ] = logger.handlers + for loggerHandler in listLoggerHandlers: + if type( loggerHandler ) is handlerType: + return loggerHandler + + raise ValueError( "The logger has no handler with the wanted type." ) + + +def hasLoggerHandlerType( handlerType: type, logger: logging.Logger ) -> bool: + """Check if the logger has a handler with a certain type. + + Args: + handlerType (type): The type of the handler of interest. + logger (logging.Logger): The logger to check. + + Returns: + bool: True if the logger has a handler with the same type, False otherwise. + """ + listLoggerHandlers: list[ logging.Handler ] = logger.handlers + return any( type( loggerHandler ) is handlerType for loggerHandler in listLoggerHandlers ) + + +def isHandlerInLogger( handler: logging.Handler, logger: logging.Logger ) -> bool: + """Check if the handler is in the logger. + + Args: + handler (logging.Handler): The handler of interest. + logger (logging.Logger): The logger to check. + + Returns: + bool: True if the logger has the handler, False otherwise. + """ + listLoggerHandlers: list[ logging.Handler ] = logger.handlers + for loggerHandler in listLoggerHandlers: + if type( handler ) is type( loggerHandler ) and loggerHandler.__dict__ == handler.__dict__: + return True + + return False + + # Add the convenience method for the logger def results( self: logging.Logger, message: str, *args: Any, **kws: Any ) -> None: """Logs a message with the custom 'RESULTS' severity level. diff --git a/geos-utils/tests/test_Logger.py b/geos-utils/tests/test_Logger.py new file mode 100644 index 000000000..ba9adf188 --- /dev/null +++ b/geos-utils/tests/test_Logger.py @@ -0,0 +1,133 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Romain Baville +# ruff: noqa: E402 # disable Module level import not at top of file +import pytest +import random +import logging + +import geos.utils.Logger as internLogger + + +def test_CountWarningHandler() -> None: + """Test the counter handler class and its methods.""" + loggerTest: logging.Logger = logging.getLogger( "Test CountWarningHandler" ) + loggerTest.setLevel( logging.INFO ) + loggerTest.propagate = False + + countWarningHandler: internLogger.CountWarningHandler = internLogger.CountWarningHandler() + countWarningHandler.setLevel( logging.INFO ) + + loggerTest.addHandler( countWarningHandler ) + + nbWarnings: int = random.randint( 0, 10 ) + for warning in range( nbWarnings ): + loggerTest.warning( f"Warning number { warning }." ) + assert countWarningHandler.warningCount == nbWarnings + + additionalWarnings: int = random.randint( 1, 10 ) + countWarningHandler.addExternalWarningCount( additionalWarnings ) + assert countWarningHandler.warningCount == nbWarnings + additionalWarnings + + resetValue: int = random.randint( 0, 10 ) + countWarningHandler.resetWarningCount( resetValue ) + assert countWarningHandler.warningCount == resetValue + + loggerTest.warning( "Add a warning after the handler reset." ) + assert countWarningHandler.warningCount == resetValue + 1 + + +def test_getLoggerHandlerType() -> None: + """Test the function to get a logger's handler with a certain type.""" + loggerTest: logging.Logger = logging.getLogger( "Test getLoggerHandlerType" ) + loggerTest.setLevel( logging.INFO ) + loggerTest.propagate = False + + handler1: logging.Handler = logging.Handler() + handler1.setLevel( logging.INFO ) + handlerType1: type = type( handler1 ) + loggerTest.addHandler( handler1 ) + + handler2: internLogger.CountWarningHandler = internLogger.CountWarningHandler() + handler2.setLevel( logging.INFO ) + handlerType2: type = type( handler2 ) + loggerTest.addHandler( handler2 ) + + handler3: logging.Handler = logging.Handler() + handler3.setLevel( logging.INFO ) + handlerType3: type = type( handler3 ) + loggerTest.addHandler( handler3 ) + + handlerTest1: logging.Handler = internLogger.getLoggerHandlerType( handlerType1, loggerTest ) + handlerTest2: logging.Handler = internLogger.getLoggerHandlerType( handlerType2, loggerTest ) + handlerTest3: logging.Handler = internLogger.getLoggerHandlerType( handlerType3, loggerTest ) + + assert type( handlerTest1 ) is handlerType1 + assert type( handlerTest2 ) is handlerType2 + assert type( handlerTest3 ) is handlerType3 + assert handlerTest3.__dict__ == handler1.__dict__ # If multiple Handler have the same type the first one is return + + +def test_getLoggerHandlerTypeValueError() -> None: + """Test the ValueError raises of the getLoggerHandlerType function.""" + loggerTest: logging.Logger = logging.getLogger( "Test getLoggerHandlerType raises" ) + loggerTest.setLevel( logging.INFO ) + loggerTest.propagate = False + + handler1: logging.Handler = logging.Handler() + handler1.setLevel( logging.INFO ) + handlerType1: type = type( handler1 ) + + # Test with a empty logger + with pytest.raises( ValueError ): + internLogger.getLoggerHandlerType( handlerType1, loggerTest ) + + loggerTest.addHandler( handler1 ) + + handler2: internLogger.CountWarningHandler = internLogger.CountWarningHandler() + handler2.setLevel( logging.INFO ) + handlerType2: type = type( handler2 ) + + # Test with a logger with an other type of handler + with pytest.raises( ValueError ): + internLogger.getLoggerHandlerType( handlerType2, loggerTest ) + + +def test_hasLoggerHandlerType() -> None: + """Test the function to check if a logger has a handler of a certain type.""" + loggerTest: logging.Logger = logging.getLogger( "Test hasLoggerHandlerType" ) + loggerTest.setLevel( logging.INFO ) + loggerTest.propagate = False + + handler1: logging.Handler = logging.Handler() + handler1.setLevel( logging.INFO ) + handlerType1: type = type( handler1 ) + loggerTest.addHandler( handler1 ) + + handler2: internLogger.CountWarningHandler = internLogger.CountWarningHandler() + handler2.setLevel( logging.INFO ) + handlerType2: type = type( handler2 ) + + assert internLogger.hasLoggerHandlerType( handlerType1, loggerTest ) + assert not internLogger.hasLoggerHandlerType( handlerType2, loggerTest ) + + +def test_isHandlerInLogger() -> None: + """Test the function to check if a logger has a handler.""" + loggerTest: logging.Logger = logging.getLogger( "Test isHandlerInLogger" ) + loggerTest.setLevel( logging.INFO ) + loggerTest.propagate = False + + handler1: logging.Handler = logging.Handler() + handler1.setLevel( logging.INFO ) + loggerTest.addHandler( handler1 ) + + handler2: internLogger.CountWarningHandler = internLogger.CountWarningHandler() + handler2.setLevel( logging.INFO ) + + handler3: logging.Handler = logging.Handler() + handler3.setLevel( logging.INFO ) + + assert internLogger.isHandlerInLogger( handler1, loggerTest ) + assert not internLogger.isHandlerInLogger( handler2, loggerTest ) + assert not internLogger.isHandlerInLogger( handler3, loggerTest ) # Same type but the handler is different