log.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. # This file is part of DarkFi (https://dark.fi)
  2. #
  3. # Copyright (C) 2020-2026 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. # Expand the path if home directory is specified
  41. log_path = os.path.expanduser(log_path)
  42. # Ensure the log path exists or create it if not
  43. if not os.path.exists(log_path):
  44. try:
  45. os.makedirs(log_path)
  46. print(f"created log dir: {log_path}")
  47. except OSError as e:
  48. raise RuntimeError(f"Unable to create log directory at '{log_path}': {e}")
  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. # App logger setup
  58. app_logger = setup_app_logger(log_path, env, log_level)
  59. app.logger = app_logger
  60. # Request logger setup
  61. app_log_file = os.path.join(log_path, 'app.log')
  62. setup_request_logger(app_log_file, env, log_level)
  63. # Error logger setup
  64. error_logger = setup_error_logger(log_path, env)
  65. app.error_logger = error_logger
  66. def setup_error_logger(log_path, env):
  67. """
  68. Configures the error logger to capture application errors, returning an error logger instance.
  69. Args:
  70. log_path (str): Path to the directory where logs are stored.
  71. env (str): The application environment.
  72. Returns:
  73. logging.Logger: Configured error logger instance.
  74. """
  75. error_logger = logging.getLogger('error_logger')
  76. error_log_file = os.path.join(log_path, 'error.log')
  77. error_handler = initialize_log_handler(error_log_file, env)
  78. error_handler.setLevel(logging.ERROR)
  79. error_formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')
  80. error_handler.setFormatter(error_formatter)
  81. error_logger.addHandler(error_handler)
  82. error_logger.setLevel(logging.ERROR)
  83. error_logger.propagate = False
  84. add_console_handler_if_localnet(env, error_logger, logging.ERROR)
  85. return error_logger
  86. def setup_app_logger(log_path, env, log_level=logging.INFO):
  87. """
  88. Configures the app logger for general application logging, returning an app logger instance.
  89. Args:
  90. log_path (str): Path to the directory where logs are stored.
  91. env (str): The application environment.
  92. log_level (int): The logging level (default is INFO).
  93. """
  94. app_logger = logging.getLogger('app_logger')
  95. app_log_file = os.path.join(log_path, 'app.log')
  96. app_handler = initialize_log_handler(app_log_file, env)
  97. app_handler.setLevel(log_level)
  98. app_formatter = logging.Formatter('%(asctime)s %(message)s')
  99. app_handler.setFormatter(app_formatter)
  100. app_logger.addHandler(app_handler)
  101. app_logger.setLevel(log_level)
  102. app_logger.propagate = False
  103. add_console_handler_if_localnet(env, app_logger, log_level)
  104. return app_logger
  105. def setup_request_logger(log_file, env, log_level=logging.INFO):
  106. """
  107. Configures the request logger to handle HTTP request logs based on the specified environment.
  108. If the environment is set to 'localnet', HTTP requests are logged to the console to facilitate
  109. local development and debugging. For all other environments, such as 'testnet' or 'mainnet',
  110. HTTP requests are logged to the specified log file, ensuring that logs are persisted in a location
  111. appropriate for testing or production use.
  112. Args:
  113. log_file (str): Path to the log file where HTTP requests should be logged.
  114. env (str): The application environment (e.g., 'localnet', 'testnet', 'mainnet').
  115. log_level (int): The logging level (default is INFO).
  116. """
  117. # Get the werkzeug logger that logs requests
  118. request_logger = logging.getLogger('werkzeug')
  119. request_logger.setLevel(log_level)
  120. request_logger.propagate = False
  121. file_handler = logging.FileHandler(log_file)
  122. file_handler.setLevel(log_level)
  123. request_logger.addHandler(file_handler)
  124. add_console_handler_if_localnet(env, request_logger, log_level)
  125. def initialize_log_handler(log_file, env):
  126. """
  127. Initializes and returns a log handler based on the environment.
  128. Args:
  129. log_file (str): Path to the log file.
  130. env (str): The environment (e.g., 'mainnet', 'testnet', etc.).
  131. """
  132. if env == "mainnet":
  133. return RotatingFileHandler(log_file, maxBytes=100_000_000, backupCount=5)
  134. else:
  135. return logging.FileHandler(log_file)
  136. def add_console_handler_if_localnet(env, logger, log_level=logging.INFO):
  137. """
  138. Adds a console handler to the given logger if the environment is 'localnet'.
  139. Args:
  140. env (str): The current environment (e.g., 'localnet', 'mainnet').
  141. logger (logging.Logger): The logger to which the console handler should be added.
  142. log_level (int): The logging level for the console handler.
  143. """
  144. # If localnet, also log to console
  145. if env == 'localnet':
  146. console_handler = logging.StreamHandler()
  147. console_handler.setLevel(log_level)
  148. formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
  149. console_handler.setFormatter(formatter)
  150. logger.addHandler(console_handler)