Configure Logging#

Utilize Bokeh and Panel Environment Variables#

Bokeh and Panel offer several environment variables for logging control:

  • BOKEH_LOG_LEVEL: Sets the log level of the bokeh JavaScript logger, with the default being ‘info’.

  • BOKEH_PY_LOG_LEVEL: Sets the log level of the bokeh Python logger, by default it’s “none”.

  • PANEL_LOG_LEVEL: Sets the log level of the panel Python logger, with the default being “WARNING”.

For more detailed information, refer to bokeh.util.logconfig or panel.io.logging.

Reconfigure the Root Logger#

Bokeh executes logging.basicConfig to configure the root logger. Fortunately we can redo this using force=True.

import logging
import panel as pn

FORMAT = "%(asctime)s | %(levelname)s | %(name)s | %(message)s"

@pn.cache
def reconfig_basic_config(format_=FORMAT, level=logging.INFO):
    """(Re-)configure logging"""
    logging.basicConfig(format=format_, level=level, force=True)
    logging.info("Logging.basicConfig completed successfully")

reconfig_basic_config()
logger = logging.getLogger(name="app")

logger.info("Hello World")
pn.panel("Hello World").servable()

Upon use, it should resemble:

$ panel serve app.py
2024-03-16 08:20:35,993 Starting Bokeh server version 3.4.0 (running on Tornado 6.4)
2024-03-16 08:20:35,995 User authentication hooks NOT provided (default user enabled)
2024-03-16 08:20:36,002 Bokeh app running at: http://localhost:5006/app
2024-03-16 08:20:36,002 Starting Bokeh server with process id: 25540
2024-03-16 08:20:36,930 | INFO | root | Logging.basicConfig completed successfully
2024-03-16 08:20:36,930 | INFO | app | Hello World
2024-03-16 08:20:37,080 | INFO | tornado.access | 200 GET /app (::1) 168.75ms
2024-03-16 08:20:37,184 | INFO | tornado.access | 101 GET /app/ws (::1) 0.00ms
2024-03-16 08:20:37,185 | INFO | bokeh.server.views.ws | WebSocket connection opened
2024-03-16 08:20:37,186 | INFO | bokeh.server.views.ws | ServerConnection created
2024-03-16 08:20:37,189 | INFO | tornado.access | 200 GET /static/extensions/panel/images/favicon.ico (::1) 2.00ms

Note

Please be aware that loggers like tornado, which were not displayed previously, will now be shown because we’ve modified the root log level to INFO.

Note

We utilize caching (pn.cache) because reconfig_basic_config runs each time a new Panel session starts. If you move the logging reconfiguration to a separate module or a --setup script, caching won’t be necessary.

Configure a Single Logger#

If you prefer not to reconfigure all logging, you can configure a single logger as demonstrated below.

import logging
import sys
import panel as pn

FORMAT = "%(asctime)s | %(levelname)s | %(name)s | %(message)s"

@pn.cache
def get_logger(name, format_=FORMAT, level=logging.INFO):
    logger = logging.getLogger(name)

    logger.handlers.clear()

    handler = logging.StreamHandler()
    handler.setStream(sys.stdout)
    formatter = logging.Formatter(format_)
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    logger.propagate = False

    logger.setLevel(level)
    logger.info("Logger successfully configured")
    return logger

logger = get_logger(name="app")

logger.info("Hello World")
pn.panel("Hello World").servable()

It should yield:

$ panel serve app.py
2024-03-16 08:24:02,469 Starting Bokeh server version 3.4.0 (running on Tornado 6.4)
2024-03-16 08:24:02,469 User authentication hooks NOT provided (default user enabled)
2024-03-16 08:24:02,485 Bokeh app running at: http://localhost:5006/app
2024-03-16 08:24:02,485 Starting Bokeh server with process id: 33536
2024-03-16 08:24:05,272 | INFO | app | Logger successfully configured
2024-03-16 08:24:05,272 | INFO | app | Hello World
2024-03-16 08:24:05,565 WebSocket connection opened
2024-03-16 08:24:05,566 ServerConnection created

Configure the Panel and Bokeh Loggers#

If you want to configure the Panel and Bokeh loggers as well, it’s straightforward.

import logging
import sys
import panel as pn

FORMAT = "%(asctime)s | %(levelname)s | %(name)s | %(message)s"

@pn.cache
def get_logger(name, format_=FORMAT, level=logging.INFO):
    logger = logging.getLogger(name)

    logger.handlers.clear()

    handler = logging.StreamHandler()
    handler.setStream(sys.stdout)
    formatter = logging.Formatter(format_)
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    logger.propagate = False

    logger.setLevel(level)
    logger.info("Logger successfully configured")
    return logger

get_logger(name="bokeh")
get_logger(name="panel")
logger = get_logger(name="app")

logger.info("Hello World")
pn.panel("Hello World").servable()

It will appear as follows:

$ panel serve app.py
2024-03-16 08:26:59,183 Starting Bokeh server version 3.4.0 (running on Tornado 6.4)
2024-03-16 08:26:59,183 User authentication hooks NOT provided (default user enabled)
2024-03-16 08:26:59,199 Bokeh app running at: http://localhost:5006/app
2024-03-16 08:26:59,199 Starting Bokeh server with process id: 11200
2024-03-16 08:27:00,434 | INFO | app | logger successfully configured
2024-03-16 08:27:00,435 | INFO | bokeh | logger successfully configured
2024-03-16 08:27:00,437 | INFO | panel | logger successfully configured
2024-03-16 08:27:00,437 | INFO | app | Hello World
2024-03-16 08:27:00,585 | INFO | panel.io.server | Session 2863386286800 created