question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Logger only logs error and warning logs

See original GitHub issue
from fastapi import FastAPI
from fastapi.logger import logger
import logging

app = FastAPI()

@app.get("/")
async def pong():
    logger.setLevel(logging.DEBUG)
    logger.error("Error log")
    logger.warning("Warning log")
    logger.info("Info log")
    logger.debug("Debug log")
    return {"ping": "pong"}

Description

I’m trying to get some info level logs in my FastAPI app. I have tried starting my app with uvicorn app.main:app --reload --log-level debug and also by setting the level in the code as you can see above. Unfortunately I can only get error and warning logs to display. This might be a uvicorn or logging problem or, more likely, my own misunderstanding. Any help would be greatly appreciated!

Error log
Warning log

Environment

  • OS: macOS:
  • FastAPI Version 0.61.1
  • Uvicorn Version 0.11.8
  • Python Version 3.8.5

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:1
  • Comments:5 (1 by maintainers)

github_iconTop GitHub Comments

10reactions
ArcLightSlavikcommented, Sep 6, 2020

You also need logging.basicConfig(level=logging.INFO).

It gives very weird results when running under uvicorn but to fix that you will need to go deep into uvicorn logging config. If it becomes very annoying i would advice to use gunicorn with the uvicorn worker.

8reactions
gucharboncommented, Oct 1, 2020

The comment above is suggesting to use loguru, but the example uses a middleware which is not mandatory for the use case of intercepting logs.

Here is a working example:

import logging
import sys
from enum import Enum
from pathlib import Path
from typing import Optional

from loguru import logger
from loguru._logger import Logger
from pydantic import BaseSettings


class LoggingLevel(str, Enum):
    """
    Allowed log levels for the application
    """

    CRITICAL: str = "CRITICAL"
    ERROR: str = "ERROR"
    WARNING: str = "WARNING"
    INFO: str = "INFO"
    DEBUG: str = "DEBUG"


class LoggingSettings(BaseSettings):
    """Configure your service logging using a LoggingSettings instance.

    All arguments are optional.

    Arguments:

        level (str): the minimum log-level to log. (default: "DEBUG")
        format (str): the logformat to use. (default: "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | <level>{message}</level>")
        filepath (Path): the path where to store the logfiles. (default: None)
        rotation (str): when to rotate the logfile. (default: "1 days")
        retention (str): when to remove logfiles. (default: "1 months")
    """

    level: LoggingLevel = "DEBUG"
    format: str = (
        "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
        "<level>{level: <8}</level> | "
        "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | "
        "<level>{message}</level>"
    )
    filepath: Optional[Path] = None
    rotation: str = "1 days"
    retention: str = "1 months"

    class Config:
        env_prefix = "logging_"


class InterceptHandler(logging.Handler):
    def emit(self, record):
        # Get corresponding Loguru level if it exists
        try:
            level = logger.level(record.levelname).name
        except ValueError:
            level = record.levelno

        # Find caller from where originated the logged message
        frame, depth = logging.currentframe(), 2
        while frame.f_code.co_filename == logging.__file__:
            frame = frame.f_back
            depth += 1

        logger.opt(depth=depth, exception=record.exc_info).log(
            level, record.getMessage()
        )


def setup_logger(
    level: str,
    format: str,
    filepath: Optional[Path] = None,
    rotation: Optional[str] = None,
    retention: Optional[str] = None,
) -> Logger:
    """Define the global logger to be used by your entire service.

    Arguments:

        level: the minimum log-level to log.
        format: the logformat to use.
        filepath: the path where to store the logfiles.
        rotation: when to rotate the logfile.
        retention: when to remove logfiles.

    Returns:

        the logger to be used by the service.

    References:

        - [Loguru: Intercepting logging logs #247](https://github.com/Delgan/loguru/issues/247)
        - [Gunicorn: generic logging options #1572](https://github.com/benoitc/gunicorn/issues/1572#issuecomment-638391953)
    """
    # Remove loguru default logger
    logger.remove()
    # Cath all existing loggers
    LOGGERS = [logging.getLogger(name) for name in logging.root.manager.loggerDict]
    # Add stdout logger
    logger.add(
        sys.stdout,
        enqueue=True,
        colorize=True,
        backtrace=True,
        level=level.upper(),
        format=format,
    )
    # Optionally add filepath logger
    if filepath:
        Path(filepath).parent.mkdir(parents=True, exist_ok=True)
        logger.add(
            str(filepath),
            rotation=rotation,
            retention=retention,
            enqueue=True,
            colorize=False,
            backtrace=True,
            level=level.upper(),
            format=format,
        )
    # Overwrite config of standard library root logger
    logging.basicConfig(handlers=[InterceptHandler()], level=0)
    # Overwrite handlers of all existing loggers from standard library logging
    for _logger in LOGGERS:
        _logger.handlers = [InterceptHandler()]
        _logger.propagate = False

    return logger


def setup_logger_from_settings(settings: Optional[LoggingSettings] = None) -> Logger:
    """Define the global logger to be used by your entire service.

    Arguments:

        settings: the logging settings to apply.

    Returns:

        the logger instance.
    """
    # Parse from env when no settings are given
    if not settings:
        settings = LoggingSettings()
    # Return logger even though it's not necessary
    return setup_logger(
        settings.level,
        settings.format,
        settings.filepath,
        settings.rotation,
        settings.retention,
    )

It’s quite long, but with this you simply need to:

  • (Optional) Define logger options using environment variables
  • Call the setup_logger_from_settings when your application starts
  • Use from loguru import logger anywhere in your code and use logger.debug(), logger.info(), logger.warning() regardless of the location of the code. Those logs will be nicely formatted and printed to stdout by default, and optionally to file as well.

All logs produced using logging library will be intercepted and formatted as well.

Here is an example result:

image

Read more comments on GitHub >

github_iconTop Results From Across the Web

Logging, Warnings, and Error Messages · GitBook
logger.setLevel("DEBUG") will print messages from all logger functions · logger.setLevel("ERROR") will only print messages from logger.error · logger.setLevel(" ...
Read more >
Only the message from "logger.error" method is displayed in ...
In Eclipse in your project locate and open the /RulesDevelopment/config/properties/logging/log4j.xml file. Find the text as shown in the ...
Read more >
django logging - only logs warning and error - Stack Overflow
Try to configure at loggers level too: 'loggers': { 'some_module': { 'handlers': ['primary_log_handler', 'debug_log_handler',], ...
Read more >
logging – Report status, error, and informational messages.
The logger, handler, and log message call each specify a level. The log message is only emitted if the handler and logger are...
Read more >
Logging HOWTO — Python 3.11.1 documentation
The default level is WARNING , which means that only events of this level and above will be tracked, unless the logging package...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found