فهرست منبع

explorer/site: introduce logging for the explorer site

This commit introduces error, application, and request logging to the explorer site. These changes establish a robust and unified logging system for monitoring and debugging the explorer across environments. Logs are stored in the directory specified in the site configuration TOML file under `log_path` (defaulting to the current directory if not specified).

Summary of Updates:
- Added `log` module that provides functionality to setup logging for the explorer Flask application
- Added support for specifying logging directories per environment through `log_path` in `site_config.toml`
- Implemented error logging handler to capture detailed error information in `error.log`
- Added application logging for general app-level logs such as debug and informational messages
- Integrated request logging by tying the Werkzeug logger to the same log file as the application logger
- Added `RotatingFileHandler` for mainnet logging
- Updated the 500 error handler to log detailed error information prior to rendering the `500.html` page
kalm 1 سال پیش
والد
کامیت
d811a60cb4
3فایلهای تغییر یافته به همراه174 افزوده شده و 2 حذف شده
  1. 13 2
      bin/explorer/site/app.py
  2. 157 0
      bin/explorer/site/log.py
  3. 4 0
      bin/explorer/site/site_config.toml

+ 13 - 2
bin/explorer/site/app.py

@@ -33,6 +33,8 @@ from blueprints.block import block_bp
 from blueprints.contract import contract_bp
 from blueprints.transaction import transaction_bp
 
+import log
+
 def create_app():
     """
     Creates and configures the DarkFi explorer Flask application.
@@ -48,6 +50,9 @@ def create_app():
     env = os.getenv("FLASK_ENV", "localnet")
     load_toml_config(app, env)
 
+    # Setup logger
+    log.setup_logger(app, env)
+
     # Register Blueprints
     app.register_blueprint(explore_bp)
     app.register_blueprint(block_bp)
@@ -71,12 +76,18 @@ def create_app():
     @app.errorhandler(500)
     def internal_server_error(e):
         """
