快来 GET java代码日志的正确打印姿势!

使用slf4j

1.使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一
2.实现方式统一使用: Logback框架

打日志的正确方式

什么时候应该打日志

  • 当你遇到问题的时候,只能通过debug功能来确定问题,你应该考虑打日志,良好的系统,是可以通过日志进行问题定为的。
  • 当你碰到if…else 或者 switch这样的分支时,要在分支的首行打印日志,用来确定进入了哪个分支
  • 经常以功能为核心进行开发,你应该在提交代码前,可以确定通过日志可以看到整个流程

基本格式

必须使用参数化信息的方式:

logger.debug("Processing trade with id:[{}] and symbol : [{}] ", id, symbol);

对于debug日志,必须判断是否为debug级别后,才进行使用:

if (logger.isDebugEnabled()) {
    logger.debug("Processing trade with id: " +id + " symbol: " + symbol);
}

不要进行字符串拼接,那样会产生很多String对象,占用空间,影响性能。
反例(不要这么做):

logger.debug("Processing trade with id: " + id + " symbol: " + symbol);

使用[]进行参数变量隔离

如有参数变量,应该写成如下写法:

logger.debug("Processing trade with id:[{}] and symbol : [{}] ", id, symbol);

这样的格式写法,可读性更好,对于排查问题更有帮助。

不同级别的使用

ERROR:

基本概念

影响到程序正常运行、当前请求正常运行的异常情况:

1.打开配置文件失败
2.所有第三方对接的异常(包括第三方返回错误码)
3.所有影响功能使用的异常,包括:SQLException和除了业务异常之外的所有异常(RuntimeException和Exception)

不应该出现的情况:

比如要使用Azure传图片,但是Azure未响应

如果有Throwable信息,需要记录完成的堆栈信息:

log.error("获取用户[{}]的用户信息时出错",userName,e);

说明:如果进行了抛出异常操作,请不要记录error日志,由最终处理方进行处理。关注微信公众号:互联网架构师,在后台回复:2T,可以获取我整理的教程,都是干货。

反例(不要这么做):

try{
    ....
}catch(Exception ex){
  String errorMessage=String.format("Error while reading information of user [%s]",userName);
  logger.error(errorMessage,ex);
  throw new UserServiceException(errorMessage,ex);
}

WARN

基本概念

不应该出现但是不影响程序、当前请求正常运行的异常情况:

1.有容错机制的时候出现的错误情况
2.找不到配置文件,但是系统能自动创建配置文件

即将接近临界值的时候,例如:缓存池占用达到警告线

业务异常的记录,比如:当接口抛出业务异常时,应该记录此异常

INFO:

基本概念

系统运行信息

Service方法中对于系统/业务状态的变更
主要逻辑中的分步骤

外部接口部分

客户端请求参数(REST/WS)
调用第三方时的调用参数和调用结果

说明

1、并不是所有的service都进行出入口打点记录,单一、简单service是没有意义的(job除外,job需要记录开始和结束,)。
反例(不要这么做):

public List listByBaseType(Integer baseTypeId) {

    log.info("开始查询基地");
    BaseExample ex=new BaseExample();
    BaseExample.Criteria ctr = ex.createCriteria();
    ctr.andIsDeleteEqualTo(IsDelete.USE.getValue());
    Optionals.doIfPresent(baseTypeId, ctr::andBaseTypeIdEqualTo);
   log.info("查询基地结束");
    return baseRepository.selectByExample(ex);
}

2、对于复杂的业务逻辑,需要进行日志打点,以及埋点记录,比如电商系统
中的下订单逻辑,以及OrderAction操作(业务状态变更)。关注微信公众号:互联网架构师,在后台回复:2T,可以获取我整理的教程,都是干货。
3、对于整个系统的提供出的接口(REST/WS),使用info记录入参
4、如果所有的service为SOA架构,那么可以看成是一个外部接口提供方,那么必须记录入参。
5、调用其他第三方服务时,所有的出参和入参是必须要记录的(因为你很难追溯第三方模块发生的问题)

