日志输出优化实战:从“能用”到“好用”的全攻略

日志输出优化实战:从“能用”到“好用”的全攻略

在日常开发中,日志是开发者的“眼睛”——排查问题、定位故障、监控系统状态,都离不开日志。但实际项目里,很多日志输出却处于“能用但不好用”的状态:要么级别混乱( debug 日志充斥生产环境),要么内容残缺(缺少关键上下文),要么格式杂乱(难以解析),甚至因日志输出不当导致系统性能下降。今天,我们就从“为什么要优化日志”“优化什么”“怎么优化”三个维度,全面掌握日志输出的优化技巧,让日志真正成为系统运维的得力助手。

一、先搞懂:为什么要优化日志输出?

很多开发者觉得“日志只要能打出来就行”,但在生产环境中,劣质日志的代价远超想象:

  • 排查问题效率低:日志缺少请求ID、用户ID等上下文,面对海量日志无法快速定位某一次请求的完整链路;级别混乱导致关键错误日志被淹没在 debug 日志中;
  • 系统性能损耗大:无节制输出大量日志(尤其是 debug 级),会占用大量磁盘IO和网络带宽;同步日志写入在高并发场景下,还会拖慢接口响应速度;
  • 安全风险高:日志中明文输出用户密码、手机号、银行卡号等敏感信息,违反数据安全规范,可能引发合规风险;
  • 日志解析困难:非结构化日志(如自由文本)无法被ELK等日志分析工具高效解析,无法实现自动化监控告警,只能靠人工检索,效率极低。

而优化后的日志,能实现“快速定位问题、低性能损耗、安全合规、可自动化分析”的目标,这也是分布式系统运维的基础要求。

二、日志输出优化的核心方向:6个“必须搞定”的关键点

日志优化不是“炫技”,而是围绕“实用、高效、安全”展开的系统性优化。核心聚焦以下6个关键点,覆盖从输出规范到性能、安全的全维度:

1. 规范日志级别:不滥用、不混淆

日志级别是日志的“优先级标签”,不同级别对应不同的使用场景,滥用级别会直接导致日志失效。主流日志框架(SLF4J+Logback/Log4j2)的级别从高到低分为:ERROR > WARN > INFO > DEBUG > TRACE,每个级别都有明确的使用边界:

  • ERROR:系统核心流程异常,必须人工介入处理。例如:数据库连接失败、第三方服务调用超时(无法重试)、核心业务逻辑异常(如订单创建失败); 注意:只记录“异常事实”,不记录“预期内的失败”(如用户输入参数错误),且必须附带异常堆栈信息;
  • WARN:系统出现非核心流程异常,但不影响主流程运行,需关注后续趋势。例如:缓存击穿、非核心接口调用超时(已重试成功)、配置项缺失(使用默认值);
  • INFO:系统核心流程的关键节点,用于追踪业务链路。例如:用户登录成功、订单支付完成、服务启动成功; 注意:INFO级别日志应“少而精”,避免每一步操作都打INFO,否则会导致日志冗余;
  • DEBUG:开发/测试环境用于定位问题的详细信息,生产环境必须关闭。例如:方法入参出参、循环内的中间结果、第三方接口的详细响应;
  • TRACE:比DEBUG更细致的调试信息(如框架内部调用细节),一般仅在框架调试时使用,业务代码尽量不使用。

反例与正例对比:

scss

// 反例1:用ERROR记录预期内的失败(用户输入错误) if (StringUtils.isEmpty(userId)) { log.error("用户ID为空,无法查询订单"); // 错误:用户输入错误属于预期内,应使用WARN return Result.fail("用户ID不能为空"); } // 反例2:用INFO记录调试信息 log.info("查询订单入参:orderNo={}", orderNo); // 错误:入参记录属于调试信息,应使用DEBUG Order order = orderService.getByOrderNo(orderNo); // 正例 if (StringUtils.isEmpty(userId)) { log.warn("用户ID为空,拒绝查询订单,请求参数:{}", requestParams); // WARN记录预期内异常,附带参数 return Result.fail("用户ID不能为空"); } try { Order order = orderService.getByOrderNo(orderNo); log.info("订单查询成功,orderNo={}, userId={}", orderNo, userId); // INFO记录核心流程节点 } catch (Exception e) { log.error("订单查询失败,orderNo={}, userId={}", orderNo, userId, e); // ERROR记录异常,附带堆栈 return Result.fail("查询失败"); }

