<p dir="auto">Hi, I originally filed an issue at <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="1245817692" data-permission-text="Title is private" data-url="https://github.com/rycus86/prometheus_flask_exporter/issues/135" data-hovercard-type="issue" data-hovercard-url="/rycus86/prometheus_flask_exporter/issues/135/hovercard" href="https://github.com/rycus86/prometheus_flask_exporter/issues/135">rycus86/prometheus_flask_exporter#135</a> but determined that it was impossible to resolve the problem downstream, and changes are required in Flask to support the use case of prometheus_flask_exporter.</p>
<p dir="auto">The problem is described in the issue report I linked above, but I will also summarize it here for convenience. Basically, when you invoke <code class="notranslate">flask run</code>, then Flask sets <code class="notranslate">FLASK_RUN_FROM_CLI</code> as an environment variable. Then, in <code class="notranslate">app.run()</code>, if that environment variable is set, Flask skips starting the app, and prints the following warning:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Warning: Silently ignoring app.run() because the application is run from the flask command line executable. Consider putting app.run() behind an if __name__ == &quot;__main__&quot; guard to silence this warning."><pre class="notranslate"><code class="notranslate">Warning: Silently ignoring app.run() because the application is run from the flask command line executable. Consider putting app.run() behind an if __name__ == "__main__" guard to silence this warning.
</code></pre></div>
<p dir="auto">This is all fine and good when <code class="notranslate">app.run()</code> is being invoked from the top level in the main module, and it was not intended to launch a duplicate app. However, sometimes the user actually <em>does</em> want to invoke two separate Flask applications: for example, to expose application metrics on a different port from web traffic, which is generally recommended as a security best practice.</p>
<p dir="auto">Specifically, prometheus_flask_exporter offers an option to expose metrics on a separate port by starting a separate Flask app on a separate port and thread. However, Flask's behavior around <code class="notranslate">FLASK_RUN_FROM_CLI</code> breaks this functionality, since if the web traffic app is launched using <code class="notranslate">flask run</code>, then the environment variable will prevent prometheus_flask_exporter from starting the metrics app.</p>
<p dir="auto">Code in the user application looks like this:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="metrics = prometheus_flask_exporter.PrometheusMetrics(app)
metrics.start_http_server(8081, host=config.METRICS_HOST)"><pre class="notranslate"><span class="pl-s1">metrics</span> <span class="pl-c1">=</span> <span class="pl-s1">prometheus_flask_exporter</span>.<span class="pl-v">PrometheusMetrics</span>(<span class="pl-s1">app</span>)
<span class="pl-s1">metrics</span>.<span class="pl-en">start_http_server</span>(<span class="pl-c1">8081</span>, <span class="pl-s1">host</span><span class="pl-c1">=</span><span class="pl-s1">config</span>.<span class="pl-v">METRICS_HOST</span>)</pre></div>
<p dir="auto">The <code class="notranslate">metrics.start_http_server()</code> method instantiates a separate Flask application with the provided configuration, and invokes <code class="notranslate">app.run()</code>. If the main Flask application was starting using <code class="notranslate">flask run</code>, then the following warning is printed, and no metrics are served:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="website_1   |  * Serving Flask app 'src.app:app' (lazy loading)
website_1   |  * Environment: development
website_1   |  * Debug mode: on
website_1   | /usr/local/lib/python3.8/dist-packages/prometheus_flask_exporter/__init__.py:322: Warning: Silently ignoring app.run() because the application is run from the flask command line executable. Consider putting app.run() behind an if __name__ == &quot;__main__&quot; guard to silence this warning."><pre class="notranslate"><code class="notranslate">website_1   |  * Serving Flask app 'src.app:app' (lazy loading)
website_1   |  * Environment: development
website_1   |  * Debug mode: on
website_1   | /usr/local/lib/python3.8/dist-packages/prometheus_flask_exporter/__init__.py:322: Warning: Silently ignoring app.run() because the application is run from the flask command line executable. Consider putting app.run() behind an if __name__ == "__main__" guard to silence this warning.
</code></pre></div>
<h3 dir="auto">Workarounds</h3>
<p dir="auto">Note that this problem only affects development since the Flask builtin server is not used in production. As such, there are a number of workarounds to the problem at present, but all of them have significant drawbacks.</p>
<ul dir="auto">
<li>Do not use <code class="notranslate">flask run</code>. <em>Disadvantage:</em> degraded developer velocity due to having no live reloading during development.</li>
<li>Do not serve metrics on separate Flask app. <em>Disadvantage:</em> degraded security posture due to internal metrics being (by default) available to public web traffic unless blocked by a separate firewall rule.</li>
<li>Do not run metrics server in development, or serve metrics on the same Flask app only during development. <em>Disadvantage:</em> degraded reliability posture due to loss of parity between development and production.</li>
<li>Manually unset <code class="notranslate">FLASK_RUN_FROM_CLI</code> environment variable from application code. <em>Disadvantage:</em> degraded reliability posture due to reliance on undocumented internal Flask features which may change in future versions, and mutation of the global namespace of environment variables which will affect all threads in the current process, including unrelated internal Flask code.</li>
</ul>
<h3 dir="auto">Suggested fix</h3>
<p dir="auto">Add an optional keyword argument to <code class="notranslate">app.run</code> that allows bypassing the check on <code class="notranslate">FLASK_RUN_FROM_CLI</code>. This can be provided by libraries such as prometheus_flask_exporter that invoke Flask as a server. Such a change would solve the use case described above, while preserving all the benefits of the check happening by default.</p>
<p dir="auto">I would be happy to contribute such a fix, along with documentation and tests as desired, if the maintainers of Flask agree that it is an appropriate path forward. If you have other suggestions for a better way to resolve the issue with prometheus_flask_exporter, please let me know and I would be happy to discuss.</p>
<p dir="auto">The current docs are at: <a href="https://flask.palletsprojects.com/en/1.1.x/patterns/celery/" rel="nofollow">https://flask.palletsprojects.com/en/1.1.x/patterns/celery/</a></p>
<p dir="auto">They reference Celery's old configuration options which are the <code class="notranslate">CELERY_*</code> options. Since 5.x they've been renamed to be lowercase and there's an official migration guide available on Celery's site at <a href="https://docs.celeryproject.org/en/stable/userguide/configuration.html#conf-old-settings-map" rel="nofollow">https://docs.celeryproject.org/en/stable/userguide/configuration.html#conf-old-settings-map</a>.</p>
<p dir="auto">As of Celery 5.x, the old uppercase variants throw a deprecation warning and 6.x will remove them.</p>
<p dir="auto">The current Flask docs for Celery reference things like:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="    celery = Celery(
        app.import_name,
        backend=app.config['CELERY_RESULT_BACKEND'],
        broker=app.config['CELERY_BROKER_URL']
    )"><pre class="notranslate">    <span class="pl-s1">celery</span> <span class="pl-c1">=</span> <span class="pl-v">Celery</span>(
        <span class="pl-s1">app</span>.<span class="pl-s1">import_name</span>,
        <span class="pl-s1">backend</span><span class="pl-c1">=</span><span class="pl-s1">app</span>.<span class="pl-s1">config</span>[<span class="pl-s">'CELERY_RESULT_BACKEND'</span>],
        <span class="pl-s1">broker</span><span class="pl-c1">=</span><span class="pl-s1">app</span>.<span class="pl-s1">config</span>[<span class="pl-s">'CELERY_BROKER_URL'</span>]
    )</pre></div>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="flask_app.config.update(
    CELERY_BROKER_URL='redis://localhost:6379',
    CELERY_RESULT_BACKEND='redis://localhost:6379'
)"><pre class="notranslate"><span class="pl-s1">flask_app</span>.<span class="pl-s1">config</span>.<span class="pl-en">update</span>(
    <span class="pl-v">CELERY_BROKER_URL</span><span class="pl-c1">=</span><span class="pl-s">'redis://localhost:6379'</span>,
    <span class="pl-v">CELERY_RESULT_BACKEND</span><span class="pl-c1">=</span><span class="pl-s">'redis://localhost:6379'</span>
)</pre></div>
<p dir="auto">These imply you're using Flask's configuration mechanism which require uppercase variable names to be available, but that conflicts with Celery's new lowercase variables.</p>
<p dir="auto">I talked with <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/ThiefMaster/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/ThiefMaster">@ThiefMaster</a> a few weeks ago about this on IRC and he came up with a great idea of doing:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="# settings.py