-        Handles 500 errors by rendering a custom 500 error page when an internal server error occurs,
-        returning a rendered template along with a 500 status code.
+        Handles 500 errors by logging the error and returning the app's 500 error page.
+
+        This function logs the error with its stack trace, file name, and line number
+        to help with debugging. It then renders the '500.html' template and returns it
+        along with a 500 HTTP status code.
 
         Args:
             e: The error object associated with the 500 error.
         """
+        # Log the error
+        app.error_logger.exception("An unexpected error occurred")
+
         # Render the custom 500 error page
         return render_template('500.html'), 500
 

+ 157 - 0
bin/explorer/site/log.py

@@ -0,0 +1,157 @@
+# This file is part of DarkFi (https://dark.fi)
+#
+# Copyright (C) 2020-2025 Dyne.org foundation
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import os
+import logging
+
+from logging.handlers import RotatingFileHandler
+
+"""
+Module: log.py
+
+This module provides functionality to setup logging for the explorer Flask application.
+"""
+
+def setup_logger(app, env):
+    """
+    Sets up logging for the explorer Flask app by setting up error, application, and request logging.
+
+    The error logger captures application errors and logs them to a dedicated error log file. The application
+    logger handles general application-level logs such as debug or informational messages. Additionally, the
+    request logger, derived from the Werkzeug logger, manages HTTP request logs and directs them to the
+    same file as the application logger.
+
+    The overall log level is determined by the LOG_LEVEL environment variable, defaulting to INFO if the
+    variable is not set or contains an invalid value. The path where logs are stored is obtained from the
+    application's TOML configuration file under the 'log_path' entry. If not specified, it defaults to the
+    current directory.
+
+    Args:
+        app (Flask): The Flask application instance.
+        env (str): The environment (e.g., 'localnet', 'mainnet', 'testnet', etc.).
+    """
+    log_path = app.config.get('log_path', '.')
+
+    # Error logger setup
+    error_logger = setup_error_logger(log_path, env)
+    app.error_logger = error_logger
+
+    # App logger setup
+    app_logger = setup_app_logger(log_path, env)
+    app.logger = app_logger
+
+    # Request logger setup
+    app_log_file = os.path.join(log_path, 'app.log')
+    setup_request_logger(app_log_file, env)
+
+    # Get log level from environment variable, default to INFO
+    log_level_name = os.environ.get('LOG_LEVEL', 'INFO').upper()
+    try:
+        log_level = getattr(logging, log_level_name)
+    except AttributeError:
+        if log_level_name:
+            app.logger.warning(f"Invalid LOG_LEVEL '{log_level_name}'. Defaulting to INFO.")
+        log_level = logging.INFO
+
+    # Set the overall logger level
+    app.logger.setLevel(log_level)
+
+def setup_error_logger(log_path, env):
+    """
+    Configures the error logger to capture application errors, returning an error logger instance.
+
+    Args:
+        log_path (str): Path to the directory where logs are stored.
+        env (str): The application environment.
+
+    Returns:
+        logging.Logger: Configured error logger instance.
+    """
+    error_logger = logging.getLogger('error_logger')
+    error_log_file = os.path.join(log_path, 'error.log')
+    error_handler = initialize_log_handler(error_log_file, env)
+    error_handler.setLevel(logging.ERROR)
+    error_formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')
+    error_handler.setFormatter(error_formatter)
+    error_logger.addHandler(error_handler)
+    error_logger.setLevel(logging.ERROR)
+    error_logger.propagate = False
+    return error_logger
+
+def setup_app_logger(log_path, env, log_level=logging.INFO):
+    """
+    Configures the app logger for general application logging, returning an app logger instance.
+
+    Args:
+        log_path (str): Path to the directory where logs are stored.
+        env (str): The application environment.
+        log_level (int): The logging level (default is INFO).
+    """
+    app_logger = logging.getLogger('app_logger')
+    app_log_file = os.path.join(log_path, 'app.log')
+    app_handler = initialize_log_handler(app_log_file, env)
+    app_handler.setLevel(log_level)
+    app_formatter = logging.Formatter('%(asctime)s %(message)s')
+    app_handler.setFormatter(app_formatter)
+    app_logger.addHandler(app_handler)
+    app_logger.setLevel(log_level)
+    app_logger.propagate = False
+    return app_logger
+
+def setup_request_logger(log_file, env):
+    """
+    Configures the request logger to handle HTTP request logs based on the specified environment.
+
+    If the environment is set to 'localnet', HTTP requests are logged to the console to facilitate
+    local development and debugging. For all other environments, such as 'testnet' or 'mainnet',
+    HTTP requests are logged to the specified log file, ensuring that logs are persisted in a location
+    appropriate for testing or production use.
+
+    Args:
+        log_file (str): Path to the log file where HTTP requests should be logged.
+        env (str): The application environment (e.g., 'localnet', 'testnet', 'mainnet').
+    """
+    # Get the werkzeug logger that logs requests
+    request_logger = logging.getLogger('werkzeug')
+    request_logger.setLevel(logging.INFO)
+    request_logger.propagate = False
+
+    # If localnet, log to console
+    if env == 'localnet':
+        console_handler = logging.StreamHandler()  # Outputs to the console
+        console_handler.setLevel(logging.INFO)
+        request_logger.addHandler(console_handler)
+    # Log to file for other environments
+    else:
+        file_handler = logging.FileHandler(log_file)
+        file_handler.setLevel(logging.INFO)
+        request_logger.addHandler(file_handler)
+
+def initialize_log_handler(log_file, env):
+    """
+    Initializes and returns a log handler based on the environment.
+
+    Args:
+        log_file (str): Path to the log file.
+        env (str): The environment (e.g., 'mainnet', 'testnet', etc.).
+    """
+    if env == "mainnet":
+        return RotatingFileHandler(log_file, maxBytes=100_000_000, backupCount=5)
+    else:
+        return logging.FileHandler(log_file)
+
+

+ 4 - 0
bin/explorer/site/site_config.toml

@@ -19,18 +19,22 @@
 [localnet]
 explorer_rpc_url = "127.0.0.1"
 explorer_rpc_port = 14567
+log_path = "."
 
 # devnet configuration
 [devnet]
 explorer_rpc_url = "TODO"
 explorer_rpc_port = "TODO"
+log_path = "TODO"
 
 # testnet configuration
 [testnet]
 explorer_rpc_url = "TODO"
 explorer_rpc_port = "TODO"
+log_path = "TODO"
 
 # mainnet configuration
 [mainnet]
 explorer_rpc_url = "TODO"
 explorer_rpc_port = "TODO"
+log_path = "TODO"