Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
57cfc13
REFACTOR: Enhance & Fix Logs
bewithgaurav Jul 14, 2025
f95309e
tests and minor fix
bewithgaurav Jul 14, 2025
98e2234
conflicts
bewithgaurav Jul 16, 2025
9534896
restoring branch
bewithgaurav Jul 16, 2025
0ed415f
restoring ddbc bindings for branch:
bewithgaurav Jul 16, 2025
7ba4115
restoring logging_config
bewithgaurav Jul 16, 2025
0562f8a
restoring logging_other files
bewithgaurav Jul 16, 2025
1b67d51
eliminated auth changes
bewithgaurav Jul 16, 2025
558cdd6
logging tests only
bewithgaurav Jul 16, 2025
3266fe6
refactor
bewithgaurav Jul 16, 2025
d116a73
restored resource cleanup changes
bewithgaurav Jul 16, 2025
8baaff3
fixed tests
bewithgaurav Jul 16, 2025
fb1289f
escape connection string properly
bewithgaurav Jul 16, 2025
388a3c3
restore connection tests
bewithgaurav Jul 16, 2025
56634ce
minor changes
bewithgaurav Jul 16, 2025
6e00afb
stopped logging during destruction since that cause GIL issues
bewithgaurav Jul 16, 2025
9642fd5
logger default status fixed
bewithgaurav Jul 16, 2025
1e74bf0
Merge branch 'main' into bewithgaurav/enhance_logging
bewithgaurav Jul 17, 2025
6acd665
refactored
bewithgaurav Jul 17, 2025
03e3b5f
Merge branch 'bewithgaurav/enhance_logging' of https://github.com/mic…
bewithgaurav Jul 17, 2025
97b9131
cleanup
bewithgaurav Jul 17, 2025
d85fecc
main cleanup
bewithgaurav Jul 17, 2025
5706644
fixes
bewithgaurav Jul 17, 2025
d58c71c
merge conflicts
bewithgaurav Jul 17, 2025
596db8b
Merge branch 'main' into bewithgaurav/enhance_logging
jahnvi480 Jul 17, 2025
094f514
Merge branch 'main' into bewithgaurav/enhance_logging
bewithgaurav Jul 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions mssql_python/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@
import platform
import struct
from typing import Tuple, Dict, Optional, Union
from mssql_python.logging_config import get_logger, ENABLE_LOGGING
from mssql_python.constants import AuthType

logger = get_logger()

class AADAuth:
"""Handles Azure Active Directory authentication"""

Expand Down
42 changes: 16 additions & 26 deletions mssql_python/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,12 @@
import weakref
import re
from mssql_python.cursor import Cursor
from mssql_python.logging_config import get_logger, ENABLE_LOGGING
from mssql_python.constants import ConstantsDDBC as ddbc_sql_const
from mssql_python.helpers import add_driver_to_connection_str, check_error
from mssql_python.helpers import add_driver_to_connection_str, sanitize_connection_string, log
from mssql_python import ddbc_bindings
from mssql_python.pooling import PoolingManager
from mssql_python.exceptions import DatabaseError, InterfaceError
from mssql_python.exceptions import InterfaceError
from mssql_python.auth import process_connection_string

logger = get_logger()


class Connection:
"""
Expand Down Expand Up @@ -126,8 +122,7 @@ def _construct_connection_string(self, connection_str: str = "", **kwargs) -> st
continue
conn_str += f"{key}={value};"

if ENABLE_LOGGING:
logger.info("Final connection string: %s", conn_str)
log('info', "Final connection string: %s", sanitize_connection_string(conn_str))

return conn_str

Expand All @@ -150,8 +145,7 @@ def autocommit(self, value: bool) -> None:
None
"""
self.setautocommit(value)
if ENABLE_LOGGING:
logger.info("Autocommit mode set to %s.", value)
log('info', "Autocommit mode set to %s.", value)

def setautocommit(self, value: bool = True) -> None:
"""
Expand Down Expand Up @@ -189,6 +183,7 @@ def cursor(self) -> Cursor:
)

cursor = Cursor(self)
self._cursors.add(cursor) # Track the cursor
return cursor

def commit(self) -> None:
Expand All @@ -205,8 +200,7 @@ def commit(self) -> None:
"""
# Commit the current transaction
self._conn.commit()
if ENABLE_LOGGING:
logger.info("Transaction committed successfully.")
log('info', "Transaction committed successfully.")

def rollback(self) -> None:
"""
Expand All @@ -221,8 +215,7 @@ def rollback(self) -> None:
"""
# Roll back the current transaction
self._conn.rollback()
if ENABLE_LOGGING:
logger.info("Transaction rolled back successfully.")
log('info', "Transaction rolled back successfully.")

