使用 go-mssqldb 進行日誌與診斷

驅動 go-mssqldb 程式提供可配置的日誌功能,用於排查連線問題、查詢問題及效能分析。 本文說明可用的日誌旗標以及如何使用自訂記錄器。

記錄旗標

使用 log 連線參數來啟用診斷輸出。 記錄旗標是位元遮罩值,這表示你可以將它們各自的整數值相加來加以組合:

旗標值 類別 Description
1 Errors 記錄錯誤訊息。
2 Messages 記錄伺服器的資訊訊息。
4 Rows 記錄列資料。
8 SQL 記錄發送給伺服器的 SQL 陳述。
16 參數 記錄參數名稱與數值。
32 Transactions 記錄交易開始、提交和回滾事件。
64 Debug 記錄低階協定與TDS細節。
128 重試 記錄連線重試次數。

旗標 4 (列)和 16 (參數)可以揭露應用程式資料、秘密或個人識別資訊。 將它們視為短暫的診斷標記,而非例行的生產設定。

Examples

僅記錄錯誤:

sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&log=1

日誌錯誤、SQL 語句與參數:

sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&log=25

備註

該值 25 的計算方式為 1 + 8 + 16 (錯誤 + SQL + 參數)。

記錄所有內容:

sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&log=255

Warning

高 log 標誌值(64128255)會產生冗長的輸出,並可能影響效能。 僅用於偵錯。

預設記錄器

預設情況下,驅動程式會記錄到 Go 的標準 log 套件,該套件會寫入 os.Stderr。 輸出包含時間戳記及日誌類別:

2026/03/28 10:15:30 mssql: login successful
2026/03/28 10:15:30 mssql: SQL: SELECT 1

使用 SetLogger 的自訂記錄器

mssql.SetLogger 來將驅動程式日誌輸出重新導向到自訂的日誌器。 記錄器必須實作以下介面:mssql.Logger

import "github.com/microsoft/go-mssqldb"

type myLogger struct{}

func (l *myLogger) Printf(format string, v ...interface{}) {
    // Write to your preferred logging system
    fmt.Printf("[MSSQL] "+format+"\n", v...)
}

func (l *myLogger) Println(v ...interface{}) {
    fmt.Println(append([]interface{}{"[MSSQL]"}, v...)...)
}

func main() {
    mssql.SetLogger(&myLogger{})
    // ... open connection
}

帶有 SetContextLogger 的情境感知記錄器

使用 mssql.SetContextLogger 提供具情境感知能力的記錄器。 記錄器必須實作 mssql.ContextLogger 介面,該介面具有單一的 Log 方法,該方法會接收上下文、記錄類別和訊息字串。 此做法可讓您將驅動程式記錄與請求範圍的追蹤資料(例如追蹤 ID)建立關聯:

import (
    "context"
    "log/slog"
    "github.com/microsoft/go-mssqldb"
    "github.com/microsoft/go-mssqldb/msdsn"
)

type contextLogger struct{}

func (l *contextLogger) Log(ctx context.Context, category msdsn.Log, msg string) {
    slog.InfoContext(ctx, msg, "category", category)
}

func main() {
    mssql.SetContextLogger(&contextLogger{})
    // ... open connection
}

診斷檢查清單

在排解連線或查詢問題時:

  1. 如果是錯誤,請從 log=1 開始;如果也需要伺服器訊息,則使用 log=3
  2. 重現問題並查看錯誤訊息,再啟用更多類別。
  3. 如果你需要確認傳送的是哪個 SQL 語句或程序呼叫,請加上 8
  4. 僅可在安全的環境中新增 164,且該環境能夠記錄參數值和傳回的資料列,而不會洩露敏感資料。
  5. 如果問題看起來屬於通訊協定層級或與重試有關,請將其提高至 log=64,或加入 128 以取得重試診斷資訊。
  6. 問題解決後移除或減少日誌記錄。

使用 log/slog 進行結構化記錄

Go 1.21 引入了用於結構化日誌記錄的 log/slog。 使用 SetContextLogger 將驅動程式輸出經由 slog 進行路由:

import (
    "context"
    "log/slog"
    "os"

    "github.com/microsoft/go-mssqldb"
    "github.com/microsoft/go-mssqldb/msdsn"
)

type slogLogger struct {
    logger *slog.Logger
}

func (l *slogLogger) Log(ctx context.Context, category msdsn.Log, msg string) {
    level := slog.LevelInfo
    if category == msdsn.LogErrors {
        level = slog.LevelError
    }
    if category == msdsn.LogDebug {
        level = slog.LevelDebug
    }

    l.logger.LogAttrs(ctx, level, msg,
        slog.Int("category", int(category)),
    )
}

func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
        Level: slog.LevelInfo,
    }))

    mssql.SetContextLogger(&slogLogger{logger: logger})

    // Connection logging now produces structured JSON.
}