2. 结构化日志:让日志“可解析、可检索”

传统的“文本日志”(如 2024-05-20 10:30:00.123 INFO [main] c.d.OrderController - 订单创建成功)虽然人类可读,但机器难以解析,无法快速提取关键信息(如订单号、用户ID)。而结构化日志(如JSON格式)能将日志字段标准化,完美适配ELK(Elasticsearch+Logstash+Kibana)等日志分析工具,实现快速检索、过滤和可视化。

Spring Boot默认使用SLF4J+Logback,实现JSON结构化日志只需简单配置:

(1)引入依赖(若使用Logback)

xml

<!-- 日志JSON格式化依赖 --> <dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>7.4.0</version> </dependency>

(2)配置logback-spring.xml

xml

<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 上下文名称:区分不同服务 --> <contextName>order-service</contextName> <!-- 定义日志输出格式:JSON格式 --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="net.logstash.logback.encoder.LogstashEncoder"> <!-- 固定字段:服务名、日志级别、时间戳等 --> <includeMdcKeyName>requestId</includeMdcKeyName> <!-- 包含请求ID(链路追踪) --> <includeMdcKeyName>userId</includeMdcKeyName> <!-- 包含用户ID --> <customFields>"service":"order-service","env":"prod"</customFields> <!-- 自定义固定字段 --> <fieldNames> <timestamp>timestamp</timestamp> <level>level</level> <message>message</message> <logger>logger</logger> <thread>thread</thread> <stack_trace>stackTrace</stack_trace> </fieldNames> </encoder> </appender> <!-- 根日志级别:生产环境设为INFO --> <root level="INFO"> <appender-ref ref="CONSOLE" /> </root> <!-- 自定义包日志级别:如mapper层设为WARN,减少冗余 --> <logger name="com.demo.mapper" level="WARN" additivity="false"> <appender-ref ref="CONSOLE" /> </logger> </configuration>

(3)输出效果(JSON格式)

json

{ "service": "order-service", "env": "prod", "requestId": "req-20240520103000123", "userId": "1001", "timestamp": "2024-05-20T10:30:00.123+08:00", "level": "INFO", "message": "订单创建成功,orderNo=ORDER20240520001", "logger": "com.demo.controller.OrderController", "thread": "http-nio-8080-exec-2" }

优势:可通过Kibana快速检索“requestId=req-20240520103000123”的所有日志,还原完整请求链路;也可按“userId”“orderNo”等字段过滤,定位特定用户的操作日志。

3. 日志内容要素:让每一条日志都“有价值”

一条有价值的日志,必须包含“谁(用户/请求)在什么时候做了什么,结果如何,关键参数是什么”。核心要素应包括:

  • 链路标识:requestId(请求ID),用于串联一次请求的全链路日志(分布式系统必备);
  • 主体标识:userId(用户ID)、tenantId(租户ID),用于定位特定用户/租户的操作;
  • 业务参数:核心业务ID(如orderNo、productId),便于定位具体业务场景;
  • 操作描述:清晰说明当前操作(如“订单创建”“支付回调处理”);
  • 结果状态:成功/失败,失败时需包含异常信息(堆栈或错误码)。

实战技巧:使用MDC(Mapped Diagnostic Context)传递链路/主体标识,避免在每个日志语句中重复拼接参数。

java

