pallets / flask

The Python micro framework for building web applications.

Home Page:https://flask.palletsprojects.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Flask logging - Other libraries - FileHandler creates duplicate log entries

signag opened this issue · comments

Description

In my web server, based on Flask 3.0.0, (raspi-cam-srv), I want to log to a log file.
For implementation, I followed the Flask Logging documentation.

For app and my own modules, I have added a FileHandler with

    # Configure loggers
    logsPath = os.path.dirname(app.instance_path) + "/logs"
    os.makedirs(logsPath, exist_ok=True)
    logFile = logsPath + "/raspiCamSrv.log"
    Path(logFile).touch(exist_ok=True)
    filehandler = logging.FileHandler(logFile)
    filehandler.setFormatter(app.logger.handlers[0].formatter)
    for logger in(
        app.logger,
        logging.getLogger("werkzeug"),
        logging.getLogger("raspiCamSrv.camera_pi"),
    ):
        logger.addHandler(filehandler)
        logger.setLevel(logging.ERROR)

As a result, I see duplicate entries for each log entry in the log file (see attachments, below).
The log output on the console has single entries.

Note

I did not add the default_handler to the loggers as described in the Flask Logging documentation.
When doing this, I get duplicate log entries also from the StreamHandler to the console.

How to reproduce the bug

This is the __init__.py: init.py.txt
This is the log file: raspiCamSrv.log
This is the console output; console.log

Expected behavior

Every log entry should occur only once

Environment:

  • Python version: 3.11.2
  • Flask version: 3.0.0
  • Wekzeug version: 3.0.1

just FYI, flask run should never be used for production setups, it's ONLY meant for development.
so dealing with the werkzeug dev server logger logging stuff should be a non-issue for production usage...

Thanks, I know

I found the solution:
It is sufficient to add the FileHandler only to the app Logger:

    # Configure loggers
    logsPath = os.path.dirname(app.instance_path) + "/logs"
    os.makedirs(logsPath, exist_ok=True)
    logFile = logsPath + "/raspiCamSrv.log"
    Path(logFile).touch(exist_ok=True)
    filehandler = logging.FileHandler(logFile)
    filehandler.setFormatter(app.logger.handlers[0].formatter)
    for logger in(
        app.logger,
        logging.getLogger("werkzeug"),
        logging.getLogger("raspiCamSrv.camera_pi"),
    ):
        logger.setLevel(logging.ERROR)
    app.logger.addHandler(filehandler)

Obviously, by some Flask magic, the other loggers get the FileHandler added, too.

With this modification, all log entries occur just once in the log file.

It's a mistake in the docs. Instead of app.logger in the loop, it should be logging.getLogger(app.name). The docs were trying to say that logging should be configured before accessing app.logger, which is undermined if it's accessed during logging configuration. 🤦🏼‍♂️

I tried with

    # Configure loggers
    logsPath = os.path.dirname(app.instance_path) + "/logs"
    os.makedirs(logsPath, exist_ok=True)
    logFile = logsPath + "/raspiCamSrv.log"
    Path(logFile).touch(exist_ok=True)
    filehandler = logging.FileHandler(logFile)
    filehandler.setFormatter(app.logger.handlers[0].formatter)
    for logger in(
        logging.getLogger(app.name),
        logging.getLogger("werkzeug"),
        logging.getLogger("raspiCamSrv.camera_pi"),
    ):
        logger.addHandler(filehandler)
        logger.setLevel(logging.ERROR)

... and get duplicate log entries in the log file again.

You accessed app.logger a few lines up.

From the docs you linked:

When you want to configure logging for your project, you should do it as soon as possible when the program starts. If app.logger is accessed before logging is configured, it will add a default handler.

You accessed app.logger a few lines up.

OK, I had overseen this one.
But nevertheless, having the default_handler added, is OK for me.

Now, I tried with (this is the complete start of __init__.py and there is no access to a logger afterwards)

import os
from pathlib import Path
from flask import Flask
import logging
from flask.logging import default_handler
from picamera2 import Picamera2
import json

def create_app(test_config=None):
    # create and configure the app
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_mapping(
        SECRET_KEY="dev",
        DATABASE=os.path.join(app.instance_path, "raspiCamSrv.sqlite"),
    )

    # ensure the instance folder exists
    try:
        os.makedirs(app.instance_path)
    except OSError:
        pass
    
    # Configure loggers
    logsPath = os.path.dirname(app.instance_path) + "/logs"
    os.makedirs(logsPath, exist_ok=True)
    logFile = logsPath + "/raspiCamSrv.log"
    Path(logFile).touch(exist_ok=True)
    filehandler = logging.FileHandler(logFile)
    filehandler.setFormatter(default_handler.formatter)
    for logger in(
        logging.getLogger(app.name),
        logging.getLogger("werkzeug"),
        logging.getLogger("raspiCamSrv.auth"),
        logging.getLogger("raspiCamSrv.auth_su"),
        logging.getLogger("raspiCamSrv.camCfg"),
        logging.getLogger("raspiCamSrv.camera_pi"),
        logging.getLogger("raspiCamSrv.config"),
        logging.getLogger("raspiCamSrv.home"),
        logging.getLogger("raspiCamSrv.images"),
        logging.getLogger("raspiCamSrv.info"),
        logging.getLogger("raspiCamSrv.settings"),
        logging.getLogger("raspiCamSrv.photoseries"),
        logging.getLogger("raspiCamSrv.photoseriesCfg"),
    ):
        logger.addHandler(default_handler)    
        logger.addHandler(filehandler)    
        logger.setLevel(logging.DEBUG)

... and get duplicate log entries for my own modules in the log file as well as in console output.
For module _internal, there is a single entry.

When I omit logger.addHandler(default_handler),
I get no log output on the console and again duplicate log output for my own modules in the log file

Sounds like you have other issues. Logging configuration is confusing in general for Python. I'll fix the docs issue I commented about, but otherwise can't help with your specific issue here.

Thanks a lot for your support.
I returned to the previous solution which works, even if it isn't according to the docs.