折腾:
【未解决】Flask部署到线上生产环境后多实例多线程中无法共享全局变量
期间,对于此处,之前在把Flask改为工厂模式去初始化的时候,
别的模块想要调用flask的app,其中主要考虑就是:想要用到app.logger
导致别的模块依赖app
导致了循环调用,递归引用的问题
而此处,从:
Creating a singleton in Python – Stack Overflow
注意到:
其实也是可以把logger弄成singleton的
从而避免了Flask中其他子模块依赖factory.py或app.py中的app
避免了递归调用,避免了多次初始化Flask的app
所以后续也要去:
把之前Flask的中的app中的logger也弄成单例
之前Flask中初始化log的方式是:
app.py
<code>from factory import create_app
app = create_app(settings)
app.app_context().push()
# register_extensions(app)
log = app.logger
log.debug("app=%s", app)
log.debug("settings.FLASK_ENV=%s", settings.FLASK_ENV)
if __name__ == "__main__":
app.run(
host=settings.FLASK_HOST,
port=settings.FLASK_PORT,
debug=settings.DEBUG,
use_reloader=False
)
</code>factory.py
<code>import os
from flask import Flask
import logging
from logging.handlers import RotatingFileHandler
...
from conf.app import settings
...
from flask import g
################################################################################
# Global Function
################################################################################
def create_app(config_object, init_extensions=True):
# global log
# app = Flask(__name__) #<Flask 'factory'>
app = Flask(config_object.FLASK_APP_NAME) #<Flask 'RobotQA'>
...
app.config.from_object(config_object)
with app.app_context():
g.app = app
log = create_log(app)
g.log = log
...
return app
...
def create_log(app):
print("create_log: before init log: app.logger=%s" % app.logger)
logFormatterStr = app.config["LOG_FORMAT"]
logFormatter = logging.Formatter(logFormatterStr)
fileHandler = RotatingFileHandler(
app.config['LOG_FILE_FILENAME'],
maxBytes=app.config["LOG_FILE_MAX_BYTES"],
backupCount=app.config["LOG_FILE_BACKUP_COUNT"],
encoding="UTF-8")
fileHandler.setLevel(logging.DEBUG)
fileHandler.setFormatter(logFormatter)
app.logger.addHandler(fileHandler)
# Note: should NOT set StreamHandler here, otherwise will duplicate debug log
app.logger.setLevel(logging.DEBUG) # set root log level
log = app.logger
log.info("app=%s", app)
# log.debug("app.config=%s", app.config)
print("create_log: after init log: app.logger=%s" % app.logger)
return log
...
</code>然后其他模块,包括celery的task中,去导入flask的g中的log:
resources/tts.py
<code>from flask import g
app = g.app
log = g.log
def createAudioTempFolder():
log.info("createAudioTempFolder")
</code>这就导致了:
不同的模块,都要依赖于Flask的app,以及Flask的g
才能获取g.log,这个全局的logger
也就导致了,容易产生互相的递归引用
以及:
对于特殊的:
celery的task:
resources/extensions_celery.py
<code># from flask_celery import Celery
from conf.app import settings
from celery import Celery
from celery.utils.log import get_task_logger
# celery = Celery()
celery = Celery(settings.FLASK_APP_NAME, broker=settings.CELERY_BROKER_URL)
celery_logger = get_task_logger(__name__)
print("in extensions_celery: celery=%s" % celery)
</code>resources/tasks.py
<code>from resources.extensions_celery import celery, celery_logger as log
log.info("create_celery_app return: celery=%s, log=%s", celery, log)
</code>此处的log,实际上在最开始初始化时:
只是获取了celery的logger,而不是Flask的app的logger,导致了:
没有达到希望:统一使用Flask的logger,不要用不同的logger
最开始初始化调试时,此处的log输出看不到
因为之后后续终端中通过
celery worker -A resources.tasks.celery –loglevel=DEBUG
才能正常的在终端中显示log
其中之前输出的log的信息是:

