code: def configure_structlog(
      log_level: Optional[int] = None,
  ) -> None:
      """Configure logging of the server."""
      if (
          log_level is None
      ):  # Log level NOTSET is 0 so we use `is None` here
          log_level_name = os.environ.get(
              ENV_LOG_LEVEL, DEFAULT_LOG_LEVEL
          )
          # Change log level from str to int (note that log_level in function parameter
          # int already, coming from CLI argparse parameter).
          log_level = logging.getLevelName(log_level_name)
  
      logging.basicConfig(
          format="%(message)s",
          stream=sys.stdout,
          level=log_level,
      )
  
      shared_processors = [
          _anonymizer,
          # Processors that have nothing to do with output,
          # e.g., add timestamps or log level names.
          # If log level is too low, abort pipeline and throw away log entry.
          structlog.stdlib.filter_by_level,
          structlog.contextvars.merge_contextvars,
          # Add the name of the logger to event dict.
          # structlog.stdlib.add_logger_name,
          # Add log level to event dict.
          structlog.processors.add_log_level,
          # If the "stack_info" key in the event dict is true, remove it and
          # render the current stack trace in the "stack" key.
          structlog.processors.StackInfoRenderer(),
          # If some value is in bytes, decode it to a unicode str.
          structlog.processors.UnicodeDecoder(),
          structlog.dev.set_exc_info,
          # add structlog sentry integration. only log fatal log entries
          # as events as we are tracking exceptions anyways
          SentryProcessor(event_level=logging.FATAL),
      ]
  
      if not FORCE_JSON_LOGGING and sys.stderr.isatty():
          # Pretty printing when we run in a terminal session.
          # Automatically prints pretty tracebacks when "rich" is installed
          processors = shared_processors + [
              HumanConsoleRenderer(),
          ]
      else:
          # Print JSON when we run, e.g., in a Docker container.
          # Also print structured tracebacks.
          processors = shared_processors + [
              structlog.processors.dict_tracebacks,
              structlog.processors.JSONRenderer(),
          ]
  
      structlog.configure(
          processors=processors,  # type: ignore
          context_class=dict,
          # `logger_factory` is used to create wrapped loggers that are used for
          # OUTPUT. This one returns a `logging.Logger`. The final value (a JSON
          # string) from the final processor (`JSONRenderer`) will be passed to
          # the method of the same name as that you've called on the bound logger.
          logger_factory=structlog.stdlib.LoggerFactory(),
          # `wrapper_class` is the bound logger that you get back from
          # get_logger(). This one imitates the API of `logging.Logger`.
          wrapper_class=structlog.make_filtering_bound_logger(
              log_level
          ),
          # Effectively freeze configuration after creating the first bound
          # logger.
          cache_logger_on_first_use=True,
      )
  
file_path: rasa/rasa/utils/log_utils.py
imports: from __future__ import annotations
  import os
  import logging
  import sys
  from typing import Any, Dict, Optional
  import structlog
  from structlog_sentry import SentryProcessor
  from structlog.dev import ConsoleRenderer
  from structlog.typing import EventDict, WrappedLogger
  from rasa.shared.constants import ENV_LOG_LEVEL, DEFAULT_LOG_LEVEL
  from rasa.plugin import plugin_manager
label_desc: Use image node_id 2 for calling a global function with example usage: configure_structlog(log_level) without return types