Skip to content

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.

LevelWhen to use
DEBUGDetailed information for developers while debugging
INFONormal events: application started, record saved
WARNINGSomething unexpected, but the application still works
ERRORAn operation failed
CRITICALA 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()