Skip to content

Commit

Permalink
Setup loggers (#235)
Browse files Browse the repository at this point in the history
* Setup logger for better observability

* Utilize builtin logger
  • Loading branch information
alukach authored Mar 4, 2025
1 parent 01c9a4a commit 140c1e6
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 19 deletions.
6 changes: 5 additions & 1 deletion ee_plugin/ee_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from qgis.PyQt.QtGui import QIcon
import ee

from . import provider, config, ee_auth, utils
from . import provider, config, ee_auth, utils, logging
from .ui import menus
from .ui.forms import add_feature_collection, add_ee_image

Expand Down Expand Up @@ -82,6 +82,8 @@ def __init__(self, iface: gui.QgisInterface, ee_config: config.EarthEngineConfig
)
)

logging.setup_logger(plugin_name="Google Earth Engine Plugin")

# noinspection PyMethodMayBeStatic
def tr(self, message):
"""Get the translation for a string using Qt translation API.
Expand Down Expand Up @@ -183,6 +185,8 @@ def unload(self):
if self.toolButton:
self.toolButton.deleteLater()

logging.teardown_logger()

@property
def _project_button_text(self):
"""Get the text for the project button."""
Expand Down
103 changes: 103 additions & 0 deletions ee_plugin/logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import logging

from qgis.core import Qgis, QgsMessageLog

from .Map import get_iface


MODULE_NAME = __name__.split(".")[0]


class QGISMessageLogHandler(logging.Handler):
"""
Handler that routes Python logging messages to the QGIS Message Log.
"""

def __init__(self, plugin_name: str):
self.plugin_name = plugin_name
super().__init__()

def emit(self, record):
"""
Overridden method from logging.Handler:
Called with a LogRecord whenever a new log message is issued.
"""
notify_user = False
if record.levelno >= logging.ERROR:
qgis_level = Qgis.Critical
notify_user = True
elif record.levelno >= logging.WARNING:
qgis_level = Qgis.Warning
else:
# INFO, DEBUG, etc. can map to Qgis.Info
qgis_level = Qgis.Info

# Format the log message
msg = self.format(record)

# Actually write the message to the QGIS Message Log
QgsMessageLog.logMessage(
message=msg,
tag=self.plugin_name,
level=qgis_level,
notifyUser=notify_user,
)


class QGISMessageBarHandler(logging.Handler):
"""
Handler that routes Python logging messages to the QGIS Message Bar.
"""

def __init__(self, plugin_name: str, duration: int = 0):
self.plugin_name = plugin_name
self.duration = duration
super().__init__()

def emit(self, record):
# Map Python log levels to QGIS message bar levels
if record.levelno >= logging.ERROR:
qgis_level = Qgis.Critical
elif record.levelno >= logging.WARNING:
qgis_level = Qgis.Warning
else:
# TODO: Support Qgis.Success for positive messages?
qgis_level = Qgis.Info

# Format the log message
msg = self.format(record)

# Push the message to the QGIS Message Bar
get_iface().messageBar().pushMessage(
f"{self.plugin_name}",
msg,
level=qgis_level,
duration=self.duration,
)


def setup_logger(plugin_name: str, logger_name: str = MODULE_NAME) -> None:
"""Configure and return a logger for your QGIS Plugin."""
logger = logging.getLogger(logger_name)
logger.setLevel(logging.DEBUG)

qgis_log_handler = QGISMessageLogHandler(plugin_name=plugin_name)
qgis_log_handler.setLevel(logging.INFO)
qgis_log_handler.setFormatter(
logging.Formatter("%(levelname)s: %(name)s - %(message)s")
)
logger.addHandler(qgis_log_handler)

qgis_bar_handler = QGISMessageBarHandler(plugin_name=plugin_name, duration=5)
qgis_bar_handler.setLevel(logging.WARNING)
qgis_bar_handler.setFormatter(logging.Formatter("%(message)s"))
logger.addHandler(qgis_bar_handler)


def teardown_logger(logger_name: str = MODULE_NAME) -> None:
"""Remove all handlers from the logger to clean up resources."""
logger = logging.getLogger(logger_name)
# Copy the list to avoid modification during iteration
handlers = logger.handlers[:]
for handler in handlers:
logger.removeHandler(handler)
6 changes: 4 additions & 2 deletions ee_plugin/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
Create and init the Earth Engine Qgis data provider
"""