DEBUG

基本概念
1.可以填写所有的想知道的相关信息(但不代表可以随便写,debug信息要有意义,最好有相关参数)
2.生产环境需要关闭DEBUG信息
3.如果在生产情况下需要开启DEBUG,需要使用开关进行管理,不能一直开启。

说明

如果代码中出现以下代码,可以进行优化:

//1. 获取用户基本薪资

//2. 获取用户休假情况

//3. 计算用户应得薪资

优化后的代码:

logger.debug("开始获取员工[{}] [{}]年基本薪资",employee,year);

logger.debug("获取员工[{}] [{}]年的基本薪资为[{}]",employee,year,basicSalary);
logger.debug("开始获取员工[{}] [{}]年[{}]月休假情况",employee,year,month);

logger.debug("员工[{}][{}]年[{}]月年假/病假/事假为[{}]/[{}]/[{}]",employee,year,month,annualLeaveDays,sickLeaveDays,noPayLeaveDays);
logger.debug("开始计算员工[{}][{}]年[{}]月应得薪资",employee,year,month);

logger.debug("员工[{}] [{}]年[{}]月应得薪资为[{}]",employee,year,month,actualSalary);

TRACE

基本概念

特别详细的系统运行完成信息,业务代码中,不要使用.(除非有特殊用意,否则请使用DEBUG级别替代)关注微信公众号:互联网架构师,在后台回复:2T,可以获取我整理的教程,都是干货。

规范示例说明

@Override
@Transactional
public void createUserAndBindMobile(@NotBlank String mobile, @NotNull User user) throws CreateConflictException{
    boolean debug = log.isDebugEnabled();
    if(debug){
        log.debug("开始创建用户并绑定手机号. args[mobile=[{}],user=[{}]]", mobile, LogObjects.toString(user));
    }
    try {
        user.setCreateTime(new Date());
        user.setUpdateTime(new Date());
        userRepository.insertSelective(user);
        if(debug){
            log.debug("创建用户信息成功. insertedUser=[{}]",LogObjects.toString(user));
        }
        UserMobileRelationship relationship = new UserMobileRelationship();
        relationship.setMobile(mobile);
        relationship.setOpenId(user.getOpenId());
        relationship.setCreateTime(new Date());
        relationship.setUpdateTime(new Date());
        userMobileRelationshipRepository.insertOnDuplicateKey(relationship);
        if(debug){
            log.debug("绑定手机成功. relationship=[{}]",LogObjects.toString(relationship));
        }
        log.info("创建用户并绑定手机号. userId=[{}],openId=[{}],mobile=[{}]",user.getId(),user.getOpenId(),mobile); // 如果考虑安全,手机号记得脱敏
    }catch(DuplicateKeyException e){
        log.info("创建用户并绑定手机号失败,已存在相同的用户. openId=[{}],mobile=[{}]",user.getOpenId(),mobile);
        throw new CreateConflictException("创建用户发生冲突, openid=[%s]",user.getOpenId());
    }
}

最近整理了java架构文档和学习笔记文件以及架构视频资料和高清架构进阶学习导图免费分享给大家(包括Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术资料),希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习,也可以关注我一下以后会有更多干货分享。

添加小助手VX:xuanwo008 领取资料备注好“优快云”信息!

