Logging is an essential tool in software development, especially when working with complex applications. It allows you to track events, monitor the behavior of the application, and troubleshoot issues. Python provides a built-in logging module that is flexible, powerful, and easy to use. Here’s a detailed explanation of Python’s logging module, including different features and best practices for logging in complex projects.
Overview of Python Logging
The logging
module in Python provides a way to log messages from your application. It allows for different log levels, loggers, and handlers to customize how and where the log messages are stored and displayed. The main advantage of using the logging
module over using print()
statements is its flexibility and configurability, including logging to files, sending logs over the network, formatting messages, and controlling log levels.
Key Components of Logging
- Logger: The logger is the main interface that you interact with when logging messages. It handles messages and sends them to the appropriate handler.
- Log Levels: The log levels allow you to categorize log messages based on their severity. The standard log levels in order of severity are:
DEBUG
: Detailed information, typically useful only for diagnosing problems.INFO
: General information about the program’s execution.WARNING
: Indication that something unexpected happened, or that there is a problem.ERROR
: More serious problems that may cause part of the program to fail.CRITICAL
: Very serious errors that may cause the program to stop.
- Handler: Handlers are responsible for sending the log messages to their final destination (e.g., console, file, or network). Common handlers include:
StreamHandler
: Sends log messages to the console.FileHandler
: Writes log messages to a file.RotatingFileHandler
: Writes logs to a file and rotates the logs when a certain size is reached.SMTPHandler
: Sends logs via email.SysLogHandler
: Sends logs to a syslog server.
- Formatter: A formatter defines the structure of the log messages, such as the time, log level, message, and other relevant details.
- Filter: Filters allow you to filter log messages based on specific criteria (e.g., log level or message content).
Basic Logging Setup
The simplest logging setup logs messages to the console with a default log level of WARNING
. Here’s an example of basic logging:
import logging
# Basic configuration for logging
logging.basicConfig(level=logging.DEBUG)
# Logging messages at different levels
logging.debug("This is a debug message")
logging.info("This is an info message")
logging.warning("This is a warning message")
logging.error("This is an error message")
logging.critical("This is a critical message")
In this setup:
- The
basicConfig
function sets the logging configuration. Thelevel=logging.DEBUG
means that messages with levelDEBUG
and above (i.e.,DEBUG
,INFO
,WARNING
,ERROR
,CRITICAL
) will be logged. - The messages will be printed to the console by default.
Customizing Log Output with Formatters
To control the format of the log messages, you can specify a custom formatter. The formatter
defines how the log message will look.
import logging
# Custom format for log messages
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
logging.basicConfig(level=logging.DEBUG, format=log_format)
logging.debug("This is a debug message")
logging.info("This is an info message")
logging.warning("This is a warning message")
In the format:
%(asctime)s
: The time the log message was created.%(name)s
: The name of the logger.%(levelname)s
: The severity level of the log message.%(message)s
: The actual log message.
Output:
2025-05-05 14:30:19,803 - root - DEBUG - This is a debug message
2025-05-05 14:30:19,803 - root - INFO - This is an info message
2025-05-05 14:30:19,803 - root - WARNING - This is a warning message
Using Loggers, Handlers, and Formatters
While logging.basicConfig
is quick and easy, for more complex projects, it’s better to set up your logging with multiple loggers, handlers, and formatters.
import logging
# Create a custom logger
logger = logging.getLogger('my_logger')
# Create a console handler and set its log level
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# Create a file handler and set its log level
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.ERROR)
# Create a custom formatter
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# Add the formatter to the handlers
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# Add the handlers to the logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)
# Logging messages
logger.debug("This is a debug message")
logger.info("This is an info message")
logger.warning("This is a warning message")
logger.error("This is an error message")
logger.critical("This is a critical message")
Explanation:
- Logger: The logger is created with
logging.getLogger('my_logger')
. You can use different loggers for different parts of your application. - Handlers:
- The
StreamHandler
sends log messages to the console. - The
FileHandler
sends log messages to a file, but only logsERROR
and above.
- The
- Formatter: The formatter is used to specify how the log messages are formatted.
- Adding Handlers: Both handlers are added to the logger, so logs will be sent to both the console and the file.
Rotating Logs with RotatingFileHandler
When working with complex projects, logs can grow large over time. To avoid logs taking up too much disk space, you can use RotatingFileHandler
to automatically rotate logs when they reach a certain size.
import logging
from logging.handlers import RotatingFileHandler
# Create a rotating file handler that logs to 'app.log'
rotating_handler = RotatingFileHandler('app.log', maxBytes=2000, backupCount=3)
rotating_handler.setLevel(logging.DEBUG)
# Create a formatter and add it to the handler
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
rotating_handler.setFormatter(formatter)
# Create the logger, set its level, and add the handler
logger = logging.getLogger('my_logger')
logger.setLevel(logging.DEBUG) # Ensure the logger has a level set
logger.addHandler(rotating_handler)
# Logging some messages
for i in range(10):
logger.info(f"Log message {i}")
print("Logging finished!")
In this setup:
- The
RotatingFileHandler
will write log messages toapp.log
. - When the file size exceeds
2000
bytes, it will rotate, keeping a maximum of 3 backup files.
Advanced Logging Configurations with logging.config
For large projects, you may want to configure logging using a configuration file, especially when you have many different handlers, loggers, and formatters. You can use the logging.config
module to load configuration from a dictionary or a file.
Example with Dictionary Configuration:
import logging
import logging.config
# Dictionary-based logging configuration
log_config = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'default',
},
'file': {
'level': 'ERROR',
'class': 'logging.FileHandler',
'filename': 'app.log',
'formatter': 'default',
},
},
'loggers': {
'': {
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': True,
},
},
'formatters': {
'default': {
'format': '%(asctime)s - %(levelname)s - %(message)s',
},
},
}
# Load the configuration
logging.config.dictConfig(log_config)
# Create a logger
logger = logging.getLogger('my_logger')
# Logging messages
logger.debug("This is a debug message")
logger.info("This is an info message")
logger.error("This is an error message")
Summary of Key Concepts:
- Loggers: Use
getLogger()
to create and manage loggers. - Handlers: Direct logs to various destinations like console, files, or remote services.
- Formatters: Define how logs should appear.
- Levels: Control the verbosity of logs with different severity levels (
DEBUG
,INFO
,WARNING
,ERROR
,CRITICAL
). - Rotating Logs: Use
RotatingFileHandler
for managing log file sizes. - Advanced Configuration: Use
logging.config
to handle complex logging setups in large projects.
By understanding and configuring logging in Python, you can handle complex logging needs in a scalable and organized way. With the flexibility of logging
handlers, filters, and formatters, you can set up different logging behaviors depending on the environment (e.g., development, testing, production).
To configure different logging for test, development, and production environments in Python, you can create different logging configurations based on the environment your application is running in. This allows you to adjust the verbosity, handlers, and formatting according to the needs of each environment.
Step-by-Step Approach:
You can use environment variables or configuration files to determine which environment you're in (e.g., test, dev, production), and then adjust your logging configuration accordingly.
1. Use Environment Variables to Detect the Current Environment
You can use the os
module to detect the current environment and set up the logging configuration accordingly.
Example Setup:
import os
import logging
from logging.handlers import RotatingFileHandler
# Set environment variables (usually set outside the application in production, dev, and test environments)
os.environ["APP_ENV"] = "development" # Or "production", "test" based on the environment
# Function to set up logging based on environment
def setup_logging():
env = os.getenv("APP_ENV", "development") # Default to "development" if not set
# Common settings
log_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# Create rotating file handler for all environments
rotating_handler = RotatingFileHandler('app.log', maxBytes=2000, backupCount=3)
rotating_handler.setFormatter(log_formatter)
# Create console handler for log output in development
console_handler = logging.StreamHandler()
console_handler.setFormatter(log_formatter)
# Create a logger
logger = logging.getLogger("my_logger")
logger.setLevel(logging.DEBUG) # Default level
# Set up logging configuration based on environment
if env == "production":
# For production, log only ERROR and CRITICAL messages and send logs to file
logger.setLevel(logging.ERROR)
logger.addHandler(rotating_handler)
elif env == "test":
# For test, log only WARNING and higher, and send logs to console
logger.setLevel(logging.WARNING)
logger.addHandler(console_handler)
elif env == "development":
# For development, log everything (DEBUG level) and output to console
logger.setLevel(logging.DEBUG)
logger.addHandler(console_handler)
logger.addHandler(rotating_handler)
return logger
# Set up logging for the current environment
logger = setup_logging()
# Logging some messages
logger.debug("This is a debug message.")
logger.info("This is an info message.")
logger.warning("This is a warning message.")
logger.error("This is an error message.")
logger.critical("This is a critical message.")
Breakdown of the Solution:
- Environment Variable Setup:
- The
APP_ENV
environment variable is used to determine the current environment (development
,test
,production
). - In a real project, this variable should be set through the system's environment configuration,
.env
files, or deployment configurations.
- The
- Common Log Formatter:
- A common formatter is created for all environments, which will print the log messages in the format
timestamp - log level - message
.
- A common formatter is created for all environments, which will print the log messages in the format
- Rotating File Handler:
- A
RotatingFileHandler
is used to write logs to a file with automatic rotation. This is common in all environments except for testing (for testing, we might just want console logs).
- A
- Console Handler:
- A
StreamHandler
(console handler) is used for outputting logs to the console, primarily useful in development and testing.
- A
- Conditional Logger Configuration:
- Based on the environment (using the
APP_ENV
variable), the logger is configured differently:- Production: Only
ERROR
andCRITICAL
logs are recorded, and logs are stored in files (RotatingFileHandler
). - Test: Only
WARNING
and higher levels are logged, and logs are displayed on the console (useful for quick test feedback). - Development: All levels (including
DEBUG
) are logged to the console and file to capture detailed information during development.
- Production: Only
- Based on the environment (using the
- Logging Messages:
- Example messages are logged at different levels (
DEBUG
,INFO
,WARNING
,ERROR
,CRITICAL
), but the output depends on the environment.
- Example messages are logged at different levels (
2. Alternative: Using Logging Configuration Files
For more complex configurations, especially in large projects, it’s common to use a configuration file (either JSON, YAML, or INI format) for logging setup.
Example of a Logging Configuration File (logging_config.json
):
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"default": {
"format": "%(asctime)s - %(levelname)s - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "default",
"level": "DEBUG"
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "default",
"filename": "app.log",
"maxBytes": 2000,
"backupCount": 3,
"level": "DEBUG"
}
},
"loggers": {
"my_logger": {
"level": "DEBUG",
"handlers": ["console", "file"]
}
}
}
Example of Loading Configuration
import logging
import logging.config
import json
# Load logging configuration from the file
with open('logging_config.json', 'r') as f:
config = json.load(f)
# Apply the configuration
logging.config.dictConfig(config)
# Get the logger
logger = logging.getLogger('my_logger')
# Logging some messages
logger.debug("This is a debug message.")
logger.info("This is an info message.")
logger.warning("This is a warning message.")
logger.error("This is an error message.")
logger.critical("This is a critical message.")
Key Advantages of This Approach:
- Separation of Concerns: The configuration is separate from the code, which helps you manage different logging behaviors for various environments.
- Easy Updates: You can change logging configurations without modifying your code—just update the config file.
- Environment-Specific Configurations: You can use different config files or environment variables to load the appropriate settings for development, testing, and production.
To add email (SMTPHandler
) and syslog (SysLogHandler
) logging handlers to your configuration, you can extend your existing logging setup by adding these handlers to your logging configuration.
1. SMTPHandler: Sending Logs via Email
SMTPHandler
is used to send log messages via email. This handler requires an SMTP server setup, so you need to specify the SMTP server, port, from and to addresses, etc.
The SMTP server (Simple Mail Transfer Protocol) is a server that is responsible for sending emails over the internet. It allows you to send email messages from one email client or application to another.
1. SMTP Server
An SMTP server is a specialized server used for sending and relaying email messages. Popular email providers (like Gmail, Outlook, Yahoo) offer their own SMTP servers for sending emails programmatically or through applications.
2. SMTP Port
The SMTP port is the communication channel through which the email server sends the email message. There are several ports commonly used for SMTP communication:
- Port 25: Traditionally used for sending email over SMTP. However, it is commonly blocked by ISPs (Internet Service Providers) for outgoing email because it’s often abused for spam.
- Port 587: The recommended port for sending email using SMTP with authentication and encryption (STARTTLS). This is typically used for outgoing email from email clients or applications.
- Port 465: This was previously used for sending SMTP with SSL encryption (SMTPS), but it is now considered deprecated. Port 587 is preferred for secure SMTP communication.
Common SMTP Servers and Ports
Here are some common SMTP server settings from popular email providers:
1. Gmail SMTP Settings
- SMTP Server:
smtp.gmail.com
- Port:
587
(recommended for TLS) - SSL: No (Use STARTTLS instead)
- Authentication Required: Yes
- Username: Your Gmail email address (e.g.,
[email protected]
) - Password: Your Gmail password (or an app-specific password if 2FA is enabled)
2. Outlook (Microsoft) SMTP Settings
- SMTP Server:
smtp.office365.com
- Port:
587
(recommended for TLS) - SSL: No (Use STARTTLS instead)
- Authentication Required: Yes
- Username: Your Outlook email address (e.g.,
[email protected]
) - Password: Your Outlook password
3. Yahoo SMTP Settings
- SMTP Server:
smtp.mail.yahoo.com
- Port:
587
(recommended for TLS) - SSL: No (Use STARTTLS instead)
- Authentication Required: Yes
- Username: Your Yahoo email address (e.g.,
[email protected]
) - Password: Your Yahoo password (or app-specific password if 2FA is enabled)
4. SendGrid SMTP Settings (for transactional emails)
- SMTP Server:
smtp.sendgrid.net
- Port:
587
(recommended for TLS) - SSL: No (Use STARTTLS instead)
- Authentication Required: Yes
- Username:
apikey
(the literal string "apikey") - Password: Your SendGrid API key
from dotenv import load_dotenv
import logging
from logging.handlers import SMTPHandler
load_dotenv()
import os
MAIL_HOST=os.getenv("MAIL_HOST")
SENDER_EMAIL=os.getenv("SENDER_EMAIL")
SENDER_PASS=os.getenv("SENDER_PASS")
MAIL_PORT=os.getenv("MAIL_PORT")
RECIPIENT_EMAIL=os.getenv("RECIPIENT_EMAIL")
# Example of configuring the SMTP handler for sending error logs via email
mailhost = (f'{MAIL_HOST}', 587) # Gmail's SMTP server and port for STARTTLS
fromaddr = f'{SENDER_EMAIL}' # Your Gmail address
toaddrs = [RECIPIENT_EMAIL] # Recipient's email address
subject = 'Log Message - ERROR'
# Set up the SMTPHandler for sending emails with STARTTLS (port 587)
smtp_handler = SMTPHandler(
mailhost=mailhost,
fromaddr=fromaddr,
toaddrs=toaddrs,
subject=subject,
credentials=(f'{SENDER_EMAIL}', f'{SENDER_PASS}'), # Use app-specific password
secure=() # Ensures the connection is encrypted using STARTTLS
)
# Set the logging level and add the handler
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
logger.addHandler(smtp_handler)
# Example log message
logger.info("This is an info message.")
logger.error("This is an error message, and it will be sent via email.")
.env file
MAIL_HOST='smtp.gmail.com'
SENDER_EMAIL=''
SENDER_PASS=''
MAIL_PORT=587
RECIPIENT_EMAIL=''
Using Logging Configuration Files(alternative)
import logging
import logging.config
from dotenv import load_dotenv
import os
load_dotenv()
MAIL_HOST = os.getenv("MAIL_HOST")
SENDER_EMAIL = os.getenv("SENDER_EMAIL")
SENDER_PASS = os.getenv("SENDER_PASS") # Make sure to use an app-specific password if using Gmail with 2FA
MAIL_PORT = os.getenv("MAIL_PORT")
RECIPIENT_EMAIL = os.getenv("RECIPIENT_EMAIL")
# Dictionary-based logging configuration with console, file, email, and syslog output
log_config = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
# Console handler for logging to the terminal
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler', # Use StreamHandler for console logging
'formatter': 'default',
},
# File handler for writing logs to a file
'file': {
'level': 'ERROR',
'class': 'logging.FileHandler',
'filename': 'app.log',
'formatter': 'default',
},
# SMTPHandler sends logs via email
'smtp': {
'level': 'ERROR',
'class': 'logging.handlers.SMTPHandler',
'mailhost': (f'{MAIL_HOST}', 587), # Use appropriate mail server and port
'fromaddr': f'{SENDER_EMAIL}', # Sender email
'toaddrs': [RECIPIENT_EMAIL], # List of recipient emails
'subject': 'Log Message - ERROR', # Email subject
'credentials': (f'{SENDER_EMAIL}', f'{SENDER_PASS}'), # SMTP credentials (use app-specific password if needed)
'secure': () # Ensures the connection is encrypted using STARTTLS
},
# SysLogHandler sends logs to syslog server
'syslog': {
'level': 'ERROR',
'class': 'logging.handlers.SysLogHandler',
'address': '/dev/log', # For local syslog on Unix/Linux (use IP/hostname for remote syslog server)
'formatter': 'default',
},
},
'loggers': {
'': {
'handlers': ['console', 'file', 'smtp', 'syslog'],
'level': 'DEBUG', # Set to DEBUG to capture all logs
'propagate': True,
},
},
'formatters': {
'default': {
'format': '%(asctime)s - %(levelname)s - %(message)s',
},
},
}
# Load the logging configuration
logging.config.dictConfig(log_config)
# Create the logger
logger = logging.getLogger('my_logger')
# Logging some messages at different levels
logger.debug("This is a debug message")
logger.info("This is an info message")
logger.warning("This is a warning message")
logger.error("This is an error message")
logger.critical("This is a critical message")
2. SysLogHandler: Sending Logs to a Syslog Server
SysLogHandler
is used to send log messages to a remote syslog server. This can be useful for central logging in a system environment that uses syslog for log management.
Setting the Address for Syslog
- For Local Syslog Server (Unix/Linux):
- The default syslog address for most Unix-like systems (such as Linux or macOS) is
/dev/log
. - This is the typical address when your syslog server is running locally.
- The default syslog address for most Unix-like systems (such as Linux or macOS) is
- For Remote Syslog Server:
- You can also send logs to a remote syslog server by specifying the IP address or hostname of the syslog server, along with the port (usually
514
for both UDP and TCP). - The address format for a remote syslog server would be
(hostname, port)
.
- You can also send logs to a remote syslog server by specifying the IP address or hostname of the syslog server, along with the port (usually
Example of Using Local Syslog (/dev/log
):
For most local syslog servers on Unix/Linux, you can use the default address /dev/log
'syslog': {
'level': 'ERROR',
'class': 'logging.handlers.SysLogHandler',
'address': '/dev/log', # Local syslog on Unix/Linux
'formatter': 'default',
}
Example of Using Remote Syslog Server (UDP or TCP):
If you want to send logs to a remote syslog server, you can use the following formats:
Using UDP (Port 514):
You would replace /dev/log
with a tuple containing the remote syslog server's IP address or hostname and the UDP port number (514
):
'syslog': {
'level': 'ERROR',
'class': 'logging.handlers.SysLogHandler',
'address': ('your-syslog-server.com', 514), # Remote syslog server (UDP)
'formatter': 'default',
}
To send logs to Azure Blob Storage, you can use Python's logging module in combination with the azure-storage-blob
SDK to create a custom logging handler that writes logs directly to an Azure Blob container.
Here’s how you can set up logging to send logs to Azure Blob Storage:
Steps to Configure Azure Blob Storage Logging:
1.Install the Azure SDK for Python:
You need the azure-storage-blob
package to interact with Azure Blob Storage. Install it via pip:
pip install azure-storage-blob
2.Create a Custom Logging Handler:
Since Python’s built-in logging module doesn’t have a built-in handler for Azure Blob Storage, we’ll create a custom handler that sends logs to Blob Storage.
Create an Azure Blob Storage Account:
- Create a storage account on Azure if you don’t have one.
- Create a Blob container in the Azure portal (e.g.,
logs-container
). - Obtain your Azure Storage connection string from the Azure portal.
import logging
from azure.storage.blob import BlobServiceClient
import os
from logging import Handler
from datetime import datetime
class AzureBlobHandler(Handler):
def __init__(self, connection_string, container_name, blob_name):
super().__init__()
self.connection_string = connection_string
self.container_name = container_name
self.blob_name = blob_name
self.blob_service_client = BlobServiceClient.from_connection_string(connection_string)
self.container_client = self.blob_service_client.get_container_client(container_name)
self.blob_client = self.container_client.get_blob_client(blob_name)
# Ensure the container exists
try:
self.container_client.create_container()
except Exception as e:
# Container already exists
pass
def emit(self, record):
log_entry = self.format(record)
try:
# Append log to the blob by appending to its existing content
current_blob_content = self.download_blob()
updated_blob_content = current_blob_content + "\n" + log_entry
self.upload_blob(updated_blob_content)
except Exception as e:
print(f"Error writing log to Azure Blob: {e}")
def download_blob(self):
try:
# Download existing log content if it exists
blob_data = self.blob_client.download_blob()
return blob_data.readall().decode("utf-8")
except Exception:
return ""
def upload_blob(self, content):
# Upload the new log content to the blob
self.blob_client.upload_blob(content, overwrite=True)
3.Set Up the Logging Configuration:
Define the custom handler and use it in your logging configuration.
import logging
import logging.config
from dotenv import load_dotenv
import os
load_dotenv()
# Retrieve Azure Blob Storage settings from environment variables or .env file
AZURE_CONNECTION_STRING = os.getenv("AZURE_CONNECTION_STRING")
CONTAINER_NAME = os.getenv("CONTAINER_NAME")
BLOB_NAME = os.getenv("BLOB_NAME")
# Dictionary-based logging configuration with Azure Blob Storage handler
log_config = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
# Azure Blob handler for logging to Azure Blob Storage
'azure_blob': {
'level': 'DEBUG',
'class': '__main__.AzureBlobHandler', # Use the custom handler class
'connection_string': AZURE_CONNECTION_STRING,
'container_name': CONTAINER_NAME,
'blob_name': BLOB_NAME,
'formatter': 'default',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'default',
},
'file': {
'level': 'ERROR',
'class': 'logging.FileHandler',
'filename': 'app.log',
'formatter': 'default',
},
},
'loggers': {
'': {
'handlers': ['azure_blob', 'console', 'file'],
'level': 'DEBUG',
'propagate': True,
},
},
'formatters': {
'default': {
'format': '%(asctime)s - %(levelname)s - %(message)s',
},
},
}
# Load the logging configuration
logging.config.dictConfig(log_config)
# Create the logger
logger = logging.getLogger('my_logger')
# Logging some messages at different levels
logger.debug("This is a debug message")
logger.info("This is an info message")
logger.warning("This is a warning message")
logger.error("This is an error message")
logger.critical("This is a critical message")
Explanation of the Code:
- Custom Azure Blob Storage Handler:
AzureBlobHandler
: This class extendslogging.Handler
and overrides theemit()
method to send logs to Azure Blob Storage.- The
__init__
method acceptsconnection_string
,container_name
, andblob_name
to set up the Azure Blob client. - The
emit()
method formats the log message and appends it to the blob. download_blob()
andupload_blob()
are used to read and write content to the blob.
- Environment Variables:
- The connection string, container name, and blob name are fetched from environment variables (
.env
file or system environment).
- The connection string, container name, and blob name are fetched from environment variables (
- Logging Configuration:
log_config
defines the logging handlers. It usesAzureBlobHandler
to write logs to Azure Blob Storage.- The logger sends logs to the console, file, and Azure Blob Storage based on the handler setup.
- Azure Blob Storage:
- Logs are saved to the specified blob in Azure Blob Storage.