Логирование и диагностика с помощью go-mssqldb

go-mssqldb драйвер поддерживает настраиваемое журналирование для диагностики проблем с подключением, проблем с запросами и анализа производительности. В этой статье описаны доступные флаги лога и способы использования пользовательских логгеров.

Флаги журнала

Используйте log параметр подключения для включения диагностического вывода. Логарифмические флаги — это значения битовой маски, то есть их можно объединить, добавив их целочисленные значения:

Значение флага Category Description
1 Errors Регистрировать сообщения об ошибках.
2 Messages Регистрировать информационные сообщения с сервера.
4 Rows Записать в журнал данные строки.
8 SQL Регистрируйте в журнале операторы SQL, отправляемые на сервер.
16 Параметры Записывайте в журнал имена и значения параметров.
32 Transactions Регистрировать в журнале события начала, подтверждения и отката транзакций.
64 Debug Регистрировать данные низкоуровневого протокола и сведения о TDS.
128 Повторные попытки Логировать попытки повторного подключения.

Флаги 4 (строки) и 16 (параметры) могут раскрывать данные приложений, секреты или лично идентифицируемую информацию. Относитесь к ним как к кратковременным диагностическим сигналам, а не как к обычным производственным параметрам.

Примеры

Регистрировать только ошибки:

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

Регистрировать ошибки, SQL-запросы и параметры:

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

Note

Значение 25 вычисляется как 1 + 8 + 16 (ошибки + SQL + параметры).

Записывайте всё:

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

Предупреждение

Высокие значения логарифма (64, 128, 255) дают длинный результат и могут влиять на производительность. Используйте их только для отладки.

Стандартный логгер

По умолчанию драйвер входит в стандартный log пакет Go, который записывает в 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 метод, принимающий контекст, категорию логарифма и строку сообщений. Этот подход позволяет сопоставлять журналы драйверов с данными трассировки, относящимися к конкретному запросу (например, с идентификаторами трассировки):

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. Добавьте 8, если нужно уточнить, какая инструкция SQL или какой вызов процедуры был отправлен.
  4. Добавляйте 16 или 4 только в безопасной среде, где значения параметров и возвращаемые строки можно логировать без раскрытия конфиденциальных данных.
  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})
}

Распространение корреляционных идентификаторов

В распределённых системах распространяйте идентификатор корреляции через контекст, чтобы логи драйверов можно было сопоставить с запросом, который их инициировал:

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)),
    )
}

Затем передайте идентификатор корреляции через контекст вашего запроса:

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. Генерируйте или принимайте идентификатор корреляции на границе запроса.
  2. Храните его в контексте запроса и включайте в журналы приложений и драйверов.
  3. Для устранения неполадок на стороне SQL задайте его в контексте сеанса с помощью sp_set_session_context и считайте с помощью SESSION_CONTEXT.

Идентификаторы корреляции не хранятся автоматически в таблицах SQL Server. SESSION_CONTEXT он ограничен сессионным диапазоном, поэтому значения применяются только к текущему соединению и не сохраняются после окончания сессии.

Если вам нужно долговременное хранение истории, явно записывайте идентификатор корреляции в таблицы аудита или бизнес-таблицы (например, в таблицу AuditLog с correlation_id временной меткой, операцией и статусом).

Конфигурация производственного логирования

В продакшене можно минимизировать логирование, чтобы избежать накладных расходов производительности и предотвратить появление конфиденциальных данных в журналах:

Окружающая среда Рекомендуемое log значение То, что он захватывает
Development 63 (ошибки + сообщения + строки + SQL + параметры + транзакции) Полная видимость для отладки.
Staging 3 (ошибки + сообщения) Ошибки и сообщения сервера без деталей запроса.
Производство 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"
}