Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 53
Logging
The mssql-python module includes a comprehensive logging system designed for diagnostics and troubleshooting. This logging system provides detailed visibility into driver operations, enabling developers to efficiently debug connection issues, query execution, and internal driver behavior.
importmssql_python# Enable logging - shows EVERYTHING (one line)mssql_python.setup_logging()
# Use the driver - all operations are now loggedconn=mssql_python.connect("Server=localhost;Database=test")
# Check the log file: ./mssql_python_logs/mssql_python_trace_*.logimportmssql_python# Enable logging (default: file only)mssql_python.setup_logging()
# Output to stdout instead of filemssql_python.setup_logging(output='stdout')
# Output to both file and stdoutmssql_python.setup_logging(output='both')
# Custom log file pathmssql_python.setup_logging(log_file_path="/var/log/myapp.log")Simple and Purposeful:
- One Level: All logs are DEBUG level - no categorization needed
- All or Nothing: When you enable logging, you see EVERYTHING (SQL, parameters, internal operations)
- Troubleshooting Focus: Turn on logging when something is broken, turn it off otherwise
⚠️ Performance Warning: Logging has overhead - DO NOT enable in production without reason
Why No Multiple Levels?
- If you need logging, you need to see what's broken - partial information doesn't help
- Simplifies the API and mental model
- Future enhancement: Universal profiler for performance analysis (separate from logging)
When to Enable Logging:
- ✅ Debugging connection issues
- ✅ Troubleshooting query execution problems
- ✅ Investigating unexpected behavior
- ✅ Reproducing customer issues
- ❌ Evaluating query performance (use profiler instead - coming soon)
- ❌ Production monitoring (use proper monitoring tools)
- ❌ "Just in case" logging (adds unnecessary overhead)
importmssql_python# Enable logging (logs to file by default)mssql_python.setup_logging()
# Use the library - logs will appear in fileconn=mssql_python.connect(server='localhost', database='testdb')
cursor=conn.cursor()
cursor.execute("SELECT * FROM users")
# Access logger for file path (advanced)frommssql_python.loggingimportloggerprint(f"Logs written to: {logger.log_file}")importmssql_python# Enable logging to stdoutmssql_python.setup_logging(output='stdout')
# Now use the library - logs will appear in consoleconn=mssql_python.connect(server='localhost', database='testdb')
cursor=conn.cursor()
cursor.execute("SELECT * FROM users")importmssql_python# Enable logging to both file and stdoutmssql_python.setup_logging(output='both')
# Logs appear in both console and fileconn=mssql_python.connect(server='localhost', database='testdb')importmssql_python# Specify custom log file pathmssql_python.setup_logging(log_file_path="/var/log/myapp/mssql.log")
# Or with both file and stdoutmssql_python.setup_logging(output='both', log_file_path="/tmp/debug.log")
conn=mssql_python.connect(server='localhost', database='testdb')
# Check log file locationfrommssql_python.loggingimportloggerprint(f"Logging to: {logger.log_file}")
# Output: Logging to: /var/log/myapp/mssql.logimportmssql_python# File logging is enabled by defaultmssql_python.setup_logging()
# Files are automatically rotated at 512MB, keeps 5 backups# File location: ./mssql_python_logs/mssql_python_trace_YYYYMMDDHHMMSS_PID.log# (mssql_python_logs folder is created automatically if it doesn't exist)conn=mssql_python.connect(server='localhost', database='testdb')
frommssql_python.loggingimportloggerprint(f"Logging to: {logger.log_file}")importmssql_python# Log to stdout only (useful for CI/CD, Docker containers)mssql_python.setup_logging(output='stdout')
conn=mssql_python.connect(server='localhost', database='testdb')
# Logs appear in console, no file createdimportmssql_python# Log to both destinations (useful for development)mssql_python.setup_logging(output='both')
conn=mssql_python.connect(server='localhost', database='testdb')
# Logs appear in both console and fileWhen logging is enabled, you see EVERYTHING - SQL statements, parameters, internal operations.
File Header:
# MSSQL-Python Driver Log | Script: main.py | PID: 12345 | Log Level: DEBUG | Python: 3.13.7 | Start: 2025-11-06 10:30:15
Timestamp, ThreadID, Level, Location, Source, Message
Sample Entries:
2025-11-06 10:30:15.100, 8581947520, DEBUG, connection.py:156, Python, Allocating environment handle
2025-11-06 10:30:15.101, 8581947520, DEBUG, connection.cpp:22, DDBC, Allocating ODBC environment handle
2025-11-06 10:30:15.123, 8581947520, DEBUG, connection.py:42, Python, Connecting to server: localhost
2025-11-06 10:30:15.456, 8581947520, DEBUG, cursor.py:28, Python, Executing query: SELECT * FROM users WHERE id = ?
2025-11-06 10:30:15.457, 8581947520, DEBUG, cursor.py:89, Python, Query parameters: [42]
2025-11-06 10:30:15.789, 8581947520, DEBUG, cursor.py:145, Python, Fetched 1 row
2025-11-06 10:30:15.790, 8581947520, DEBUG, cursor.py:201, Python, Row buffer allocated
Log Format:
- Timestamp: Date and time with milliseconds
- ThreadID: OS native thread ID (matches debugger thread IDs)
- Level: DEBUG, INFO, WARNING, ERROR
- Location: filename:line_number
- Source: Python or DDBC (C++ layer)
- Message: The actual log message
What You'll See:
- ✅ Connection establishment and configuration
- ✅ SQL query text
- ✅ Query parameters (with PII sanitization)
- ✅ Result set information
- ✅ Internal ODBC operations
- ✅ Memory allocations and handle management
- ✅ Transaction state changes
- ✅ Everything the driver does
Sensitive data like passwords and access tokens are automatically sanitized in logs:
conn=mssql_python.connect(
server='localhost',
database='testdb',
username='admin',
password='MySecretPass123!'
)
# Log output shows:# Connection string: Server=localhost;Database=testdb;UID=admin;PWD=***REDACTED***Keywords automatically sanitized:
password,pwd,passwdaccess_token,accesstokensecret,api_key,apikeytoken,auth,authentication
Each log entry includes the OS native thread ID for tracking operations in multi-threaded applications:
Thread ID Benefits:
- Debugger Compatible: Thread IDs match those shown in debuggers (Visual Studio, gdb, lldb)
- OS Native: Same thread ID visible in system monitoring tools
- Multi-threaded Tracking: Easily identify which thread performed which operations
- Performance Analysis: Correlate logs with profiler/debugger thread views
Example:
importmssql_pythonimportthreading# Enable loggingmssql_python.setup_logging()
conn=mssql_python.connect("Server=localhost;Database=test")
cursor=conn.cursor()
cursor.execute("SELECT * FROM users")
# Log output shows (CSV format):# 2025-11-06 10:30:15.100, 8581947520, DEBUG, connection.py:42, Python, Connection established# 2025-11-06 10:30:15.102, 8581947520, DEBUG, cursor.py:15, Python, Cursor created# 2025-11-06 10:30:15.103, 8581947520, DEBUG, cursor.py:28, Python, Executing query: SELECT * FROM users# Different thread/connection (note different ThreadID):# 2025-11-06 10:30:15.200, 8582001664, DEBUG, connection.py:42, Python, Connection establishedWhy Thread IDs Matter:
- Multi-threading: Distinguish logs from different threads writing to the same file
- Connection pools: Track which thread is handling which connection
- Debugging: Filter logs by thread ID using text tools (grep, awk, etc.)
- Performance analysis: Measure duration of specific operations per thread
- Debugger Correlation: Thread ID matches debugger views for easy debugging
You can access the same logger used by mssql-python in your application code:
importmssql_pythonfrommssql_python.loggingimportdriver_logger# Enable logging firstmssql_python.setup_logging()
# Now use driver_logger in your applicationdriver_logger.debug("[App] Starting data processing")
driver_logger.info("[App] Processing complete")
driver_logger.warning("[App] Resource usage high")
driver_logger.error("[App] Failed to process record")
# Your logs will appear in the same file as driver logs,# with the same format and thread trackingBenefits:
- Unified logging - all logs in one place
- Same format and structure as driver logs
- Automatic thread ID tracking
- No need to configure separate loggers
Log files use comma-separated format and can be imported into spreadsheet tools:
importpandasaspd# Import log file (skip header lines starting with #)df=pd.read_csv('mssql_python_logs/mssql_python_trace_20251106103015_12345.log', comment='#')
# Filter by thread, analyze queries, etc.thread_logs=df[df['ThreadID'] ==8581947520]importmssql_pythonfrommssql_python.loggingimportloggerimportloggingaspy_logging# Add custom handler to process logs programmaticallyclassMyLogHandler(py_logging.Handler):
defemit(self, record):
# Process log recordprint(f"Custom handler: {record.getMessage()}")
# Access thread IDthread_id=getattr(record, 'thread_id', None)
ifthread_id:
print(f" Thread ID: {thread_id}")
handler=MyLogHandler()
logger.addHandler(handler)
# Now enable loggingmssql_python.setup_logging()mssql_python.setup_logging(output: str = 'file', log_file_path: str = None) -> None
Enable comprehensive DEBUG logging for troubleshooting.
Parameters:
output(str, optional): Where to send logs. Options:'file'(default),'stdout','both'log_file_path(str, optional): Custom log file path. Must have extension:.txt,.log, or.csv. If not specified, auto-generates path in./mssql_python_logs/
Raises:
ValueError: Iflog_file_pathhas an invalid extension (only.txt,.log,.csvare allowed)
Examples:
importmssql_python# Basic usage - file logging (default, auto-generated path)mssql_python.setup_logging()
# Output to stdout onlymssql_python.setup_logging(output='stdout')
# Output to both file and stdoutmssql_python.setup_logging(output='both')
# Custom log file path (must use .txt, .log, or .csv extension)mssql_python.setup_logging(log_file_path="/var/log/myapp.log")
mssql_python.setup_logging(log_file_path="/tmp/debug.txt")
mssql_python.setup_logging(log_file_path="/tmp/data.csv")
# Custom path with both outputsmssql_python.setup_logging(output='both', log_file_path="/tmp/debug.log")
# Invalid extensions will raise ValueErrortry:
mssql_python.setup_logging(log_file_path="/tmp/debug.json") # ✗ ErrorexceptValueErrorase:
print(e) # "Invalid log file extension '.json'. Allowed extensions: .csv, .log, .txt"Access the same logger used by mssql-python in your application:
frommssql_python.loggingimportdriver_loggerimportmssql_python# Enable loggingmssql_python.setup_logging()
# Use driver_logger in your applicationdriver_logger.debug("[App] Starting data processing")
driver_logger.info("[App] Processing complete")
driver_logger.warning("[App] Resource usage high")
driver_logger.error("[App] Failed to process record")
# Your logs appear in the same file with same formatFor advanced use cases, you can access the logger instance directly:
frommssql_python.loggingimportlogger# Get log file pathprint(f"Logging to: {logger.log_file}")
# Add custom handlers (for integration)importloggingaspy_loggingcustom_handler=py_logging.StreamHandler()
logger.addHandler(custom_handler)
# Direct logging calls (if needed)logger.debug("Custom debug message")If you want to use the driver's logger for your own application logging:
importmssql_pythonfrommssql_python.loggingimportlogger# Enable driver loggingmssql_python.setup_logging(output='stdout')
# Use the logger in your applicationclassMyApp:
def__init__(self):
logger.debug("Application starting")
self.db=self._connect_db()
logger.debug("Application ready")
def_connect_db(self):
logger.debug("Connecting to database")
conn=mssql_python.connect("Server=localhost;Database=test")
logger.debug("Database connected successfully")
returnconndefprocess_data(self):
logger.debug("Processing data")
cursor=self.db.cursor()
cursor.execute("SELECT COUNT(*) FROM users")
count=cursor.fetchone()[0]
logger.debug(f"Processed {count} users")
returncountif__name__=='__main__':
app=MyApp()
result=app.process_data()Output shows unified logging:
2025-11-03 10:15:22 - mssql_python - DEBUG - Application starting
2025-11-03 10:15:22 - mssql_python - DEBUG - Connecting to database
2025-11-03 10:15:22 - mssql_python - DEBUG - [Python] Initializing connection
2025-11-03 10:15:22 - mssql_python - DEBUG - Database connected successfully
2025-11-03 10:15:22 - mssql_python - DEBUG - Application ready
2025-11-03 10:15:22 - mssql_python - DEBUG - Processing data
2025-11-03 10:15:22 - mssql_python - DEBUG - [Python] Executing query
2025-11-03 10:15:22 - mssql_python - DEBUG - Processed 1000 users
If you already have application logging configured and want to integrate driver logs:
importloggingimportmssql_pythonfrommssql_python.loggingimportloggerasmssql_logger# Your existing application logger setupapp_logger=logging.getLogger('myapp')
app_logger.setLevel(logging.INFO)
# Your existing handler and formatterhandler=logging.StreamHandler()
formatter=logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
app_logger.addHandler(handler)
# Now plug the driver logger into your handlermssql_logger.addHandler(handler) # Use your handlermssql_python.setup_logging() # Enable driver diagnostics# Use your app logger as normalapp_logger.info("Application starting")
# Driver logs go to the same destinationconn=mssql_python.connect("Server=localhost;Database=test")
app_logger.info("Querying database")
cursor=conn.cursor()
cursor.execute("SELECT * FROM users")
app_logger.info("Application complete")Output shows both app and driver logs in your format:
2025-11-03 10:15:22 - myapp - INFO - Application starting
2025-11-03 10:15:22 - mssql_python - DEBUG - [Python] Initializing connection
2025-11-03 10:15:22 - mssql_python - DEBUG - [Python] Connection established
2025-11-03 10:15:22 - myapp - INFO - Querying database
2025-11-03 10:15:22 - mssql_python - DEBUG - [Python] Executing query
2025-11-03 10:15:22 - myapp - INFO - Application complete
Key Benefits:
- All logs go to your existing handlers (file, console, cloud, etc.)
- Use your existing formatters and filters
- Centralized log management
- No separate log files to manage
For advanced scenarios where you want to process driver logs programmatically:
importloggingimportmssql_pythonfrommssql_python.loggingimportloggerasmssql_loggerclassDatabaseAuditHandler(logging.Handler):
"""Custom handler that audits database operations."""def__init__(self):
super().__init__()
self.queries= []
self.connections= []
defemit(self, record):
msg=record.getMessage()
# Track queriesif'Executing query'inmsg:
self.queries.append({
'time': record.created,
'query': msg
})
# Track connectionsif'Connection established'inmsg:
self.connections.append({
'time': record.created,
'level': record.levelname
})
# Setup audit handleraudit_handler=DatabaseAuditHandler()
mssql_logger.addHandler(audit_handler)
mssql_python.setup_logging()
# Use the driverconn=mssql_python.connect("Server=localhost;Database=test")
cursor=conn.cursor()
cursor.execute("SELECT * FROM users")
cursor.execute("SELECT * FROM orders")
conn.close()
# Access audit dataprint(f"Total queries executed: {len(audit_handler.queries)}")
print(f"Total connections: {len(audit_handler.connections)}")
forqueryinaudit_handler.queries:
print(f" - {query['query']}")importmssql_python# Both console and file - see everythingmssql_python.setup_logging(output='both')
# Use the driver - see everything in console and fileconn=mssql_python.connect("Server=localhost;Database=test")importmssql_python# ⚠️ DO NOT enable logging in production without reason# Logging adds overhead and should only be used for troubleshooting# If needed for specific troubleshooting:# mssql_python.setup_logging() # Temporary only!importmssql_python# Stdout only (captured by CI system, no files)mssql_python.setup_logging(output='stdout')
# CI will capture all driver logsconn=mssql_python.connect(connection_string)importmssql_python# For ANY debugging - just enable logging (shows everything)mssql_python.setup_logging(output='both') # See in console + save to file# Save debug logs to specific location for analysismssql_python.setup_logging(log_file_path="/tmp/mssql_debug.log")
# For CI/CD troubleshootingmssql_python.setup_logging(output='stdout')importloggingaspy_loggingimportmssql_pythonfrommssql_python.loggingimportloggerasmssql_logger# Setup your application loggerapp_logger=py_logging.getLogger('myapp')
app_logger.setLevel(py_logging.INFO)
# Setup handlerhandler=py_logging.StreamHandler()
handler.setFormatter(py_logging.Formatter('%(name)s - %(message)s'))
app_logger.addHandler(handler)
# Plug driver logger into your handlermssql_logger.addHandler(handler)
mssql_python.setup_logging()
# Both logs go to same destinationapp_logger.info("App started")
conn=mssql_python.connect("Server=localhost;Database=test")
app_logger.info("Database connected")importmssql_pythonfrommssql_python.loggingimportlogger# Make sure you called setup_loggingmssql_python.setup_logging(output='stdout') # Force stdout output# Check logger levelprint(f"Logger level: {logger.level}")importmssql_pythonfrommssql_python.loggingimportlogger# Enable logging firstmssql_python.setup_logging()
# Then check locationprint(f"Log file: {logger.log_file}")
# Output: ./mssql_python_logs/mssql_python_trace_20251103_101522_12345.log# Use stdout for CI/CD systemsimportmssql_pythonmssql_python.setup_logging(output='stdout')
# Now logs go to stdout and CI can capture them⚠️ Performance Warning: Logging has overhead - only enable when troubleshooting# ❌ DON'T enable logging by default# ✅ DO enable only when investigating issues
Enable Early: Configure logging before creating connections
mssql_python.setup_logging() # Do this firstconn=mssql_python.connect(...) # Then connect
Choose Right Output Destination:
- Development/Troubleshooting:
output='both'(see logs immediately + keep file) - CI/CD:
output='stdout'(no file clutter, captured by CI) - Customer debugging:
output='file'with custom path (default)
- Development/Troubleshooting:
Log Files Auto-Rotate: Files automatically rotate at 512MB, keeps 5 backups
Sanitization is Automatic: Passwords are automatically redacted in logs
One-Line Setup: Simple API:
mssql_python.setup_logging() # That's it!
Not for Performance Analysis: Use profiler (future enhancement) for query performance, not logging
#!/usr/bin/env python3"""Example application with optional logging."""importsysimportmssql_pythonfrommssql_python.loggingimportloggerdefmain(debug: bool=False):
"""Run the application with optional debug logging."""# Setup logging only if debuggingifdebug:
# Development: both file and consolemssql_python.setup_logging(output='both')
print(f"Logging to: {logger.log_file}")
# Connect to databaseconn=mssql_python.connect(
server='localhost',
database='testdb',
trusted_connection='yes'
)
# Execute querycursor=conn.cursor()
cursor.execute("SELECT TOP 10 * FROM users WHERE active = ?", (1,))
# Process resultsforrowincursor:
print(f"User: {row.username}")
# Cleanupcursor.close()
conn.close()
if__name__=='__main__':
importsysdebug='--debug'insys.argvmain(debug=debug)⚠️ Logging Has Overhead: When enabled, logging adds performance overhead# Logging disabled by default - no overheadconn=mssql_python.connect(...) # Full performance# Enable only when troubleshootingmssql_python.setup_logging() # Now has overhead
Not for Performance Analysis: Do NOT use logging to measure query performance
- Logging itself adds latency
- Use profiler (future enhancement) for accurate performance metrics
Lazy Initialization: Handlers are only created when
setup_logging()is calledFile I/O: File logging has minimal overhead with buffering
Automatic Rotation: Files rotate at 512MB to prevent disk space issues
Simple and Purposeful
- All or Nothing: No levels to choose from - either debug everything or don't log
- Troubleshooting Tool: Logging is for diagnosing problems, not production monitoring
- Performance Conscious: Clear warning that logging has overhead
- Future-Proof: Profiler (future) will handle performance analysis properly
Most users only need one line:
mssql_python.setup_logging() # That's it!This follows the Zen of Python: "Simple is better than complex."
For issues or questions:
- GitHub Issues: microsoft/mssql-python