import org.slf4j.MDC; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.UUID; // 自定义拦截器:生成requestId并放入MDC public class LogInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 生成唯一requestId String requestId = UUID.randomUUID().toString().replace("-", ""); MDC.put("requestId", requestId); // 从请求头获取userId(如登录后放入) String userId = request.getHeader("userId"); if (userId != null) { MDC.put("userId", userId); } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 清除MDC,避免线程复用导致的参数污染 MDC.clear(); } } // 配置拦截器 @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LogInterceptor()).addPathPatterns("/**"); } } // 业务代码中使用(无需拼接requestId和userId) @RestController public class OrderController { private final Logger log = LoggerFactory.getLogger(OrderController.class); @PostMapping("/order/create") public Result createOrder(@RequestBody OrderCreateDTO dto) { try { String orderNo = orderService.create(dto); // 日志自动包含MDC中的requestId和userId log.info("订单创建成功,orderNo={}, 商品ID列表={}", orderNo, dto.getProductIds()); return Result.success(orderNo); } catch (Exception e) { log.error("订单创建失败,请求参数={}", dto, e); return Result.fail("创建失败"); } } }

4. 性能优化:减少日志对系统的损耗

日志输出本质是IO操作,高并发场景下,不恰当的日志输出会严重影响系统性能。核心优化手段包括:

(1)避免无效日志拼接

使用SLF4J的占位符 {} 而非字符串拼接(+),避免在日志级别不满足时产生无效的字符串拼接开销。

c

// 反例:即使日志级别是WARN,也会执行字符串拼接,产生性能损耗 log.debug("订单查询,orderNo=" + orderNo + ", userId=" + userId); // 正例:使用占位符,日志级别不满足时不会执行参数拼接 log.debug("订单查询,orderNo={}, userId={}", orderNo, userId);

(2)高并发场景使用异步日志

同步日志会阻塞业务线程,直到日志写入完成;异步日志则通过独立线程写入日志,不阻塞业务线程,适合高并发场景。Logback配置异步日志示例:

xml

<!-- 异步日志配置 --> <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender"> <discardingThreshold>0</discardingThreshold> <!-- 不丢弃日志(关键业务建议) --> <queueSize>1024</queueSize> <!-- 队列大小:根据并发量调整 --> <appender-ref ref="FILE" /> <!-- 关联文件输出appender --> </appender>

(3)控制日志输出量

  • 生产环境关闭DEBUG/TRACE级别日志;
  • 避免在循环内输出日志(如遍历1000条数据时,每条都打日志);
  • 对高频接口的日志进行抽样输出(如每100次请求输出1次日志)。

(4)合理设置日志滚动策略

避免单个日志文件过大,导致检索缓慢。通过日志滚动策略按时间/大小分割日志,例如:按天滚动,每天一个日志文件;单个文件超过100MB时强制滚动。Logback配置示例:

xml

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/order-service.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>logs/order-service.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>30</maxHistory> <!-- 保留30天日志 --> <totalSizeCap>10GB</totalSizeCap> <!-- 日志总大小限制 --> </rollingPolicy> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <maxFileSize>100MB</maxFileSize> <!-- 单个文件最大100MB --> </triggeringPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n&lt;/pattern&gt; &lt;/encoder&gt; &lt;/appender&gt;

5. 安全脱敏:保护敏感信息不泄露

日志中若包含用户密码、手机号、身份证号、银行卡号等敏感信息,会违反《个人信息保护法》等合规要求。核心脱敏策略:“能不输出就不输出,必须输出则脱敏”。

(1)常见脱敏场景与方法

  • 接口入参/出参脱敏:对包含敏感信息的DTO字段进行脱敏,可通过自定义JSON序列化器实现;
  • 日志语句脱敏:对关键敏感参数手动脱敏(如手机号保留前3后4位);
  • 全局拦截脱敏:通过Logback的自定义转换器,对日志中的敏感信息自动拦截脱敏。

(2)实战:自定义JSON序列化器脱敏

scala

import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; // 手机号脱敏序列化器 public class MobileDesensitizer extends JsonSerializer<String> { @Override public void serialize(String mobile, JsonGenerator gen, SerializerProvider serializers) throws IOException { if (mobile != null && mobile.length() == 11) { gen.writeString(mobile.replaceAll("(\d{3})\d{4}(\d{4})", "$1****$2")); } else { gen.writeString(mobile); } } } // 在DTO中使用 public class UserDTO { private String userId; @JsonSerialize(using = MobileDesensitizer.class) private String mobile; // 序列化时自动脱敏 private String username; // 省略getter/setter } // 日志输出效果:mobile=138****1234 log.info("用户登录成功,用户信息={}", userDTO);

