Skip to content

Instantly share code, notes, and snippets.

@clarksun
Forked from nkhitrov/logger.py
Created September 18, 2020 23:19
Show Gist options
  • Select an option

  • Save clarksun/73024bfcbcff9e47cc79e83a2f687a76 to your computer and use it in GitHub Desktop.

Select an option

Save clarksun/73024bfcbcff9e47cc79e83a2f687a76 to your computer and use it in GitHub Desktop.

Revisions

  1. @nkhitrov nkhitrov revised this gist Jul 25, 2020. 2 changed files with 102 additions and 96 deletions.
    97 changes: 97 additions & 0 deletions logger.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,97 @@
    """Configure handlers and formats for application loggers."""
    import logging
    import sys
    from pprint import pformat

    # if you dont like imports of private modules
    # you can move it to typing.py module
    from loguru import logger
    from loguru._defaults import LOGURU_FORMAT


    class InterceptHandler(logging.Handler):
    """
    Default handler from examples in loguru documentaion.
    See https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging
    """

    def emit(self, record: logging.LogRecord):
    # 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 format_record(record: dict) -> str:
    """
    Custom format for loguru loggers.
    Uses pformat for log any data like request/response body during debug.
    Works with logging if loguru handler it.
    Example:
    >>> payload = [{"users":[{"name": "Nick", "age": 87, "is_active": True}, {"name": "Alex", "age": 27, "is_active": True}], "count": 2}]
    >>> logger.bind(payload=).debug("users payload")
    >>> [ { 'count': 2,
    >>> 'users': [ {'age': 87, 'is_active': True, 'name': 'Nick'},
    >>> {'age': 27, 'is_active': True, 'name': 'Alex'}]}]
    """

    format_string = LOGURU_FORMAT
    if record["extra"].get("payload") is not None:
    record["extra"]["payload"] = pformat(
    record["extra"]["payload"], indent=4, compact=True, width=88
    )
    format_string += "\n<level>{extra[payload]}</level>"

    format_string += "{exception}\n"
    return format_string


    def init_logging():
    """
    Replaces logging handlers with a handler for using the custom handler.
    WARNING!
    if you call the init_logging in startup event function,
    then the first logs before the application start will be in the old format
    >>> app.add_event_handler("startup", init_logging)
    stdout:
    INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
    INFO: Started reloader process [11528] using statreload
    INFO: Started server process [6036]
    INFO: Waiting for application startup.
    2020-07-25 02:19:21.357 | INFO | uvicorn.lifespan.on:startup:34 - Application startup complete.
    """

    # disable handlers for specific uvicorn loggers
    # to redirect their output to the default uvicorn logger
    # works with uvicorn==0.11.6
    loggers = (
    logging.getLogger(name)
    for name in logging.root.manager.loggerDict
    if name.startswith("uvicorn.")
    )
    for uvicorn_logger in loggers:
    uvicorn_logger.handlers = []

    # change handler for default uvicorn logger
    intercept_handler = InterceptHandler()
    logging.getLogger("uvicorn").handlers = [intercept_handler]

    # set logs output, level and format
    logger.configure(
    handlers=[{"sink": sys.stdout, "level": logging.DEBUG, "format": format_record}]
    )
    101 changes: 5 additions & 96 deletions main.py
    Original file line number Diff line number Diff line change
    @@ -1,104 +1,13 @@
    """Gist for issue https://github.com/tiangolo/fastapi/issues/1276#issuecomment-663748916"""
    import logging
    import sys
    from pprint import pformat

    """Gist for original issue https://github.com/tiangolo/fastapi/issues/1276#issuecomment-663748916"""
    from fastapi import FastAPI
    from loguru import logger
    from loguru._defaults import LOGURU_FORMAT
    from starlette.requests import Request
    from logger import init_logging

    # handler definition

    class InterceptHandler(logging.Handler):
    """
    Default handler from examples in loguru documentaion.
    See https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging
    """

    def emit(self, record: logging.LogRecord):
    # 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 format_record(record: dict) -> str:
    """
    Custom format for loguru loggers.
    Uses pformat for log any data like request/response body during debug.
    Works with logging if loguru handler it.
    Example:
    >>> payload = [{"users":[{"name": "Nick", "age": 87, "is_active": True}, {"name": "Alex", "age": 27, "is_active": True}], "count": 2}]
    >>> logger.bind(payload=).debug("users payload")
    >>> [ { 'count': 2,
    >>> 'users': [ {'age': 87, 'is_active': True, 'name': 'Nick'},
    >>> {'age': 27, 'is_active': True, 'name': 'Alex'}]}]
    """

    format_string = LOGURU_FORMAT
    if record["extra"].get("payload") is not None:
    record["extra"]["payload"] = pformat(
    record["extra"]["payload"], indent=4, compact=True, width=88
    )
    format_string += "\n<level>{extra[payload]}</level>"

    format_string += "{exception}\n"
    return format_string


    def init_logging(custom_handler: logging.Handler):
    """Replaces logging handlers with a handler for using the custom handler."""

    # NOTE:
    # to get all loggers
    # >>> loggers = [logging.getLogger(name) for name in logging.root.manager.loggerDict]

    # disable handlers for specific uvicorn loggers
    # to redirect their output to the default uvicorn logger
    for name in ("uvicorn.error", "uvicorn.access"):
    logging.getLogger(name).handlers = []

    # change handler for default uvicorn logger
    logging.getLogger("uvicorn").handlers = [custom_handler]

    # set logs output, level and format
    logger.configure(
    handlers=[{"sink": sys.stdout, "level": logging.DEBUG, "format": format_record}]
    )

    ### main.py

    app = FastAPI(title="Logger Handler", debug=True)

    # WARNING!
    # if you call the init_logging in startup event function,
    # then the first logs before the application start will be in the old format
    #
    # >>> app.add_event_handler("startup", init_logging)
    # stdout:
    # INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
    # INFO: Started reloader process [11528] using statreload
    # INFO: Started server process [6036]
    # INFO: Waiting for application startup.
    # 2020-07-25 02:19:21.357 | INFO | uvicorn.lifespan.on:startup:34 - Application startup complete.
    intercept_handler = InterceptHandler()
    init_logging(intercept_handler)
    app = FastAPI(title="Test Uvicorn Handlers")

    # view.py
    init_logging()

    # view.py
    @app.get("/")
    def index(request: Request) -> None:
    logger.info("loguru info log")
  2. @nkhitrov nkhitrov created this gist Jul 24, 2020.
    109 changes: 109 additions & 0 deletions main.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,109 @@
    """Gist for issue https://github.com/tiangolo/fastapi/issues/1276#issuecomment-663748916"""
    import logging
    import sys
    from pprint import pformat

    from fastapi import FastAPI
    from loguru import logger
    from loguru._defaults import LOGURU_FORMAT
    from starlette.requests import Request

    # handler definition

    class InterceptHandler(logging.Handler):
    """
    Default handler from examples in loguru documentaion.
    See https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging
    """

    def emit(self, record: logging.LogRecord):
    # 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 format_record(record: dict) -> str:
    """
    Custom format for loguru loggers.
    Uses pformat for log any data like request/response body during debug.
    Works with logging if loguru handler it.
    Example:
    >>> payload = [{"users":[{"name": "Nick", "age": 87, "is_active": True}, {"name": "Alex", "age": 27, "is_active": True}], "count": 2}]
    >>> logger.bind(payload=).debug("users payload")
    >>> [ { 'count': 2,
    >>> 'users': [ {'age': 87, 'is_active': True, 'name': 'Nick'},
    >>> {'age': 27, 'is_active': True, 'name': 'Alex'}]}]
    """

    format_string = LOGURU_FORMAT
    if record["extra"].get("payload") is not None:
    record["extra"]["payload"] = pformat(
    record["extra"]["payload"], indent=4, compact=True, width=88
    )
    format_string += "\n<level>{extra[payload]}</level>"

    format_string += "{exception}\n"
    return format_string


    def init_logging(custom_handler: logging.Handler):
    """Replaces logging handlers with a handler for using the custom handler."""

    # NOTE:
    # to get all loggers
    # >>> loggers = [logging.getLogger(name) for name in logging.root.manager.loggerDict]

    # disable handlers for specific uvicorn loggers
    # to redirect their output to the default uvicorn logger
    for name in ("uvicorn.error", "uvicorn.access"):
    logging.getLogger(name).handlers = []

    # change handler for default uvicorn logger
    logging.getLogger("uvicorn").handlers = [custom_handler]

    # set logs output, level and format
    logger.configure(
    handlers=[{"sink": sys.stdout, "level": logging.DEBUG, "format": format_record}]
    )

    ### main.py

    app = FastAPI(title="Logger Handler", debug=True)

    # WARNING!
    # if you call the init_logging in startup event function,
    # then the first logs before the application start will be in the old format
    #
    # >>> app.add_event_handler("startup", init_logging)
    # stdout:
    # INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
    # INFO: Started reloader process [11528] using statreload
    # INFO: Started server process [6036]
    # INFO: Waiting for application startup.
    # 2020-07-25 02:19:21.357 | INFO | uvicorn.lifespan.on:startup:34 - Application startup complete.
    intercept_handler = InterceptHandler()
    init_logging(intercept_handler)

    # view.py

    @app.get("/")
    def index(request: Request) -> None:
    logger.info("loguru info log")
    logging.info("logging info log")

    logging.getLogger("fastapi").debug("fatapi info log")
    logger.bind(payload=dict(request.query_params)).debug("params with formating")
    return None