java项目日志输出思考

120 阅读2分钟

背景

项目需要快速跟踪方法调用链与当前节点信息

  • 开发模式使用debug
  • 生产模式使用日志

需要达到几点要求

  • 项目上线时不需要临时修改配置文件打包应用(临时切换参数往往造成上线升级失败),而是通过打包命令(如maven -Ppro )或是启动命令来指定(启动脚本)
  • 日志中能体现服务名称 & 端口 。便于nginx日志快速定位到服务节点。
[pro-recharge-txp-8091]07-24 05:59:55.806
[pro-recharge-txp-8191]07-24 05:59:55.745
[pro-recharge-boss]07-24 06:39:26.042
// 上面三个内容分别是代表 txp二个节点; boss一个节点
  • 单个日志文件不能过大,太大不便于view.自动按指定的大小进行切割归档。
  • 日志服务调用链唯一标识

单体应用

指非微服务模式,支持高可用分布式部署(多机或是一机多包)。通过nginx时转发ip与端口快速定位节点,另nginx开启请求内容输出更方便。

103.45.248.43 - - [25/Jul/2022:06:44:16 +0800] "POST /api/v1.1.0/query HTTP/1.1" 200 424 "-" "-" "-" ["customerNo=1000055&outTradeNo=202207258884409924×tamp=2

0220725064415&sign=450DF9FEA5C851B4CDB00568C8BA92B4"] "103.92.132.118:8191" "200" "0.099" "0.042"
-- 此处有请求内容唯一标识 & 对应转发节点信息

springmvc+tomcat

springmvc项目很多跑在独立tomcat服务器上,对外提供的端口由tomcat指定。springmvc项目启动需要获取tomcat指定端口注入到日志框架(logback.xml)

如何在logback日志配置里获取服务器ip和端口

以下为结合片段

  • 启动服务时,获取tomcat的端口

import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
 
/**
 * 配合logback.xml 获取web服务器(如tomcat) 的端口。
 * @author xiaoming
 * @date 2019/5/14 11:37
 * @description
 */
public class LogPortConfig extends ClassicConverter {
    private static final Logger logger = LoggerFactory.getLogger(LogPortConfig.class);
    private static String webPort;
 
    static {
        try {
            List<MBeanServer> serverList = MBeanServerFactory.findMBeanServer(null);
            for (MBeanServer server : serverList) {
                Set<ObjectName> names = new HashSet<ObjectName>();
                names.addAll(server.queryNames(new ObjectName("Catalina:type=Connector,*"), null));
                Iterator<ObjectName> it = names.iterator();
                while (it.hasNext()) {
                    ObjectName oName = (ObjectName) it.next();
                    String pValue = (String) server.getAttribute(oName, "protocol");
                    if (StringUtils.equals("HTTP/1.1", pValue) || StringUtils.contains(pValue,"Http11NioProtocol")) {
                        webPort = ObjectUtils.toString(server.getAttribute(oName, "port"));
                    }
                }
            }
        } catch (Exception e) {
            logger.error("获取port失败,影响logback的文件拼接", e);
            webPort = null;
        }
    }
 
    @Override
    public String convert(ILoggingEvent event) {
        return webPort;
    }
}
  • 在logback.xml中进行配置: 查看tport用法
```
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
  <property name="logPath" value="${catalina.base}/logs"/>

  <conversionRule conversionWord="tport" converterClass="com.dx.exiaoka.recharge.txp.config.LogPortConfig"/>

  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{0} - %msg%n</pattern>
    </encoder>
  </appender>


  <!--  按日期与小时来进行配置 -->
  <appender name="infoFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${logPath}/recharge_txp.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

      <!--文件的路径与名称,{yyyy-MM-dd.HH}精确到小时,则按小时分割保存${logPath}/recharge_txp.%d{yyyy-MM-dd.HH}.log  ; 分为${logPath}/recharge_txp.%d{yyyy-MM-dd.HH.mm}.log -->
      <fileNamePattern>${logPath}/recharge_txp.%d{yyyy-MM-dd-HH}.log</fileNamePattern>

      <!-- 如果当前是按小时保存,则保存72小时(=3天)内的日志 -->
      <maxHistory>72</maxHistory>
    </rollingPolicy>
    <encoder>
      <pattern>[dev-recharge-txp-%tport] %d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger:[%M] %line - %msg%n
      </pattern>
    </encoder>
  </appender>

  <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="infoFile"/>
  </appender>

  <appender name="warnFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${logPath}/warn_recharge_txp.log</file>
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>WARN</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${logPath}/warn_recharge_txp.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>
    <encoder>
      <pattern>%d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{0} - %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="errorFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${logPath}/error_recharge_txp.log</file>
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
      <level>ERROR</level>
    </filter>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${logPath}/error_recharge_txp.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>
    <encoder>
      <pattern>%d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{0} - %msg%n</pattern>
    </encoder>
  </appender>

  <!--  <logger name="com.dx" level="INFO" />-->
  <logger name="com.alisoft" level="WARN"/>
  <logger name="com.google.code.yanf4j" level="WARN"/>
  <logger name="net.rubyeye" level="WARN"/>
  <logger name="com.ibatis.common.jdbc" level="debug"/>
  <logger name="Java.sql" level="debug"/>
  <logger name="org.springframework.scheduling.quartz" level="info" additivity="false"/>
  <logger name="org.quartz.impl" level="info" additivity="false"/>
  <logger name="httpclient" level="INFO" additivity="false"/>


  <root level="debug">
    <appender-ref ref="console"/>
    <!--    <appender-ref ref="ASYNC"/>-->
    <appender-ref ref="infoFile"/>
    <appender-ref ref="warnFile"/>
    <appender-ref ref="errorFile"/>
  </root>
</configuration>

微服务

....