我们可以停止 Dash“POST /dash/_dash-update-component HTTP/1.1”日志消息吗?

问题描述 投票:0回答:1

有人知道如何停止以下 Dash 日志消息吗?它们使我的日志变得混乱,并且在繁忙的网站上,当错误发生时几乎不可能看到实际、有用的日志消息。

[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"

我已经尝试了达世币创建者here的建议。他说尝试以下方法,但这对我没有任何作用:

import logging

logging.getLogger('werkzeug').setLevel(logging.ERROR)

如果您想尝试一下,这里有一个来自此链接的更完整示例:

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()

这更像是我在 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

这个问题自 2018 年以来一直在 在 Github 上,显然它已经关闭/修复了,但不适合我......

我在生产中使用以下

pyproject.toml

[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"

这是我的

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"

这是我在生产中使用的 Dockerfile:

# 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"]

我的

entrypoint.sh
文件开始一切如下:

#!/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

这是上面

wsgi.py
所指的
entrypoint.sh
文件:

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 ✅")

2023 年 11 月 20 日更新: 我在代码中添加了以下内容,但它仍然输出无用的 Dash

POST /dash/_dash-update-component
日志...

gunicorn_logger = logging.getLogger("gunicorn.error")
gunicorn_logger.setLevel(logging.ERROR)

dash_logger = logging.getLogger("dash")
dash_logger.setLevel(logging.ERROR)
python flask plotly-dash
1个回答
0
投票

我想我终于解决了这个问题。正如 @EricLavault 所建议的,这是一个 Gunicorn 问题,而不是 Dash/Flask 日志记录问题。我看到的是“访问日志”,如下所示:

[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"

要从生产中的 Docker 日志输出中删除“访问日志”,我只需将

gunicorn.conf.py
设置更改为以下内容,其中
accesslog = None
是关键行:

accesslog = None
errorlog = "-"
loglevel = "error"
© www.soinside.com 2019 - 2024. All rights reserved.