Java 日志中打印服务器 IP,快速区分多服务器日志归属
方案类型适用场景优点缺点手动拼接 IP临时调试实现简单、快速生效业务代码侵入、不优雅MDC + 日志配置生产环境无代码侵入、全局生效需配置日志框架。
·
一、核心需求与背景
当多台服务器(如两台应用服务器)运行相同代码时,日志文件 / 日志平台中无法直接区分日志来自哪台机器,排查问题时效率极低。解决思路是:在日志中固定输出当前服务器的 IPv4 地址,通过 IP 字段快速定位日志归属。
二、前置知识:Java 获取服务器有效 IPv4
要打印 IP 首先要能正确获取服务器的真实业务 IPv4(排除回环地址、虚拟网卡),以下是封装好的通用工具类:
1. IPv4 获取工具类(ServerIpUtils)
import java.net.*; |
|
import java.util.Enumeration; |
|
/** |
|
* 服务器IP工具类:获取真实业务IPv4,缓存IP提升性能 |
|
*/ |
|
public class ServerIpUtils { |
|
// 静态缓存本机IP,仅应用启动时获取一次 |
|
private static final String LOCAL_IP; |
|
// 静态代码块初始化IP |
|
static { |
|
LOCAL_IP = getMainLocalIpv4(); |
|
} |
|
/** |
|
* 核心方法:获取服务器对外通信的主IPv4 |
|
* 过滤回环地址、禁用网卡、虚拟网卡(Docker/VPN等) |
|
*/ |
|
private static String getMainLocalIpv4() { |
|
try { |
|
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); |
|
while (interfaces.hasMoreElements()) { |
|
NetworkInterface ni = interfaces.nextElement(); |
|
// 过滤规则:跳过回环/禁用/虚拟网卡 |
|
if (ni.isLoopback() || !ni.isUp() |
|
|| ni.getName().startsWith("docker") |
|
|| ni.getName().startsWith("veth")) { |
|
continue; |
|
} |
|
// 遍历网卡下的IP地址 |
|
Enumeration<InetAddress> addresses = ni.getInetAddresses(); |
|
while (addresses.hasMoreElements()) { |
|
InetAddress addr = addresses.nextElement(); |
|
// 仅保留IPv4地址 |
|
if (addr instanceof Inet4Address) { |
|
return addr.getHostAddress(); |
|
} |
|
} |
|
} |
|
} catch (SocketException e) { |
|
e.printStackTrace(); |
|
} |
|
// 兜底返回回环地址 |
|
return "127.0.0.1"; |
|
} |
|
/** |
|
* 对外提供获取本机IP的方法 |
|
*/ |
|
public static String getLocalIp() { |
|
return LOCAL_IP; |
|
} |
|
} |
工具类关键说明
- 缓存优化:通过静态代码块初始化 IP,仅在应用启动时获取一次,避免每次打印日志遍历网卡,提升性能;
- 精准过滤:排除回环地址(127.0.0.1)、禁用网卡、Docker/VPN 虚拟网卡,确保获取服务器真实业务 IP;
- 跨平台兼容:适配 Linux/Windows 服务器,无需修改即可使用。
三、两种日志打印 IP 的实现方案
方案 1:快速调试 - 手动拼接 IP(适合临时排查)
直接在日志语句中拼接 IP 字段,快速生效,适合临时调试场景。
代码示例
import org.slf4j.Logger; |
|
import org.slf4j.LoggerFactory; |
|
public class BusinessService { |
|
// 初始化日志对象 |
|
private static final Logger logger = LoggerFactory.getLogger(BusinessService.class); |
|
// 获取服务器IP(启动时加载,全局复用) |
|
private static final String SERVER_IP = ServerIpUtils.getLocalIp(); |
|
public void doBusiness() { |
|
// 日志中拼接IP前缀,清晰标识服务器 |
|
logger.info("[SERVER_IP:{}] 执行业务逻辑,参数:{}", SERVER_IP, "test123"); |
|
logger.error("[SERVER_IP:{}] 业务执行失败,异常信息:{}", SERVER_IP, "空指针异常"); |
|
} |
|
public static void main(String[] args) { |
|
new BusinessService().doBusiness(); |
|
} |
|
} |
输出效果
2026-02-25 10:00:00.123 INFO [main] com.example.BusinessService - [SERVER_IP:192.168.10.20] 执行业务逻辑,参数:test123 |
|
2026-02-25 10:00:01.456 ERROR [main] com.example.BusinessService - [SERVER_IP:192.168.10.21] 业务执行失败,异常信息:空指针异常 |
方案 2:生产环境 - MDC + 日志框架配置(推荐)
通过日志框架(Logback/Log4j2)的 MDC(映射诊断上下文)实现 IP 自动附加,业务代码无侵入,符合生产环境最佳实践。
步骤 1:SpringBoot 项目初始化 MDC
在应用启动时将 IP 放入 MDC,全局生效:
import org.slf4j.MDC; |
|
import org.springframework.boot.SpringApplication; |
|
import org.springframework.boot.autoconfigure.SpringBootApplication; |
|
import javax.annotation.PostConstruct; |
|
@SpringBootApplication |
|
public class ServerApplication { |
|
public static void main(String[] args) { |
|
SpringApplication.run(ServerApplication.class, args); |
|
} |
|
/** |
|
* 项目启动后初始化MDC,添加服务器IP |
|
* @PostConstruct:Bean初始化完成后执行 |
|
*/ |
|
@PostConstruct |
|
public void initMdc() { |
|
MDC.put("SERVER_IP", ServerIpUtils.getLocalIp()); |
|
} |
|
} |
步骤 2:配置 Logback 日志格式(logback-spring.xml)
修改日志输出模板,自动包含 MDC 中的SERVER_IP字段:
<?xml version="1.0" encoding="UTF-8"?> |
|
<configuration> |
|
<!-- 控制台输出 --> |
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> |
|
<encoder> |
|
<!-- 日志格式:时间 [线程] [服务器IP] 级别 类名 - 内容 --> |
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [SERVER_IP:%X{SERVER_IP}] %-5level %logger{50} - %msg%n</pattern> |
|
<charset>UTF-8</charset> |
|
</encoder> |
|
</appender> |
|
<!-- 文件输出 --> |
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
|
<file>/logs/app.log</file> |
|
<!-- 按天分割日志 --> |
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> |
|
<fileNamePattern>/logs/app.%d{yyyy-MM-dd}.log</fileNamePattern> |
|
</rollingPolicy> |
|
<encoder> |
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [SERVER_IP:%X{SERVER_IP}] %-5level %logger{50} - %msg%n</pattern> |
|
<charset>UTF-8</charset> |
|
</encoder> |
|
</appender> |
|
<!-- 根日志级别 --> |
|
<root level="INFO"> |
|
<appender-ref ref="CONSOLE"/> |
|
<appender-ref ref="FILE"/> |
|
</root> |
|
</configuration> |
步骤 3:业务代码正常打印日志
无需手动拼接 IP,日志框架自动附加:
logger.info("执行业务逻辑,参数:{}", "test123"); |
|
logger.error("业务执行失败,异常:{}", e.getMessage()); |
最终输出效果
2026-02-25 10:05:00.123 [main] [SERVER_IP:192.168.10.20] INFO com.example.BusinessService - 执行业务逻辑,参数:test123 |
|
2026-02-25 10:05:01.456 [main] [SERVER_IP:192.168.10.21] ERROR com.example.BusinessService - 业务执行失败,异常:空指针异常 |
四、关键注意事项
- IP 准确性:工具类过滤了 Docker、VPN 等虚拟网卡,确保获取的是服务器真实业务 IP;
- 性能优化:IP 仅在应用启动时获取一次并缓存,避免高频日志场景下重复遍历网卡;
- 兼容性:工具类兼容 JDK8+,支持 Linux/Windows 服务器,无需额外依赖;
- 日志框架适配:Log4j2 配置逻辑与 Logback 一致,仅需修改日志格式中的
%X{SERVER_IP}(Log4j2 中同样生效)。
五、总结
| 方案类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 手动拼接 IP | 临时调试 | 实现简单、快速生效 | 业务代码侵入、不优雅 |
| MDC + 日志配置 | 生产环境 | 无代码侵入、全局生效 | 需配置日志框架 |
更多推荐
所有评论(0)