<code>[2018-08-29 13:43:21,759 DEBUG app.py:32 <module>] app=<Flask 'RobotQA'> [2018-08-29 13:43:21,762 DEBUG app.py:34 <module>] log=<Logger flask.app (DEBUG)> </code>
所以为了避免这些问题:
现在要去改为多线程安全的单例:
期间,出错:
【已解决】Flask中logging的单例初始化出错:AttributeError: ‘Formatter’ object has no attribute find
然后log的单例就弄好了。
【总结】
目前,至少实现了多线程thread(暂时不支持多进程process)的logging/logger的单例:
代码如下:
common/FlaskLogSingleton.py
<code>import logging
from logging.handlers import RotatingFileHandler
from conf.app import settings
from common.ThreadSafeSingleton import ThreadSafeSingleton
# from sys import stdout
def init_logger(flask_settings, enableConsole=True):
print("init_logger")
flaskAppLogger = logging.getLogger(flask_settings.FLASK_APP_NAME) # <Logger RobotQA (WARNING)>
print("flaskAppLogger=%s" % flaskAppLogger)
flaskAppLogger.setLevel(flask_settings.LOG_LEVEL_FILE)
logFormatter = logging.Formatter(flask_settings.LOG_FORMAT)
fileHandler = RotatingFileHandler(
flask_settings.LOG_FILE_FILENAME,
maxBytes=flask_settings.LOG_FILE_MAX_BYTES,
backupCount=flask_settings.LOG_FILE_BACKUP_COUNT,
encoding="UTF-8")
fileHandler.setLevel(flask_settings.LOG_LEVEL_FILE)
fileHandler.setFormatter(logFormatter)
flaskAppLogger.addHandler(fileHandler)
if enableConsole :
# define a Handler which writes INFO messages or higher to the sys.stderr
console = logging.StreamHandler()
# console = logging.StreamHandler(stdout)
console.setLevel(flask_settings.LOG_LEVEL_CONSOLE)
# set a format which is simpler for console use
formatter = logging.Formatter(
# fmt=logFormatter)
# fmt=logFormatter,
fmt=flask_settings.LOG_FORMAT,
datefmt=flask_settings.LOG_CONSOLE_DATA_FORMAT)
# tell the handler to use this format
console.setFormatter(formatter)
flaskAppLogger.addHandler(console)
print("init_logger: after init flaskAppLogger%s" % flaskAppLogger)
return flaskAppLogger
class LoggerSingleton(metaclass=ThreadSafeSingleton):
curLog = ""
def __init__(self):
self.curLog = init_logger(settings)
# Note: during __init__, AVOID use log, otherwise will deadlock
# log.info("LoggerSingleton __init__: curLog=%s", self.curLog)
print("LoggerSingleton __init__: curLog=%s" % self.curLog)
logSingleton = LoggerSingleton()
log = logSingleton.curLog
log.info("LoggerSingleton inited, logSingleton=%s", logSingleton) # <factory.LoggerSingleton object at 0x10cbcafd0>
log.info("log=%s", log) # <Logger RobotQA (DEBUG)>
# # debug for singleton log
# log2 = LoggerSingleton()
# print("log2=%s" % log2)
</code>其中的配置是:
conf/app/settings.py
<code>FLASK_APP_NAME = "RobotQA" # Log File LOG_LEVEL_FILE = logging.DEBUG LOG_FILE_FILENAME = "logs/" + FLASK_APP_NAME + ".log" LOG_FORMAT = "[%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(funcName)s] %(message)s" LOG_FILE_MAX_BYTES = 2 * 1024 * 1024 LOG_FILE_BACKUP_COUNT = 10 # Log Console LOG_LEVEL_CONSOLE = logging.INFO LOG_CONSOLE_DATA_FORMAT = '%Y%m%d %I:%M:%S' </code>
转载请注明:在路上 » 【已解决】把Flask中的app的logger改造成单例以避免循环引用和多次初始化Flask的实例