import logging
import json

import ee
from qgis.core import (
Qgis,
QgsCoordinateReferenceSystem,
QgsDataProvider,
QgsMessageLog,
QgsProviderMetadata,
QgsProviderRegistry,
QgsRaster,
Expand Down Expand Up @@ -38,6 +38,8 @@
"double": Qgis.Float64,
}

logger = logging.getLogger(__name__)


class EarthEngineRasterDataProvider(QgsRasterDataProvider):
PARENT = QObject()
Expand Down Expand Up @@ -394,4 +396,4 @@ def register_data_provider():
)
registry = QgsProviderRegistry.instance()
registry.registerProvider(metadata)
QgsMessageLog.logMessage("EE provider registered")
logger.info("EE provider registered")
19 changes: 8 additions & 11 deletions ee_plugin/ui/forms/add_ee_image.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import logging
import json
from typing import Callable, Optional

import ee
from qgis.PyQt import QtWidgets
from qgis.core import QgsMessageLog, Qgis

from ...Map import addLayer
from ..widgets import build_form_group_box, build_vbox_dialog
from ..utils import call_func_with_values
from ...utils import translate as _

logger = logging.getLogger(__name__)


def form(accepted: Optional[Callable] = None, **dialog_kwargs) -> QtWidgets.QDialog:
"""Display a dialog to add a GEE dataset to the QGIS map."""
Expand Down Expand Up @@ -62,8 +64,7 @@ def form(accepted: Optional[Callable] = None, **dialog_kwargs) -> QtWidgets.QDia
def callback(image_id: str = None, viz_params: dict = None):
"""Fetch and add the selected Earth Engine dataset to the map with user-defined visualization parameters."""
if not image_id:
message = "Image ID is required."
QgsMessageLog.logMessage(message, "GEE Plugin", level=Qgis.Critical)
logger.error("Image ID is required.")
return

try:
Expand All @@ -88,19 +89,15 @@ def callback(image_id: str = None, viz_params: dict = None):
# Add the dataset to QGIS map
addLayer(ee_object, viz_params, image_id)

success_message = (
logger.info(
f"Successfully added {image_id} to the map with custom visualization."
)
QgsMessageLog.logMessage(success_message, "GEE Plugin", level=Qgis.Success)

except ee.EEException as e:
error_message = f"Earth Engine Error: {str(e)}"
QgsMessageLog.logMessage(error_message, "GEE Plugin", level=Qgis.Critical)
logger.exception(f"Earth Engine Error: {str(e)}")

except ValueError as e:
error_message = str(e)
QgsMessageLog.logMessage(error_message, "GEE Plugin", level=Qgis.Critical)
logger.exception(str(e))

except Exception as e:
error_message = f"Unexpected error: {str(e)}"
QgsMessageLog.logMessage(error_message, "GEE Plugin", level=Qgis.Critical)
logger.exception(f"Unexpected error: {str(e)}")
10 changes: 5 additions & 5 deletions ee_plugin/ui/forms/add_feature_collection.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
from typing import Optional, Callable
from qgis import gui
from qgis.PyQt import QtWidgets
Expand All @@ -8,6 +9,9 @@
from ...utils import translate as _


logger = logging.getLogger(__name__)


def form(
accepted: Optional[Callable] = None,
**dialog_kwargs,
Expand Down Expand Up @@ -153,11 +157,7 @@ def callback(
try:
utils.add_ee_vector_layer(fc, layer_name)
except ee.ee_exception.EEException as e:
Map.get_iface().messageBar().pushMessage(
"Error",
f"Failed to load the Feature Collection: {e}",
level=gui.Qgis.Critical,
)
logger.error(f"Failed to load the Feature Collection: {e}")
else:
Map.addLayer(fc, {"palette": viz_color_hex}, layer_name)
return fc

0 comments on commit 140c1e6

Please sign in to comment.