(3)实战:Logback全局脱敏转换器

java

import ch.qos.logback.classic.pattern.ClassicConverter; import ch.qos.logback.classic.spi.ILoggingEvent; import java.util.regex.Pattern; // 日志全局脱敏转换器:匹配手机号、身份证号 public class LogDesensitizerConverter extends ClassicConverter { // 手机号正则:11位数字 private static final Pattern MOBILE_PATTERN = Pattern.compile("1[3-9]\d{9}"); // 身份证号正则:18位(含X) private static final Pattern ID_CARD_PATTERN = Pattern.compile("\d{17}[0-9Xx]"); @Override public String convert(ILoggingEvent event) { String message = event.getMessage(); if (message == null) { return ""; } // 手机号脱敏 message = MOBILE_PATTERN.matcher(message).replaceAll("****"); // 身份证号脱敏(保留前6后4) message = ID_CARD_PATTERN.matcher(message).replaceAll("$1****$2"); return message; } } // 在logback-spring.xml中配置 <conversionRule conversionWord="msg" converterClass="com.demo.log.LogDesensitizerConverter" />

6. 日志监控告警:让日志“主动说话”

优化后的日志不仅要“可检索”,还要能“主动告警”——当系统出现异常时,通过日志监控工具及时触发告警,避免故障扩大。核心实现方案:ELK栈 + 告警插件(如Elastic Alert)。

(1)ELK栈日志流转流程

  1. Logback将结构化日志输出到文件;
  2. Logstash读取日志文件,过滤、解析日志(如提取字段);
  3. Logstash将处理后的日志写入Elasticsearch;
  4. Kibana对接Elasticsearch,实现日志检索、可视化;
  5. 配置告警规则(如ERROR日志5分钟内超过10条、出现“数据库连接失败”日志),触发钉钉/短信/邮件告警。

(2)关键告警规则建议

  • ERROR级别日志数量突增(如5分钟内超过阈值);
  • 出现特定错误日志(如“数据库连接超时”“NPE”“服务熔断”);
  • 核心业务日志缺失(如10分钟内无“订单支付成功”日志,可能支付服务异常);
  • 接口响应时间异常(通过日志中的耗时字段监控,如耗时超过5秒)。

三、日志输出优化的“避坑指南”

在日志优化过程中,容易陷入一些误区,以下是常见坑点及规避方法:

  • 坑点1:日志级别“一刀切”:所有包都使用相同的日志级别(如根日志设为DEBUG),导致生产环境日志爆炸。 规避:根日志设为INFO,对特定包(如mapper、第三方框架)单独设为WARN;
  • 坑点2:日志内容“越详细越好”:输出完整的请求体、响应体,包含大量冗余信息。 规避:只输出核心业务参数,敏感信息脱敏,避免输出大文本(如JSON数组、文件内容);
  • 坑点3:异步日志“滥用”:所有日志都用异步输出,导致日志丢失(队列满时可能丢弃日志)。 规避:关键业务日志(如支付、订单)使用同步日志或配置不丢弃的异步日志;非关键日志可使用异步;
  • 坑点4:忽略日志清理:日志无保留期限,导致磁盘空间被占满。 规避:配置日志保留期限(如30天)和总大小限制,定期清理过期日志;
  • 坑点5:MDC未清理:线程池复用导致MDC中的参数污染(如前一个请求的requestId被后续请求复用)。 规避:在请求结束后(拦截器afterCompletion)清除MDC;线程池使用时,手动传递MDC参数。

四、总结:日志优化的核心原则

日志输出优化的核心不是“追求复杂的配置”,而是围绕“规范、实用、安全、高效”四个关键词:

  • 规范:按场景使用日志级别,统一日志格式;
  • 实用:日志内容包含关键要素,能快速定位问题;
  • 安全:敏感信息脱敏,符合合规要求;
  • 高效:减少日志对系统性能的损耗,实现自动化监控告警。

