驱动 go-mssqldb 提供可配置的日志功能,用于排查连接问题、查询问题和性能分析。 本文介绍了可用的日志标志以及如何使用自定义日志器。
日志标志
使用 log 连接参数来启用诊断输出。 日志标志是位掩码值,意味着你可以通过将它们的整数值相加来组合:
| 标志值 | 类别 | 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
注释
该值 25 的计算方式为 1 + 8 + 16 (错误 + SQL + 参数)。
记录所有内容:
sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&log=255
Warning
较高的log flag值(64, 128, 255)会产生冗长的输出,并可能影响性能。 仅将它们用于调试。
默认记录器
默认情况下,驱动会登录到 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
}
诊断清单
排查连接或查询问题时:
- 先使用
log=1查看错误;如果还需要服务器消息,则使用log=3。 - 先复现该问题并检查错误信息,然后再启用更多类别。
- 如果你需要确认发送的是哪个SQL语句或过程调用,请添加
8。 - 仅在不会暴露敏感数据的安全环境中添加
16或4,在该环境中可以记录参数值和返回的行。 - 如果问题看起来涉及协议层级或重试,请将其增加到
log=64,或添加128以获取重试诊断信息。 - 问题解决后移除或减少日志记录。
使用 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 传递
在分布式系统中,通过上下文传播相关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")
// ...
}
如何实际使用相关ID
使用关联ID作为日志、重试和数据库调用的追踪键:
- 在请求边界生成或接受相关ID。
- 将其存储在请求上下文中,并包含在应用和驱动程序日志中。
- 对于 SQL 端故障排查,请使用
sp_set_session_context在会话上下文中设置它,并使用SESSION_CONTEXT读取它。
相关ID并不会自动存储在SQL Server表中。
SESSION_CONTEXT 是会话范围的,因此值仅适用于当前连接,会话结束后不会持续存在。
如果你需要持久历史,可以把相关ID明确写入审计或业务表(例如 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"
}