.NET 配置、选项绑定与日志实践
很多 .NET 初学者写项目时,最容易忽视的不是控制器和数据库,而是配置和日志。可一旦项目开始走向多人协作、分环境部署和线上排障,这两部分就会立刻变成核心基础设施。
你可以把它们简单理解成:
- 配置:让程序知道“该怎么运行”
- 日志:让你知道“它刚刚做了什么、哪里出了问题”
如果这两块做得差,项目即使功能能跑,也很难维护。
第一章 ASP.NET Core 的配置体系在解决什么问题
一个真实项目通常会有很多可变化的信息:
- 数据库连接串
- JWT 密钥
- Redis 地址
- 第三方接口地址
- 文件存储路径
- 日志级别
- 环境名称
这些内容不应该硬编码在代码里,而应该通过配置体系注入进来。
第二章 常见配置来源
在 ASP.NET Core 中,常见配置来源包括:
appsettings.jsonappsettings.Development.json- 环境变量
- 命令行参数
- 用户机密或外部配置中心
2.1 一个基础配置文件示例
{
"ConnectionStrings": {
"Default": "Server=.;Database=DemoDb;Trusted_Connection=True;TrustServerCertificate=True;"
},
"Jwt": {
"Issuer": "demo-api",
"Audience": "demo-client",
"Key": "your-secret-key"
},
"Storage": {
"UploadPath": "uploads"
}
}
2.2 环境配置文件
开发环境通常会额外有:
appsettings.Development.json
里面只放和开发环境不同的配置,例如:
{
"Storage": {
"UploadPath": "uploads-dev"
}
}
第三章 配置优先级怎么理解
一般来说,配置来源存在覆盖关系。常见认知可以先这样记:
- 默认文件提供基础配置
- 环境文件覆盖环境差异
- 环境变量可进一步覆盖
- 命令行参数通常优先级更高
这意味着:
- 本地开发可以用
appsettings.Development.json - 生产部署可以通过环境变量覆盖敏感值
这也是为什么“同一套代码部署到不同环境仍能工作”。
第四章 三种常见读取配置的方式
4.1 直接读取单个配置项
var issuer = builder.Configuration["Jwt:Issuer"];
这种方式简单,但不适合大量散落使用。
4.2 在服务里读取 IConfiguration
public class TokenService
{
private readonly IConfiguration _configuration;
public TokenService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GetIssuer()
{
return _configuration["Jwt:Issuer"] ?? string.Empty;
}
}
这种方式在少量使用时没问题,但如果每个服务都到处读字符串路径,可维护性会变差。
4.3 选项绑定 Options Pattern
这是更推荐的做法。
定义配置类:
public class JwtOptions
{
public string Issuer { get; set; } = string.Empty;
public string Audience { get; set; } = string.Empty;
public string Key { get; set; } = string.Empty;
}
注册:
builder.Services.Configure<JwtOptions>(
builder.Configuration.GetSection("Jwt"));
使用:
using Microsoft.Extensions.Options;
public class TokenService
{
private readonly JwtOptions _jwtOptions;
public TokenService(IOptions<JwtOptions> options)
{
_jwtOptions = options.Value;
}
public string GetIssuer()
{
return _jwtOptions.Issuer;
}
}
第五章 为什么推荐选项绑定
选项绑定的好处不仅仅是“写法更优雅”,更关键的是:
- 配置结构更清晰
- 类型安全更好
- 便于集中维护
- 更适合校验
尤其当配置项越来越多时,Options Pattern 会比到处写:
_configuration["Jwt:Issuer"]
更稳。
第六章 配置校验
很多线上问题,不是代码逻辑错了,而是配置缺了、配错了。
所以配置最好尽量在启动阶段就校验。
6.1 一个简单示例
using System.ComponentModel.DataAnnotations;
public class StorageOptions
{
[Required]
public string UploadPath { get; set; } = string.Empty;
}
注册时结合校验:
builder.Services
.AddOptions<StorageOptions>()
.Bind(builder.Configuration.GetSection("Storage"))
.ValidateDataAnnotations()
.ValidateOnStart();
这样如果关键配置没填,程序会尽早在启动阶段暴露问题,而不是等运行到一半才炸。
第七章 配置敏感信息时要注意什么
7.1 不要把密钥写死在仓库里
例如下面这些都不应该直接提交到公共仓库:
- 数据库密码
- JWT Key
- 对象存储密钥
- 第三方平台 Access Key
7.2 生产环境优先用环境变量或密钥管理
常见做法:
- 本地开发:配置文件
- 测试和生产:环境变量、密钥服务、配置中心
7.3 连接串不要散落在多个文件里
如果配置位置混乱,排障会非常痛苦。
第八章 日志在项目中到底有什么用
很多初学者会把日志理解成“出错时打印一句”。实际上日志的作用远不止如此。
日志常用于:
- 记录系统运行状态
- 排查线上问题
- 审计关键行为
- 定位性能瓶颈
- 还原请求链路
你可以把日志理解成系统的“运行痕迹”。
第九章 .NET 默认日志体系
ASP.NET Core 内置了统一日志抽象,最常见的是通过 ILogger<T> 使用。
9.1 基础示例
public class UserService
{
private readonly ILogger<UserService> _logger;
public UserService(ILogger<UserService> logger)
{
_logger = logger;
}
public void CreateUser(string userName)
{
_logger.LogInformation("开始创建用户: {UserName}", userName);
_logger.LogInformation("用户创建完成: {UserName}", userName);
}
}
9.2 为什么推荐结构化日志占位符
推荐:
_logger.LogInformation("创建订单成功, OrderId: {OrderId}", orderId);
不推荐:
_logger.LogInformation($"创建订单成功, OrderId: {orderId}");
前者更适合后续日志采集、检索和结构化分析。
第十章 日志级别怎么用
常见日志级别从低到高大致是:
TraceDebugInformationWarningErrorCritical
10.1 常见使用建议
Information:记录正常业务流程的关键节点Warning:记录可恢复但值得关注的问题Error:记录操作失败或异常Critical:记录严重系统故障
10.2 一个简单例子
_logger.LogInformation("开始处理登录请求");
_logger.LogWarning("用户多次密码输入错误: {UserName}", userName);
_logger.LogError(ex, "创建订单失败, OrderId: {OrderId}", orderId);
第十一章 如何配置日志级别
appsettings.json 中可以配置日志级别:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
这段配置的意义是:
- 默认日志输出
Information及以上 - 框架内部大量噪声日志只保留到
Warning
这样能减少无效输出。
第十二章 业务项目里日志应该记什么,不该记什么
12.1 推荐记录
- 请求开始和结束
- 关键业务动作
- 外部依赖调用失败
- 核心参数和标识
- 异常详情
12.2 不推荐记录
- 明文密码
- 完整身份证号、银行卡号等敏感信息
- 过多无意义的重复日志
日志不是越多越好,而是要“有用且可检索”。
第十三章 一个简单的请求日志中间件示例
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(
RequestDelegate next,
ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
_logger.LogInformation(
"请求开始: {Method} {Path}",
context.Request.Method,
context.Request.Path);
await _next(context);
_logger.LogInformation(
"请求结束: {StatusCode}",
context.Response.StatusCode);
}
}
注册:
app.UseMiddleware<RequestLoggingMiddleware>();
这类中间件非常适合建立最基础的请求链路日志。
第十四章 初学者常见坑
14.1 配置项到处直接字符串读取
时间一长,维护会变得很差,建议逐步收敛到选项绑定。
14.2 把生产密钥写进 appsettings.json
这会带来很大安全风险。
14.3 日志全都用 Information
这样日志就失去了分级意义。
14.4 异常只 Console.WriteLine
在 Web 项目和服务项目里,这远远不够。
14.5 日志里记录敏感信息
这是很多团队上线后才发现的问题,但代价通常很高。
第十五章 入门阶段建议掌握到什么程度
如果你刚开始学 .NET 工程基础,建议至少做到:
- 了解配置来源和环境覆盖关系
- 会用
appsettings.json和环境配置文件 - 会用
Options Pattern做选项绑定 - 知道如何做配置校验
- 会使用
ILogger<T>记录结构化日志 - 知道日志级别如何划分
做到这一步后,你的 .NET 项目就已经具备了较好的工程基础。
延伸阅读
- 如果你还没掌握
.NET工程基础,先阅读.NET CLI 与项目结构 - 如果你想了解请求处理和中间件如何配合日志与配置,继续阅读
ASP.NET Core 基础 - 如果你想把异常也纳入统一治理,继续阅读
ASP.NET Core 统一异常处理与统一返回
本文小结
配置和日志看似不像控制器、数据库那样“能立刻看到业务效果”,但它们是项目从“能跑”走向“能维护、能排障、能部署”的关键基础设施。
把这两块做好,你的 .NET 项目才真正有工程化的味道。