其实日志优化没有“银弹”,需要结合业务场景不断调整。建议从“规范日志级别”“添加链路标识”“结构化输出”这三个基础点入手,逐步落地性能优化和安全脱敏,让日志真正成为系统稳定性的“守护者”。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1120082.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity3d之修改子物体的层级关系

修改子物体的层级的1.go.transform.SetAsFirstSibling(); 放到最上面2.go.transform.SetAsLastSibling(); 放大最下面3.go.transform.SetSiblingIndex(count); 根据物体下标修改count0&#xff1b;为最上面的count-1&#xff1b;为最下面的再补充一句解除父子关系的代码也有可能…

全面解析 Agent Engineering 的 10 大工程维度:生产级 Agent 系统的炼成之路

2026新年快乐 HAPPY NEW YEAR 技术层面的突破让AI智能体&#xff08;Agent&#xff09;成为当前绝对的AI热点。但随着Agent应用走向真实业务&#xff0c;也逐渐暴露出其工程瓶颈&#xff1a;不确定性、幻觉、不可观测、性能、安全、成本等多方面存在挑战…这些问题不会因为模型…

如何高效安全地管理多个社媒账号?

您是否常常为多社交账号的管理难题所困扰&#xff1f;市面上已有的管理工具固然提供了一定便利&#xff0c;但也并非无所不能。假设某社交平台规定每台设备最多同时登录2个账号&#xff0c;那么即便使用账号管理工具&#xff0c;您也只能在这台设备上管理2个账号。若您需要管理…

AI产品经理全景图:从NLP/CV到金融医疗,9大岗位类型详解与跃迁路径

文章从技术方向、业务场景和业务阶段三个维度解析AI产品经理九大类型&#xff1a;技术深耕型(NLP/CV/推荐)、垂直领域型(行业解决方案/通用工具)和全生命周期型(0-1孵化/增长型/平台型)。无论哪种类型&#xff0c;AI产品经理都需具备技术理解、业务转化和跨团队协作三大核心能力…

计算机视觉在零售行业的AI原生应用探索

计算机视觉在零售行业的AI原生应用探索 关键词&#xff1a;计算机视觉、零售行业、AI原生、智能货架、消费者行为分析、无人零售、多模态融合 摘要&#xff1a;本文以"AI原生"为核心视角&#xff0c;深入探讨计算机视觉技术如何从底层设计重构零售行业的人货场关系。…

主机安全功能:主机的风险与监测

检测多项主机安全信息&#xff0c;实现对主机全方位保护&#xff0c;包含账户检测、文件检测、网络检测等重点安全信息&#xff0c;对各安全元素多点全方位实现防御把控&#xff0c;将现有安全策略下沉到防护主机当中。如病毒、木马、勒索软件及恶意攻击&#xff0c;系统通过先…

2025年AI产品经理生存指南:为什么现在学AI反而更简单?值得收藏

文章指出AI产品经理已成为新物种&#xff0c;与传统产品经理有根本变化。尽管30%产品经理选择躺平不学AI&#xff0c;但AI学习正变得简单&#xff1a;模型榜单已固定&#xff0c;模型管理平台降低研发成本&#xff0c;各领域已有头部AI产品代表。AI产品经理只需关注特定领域模型…

基于DWA的动态环境下无人机自主避障路径优化附MATLAB代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 &#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室 &#x1f447; 关注我领取海量matlab电子书和数学建模资料 &#x1…

nodejs v24.12.0+Express 5.0 迁移升级

2025年过去了,2026新年伊始,翻了一下过去一年写的代码,使用nodejs也快10年了,最新的nodejs版本已经发展到了v25,使用dp分析对比,推荐升级到v24版本,由此带来了系统升级,代码调整。 原系统: node v18.19.1 + Express 4 升级:nodej v24.12.0 + Express 5 一、安装no…

环境仿真软件:SWAT_(7).SWAT在非点源污染模拟中的应用