CELERY_CONFIG = {
  &quot;broker_url&quot;: &quot;redis://localhost:6379/0&quot;,
  &quot;result_backend&quot;: &quot;redis://localhost:6379/0&quot;
}"><pre class="notranslate"><span class="pl-c"># settings.py</span>

<span class="pl-v">CELERY_CONFIG</span> <span class="pl-c1">=</span> {
  <span class="pl-s">"broker_url"</span>: <span class="pl-s">"redis://localhost:6379/0"</span>,
  <span class="pl-s">"result_backend"</span>: <span class="pl-s">"redis://localhost:6379/0"</span>
}</pre></div>
<p dir="auto">And then in your Celery factory you can do <code class="notranslate">celery.conf.update(app.config.get("CELERY_CONFIG", {}))</code>.</p>
<p dir="auto">This solves the problem and is working out nicely in practice. I'm doing that in a real project, along with extracting out the duplicate Redis connection string into an environment variable, etc.. A working example of that pattern is at <a href="https://github.com/nickjj/docker-flask-example/blob/ca1d48499a4007378bbe363bd5dc117c1a33cc3a/config/settings.py#L21-L28">https://github.com/nickjj/docker-flask-example/blob/ca1d48499a4007378bbe363bd5dc117c1a33cc3a/config/settings.py#L21-L28</a>.</p>
<p dir="auto">I didn't want to open a PR without asking first because I'm not sure which direction you want to go with the docs. To be honest the current docs are a little confusing because you jump straight into using Flask's app configuration values but there's no reference to setting up those config values.</p>
<p dir="auto">Also with the current docs if you attempt to break things into separate files you'll end up with circular import issues when importing your tasks unless you start importing your tasks directly inside of the function you're using instead of the top of the file, but that's another issue. Although potentially semi-related if you plan to revamp the Celery docs.</p>