def close(self) -> None:
"""
Expand All @@ -246,20 +239,19 @@ def close(self) -> None:
# Convert to list to avoid modification during iteration
cursors_to_close = list(self._cursors)
close_errors = []

for cursor in cursors_to_close:
try:
if not cursor.closed:
cursor.close()
except Exception as e:
# Collect errors but continue closing other cursors
close_errors.append(f"Error closing cursor: {e}")
if ENABLE_LOGGING:
logger.warning(f"Error closing cursor: {e}")
log('warning', f"Error closing cursor: {e}")

# If there were errors closing cursors, log them but continue
if close_errors and ENABLE_LOGGING:
logger.warning(f"Encountered {len(close_errors)} errors while closing cursors")
if close_errors:
log('warning', f"Encountered {len(close_errors)} errors while closing cursors")

# Clear the cursor set explicitly to release any internal references
self._cursors.clear()
Expand All @@ -270,26 +262,24 @@ def close(self) -> None:
self._conn.close()
self._conn = None
except Exception as e:
if ENABLE_LOGGING:
logger.error(f"Error closing database connection: {e}")
log('error', f"Error closing database connection: {e}")
# Re-raise the connection close error as it's more critical
raise
finally:
# Always mark as closed, even if there were errors
self._closed = True

if ENABLE_LOGGING:
logger.info("Connection closed successfully.")
log('info', "Connection closed successfully.")

def __del__(self):
"""
Destructor to ensure the connection is closed when the connection object is no longer needed.
This is a safety net to ensure resources are cleaned up
even if close() was not called explicitly.
"""
if not self._closed:
if "_closed" not in self.__dict__ or not self._closed:
try:
self.close()
except Exception as e:
if ENABLE_LOGGING:
logger.error(f"Error during connection cleanup in __del__: {e}")
# Dont raise exceptions from __del__ to avoid issues during garbage collection
log('error', f"Error during connection cleanup: {e}")
60 changes: 22 additions & 38 deletions mssql_python/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@
import datetime
from typing import List, Union
from mssql_python.constants import ConstantsDDBC as ddbc_sql_const
from mssql_python.helpers import check_error
from mssql_python.logging_config import get_logger, ENABLE_LOGGING
from mssql_python.helpers import check_error, log
from mssql_python import ddbc_bindings
from mssql_python.exceptions import InterfaceError
from .row import Row

logger = get_logger()

class Cursor:
"""
Expand Down Expand Up @@ -415,36 +414,22 @@ def _initialize_cursor(self) -> None:
"""
Initialize the DDBC statement handle.
"""
# Allocate the DDBC statement handle
self._allocate_statement_handle()
# Add the cursor to the connection's cursor set
self.connection._cursors.add(self)

def _allocate_statement_handle(self):
"""
Allocate the DDBC statement handle.
"""
self.hstmt = self.connection._conn.alloc_statement_handle()

def _free_cursor(self) -> None:
def _reset_cursor(self) -> None:
"""
Free the DDBC statement handle and remove the cursor from the connection's cursor set.
Reset the DDBC statement handle.
"""
if self.hstmt:
self.hstmt.free()
self.hstmt = None
if ENABLE_LOGGING:
logger.debug("SQLFreeHandle succeeded")
# We don't need to remove the cursor from the connection's cursor set here,
# as it is a weak reference and will be automatically removed
# when the cursor is garbage collected.

def _reset_cursor(self) -> None:
"""
Reset the DDBC statement handle.
"""
# Free the current cursor if it exists
self._free_cursor()
log('debug', "SQLFreeHandle succeeded")
# Reinitialize the statement handle
self._initialize_cursor()

Expand All @@ -461,8 +446,7 @@ def close(self) -> None:
if self.hstmt:
self.hstmt.free()
self.hstmt = None
if ENABLE_LOGGING:
logger.debug("SQLFreeHandle succeeded")
log('debug', "SQLFreeHandle succeeded")
self.closed = True