SWAT在非点源污染模拟中的应用 1. 非点源污染的定义与特点 非点源污染&#xff08;Non-Point Source Pollution, NPS&#xff09;是指没有明确排放口或点源的污染&#xff0c;其来源广泛&#xff0c;包括农业活动、城市径流、大气沉降等。与点源污染&#xff08;Point Source P…

【时变频率估计】基于扩展卡尔曼滤波(EKF)和无迹卡尔曼滤波(UKF)对窄带信号的时变频率估计,评估两种滤波算法在动态信号跟踪任务中的性能附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 &#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室 &#x1f447; 关注我领取海量matlab电子书和数学建模资料 &#x1…

Qwen3VL开源图文多模态大模型

原文出处&#xff1a; https://zhuanlan.zhihu.com/p/1978593520458696605 Qwen3-VL 系列包含以下变体&#xff1a; Dense 模型&#xff1a; Qwen3-VL-2B, 4B, 8B, 32B。 MoE 模型&#xff1a; Qwen3-VL-30B-A3B (Active 3B), Qwen3-VL-235B-A22B (Total 235B, Active 22B)。 …

2026大模型高薪攻略:小白到年薪百万的黄金入行指南_2026年AI大模型领域薪资爆发,抓住五大热门岗位

文章解析2025年AI大模型高薪机遇&#xff0c;指出算法工程师月薪7万、科学家11万。详细介绍五大岗位技能要求、涨薪策略及避坑指南&#xff0c;强调现在是入行黄金期&#xff0c;错过将错失百万年薪机遇。2025年AI大模型领域迎来爆发期&#xff0c;算法工程师月薪7万&#xff0…

Python实现功能完整的扫雷小游戏

一、游戏功能亮点本次实现的扫雷游戏在基础玩法之上&#xff0c;新增了三大核心功能&#xff0c;提升游戏体验与挑战性&#xff1a;计时器功能&#xff1a;游戏启动后自动计时&#xff0c;结束时显示通关或失败用时&#xff0c;增强竞技感&#xff1b;难度选择功能&#xff1a;…

langchain的中文文档地址

中文文档地址&#xff1a;https://www.langchain.com.cn/docs/introduction/

一分钟读懂代付业务

代付业务主要分为个人代付与企业代付两大类型。对于企业而言&#xff0c;开通代付功能堪称降本增效的利器&#xff0c;核心优势有四&#xff1a;1. 724小时全天候服务&#xff1a;支持自动抵扣转账&#xff0c;不受节假日、上下班时间限制&#xff0c;资金流转更灵活。2. 解放财…

飞越中国沉浸式体验馆:7D互动影院引领全新娱乐风潮

飞越中国沉浸式体验馆的创新娱乐体验 在飞越中国沉浸式体验馆中&#xff0c;7D互动影院成为众多观众探寻新娱乐体验的首选。该影院利用先进技术&#xff0c;为观众提供超高清画质并结合动感座椅&#xff0c;创造出独特的沉浸感。在这里&#xff0c;观众不仅仅是被动观看&#x…

高考学校和专业的选择

高考学校和专业的选择是&#xff1a;专业占比&#xff1a;40%学校占比&#xff1a;30%地域占比&#xff1a;30%学校占比&#xff0c;国内就是按这个顺序&#xff0c;清北、C9、985、211、其它有一定名气的一二本、普通二本、末流二本原三本、大专。这儿值得一提的是&#xff0c…

CSS3 伸缩盒模型

一、伸缩容器、伸缩项目二、主轴与侧轴三、主轴方向四、主轴换行方式五、flex-flow六、主轴对齐方式七、侧轴对齐方式1、只有一行的情况2、多行的情况八、水平垂直居中九、基准长度十、flex复合属性十一、项目排序

Expected type ‘SecretStr | None‘, got ‘str‘ instead

错误原因 代码中有一个类型不匹配的问题&#xff1a;函数或方法期望接收的类型是 SecretStr | None&#xff08;即 SecretStr 类型或 None&#xff09;&#xff0c;但实际传入了一个普通的 str 字符串。 原因分析 使用了类型检查工具&#xff1a;你可能在使用像 mypy、pydantic…