與 zerolog 整合

zerolog 是一種零分配結構化記錄器。 將驅動程式日誌路由至 zerolog:

import (
    "context"
    "os"

    "github.com/microsoft/go-mssqldb"
    "github.com/microsoft/go-mssqldb/msdsn"
    "github.com/rs/zerolog"
)

type zerologAdapter struct {
    logger zerolog.Logger
}

func (l *zerologAdapter) Log(ctx context.Context, category msdsn.Log, msg string) {
    event := l.logger.Info()
    if category == msdsn.LogErrors {
        event = l.logger.Error()
    }
    event.Int("category", int(category)).Msg(msg)
}

func main() {
    logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
    mssql.SetContextLogger(&zerologAdapter{logger: logger})
}

與 zap 的整合

ZAP 是一款高效能的結構化記錄器。 路線司機透過 Zap 登入:

import (
    "context"

    "github.com/microsoft/go-mssqldb"
    "github.com/microsoft/go-mssqldb/msdsn"
    "go.uber.org/zap"
)

type zapAdapter struct {
    logger *zap.Logger
}

func (l *zapAdapter) Log(ctx context.Context, category msdsn.Log, msg string) {
    if category == msdsn.LogErrors {
        l.logger.Error(msg, zap.Int("category", int(category)))
    } else {
        l.logger.Info(msg, zap.Int("category", int(category)))
    }
}

func main() {
    logger, _ := zap.NewProduction()
    defer logger.Sync()

    mssql.SetContextLogger(&zapAdapter{logger: logger})
}

關聯識別碼傳遞

在分散式系統中,透過上下文傳播相關 ID,使驅動日誌能與觸發它們的請求相互關聯:

type correlationKey struct{}

func WithCorrelationID(ctx context.Context, id string) context.Context {
    return context.WithValue(ctx, correlationKey{}, id)
}

func CorrelationID(ctx context.Context) string {
    if id, ok := ctx.Value(correlationKey{}).(string); ok {
        return id
    }
    return "unknown"
}

type correlatedLogger struct {
    logger *slog.Logger
}

func (l *correlatedLogger) Log(ctx context.Context, category msdsn.Log, msg string) {
    l.logger.LogAttrs(ctx, slog.LevelInfo, msg,
        slog.String("correlation_id", CorrelationID(ctx)),
        slog.Int("category", int(category)),
    )
}

然後將相關 ID 透過你的請求上下文傳遞:

func handleRequest(w http.ResponseWriter, r *http.Request) {
    correlationID := r.Header.Get("X-Correlation-ID")
    if correlationID == "" {
        correlationID = uuid.NewString()
    }

    ctx := WithCorrelationID(r.Context(), correlationID)

    // All database operations using this context will include the correlation ID.
    rows, err := db.QueryContext(ctx, "SELECT TOP (10) ProductID, Name FROM Production.Product")
    // ...
}

實務上如何使用相關識別碼

使用關聯識別碼作為貫穿日誌、重試和資料庫呼叫的追蹤索引鍵:

  1. 在請求邊界產生或接受相關ID。
  2. 將它儲存在請求上下文中,並包含在應用程式和驅動程式日誌中。
  3. 若要從 SQL 端進行疑難排解,請在工作階段內容中使用 sp_set_session_context 進行設定,並使用 SESSION_CONTEXT 讀取。

相關 ID 不會自動儲存在 SQL Server 資料表中。 SESSION_CONTEXT 是會話範圍,因此值只適用於目前連線,且不會在會話結束後持續存在。

如果你需要持久歷史,請將相關性 ID 明確寫入你的審計或業務資料表(例如 AuditLog ,包含 correlation_id、 、 時間戳、營運和狀態的表格)。

生產日誌配置

在生產環境中,啟用最小記錄以避免效能負擔並防止敏感資料出現在日誌中:

環境 建議 log 它捕捉了什麼
Development 63 (錯誤 + 訊息 + 列 + SQL + 參數 + 交易) 完整掌握除錯資訊。
Staging 3 (錯誤+訊息) 錯誤和伺服器訊息,不含查詢詳細資料。
Production 1 (錯誤)或 0 (關閉) 僅記錄錯誤,或完全停用驅動程式記錄。

警告

Parameters旗標(16)記錄實際參數值,可能包括個人識別資訊(PII)、密碼或其他敏感資料。 在生產環境中切勿啟用此旗標。 欲了解每個旗幟的完整風險評估,請參閱 安全最佳實務

抑制生產中的伐木

使用環境變數控制每個部署階段的日誌等級:

if os.Getenv("APP_ENV") == "production" {
    // Use only error-level logging in production.
    connString += "&log=1"
} else {
    // Full logging in development.
    connString += "&log=63"
}