def _check_closed(self):
Expand Down Expand Up @@ -596,15 +580,14 @@ def execute(
# Executing a new statement. Reset is_stmt_prepared to false
self.is_stmt_prepared = [False]

if ENABLE_LOGGING:
logger.debug("Executing query: %s", operation)
for i, param in enumerate(parameters):
logger.debug(
"""Parameter number: %s, Parameter: %s,
Param Python Type: %s, ParamInfo: %s, %s, %s, %s, %s""",
i + 1,
param,
str(type(param)),
log('debug', "Executing query: %s", operation)
for i, param in enumerate(parameters):
log('debug',
"""Parameter number: %s, Parameter: %s,
Param Python Type: %s, ParamInfo: %s, %s, %s, %s, %s""",
i + 1,
param,
str(type(param)),
parameters_type[i].paramSQLType,
parameters_type[i].paramCType,
parameters_type[i].columnSize,
Expand Down Expand Up @@ -709,10 +692,9 @@ def executemany(self, operation: str, seq_of_parameters: list) -> None:
)

columnwise_params = self._transpose_rowwise_to_columnwise(seq_of_parameters)
if ENABLE_LOGGING:
logger.info("Executing batch query with %d parameter sets:\n%s",
len(seq_of_parameters),"\n".join(f" {i+1}: {tuple(p) if isinstance(p, (list, tuple)) else p}" for i, p in enumerate(seq_of_parameters))
)
log('info', "Executing batch query with %d parameter sets:\n%s",
len(seq_of_parameters), "\n".join(f" {i+1}: {tuple(p) if isinstance(p, (list, tuple)) else p}" for i, p in enumerate(seq_of_parameters))
)

# Execute batched statement
ret = ddbc_bindings.SQLExecuteMany(
Expand Down Expand Up @@ -784,6 +766,7 @@ def fetchall(self) -> List[Row]:
# Fetch raw data
rows_data = []
ret = ddbc_bindings.DDBCSQLFetchAll(self.hstmt, rows_data)

# Convert raw data to Row objects
return [Row(row_data, self.description) for row_data in rows_data]

Expand All @@ -805,15 +788,16 @@ def nextset(self) -> Union[bool, None]:
if ret == ddbc_sql_const.SQL_NO_DATA.value:
return False
return True

def __del__(self):
"""
Destructor to ensure the cursor is closed when it is no longer needed.
This is a safety net to ensure resources are cleaned up
even if close() was not called explicitly.
"""
if not self.closed:
if "_closed" not in self.__dict__ or not self._closed:
try:
self.close()
except Exception as e:
logger.error(f"Error closing cursor: {e}")
# Don't raise an exception in __del__, just log it
log('error', "Error during cursor cleanup in __del__: %s", e)
6 changes: 3 additions & 3 deletions mssql_python/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
This module contains custom exception classes for the mssql_python package.
These classes are used to raise exceptions when an error occurs while executing a query.
"""
from mssql_python.logging_config import get_logger, ENABLE_LOGGING
from mssql_python.logging_config import get_logger

logger = get_logger()

Expand Down Expand Up @@ -621,7 +621,7 @@ def truncate_error_message(error_message: str) -> str:
string_third = string_second[string_second.index("]") + 1 :]
return string_first + string_third
except Exception as e:
if ENABLE_LOGGING:
if logger:
logger.error("Error while truncating error message: %s",e)
return error_message

Expand All @@ -641,7 +641,7 @@ def raise_exception(sqlstate: str, ddbc_error: str) -> None:
"""
exception_class = sqlstate_to_exception(sqlstate, ddbc_error)
if exception_class:
if ENABLE_LOGGING:
if logger:
logger.error(exception_class)
raise exception_class
raise DatabaseError(
Expand Down
32 changes: 30 additions & 2 deletions mssql_python/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from mssql_python import ddbc_bindings
from mssql_python.exceptions import raise_exception
from mssql_python.logging_config import get_logger, ENABLE_LOGGING
from mssql_python.logging_config import get_logger
import platform
from pathlib import Path
from mssql_python.ddbc_bindings import normalize_architecture
Expand Down Expand Up @@ -73,7 +73,7 @@ def check_error(handle_type, handle, ret):
"""
if ret < 0:
error_info = ddbc_bindings.DDBCSQLCheckError(handle_type, handle, ret)
if ENABLE_LOGGING:
if logger:
logger.error("Error: %s", error_info.ddbcErrorMsg)
raise_exception(error_info.sqlState, error_info.ddbcErrorMsg)

Expand Down Expand Up @@ -184,3 +184,31 @@ def get_driver_path(module_dir, architecture):
raise RuntimeError(f"ODBC driver not found at: {driver_path_str}")

return driver_path_str


def sanitize_connection_string(conn_str: str) -> str:
"""
Sanitize the connection string by removing sensitive information.
Args:
conn_str (str): The connection string to sanitize.
Returns:
str: The sanitized connection string.
"""
# Remove sensitive information from the connection string, Pwd section
# Replace Pwd=...; or Pwd=... (end of string) with Pwd=***;
import re
return re.sub(r"(Pwd\s*=\s*)[^;]*", r"\1***", conn_str, flags=re.IGNORECASE)


def log(level: str, message: str, *args) -> None:
"""
Universal logging helper that gets a fresh logger instance.

Args:
level: Log level ('debug', 'info', 'warning', 'error')
message: Log message with optional format placeholders
*args: Arguments for message formatting
"""
logger = get_logger()
if logger:
getattr(logger, level)(message, *args)
Loading