log.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. # This file is part of DarkFi (https://dark.fi)
  2. #
  3. # Copyright (C) 2020-2025 Dyne.org foundation
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU Affero General Public License as
  7. # published by the Free Software Foundation, either version 3 of the
  8. # License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU Affero General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Affero General Public License
  16. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. import os
  18. import logging
  19. from logging.handlers import RotatingFileHandler
  20. """
  21. Module: log.py
  22. This module provides functionality to setup logging for the explorer Flask application.
  23. """
  24. def setup_logger(app, env):
  25. """
  26. Sets up logging for the explorer Flask app by setting up error, application, and request logging.
  27. The error logger captures application errors and logs them to a dedicated error log file. The application
  28. logger handles general application-level logs such as debug or informational messages. Additionally, the
  29. request logger, derived from the Werkzeug logger, manages HTTP request logs and directs them to the
  30. same file as the application logger.
  31. The overall log level is determined by the LOG_LEVEL environment variable, defaulting to INFO if the
  32. variable is not set or contains an invalid value. The path where logs are stored is obtained from the
  33. application's TOML configuration file under the 'log_path' entry. If not specified, it defaults to the
  34. current directory.
  35. Args:
  36. app (Flask): The Flask application instance.
  37. env (str): The environment (e.g., 'localnet', 'mainnet', 'testnet', etc.).
  38. """
  39. log_path = app.config.get('log_path', '.')
  40. # Error logger setup
  41. error_logger = setup_error_logger(log_path, env)
  42. app.error_logger = error_logger
  43. # App logger setup
  44. app_logger = setup_app_logger(log_path, env)
  45. app.logger = app_logger
  46. # Request logger setup
  47. app_log_file = os.path.join(log_path, 'app.log')
  48. setup_request_logger(app_log_file, env)
  49. # Get log level from environment variable, default to INFO
  50. log_level_name = os.environ.get('LOG_LEVEL', 'INFO').upper()
  51. try:
  52. log_level = getattr(logging, log_level_name)
  53. except AttributeError:
  54. if log_level_name:
  55. app.logger.warning(f"Invalid LOG_LEVEL '{log_level_name}'. Defaulting to INFO.")
  56. log_level = logging.INFO
  57. # Set the overall logger level
  58. app.logger.setLevel(log_level)
  59. def setup_error_logger(log_path, env):
  60. """
  61. Configures the error logger to capture application errors, returning an error logger instance.
  62. Args:
  63. log_path (str): Path to the directory where logs are stored.
  64. env (str): The application environment.
  65. Returns:
  66. logging.Logger: Configured error logger instance.
  67. """
  68. error_logger = logging.getLogger('error_logger')
  69. error_log_file = os.path.join(log_path, 'error.log')
  70. error_handler = initialize_log_handler(error_log_file, env)
  71. error_handler.setLevel(logging.ERROR)
  72. error_formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')
  73. error_handler.setFormatter(error_formatter)
  74. error_logger.addHandler(error_handler)
  75. error_logger.setLevel(logging.ERROR)
  76. error_logger.propagate = False
  77. return error_logger
  78. def setup_app_logger(log_path, env, log_level=logging.INFO):
  79. """
  80. Configures the app logger for general application logging, returning an app logger instance.
  81. Args:
  82. log_path (str): Path to the directory where logs are stored.
  83. env (str): The application environment.
  84. log_level (int): The logging level (default is INFO).
  85. """
  86. app_logger = logging.getLogger('app_logger')
  87. app_log_file = os.path.join(log_path, 'app.log')
  88. app_handler = initialize_log_handler(app_log_file, env)
  89. app_handler.setLevel(log_level)
  90. app_formatter = logging.Formatter('%(asctime)s %(message)s')
  91. app_handler.setFormatter(app_formatter)
  92. app_logger.addHandler(app_handler)
  93. app_logger.setLevel(log_level)
  94. app_logger.propagate = False
  95. return app_logger
  96. def setup_request_logger(log_file, env):
  97. """
  98. Configures the request logger to handle HTTP request logs based on the specified environment.
  99. If the environment is set to 'localnet', HTTP requests are logged to the console to facilitate
  100. local development and debugging. For all other environments, such as 'testnet' or 'mainnet',
  101. HTTP requests are logged to the specified log file, ensuring that logs are persisted in a location
  102. appropriate for testing or production use.
  103. Args:
  104. log_file (str): Path to the log file where HTTP requests should be logged.
  105. env (str): The application environment (e.g., 'localnet', 'testnet', 'mainnet').
  106. """
  107. # Get the werkzeug logger that logs requests
  108. request_logger = logging.getLogger('werkzeug')
  109. request_logger.setLevel(logging.INFO)
  110. request_logger.propagate = False
  111. # If localnet, log to console
  112. if env == 'localnet':
  113. console_handler = logging.StreamHandler() # Outputs to the console
  114. console_handler.setLevel(logging.INFO)
  115. request_logger.addHandler(console_handler)
  116. # Log to file for other environments
  117. else:
  118. file_handler = logging.FileHandler(log_file)
  119. file_handler.setLevel(logging.INFO)
  120. request_logger.addHandler(file_handler)
  121. def initialize_log_handler(log_file, env):
  122. """
  123. Initializes and returns a log handler based on the environment.
  124. Args:
  125. log_file (str): Path to the log file.
  126. env (str): The environment (e.g., 'mainnet', 'testnet', etc.).
  127. """
  128. if env == "mainnet":
  129. return RotatingFileHandler(log_file, maxBytes=100_000_000, backupCount=5)
  130. else:
  131. return logging.FileHandler(log_file)