Logging in Python
Logging records what your application is doing: when it started, what failed and why. Unlike print(), logs have a level, a time stamp and can be saved to a file or a database, which is essential for finding problems in production applications.
| Level | When to use |
|---|---|
DEBUG | Detailed information for developers while debugging |
INFO | Normal events: application started, record saved |
WARNING | Something unexpected, but the application still works |
ERROR | An operation failed |
CRITICAL | A serious error: the application may not continue |
A logger only writes messages at its level and above. With level=logging.INFO, DEBUG messages are ignored.
Logging to a File
basicConfig() sets the file name, the minimum level, the file mode and the format of every line. filemode="w" starts a new file on every run; use "a" to keep old logs.
import logging
logging.basicConfig(
filename="myapp.log",
level=logging.INFO,
filemode='w',
format="%(asctime)s - %(levelname)s - %(message)s"
)
#in filemode w should be in small letter
logger=logging.getLogger(__name__)
logger.info("Application Started")
logger.info("Division started from here")
a=10
b=0
try:
c=a/b
print(f"value of c is {c}")
except Exception as e:
logger.error(f"something went wrong {e}")
print("Divided by zero error")
logger.info("division ended here")
logger.warning("This is a warning")
logger.error("Something went wrong")
logger.debug("This is a debug message")
logger.critical("Critical error occurred")
logging.debug("This is a debug message")
logging.critical("Critical error occurred")
The log file then contains lines like:
2026-10-18 10:15:02,114 - INFO - Application Started
2026-10-18 10:15:02,115 - INFO - Division started from here
2026-10-18 10:15:02,115 - ERROR - something went wrong division by zero
2026-10-18 10:15:02,116 - INFO - division ended here
2026-10-18 10:15:02,116 - WARNING - This is a warning
Audit Logging to SQL Server with a Custom Handler
In real applications, logs are often stored in a database so they can be searched and reported. A custom logging.Handler receives every log record in its emit() method; here it inserts the record into an audit_log table in SQL Server using pyodbc.
Install the driver first: pip install pyodbc. Change the server and database names to match your SQL Server.
import logging
import getpass
import pyodbc
# ---------- SQL Server connection settings (change these) ----------
SERVER = r"localhost\SQLEXPRESS" # e.g. "localhost" or "192.168.1.10,1433"
DATABASE = "AuditDB" # database must already exist
USE_WINDOWS_AUTH = True # False = use SQL Server login below
USERNAME = "sa"
PASSWORD = "your_password"
if USE_WINDOWS_AUTH:
CONN_STR = (
"DRIVER={ODBC Driver 17 for SQL Server};"
f"SERVER={SERVER};DATABASE={DATABASE};"
"Trusted_Connection=yes;TrustServerCertificate=yes;"
)
else:
CONN_STR = (
"DRIVER={ODBC Driver 17 for SQL Server};"
f"SERVER={SERVER};DATABASE={DATABASE};"
f"UID={USERNAME};PWD={PASSWORD};TrustServerCertificate=yes;"
)
# Create the audit table if it does not exist
def create_audit_table():
conn = pyodbc.connect(CONN_STR)
cursor = conn.cursor()
cursor.execute("""
IF OBJECT_ID('dbo.audit_log', 'U') IS NULL
CREATE TABLE dbo.audit_log (
id INT IDENTITY(1,1) PRIMARY KEY,
log_time DATETIME2 NOT NULL,
level VARCHAR(20) NOT NULL,
logger_name VARCHAR(100),
module VARCHAR(100),
function_name VARCHAR(100),
line_no INT,
username VARCHAR(100),
message NVARCHAR(MAX)
)
""")
conn.commit()
conn.close()
# Custom handler: every log record is inserted into the audit_log table
class DatabaseHandler(logging.Handler):
def emit(self, record):
try:
conn = pyodbc.connect(CONN_STR)
cursor = conn.cursor()
cursor.execute(
"""INSERT INTO dbo.audit_log
(log_time, level, logger_name, module, function_name, line_no, username, message)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
self.formatter.formatTime(record, "%Y-%m-%d %H:%M:%S"),
record.levelname,
record.name,
record.module,
record.funcName,
record.lineno,
getpass.getuser(),
record.getMessage(),
)
conn.commit()
conn.close()
except Exception:
self.handleError(record)
# Show all rows from the audit table
def show_audit_logs():
conn = pyodbc.connect(CONN_STR)
cursor = conn.cursor()
cursor.execute("SELECT id, log_time, level, username, message FROM dbo.audit_log ORDER BY id")
for row in cursor.fetchall():
print(row)
conn.close()
if __name__ == "__main__":
create_audit_table()
logger = logging.getLogger("audit")
logger.setLevel(logging.INFO)
db_handler = DatabaseHandler()
db_handler.setFormatter(logging.Formatter())
logger.addHandler(db_handler)
logger.info("Application Started")
logger.warning("This is a warning")
logger.error("Something went wrong")
logger.debug("This is a debug message") # not saved, level is below INFO
logger.critical("Critical error occurred")
print("Rows in audit_log table:")
show_audit_logs()
Using the Audit Logger from Another File
Import the module, attach the handler to a logger, and errors are saved to the database automatically.
import db_audit_logger as audit
import logging
a=10
b=0
try:
c=a/b
print(f"c is {c}")
except ZeroDivisionError as err:
audit.create_audit_table()
db_handler = audit.DatabaseHandler()
db_handler.setFormatter(logging.Formatter())
logger = logging.getLogger("MYAppAudit")
logger.setLevel(logging.INFO)
logger.addHandler(db_handler)
logger.error(f"Division failed: {err}")
audit.show_audit_logs()