diff --git a/mssql_python/cursor.py b/mssql_python/cursor.py index 3dd7aa28..c1e4124a 100644 --- a/mssql_python/cursor.py +++ b/mssql_python/cursor.py @@ -2623,6 +2623,8 @@ def _bulkcopy( pycore_connection = mssql_py_core.PyCoreConnection(pycore_context) pycore_cursor = pycore_connection.cursor() + # Call bulkcopy with explicit keyword arguments + # The API signature: bulkcopy(table_name, data_source, batch_size=0, timeout=30, ...) result = pycore_cursor.bulkcopy( table_name, iter(data), @@ -2635,6 +2637,7 @@ def _bulkcopy( keep_nulls=keep_nulls, fire_triggers=fire_triggers, use_internal_transaction=use_internal_transaction, + python_logger=logger, # Pass Python logger handle to pycore ) return result diff --git a/mssql_python/logging.py b/mssql_python/logging.py index 2cb9361f..41a94ce1 100644 --- a/mssql_python/logging.py +++ b/mssql_python/logging.py @@ -145,15 +145,20 @@ def _setup_handlers(self): # Custom formatter to extract source from message and format as CSV class CSVFormatter(logging.Formatter): def format(self, record): - # Extract source from message (e.g., [Python] or [DDBC]) - msg = record.getMessage() - if msg.startswith("[") and "]" in msg: - end_bracket = msg.index("]") - source = msg[1:end_bracket] - message = msg[end_bracket + 2 :].strip() # Skip '] ' + # Check if this is from Rust (via rust_log method) + if hasattr(record, 'funcName') and record.funcName == "rust": + source = "Rust" + message = record.getMessage() else: - source = "Unknown" - message = msg + # Extract source from message (e.g., [Python] or [DDBC]) + msg = record.getMessage() + if msg.startswith("[") and "]" in msg: + end_bracket = msg.index("]") + source = msg[1:end_bracket] + message = msg[end_bracket + 2 :].strip() # Skip '] ' + else: + source = "Unknown" + message = msg # Format timestamp with milliseconds using period separator timestamp = self.formatTime(record, "%Y-%m-%d %H:%M:%S") @@ -326,6 +331,41 @@ def _write_log_header(self): pass # Even stderr notification failed # Don't crash - logging continues without header + def rust_log(self, level: int, msg: str, filename: str = "cursor.rs", lineno: int = 0): + """ + Logging method for Rust code with custom source location. + + Args: + level: Log level (DEBUG, INFO, WARNING, ERROR) + msg: Message string (already formatted) + filename: Source filename (e.g., 'cursor.rs') + lineno: Line number in source file + """ + try: + if not self._logger.isEnabledFor(level): + return + + # Create a custom LogRecord with Rust source location + import logging as log_module + record = log_module.LogRecord( + name=self._logger.name, + level=level, + pathname=filename, + lineno=lineno, + msg=msg, + args=(), + exc_info=None, + func="rust", + sinfo=None + ) + self._logger.handle(record) + except Exception: + # Fallback - use regular logging + try: + self._logger.log(level, msg) + except: + pass + def _log(self, level: int, msg: str, add_prefix: bool = True, *args, **kwargs): """ Internal logging method with exception safety.