Question: Has anyone figured out how to stop the following Dash log messages? They clutter up my logs, and on a busy website, make it almost impossible to see actual, useful log messages when errors occur. [17/Nov/2023:16:28:10 +0000] "POST /dash/_dash-update-component HTTP/1.1" 204 0 "https://example.com/" "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1"  I've tried the suggestion here by the creator of Dash. He says to try the following, but it doesn't do anything for me: import logging logging.getLogger('werkzeug').setLevel(logging.ERROR)  Here's a fuller example from this link if you want to try it: import logging from dash import Dash from flask import Flask logging.getLogger('werkzeug').setLevel(logging.ERROR) URL_BASE_PATHNAME = '/'+'example/' server = Flask(__name__) app = Dash(name=__name__, server=server, url_base_pathname=URL_BASE_PATHNAME) if __name__ == "__main__": app.run()  Here's more like what mine looks like in production with Docker Swarm: import logging import os import time from datetime import datetime, timezone from logging.handlers import RotatingFileHandler from pathlib import Path from typing import List from dotenv import load_dotenv from flask import Flask, current_app, redirect, render_template, request, url_for from flask.globals import _request_ctx_stack from flask.logging import default_handler from flask_assets import Environment from flask_bcrypt import Bcrypt from flask_bootstrap import Bootstrap from flask_caching import Cache from flask_flatpages import FlatPages from flask_htmlmin import HTMLMIN as HTMLMin from flask_login import LoginManager, current_user from flask_mail import Mail from flask_migrate import Migrate from flask_sqlalchemy import SQLAlchemy from werkzeug.exceptions import NotFound from werkzeug.middleware.proxy_fix import ProxyFix from app import databases from app.assets import compile_assets # Dictionary pointing to classes of configs from app.config import ( INSTANCE_PATH, PROJECT_FOLDER, ROLE_ID_CUSTOMER_ADMIN, ROLE_ID_IJACK_ADMIN, ROLE_ID_IJACK_SERVICE, STATIC_FOLDER, TEMPLATE_FOLDER, app_config, ) from app.dash_setup import register_dashapps from app.utils import error_send_email_w_details # Ensure the .env file doesn't contain copies of the variables in the .flaskenv file, or it'll get confusing... load_dotenv(PROJECT_FOLDER, override=True) # Set log level globally so other modules can import it log_level = None db = SQLAlchemy() login_manager = LoginManager() bcrypt = Bcrypt() cache = Cache() mail = Mail() pages = FlatPages() assets_env = Environment() # noqa: C901 def create_app(config_name=None): """Factory function that creates the Flask app""" app = Flask( __name__, instance_path=INSTANCE_PATH, static_folder=STATIC_FOLDER, template_folder=TEMPLATE_FOLDER, static_url_path="/static", ) # Import the config class from config.py (defaults to 'development' if not in the .env file) if config_name is None: config_name = os.getenv("FLASK_CONFIG", "development") config_obj = app_config[config_name] app.config.from_object(config_obj) app.config["SECRET_KEY"] = os.getenv("SECRET_KEY") # Set up logging global log_level log_level = app.config.get("LOG_LEVEL", logging.INFO) app.logger.setLevel(log_level) # The default handler is a StreamHandler that writes to sys.stderr at DEBUG level. default_handler.setLevel(log_level) # Change default log format log_format = ( "[%(asctime)s] %(levelname)s: %(name)s: %(module)s: %(funcName)s: %(message)s" ) default_handler.setFormatter(logging.Formatter(log_format)) # Stop the useless 'dash-component-update' logging? (Unfortunately this doesn't seem to work...) # https://community.plotly.com/t/prevent-post-dash-update-component-http-1-1-messages/11132 # https://community.plotly.com/t/suppressing-component-update-output-message-in-the-terminal/7613 logging.getLogger("werkzeug").setLevel(logging.ERROR) # NOTE != means running the Flask application through Gunicorn in my workflow. if __name__ != "__main__" and not app.debug and app.env != "development": # Add a FileHandler to the Flask logger Path("logs").mkdir(exist_ok=True) file_handler = RotatingFileHandler( "logs/myijack.log", maxBytes=10240, backupCount=10 ) file_handler.setLevel(logging.ERROR) file_handler.setFormatter(logging.Formatter(log_format)) app.logger.addHandler(file_handler) app.logger.error( "Just testing Gunicorn logging in Docker Swarm service container ✅..." ) app.logger.info("myijack.com startup now...") app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1) # Initialize extensions Bootstrap(app) db.init_app(app) # SQLAlchemy databases.init_app(app) # other custom database functions cache.init_app( app, config=config_obj.cache_config ) # Simple if dev, otherwise Redis for test/prod login_manager.init_app(app) Migrate(app, db) mail.init_app(app) bcrypt.init_app(app) pages.init_app(app) # By default, when a user attempts to access a login_required view without being logged in, # Flask-Login will flash a message and redirect them to the log in view. # (If the login view is not set, it will abort with a 401 error.) login_manager.login_view = "auth.login" # login_manager.login_message = "You must be logged in to access this page." # Register blueprints if ALL0_DASH1_FLASK2_ADMIN3 in (0, 2): pass app.logger.debug("Importing blueprint views...") from app.auth.oauth import azure_bp, github_bp, google_bp from app.auth.views import auth as auth_bp from app.dashapp.views import dash_bp from app.home.views import home as home_bp from app.pwa import pwa_bp app.logger.debug("Registering blueprint views...") app.register_blueprint(auth_bp) app.register_blueprint(home_bp) app.register_blueprint(pwa_bp) app.register_blueprint(dash_bp) app.register_blueprint(github_bp, url_prefix="/login") app.register_blueprint(azure_bp, url_prefix="/login") app.register_blueprint(google_bp, url_prefix="/login") # Register API for saving Flask-Admin views' metadata via JavaScript AJAX from app.api import api api.init_app(app) if ALL0_DASH1_FLASK2_ADMIN3 in (0, 3): pass # Setup Flask-Admin site app.logger.debug("Importing Flask-Admin views...") from app.flask_admin.views_admin import admin_views from app.flask_admin.views_admin_cust import admin_cust_views app.logger.debug("Adding Flask-Admin views...") admin_views(app, db) admin_cust_views(app, db) with app.app_context(): # Flask-Assets must come before the Dash app so it # can first render the {% assets %} blocks assets_env.init_app(app) compile_assets(assets_env, app) # HTMLMin must come after Dash for some reason... # app.logger.debug("Registering HTMLMin...") app.config["MINIFY_HTML"] = True HTMLMin( app, remove_comments=True, remove_empty_space=True, # This one can cause a bug... # disable_css_min=False, ) return app, dash_app  The issue has been on Github since 2018 and apparently it's closed/fixed, but not for me... I'm using the following pyproject.toml in production: [tool.poetry.dependencies] python = ">=3.8,<3.12" dash = {extras = ["compress"], version = "^2.11.1"} scikit-learn = "1.1.3" pandas = "^1.5.3" flask-login = "^0.5.0" keras = "^2.4.3" joblib = "^1.2.0" boto3 = "^1.26.12" click = "^8.1.3" dash-bootstrap-components = "^1.4.2" dash-table = "^5.0.0" flask-caching = "2.0.1" flask-migrate = "^2.5.3" flask-sqlalchemy = "^2.4.4" flask-testing = "^0.8.0" gevent = "^22.10.2" greenlet = "^2.0.1" gunicorn = "^20.0.4" python-dotenv = "^0.19.2" python-dateutil = "^2.8.1" requests = "^2.24.0" email_validator = "^1.1.1" flask-redis = "^0.4.0" numexpr = "^2.7.1" flask-mail = "^0.9.1" python-jose = "^3.3.0" sqlalchemy = "^1.3" Flask-FlatPages = "^0.7.2" flask-bootstrap4 = "^4.0.2" colour = "^0.1.5" tenacity = "^6.3.1" psycopg2-binary = "^2.8.6" twilio = "^6.54.0" openpyxl = "^3.0.7" phonenumbers = "^8.12.29" celery = "^5.1.2" flower = "^1.0.0" Flask-Assets = "^2.0" webassets = "^2.0" cssmin = "^0.2.0" rjsmin = "^1.2.0" Flask-HTMLmin = "^2.2.0" ipinfo = "^4.2.1" dash-mantine-components = "^0.12.1" Flask = "^2.1.2" Flask-Bcrypt = "^1.0.1" Werkzeug = "2.0.3" Flask-WTF = "^1.0.1" flask-restx = "^0.5.1" flask-admin-plus = "^1.6.18" Pillow = "^9.2.0" multidict = "^6.0.2" gcld3 = "^3.0.13" plotly = "^5.14.1" flask-dance = "^7.0.0" blinker = "^1.6.2" [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api"  Here's my gunicorn.conf.py: # -*- encoding: utf-8 -*- bind = "0.0.0.0:5005" # The Access log file to write to, same as --access-logfile # Using default "-" makes gunicorn log to stdout - perfect for Docker accesslog = "-" # Same as --log-file or --error-logfile. Default "-" goes to stderr for Docker. errorlog = "-" # We overwrite the below loglevel in __init__.py # loglevel = "info" # Redirect stdout/stderr to specified file in errorlog capture_output = True enable_stdio_inheritance = True # gevent setup # workers = 4 # 4 threads (2 per CPU) # threads = 2 # 2 CPUs # Typically Docker handles the number of workers, not Gunicorn workers = 1 threads = 2 worker_class = "gevent" # The maximum number of simultaneous clients. # This setting only affects the Eventlet and Gevent worker types. worker_connections = 20 # Timeout in seconds (default is 30) timeout = 30 # Directory to use for the worker heartbeat temporary file. # Use an in-memory filesystem to avoid hanging. # In AWS an EBS root instance volume may sometimes hang for half a minute # and during this time Gunicorn workers may completely block. # https://docs.gunicorn.org/en/stable/faq.html#blocking-os-fchmod worker_tmp_dir = "/dev/shm"  Here's the Dockerfile I'm using in production: # Builder stage ############################################################################ # Build args available during build, but not when container runs. # They can have default values, and can be passed in at build time. ARG ENVIRONMENT=production FROM python:3.8.15-slim-buster AS builder ARG POETRY_VERSION=1.2.2 # Use Docker BuildKit for better caching and faster builds ARG DOCKER_BUILDKIT=1 ARG BUILDKIT_INLINE_CACHE=1 # Enable BuildKit for Docker-Compose ARG COMPOSE_DOCKER_CLI_BUILD=1 # Python package installation stuff ARG PIP_NO_CACHE_DIR=1 ARG PIP_DISABLE_PIP_VERSION_CHECK=1 ARG PIP_DEFAULT_TIMEOUT=100 # Don't write .pyc bytecode ENV PYTHONDONTWRITEBYTECODE=1 # Don't buffer stdout. Write it immediately to the Docker log ENV PYTHONUNBUFFERED=1 ENV PYTHONFAULTHANDLER=1 ENV PYTHONHASHSEED=random # Tell apt-get we're never going to be able to give manual feedback: ENV DEBIAN_FRONTEND=noninteractive WORKDIR /project RUN apt-get update && \ apt-get install -y --no-install-recommends gcc redis-server libpq-dev sass \ g++ protobuf-compiler libprotobuf-dev && \ # Clean up apt-get autoremove -y && \ apt-get clean -y && \ rm -rf /var/lib/apt/lists/* # The following only runs in the "builder" build stage of this multi-stage build. RUN pip3 install "poetry==$POETRY_VERSION" && \ # Use a virtual environment for easy transfer of builder packages python -m venv /venv && \ /venv/bin/pip install --upgrade pip wheel # Poetry exports the requirements to stdout in a "requirements.txt" file format, # and pip installs them in the /venv virtual environment. We need to copy in both # pyproject.toml AND poetry.lock for this to work! COPY pyproject.toml poetry.lock ./ RUN poetry config virtualenvs.create false && \ poetry export --no-interaction --no-ansi --without-hashes --format requirements.txt \ $(test "$ENVIRONMENT" != "production" && echo "--with dev") \ | /venv/bin/pip install -r /dev/stdin # Make sure our packages are in the PATH ENV PATH="/project/node_modules/.bin:$PATH" ENV PATH="/venv/bin:$PATH" COPY wsgi.py gunicorn.conf.py .env .flaskenv entrypoint.sh postcss.config.js ./ COPY assets assets COPY app app RUN echo "Building flask assets..." && \ # Flask assets "clean" command may fail, in which case just run "build" flask assets clean || true && \ flask assets build # Final stage of multi-stage build ############################################################ FROM python:3.8.15-slim-buster as production # For setting up the non-root user in the container ARG USERNAME=user ARG USER_UID=1000 ARG USER_GID=$USER_UID # Use Docker BuildKit for better caching and faster builds ARG DOCKER_BUILDKIT=1 ARG BUILDKIT_INLINE_CACHE=1 # Enable BuildKit for Docker-Compose ARG COMPOSE_DOCKER_CLI_BUILD=1 # Don't write .pyc bytecode ENV PYTHONDONTWRITEBYTECODE=1 # Don't buffer stdout. Write it immediately to the Docker log ENV PYTHONUNBUFFERED=1 ENV PYTHONFAULTHANDLER=1 ENV PYTHONHASHSEED=random # Tell apt-get we're never going to be able to give manual feedback: ENV DEBIAN_FRONTEND=noninteractive # Add a new non-root user and change ownership of the workdir RUN addgroup --gid $USER_GID --system $USERNAME && \ adduser --no-create-home --shell /bin/false --disabled-password --uid $USER_UID --system --group $USERNAME && \ # Get curl and netcat for Docker healthcheck apt-get update && \ apt-get -y --no-install-recommends install nano curl netcat g++ && \ apt-get clean && \ # Delete index files we don't need anymore: rm -rf /var/lib/apt/lists/* WORKDIR /project # Make the logs directory writable by the non-root user RUN mkdir -p /project/logs && \ chown -R $USER_UID:$USER_GID /project/logs # Copy in files and change ownership to the non-root user COPY --chown=$USER_UID:$USER_GID --from=builder /venv /venv # COPY --chown=$USER_UID:$USER_GID --from=builder /node_modules /node_modules COPY --chown=$USER_UID:$USER_GID --from=builder /project/assets assets COPY --chown=$USER_UID:$USER_GID app app COPY --chown=$USER_UID:$USER_GID tests tests COPY --chown=$USER_UID:$USER_GID wsgi.py gunicorn.conf.py .env .flaskenv entrypoint.sh ./ # Set the user so nobody can run as root on the Docker host (security) USER $USERNAME # Just a reminder of which port is needed in gunicorn.conf.py (in-container, in production) # EXPOSE 5005 # Make sure we use the virtualenv ENV PATH="/venv/bin:$PATH" RUN echo PATH = $PATH CMD ["/bin/bash", "/project/entrypoint.sh"]  My entrypoint.sh file starts everything as follows: #!/bin/bash # Enable exit on non 0 set -euo pipefail # Finally, start the Gunicorn app server for the Flask app. # All config options are in the gunicorn.conf.py file. echo "Starting Gunicorn with gunicorn.conf.py configuration..." gunicorn --config /project/gunicorn.conf.py wsgi:app  Here's the wsgi.py file to which the above entrypoint.sh is referring: print("Starting: importing app and packages...") try: from app import cli, create_app, db except Exception as err: print(f"ERROR: {err}") print("ERROR: Unable to import cli, create_app, and db from app. Exiting...") exit(1) print("Creating app...") try: app, _ = create_app() cli.register(app) except Exception as err: print(f"ERROR: {err}") print("ERROR: Unable to create app. Exiting...") exit(1) print("App is ready ✅")  UPDATE Nov 20, 2023: I've added the following to the code, and it still outputs the useless Dash POST /dash/_dash-update-component logs... gunicorn_logger = logging.getLogger("gunicorn.error") gunicorn_logger.setLevel(logging.ERROR) dash_logger = logging.getLogger("dash") dash_logger.setLevel(logging.ERROR) 

Answer: I finally solved the problem, I think. As @EricLavault suggested, it was a Gunicorn problem, not a Dash/Flask logging problem. What I was seeing were the "access logs", which looked like this: [17/Nov/2023:16:28:10 +0000] "POST /dash/_dash-update-component HTTP/1.1" 204 0 "https://example.com/" "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1"  To remove the "access logs" from my Docker log output in production, I just changed my gunicorn.conf.py settings to the following, where accesslog = None is the key line: accesslog = None errorlog = "-" loglevel = "error"