原文地址:Logback 整合 RabbitMQ 實現統一日志輸出
博客地址:http://www.extlight.com
一、前言
公司項目做了集群實現請求分流,由於線上或多或少會出現請求失敗或系統異常,為了查看失敗請求的日志信息,我們得將所有服務的日志文件都打開來進行問題的定位分析,操作起來非常麻煩。因此,我們開發組決定設計一套日志查看系統來解決上述問題。
二、實現思路
默認的,應用服務日志信息會保存在本地服務器的目錄中,為了方便查看日志我們應該把多台服務器日志統一輸出到一個日志文件中。
由於項目使用的 Logback 日志框架和 RabbitMQ 消息隊列,這兩者正好可以進行整合。
因此,我們可以將項目代碼中的日志輸出到 RabbitMQ 隊列中,通過 Logstash 讀取隊列數據,最后再輸出到一個日志文件中。
三、准備環境
測試環境:IP 為 192.168.2.13 的 CentOS 7 系統
3.1 RabbitMQ 配置
首先需要搭建 RabbitMQ 環境,可以參考本站《CentOS 7.2 安裝 RabbitMQ》進行搭建。
搭建完成后,登錄 RabbitMQ 的管理界面,需要操作如下步驟:
- 創建一個名為 log_queue 的隊列
- 創建一個名為 rabbit.log 的交換器(direct 類型)
- 將 log_queue 隊列綁定到 rabbit.log 交換機上
操作演示圖:
3.2 Logstash 配置文件
本站也有 Logstash 相關的博文,讀者可移至 《Logstash 基礎入門》 查看。
input {
rabbitmq {
type =>"all"
durable => true
exchange => "rabbit.log"
exchange_type => "direct"
key => "info"
host => "192.168.2.13"
port => 5672
user => "light"
password => "light"
queue => "log_queue"
auto_delete => false
}
}
output {
file {
path => "/usr/test-log/test-%{+YYYY-MM-dd}.log"
codec => multiline {
pattern => "^\d"
negate => true
what => "previous"
}
}
}
注意: multiline 是 Logstash 的插件,需要手動安裝。
配置表示 Logstash 服務從 RabbitMQ 讀取日志信息,輸出到指定的目錄文件中。
四、編碼
4.1 依賴
列出主要依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
4.2 日志文件
名為 logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定義日志文件的存儲地址 勿在 LogBack 的配置中使用相對路徑-->
<property name="LOG_HOME" value="d:/" />
<!-- 控制台輸出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化輸出,%d:日期;%thread:線程名;%-5level:級別,從左顯示5個字符寬度;%msg:日志消息;%n:換行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<appender name="RABBITMQ"
class="org.springframework.amqp.rabbit.logback.AmqpAppender">
<layout>
<pattern><![CDATA[ %d %p %t [%c] - <%m>%n ]]></pattern>
</layout>
<!--rabbitmq地址 -->
<addresses>192.168.2.13:5672</addresses>
<username>light</username>
<password>light</password>
<declareExchange>true</declareExchange>
<exchangeType>direct</exchangeType>
<exchangeName>rabbit.log</exchangeName>
<routingKeyPattern>info</routingKeyPattern>
<generateId>true</generateId>
<charset>UTF-8</charset>
<durable>true</durable>
<deliveryMode>NON_PERSISTENT</deliveryMode>
<autoDelete>false</autoDelete>
</appender>
<logger name="com.light.rabbitmq" level="info" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="RABBITMQ"/>
</logger>
<!-- 日志輸出級別,level 默認值 DEBUG,root 其實是 logger,它是 logger 的根 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="RABBITMQ" />
</root>
</configuration>
配置中的 exchangeType 和 exchangeName 就是我們上邊創建的交換機的類型和名稱。
4.3 測試類
自定義異常:
public class CustomException extends RuntimeException{
private static final long serialVersionUID = 1L;
private int code;
private String msg;
public CustomException(int code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
模擬打印日志:
@Component
public class DemoTask {
private static Logger logger = LoggerFactory.getLogger(DemoTask.class);
private int num = 1;
@Scheduled(fixedRate = 3000)
public void writeLog() {
try {
if (num % 5 == 0) {
throw new CustomException(500, "自定義異常錯誤");
}
logger.info("==={}===={}","hello rabbitmq", System.currentTimeMillis());
num++;
} catch (CustomException e) {
e.printStackTrace();
logger.error("=={}==", e);
}
}
}
五、代碼測試
執行啟動類:
@EnableScheduling
@SpringBootApplication
public class RabbitmqTestApplication {
public static void main(String[] args) {
SpringApplication.run(RabbitmqTestApplication.class, args);
}
}
執行結果如下圖:
代碼運行的日志信息已經輸出到指定日志文件中了。
補充
由於多台服務器的日志都打印到同一個文件,為了區分日志來源,我們還得需要打印出日志信息對應的主機 ip 地址。具體實現步驟如下:
- 自定義日志轉換器
需要繼承 ClassicConverter 類
public class CustomLogConverter extends ClassicConverter {
public String convert(ILoggingEvent event) {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
return null;
}
}
- 修改 logback-spring.xml 文件
以下只張貼關鍵配置信息
<conversionRule conversionWord="ip" converterClass="com.light.rabbitmq.log.CustomLogConverter" />
<appender name="RABBITMQ"
class="org.springframework.amqp.rabbit.logback.AmqpAppender">
<layout>
<pattern><![CDATA[%ip %date{yyyy-MM-dd HH:mm:ss} | %highlight(%-5level) | %yellow(%thread) | %green(%logger) | %msg%n ]]></pattern>
</layout>
</appender>