<think>好的,我现在需要帮助用户解决在使用Spring Boot封装第三方POST表单接口时遇到的问题。用户已经检查了输入参数正确,但接口仍然返回参数错误。我需要回顾之前的对话历史,看看之前给出的建议,然后思考可能用户没有正确应用那些建议,或者可能存在其他未被考虑的问题。 首先,用户之前询问了关于bodyValue传格式不正确的问题,我给出了详细的原因和解决方案,包括使用MultiValueMap、设置正确的Content-Type、处理编码问题等。现在用户的问题看起来类似,但可能是在实际应用中遇到了其他问题,或者之前提供的解决方案没有完全解决。 用户现在的情况是:已经确认输入参数正确,但第三方接口仍然返回参数错误。这可能意味着请求的格式或内容仍有问题,或者第三方接口有其他特定的要求未被满足。 我需要考虑以下几点: 1. **MultiValueMap的正确使用**:用户是否正确地使用了LinkedMultiValueMap来确保参数可以包含多个值,而不会覆盖之前的参数?比如,是否有重复的键需要添加而不是替换。 2. **Content-Type的设置**:是否在WebClient的请求中明确设置了application/x-www-form-urlencoded的Content-Type?如果漏掉这一步,服务器可能无法正确解析表单数据。 3. **参数的编码问题**:特别是当参数包含特殊字符(如空格、中文、符号)时,是否进行了正确的URL编码?手动编码或使用UriComponentsBuilder处理是否被正确应用。 4. **日志记录和调试**:用户是否启用了请求日志来查看实际发送的数据格式是否正确?比如,使用wiretap或拦截器来捕获请求头和请求体,确认数据是否符合预期。 5. **第三方接口的特殊要求**:是否第三方接口对表单参数的顺序有要求?比如,需要参数按特定顺序排列,或者需要某些特定的头信息(如User-Agent、Accept等)。 6. **多部分表单与普通表单的混淆**:如果接口需要的是multipart/form-data而不是x-www-form-urlencoded,用户是否错误地使用了普通的表单提交方式? 7. **SSL/TLS或代理问题**:是否因为SSL证书问题导致请求被拦截或修改?尤其是在测试环境或使用自签名证书时,可能需要禁用SSL验证。 8. **服务器端验证问题**:第三方接口可能对某些隐藏参数或时间戳、签名等有要求,用户是否遗漏了这些参数? 9. **重定向或Cookie处理**:如果接口有重定向或需要Cookie,WebClient默认是不自动处理的,是否需要配置相关策略? 10. **HTTP方法或URL错误**:确认POST请求的URL是否正确,是否存在拼写错误,或者是否需要使用HTTPS而不是HTTP。 接下来,我需要组织这些思考点,形成结构化的回答,帮助用户逐一排查问题。可能需要分步骤指导用户检查各个可能的问题点,并提供相应的代码示例和调试方法。 例如,首先检查MultiValueMap的使用是否正确,然后确认Content-Type设置,接着检查参数编码,然后查看日志输出,最后考虑第三方接口的特殊要求。同时,提醒用户使用工具如Postman对比请求,确保请求体的格式正确。 此外,用户可能在之前的解决方案中没有正确应用某些步骤,比如虽然设置了Content-Type,但可能由于其他配置导致覆盖了该设置,或者在构建MultiValueMap时遗漏了某些参数。需要强调详细检查每个步骤的重要性,并提供具体的代码示例来演示正确做法。 可能还需要考虑是否使用了正确的HTTP客户端,比如WebClient的配置是否正确,是否有过滤器或拦截器修改了请求,或者是否应该尝试使用RestTemplate来测试,以排除WebClient配置的问题。 总之,需要系统地引导用户检查所有可能的错误点,并提供明确的解决方案和验证步骤,确保用户能够逐步排查并解决问题。</think>在 Spring Boot 中使用 WebClient 封装第三方表单接口时,若参数已验证正确但接口仍报错,可能是 **请求构建细节** 或 **第三方接口特殊要求** 导致。以下是系统性排查指南: --- ### 一、基础代码模板验证 #### 1. 标准表单提交代码 ```java // ✅ 正确姿势:必须用 MultiValueMap 且显式声明 Content-Type MultiValueMap<String, String> formData = new LinkedMultiValueMap<>(); formData.add("username", "user123"); formData.add("password", "myp@ss"); String response = WebClient.create() .post() .uri("https://api.example.com/login") .contentType(MediaType.APPLICATION_FORM_URLENCODED) // 关键设置 .bodyValue(formData) .retrieve() .bodyToMono(String.class) .block(); ``` #### 2. 常见漏网之鱼 - **Content-Type 未设置或冲突** 若使用 `.bodyValue(普通Map)` 而非 `MultiValueMap`,Spring 可能自动序列化为 JSON,导致服务器无法解析。 - **参数顺序敏感** 部分接口要求参数按固定顺序排列(如签名计算场景),使用 `LinkedMultiValueMap` 保证顺序。 --- ### 二、高级调试技巧(关键步骤) #### 1. 捕获原始请求数据 在 `application.properties` 中开启详细网络日志: ```properties logging.level.org.springframework.web.reactive.function.client.ExchangeFunctions = DEBUG logging.level.reactor.netty.http.client = DEBUG ``` 控制台将输出实际发送的 **请求头和请求体**,例如: ```text POST /api HTTP/1.1 Content-Type: application/x-www-form-urlencoded Content-Length: 27 username=test&password=123 ``` #### 2. 对比工具验证 用 Postman 发送相同参数,抓取正常请求的原始数据,与代码生成的请求对比差异。重点关注: - 空格是否被编码为 `%20` 或 `+` - 中文字符是否转为 `%E4%B8%AD` - 参数分隔符是 `&` 还是 `;` #### 3. 编码强制验证 ```java // 手动编码单个参数 String encodedValue = URLEncoder.encode("含有空格的参数", StandardCharsets.UTF_8); formData.add("key", encodedValue); // 或使用 UriComponentsBuilder 自动编码 String queryString = UriComponentsBuilder.newInstance() .queryParam("key", "未编码值") .build() .encode() .getQuery(); ``` --- ### 三、特殊接口要求排查 #### 1. 隐藏参数需求 某些接口要求固定参数(如时间戳 `timestamp`、随机数 `nonce` 或签名 `sign`): ```java // 示例:添加签名参数 String timestamp = String.valueOf(System.currentTimeMillis()); formData.add("timestamp", timestamp); formData.add("sign", computeSign(timestamp)); ``` #### 2. 请求头校验 添加必要请求头: ```java .webClient.post() .header("User-Agent", "MyApp/1.0") // 部分接口校验 UA .header("X-Api-Key", "your-key") ``` #### 3. SSL/TLS 兼容性 若目标接口使用老旧 SSL 协议,需强制指定: ```java @Bean public WebClient webClient() { SslContext sslContext = SslContextBuilder.forClient() .protocols("TLSv1.2") // 根据目标服务器支持情况调整 .build(); HttpClient httpClient = HttpClient.create() .secure(ssl -> ssl.sslContext(sslContext)); return WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) .build(); } ``` --- ### 四、错误对照速查表 | 现象 | 快速诊断方法 | 解决方案 | |-------------------------------|--------------------------------|--------------------------------| | 返回"invalid parameter" | 对比 Postman 请求体编码格式 | 检查空格/中文是否已正确 URL 编码 | | 返回"timestamp expired" | 检查服务器时间与本地时间差 | 同步时间或添加时间容差逻辑 | | 返回"signature mismatch" | 抓取原始参数字符串重新计算签名 | 确认签名算法与参数顺序与文档一致 | | 连接超时或重置 | 使用 `curl -v` 测试网络可达性 | 配置代理或联系运维开通防火墙白名单 | --- ### 五、终极验证方案 若仍无法解决,按以下步骤定位: 1. 用 Wireshark 抓取 **代码发送的请求** 和 **Postman 成功请求** 的原始 TCP 包 2. 对比两者在 HTTP 报文层的差异(特别是消息头的顺序和请求体编码) 3. 检查是否有多余的换行符、未修剪的空格等不可见字符 通过逐层排除,可精准定位到表单提交的隐藏问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值