我們很多機器是集羣部署,如果單獨去每臺機器上面去找日誌會很麻煩。另外很多生產上機器會對部分人員進行限制,但卻需要他們去定位生產上的問題。這裏通過繼承AbstractAppender實現log4j的自定義標籤來發送到同一服務器集中處理。
1、首先定義一個類繼承AbstractAppender
/**
* name 爲log.xml裏面配置標籤
* @author yin
* @Method
*/
@Plugin(name = "LOG_SEND", category = "Core", elementType = "appender", printObject = true)
public class LogAppender extends AbstractAppender {
protected LogAppender(String name, Filter filter, Layout<? extends Serializable> layout) {
super(name, filter, layout);
}
/**
* 加載類之前會調用的方法
*/
@Override
public void start() {
//QueueUtil.execSys();
super.start();
}
/**
* 類結束之前調用方法
*/
@Override
public void stop() {
super.stop();
}
/**
* 日誌輸入的具體操作方式
* @param logEvent
*/
@Override
public void append(LogEvent logEvent) {
try {
ThrowableProxy thrownProxy = logEvent.getThrownProxy();
LogData logData;
if (Objects.isNull(thrownProxy)) {
logData= new LogData(logEvent.getMessage().getFormattedMessage(), logEvent.getThreadName(),
logEvent.getLevel().name(), logEvent.getTimeMillis(), logEvent.getLoggerName(),MachineIp.getIp());
}else {
StringBuilder sb = new StringBuilder(logEvent.getMessage().getFormattedMessage());
sb.append("\n");
dealErrorContent(thrownProxy, sb);
for (ThrowableProxy throwableProxy = thrownProxy.getCauseProxy(); !Objects.isNull(throwableProxy); throwableProxy = thrownProxy.getCauseProxy()) {
sb.append("Caused by: ");
dealErrorContent(thrownProxy, sb);
}
logData= new LogData(sb.toString(), logEvent.getThreadName(),
logEvent.getLevel().name(), logEvent.getTimeMillis(), logEvent.getLoggerName(),MachineIp.getIp());
}
//System.out.println("========================================logData====" + logData);
QueueUtil.getInstance().offer(logData);
} catch (Exception e) {
System.out.println("log4j httpAppender error !" + e);
}
}
/**
* 通過配置工廠來創建創建 LogAppender
* @param filter
* @param layout
* @param name
* @return
*/
@PluginFactory
public static LogAppender createLogAppender(@PluginElement("Filter") Filter filter, @PluginElement("layout") Layout<? extends Serializable> layout, @PluginAttribute("name") String name) {
if (name == null) {
LOGGER.error("No name provided for LogAppender");
return null;
}
if (layout == null) {
layout = PatternLayout.createDefaultLayout();
}
return new LogAppender(name, filter, layout);
}
private StringBuilder dealErrorContent(ThrowableProxy throwableProxy,StringBuilder sb ){
sb.append(throwableProxy.getName());
sb.append(": ");
sb.append(throwableProxy.getMessage());
sb.append("\n");
StackTraceElement[] elements = throwableProxy.getStackTrace();
for (int i = 0, elementLength = elements.length; i < elementLength; i++) {
sb.append("\t");
StackTraceElement element = elements[i];
sb.append(String.valueOf(element));
sb.append("\n");
}
return sb;
}
}
2、定義了標籤需要在log4j.xml添加對應標籤,需要增加
<LOG_SEND name="log_send">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%-5p] {%F:%L} - %m%n" />
</LOG_SEND>
<AppenderRef ref="log_send"/>
下面是完整的log4j.xml的內容
<?xml version="1.0" encoding="UTF-8"?>
<!--日誌級別以及優先級排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration後面的status,這個用於設置log4j2自身內部的信息輸出,可以不設置,當設置成trace時,你會看到log4j2內部各種詳細輸出-->
<!--monitorInterval:Log4j能夠自動檢測修改配置 文件和重新配置本身,設置間隔秒數-->
<configuration status="warn" monitorInterval="500">
<!--這裏相當於配置變量,後面在 <Appenders> 標籤引入 用${}-->
<Properties>
<Property name="LOG_HOME">logs</Property>
<!-- 控制檯默認輸出格式,"%d":表示打印的時間 "%5level":日誌級別,
%thread 表示打印日誌的線程 "%c":表示類 "%L":表示行號 "%msg" 表示打印的信息 %n 換行 %throwable 表示錯誤信息
%style 和{bright,green} 結合表示展示顏色 %highlight
所以影響日誌輸出的性能 -->
<!--<Property name="PATTERN">[%style{%d{yyyy-MM-dd HH:mm:ss:SSS}}] |-%-5level [%thread] %c [%L] -| %msg%n</Property>-->
<Property name="CONSOLE">[%style{%d{yyyy-MM-dd HH:mm:ss:SSS}}{bright,green}] | [%highlight{%5level}] [%thread] [%style{%c}{bright,yellow}] [%style{%L}{bright,blue}] -| %highlight{%msg}%n%style{%throwable}{red}</Property>
<Property name="FILE">%date{yyy-MM-dd HH:mm:ss.SSS} %level [%file:%line] : %msg%n</Property>
</Properties>
<Appenders>
<LOG_SEND name="log_send">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%-5p] {%F:%L} - %m%n" />
</LOG_SEND>
<!--生成文件的文件名,當天生成日誌 log.log ,保存其他天日誌爲log-%d{yyyy-MM-dd}.log-->
<RollingFile name="file" fileName="${LOG_HOME}/log.log" filePattern="${LOG_HOME}/log-%d{yyyy-MM-dd}.log">
<!--${FILE} 引入<Property> 標籤的文件格式-->
<PatternLayout pattern="${FILE}"/>
<Policies>
<!--基於時間的觸發策略。該策略主要是完成周期性的log文件封存工作。有兩個參數:
interval,integer型,指定兩次封存動作之間的時間間隔,modulate,boolean型,說明是否對封存時間進行調製。-->
<!--設置每天打包日誌一次-->
<TimeBasedTriggeringPolicy modulate="true" interval="1"></TimeBasedTriggeringPolicy>
</Policies>
<DefaultRolloverStrategy>
<!--要訪問的目錄的最大級別數。值爲0表示僅訪問起始文件,2表示能訪問一下兩級目錄-->
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="*/*.log">
<!--刪除超過十天文件-->
<IfLastModified age="10d"/>
</IfFileName>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
<!--添加一個控制檯追加器,添加之後在AppenderRef 中引用,就會生效-->
<Console name="CONSOLE" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${CONSOLE}"/>
</Console>
</Appenders>
<Loggers>
<!-- 生產環境下,將此級別配置爲適合的級別,以免日誌文件太多或影響程序性能 -->
<!--這裏指代生效的日誌級別和 輸出的內容,這裏代表生效的是 debug ,
文件和控制檯都會輸出,如果是在環境留下file就可以了,file和CONSOLE 來自於 <Appenders> 配置的標籤 -->
<Root level="info">
<AppenderRef ref="file"/>
<AppenderRef ref="CONSOLE"/>
<AppenderRef ref="log_send"/>
</Root>
</Loggers>
</configuration >
3、定義一個我們需要關於日誌基本的類
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LogData {
private String message;
private String threadName;
private String logLevel;
private long time;
private String className;
private String hostIp;
}
4、自定義一個守護線程不斷髮送日誌到日誌服務器統一管理
public class QueueUtil {
private static Logger logger = LoggerFactory.getLogger(QueueUtil.class);
private static final Integer MAX_RETRY = 3;
private QueueUtil(){
initTask();
}
private static final Executor executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("logAppendQueue");
thread.setDaemon(true);
return thread;
}
});
private static final ScheduledExecutorService scheduled_thread_pool = Executors.newScheduledThreadPool(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("scheduled thread pool");
thread.setDaemon(true);
return thread;
}
});
/* private static final DateTimeFormatter formtter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void execSys(){
scheduled_thread_pool.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("sdfdfdfdf"+formtter.format(LocalDateTime.now()));
}
}, 0L, 1L, TimeUnit.SECONDS);
}*/
private static final BlockingQueue<LogData> LOG_QUEUE = new LinkedBlockingQueue<>(2000);
private static ExecutorService executor1=new ThreadPoolExecutor(1, 1,
0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1),new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("logAppendQueue");
thread.setDaemon(true);
return thread;
}
});
private void initTask(){
executor1.execute(new Runnable() {
@Override
public void run() {
List<LogData> logDatas = new ArrayList<>();
while (true) {
while (true) {
try {
List<LogData> tmpLst = new ArrayList<>(100);
LOG_QUEUE.drainTo(tmpLst, 100);
if (!CollectionUtils.isEmpty(tmpLst)) {
logDatas.addAll(tmpLst);
if (logDatas.size() >= 500) {
sendMessageRetry(logDatas,0);
sleepSeconds(1);
}
}else {
if (logDatas.size() > 0) {
sendMessageRetry(logDatas,0);
sleepSeconds(1);
}
}
} catch (Exception e) {
logger.error("log appender send message fail:", e);
sleepSeconds(2);
}
}
}
}
});
}
public boolean offer(LogData logData) {
return LOG_QUEUE.offer(logData);
}
public static QueueUtil getInstance(){
return QueueUtil.QueueUtilHelper.queueUtil;
}
private void sleepSeconds(int time) {
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
logger.error("sleep Interrupt", e);
}
}
private void sendMessageRetry(List<LogData> logDataLst,int currentTime) {
try {
System.out.println("發送消息"+logDataLst);
} catch (Exception e) {
logger.error("sendMessageRetry fail ,current time is :{} time", currentTime, e);
if(currentTime>=MAX_RETRY){
return;
}
sendMessageRetry(logDataLst, ++currentTime);
sleepSeconds(2);
}finally {
if (!CollectionUtils.isEmpty(logDataLst)) {
logDataLst.clear();
}
}
}
public static class QueueUtilHelper {
private static QueueUtil queueUtil=new QueueUtil();
public QueueUtilHelper() {
}
}
}
6、補充上需要獲取當前機器的Ip的工具類
public class MachineIp {
private static volatile String ipAddr;
public static String getHostIp() {
String sIP = "";
InetAddress ip = null;
try {
boolean bFindIP = false;
Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
while (netInterfaces.hasMoreElements()) {
if (bFindIP){
break;
}
NetworkInterface ni = netInterfaces.nextElement();
Enumeration<InetAddress> ips = ni.getInetAddresses();
while (ips.hasMoreElements()) {
ip = ips.nextElement();
if (!ip.isLoopbackAddress()
&& ip.getHostAddress().matches("(\\d{1,3}\\.){3}\\d{1,3}")) {
bFindIP = true;
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
if (null != ip){
sIP = ip.getHostAddress();
}
return sIP;
}
public static String getIp() {
if (null != ipAddr) {
return ipAddr;
} else {
Enumeration netInterfaces;
try {
netInterfaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException var6) {
throw new HostException(var6);
}
String localIpAddress = null;
while(netInterfaces.hasMoreElements()) {
NetworkInterface netInterface = (NetworkInterface)netInterfaces.nextElement();
Enumeration ipAddresses = netInterface.getInetAddresses();
while(ipAddresses.hasMoreElements()) {
InetAddress ipAddress = (InetAddress)ipAddresses.nextElement();
if (isPublicIpAddress(ipAddress)) {
String publicIpAddress = ipAddress.getHostAddress();
ipAddr = publicIpAddress;
return publicIpAddress;
}
if (isLocalIpAddress(ipAddress)) {
localIpAddress = ipAddress.getHostAddress();
}
}
}
ipAddr = localIpAddress;
return localIpAddress;
}
}
private static boolean isPublicIpAddress(InetAddress ipAddress) {
return !ipAddress.isSiteLocalAddress() && !ipAddress.isLoopbackAddress() && !isV6IpAddress(ipAddress);
}
private static boolean isLocalIpAddress(InetAddress ipAddress) {
return ipAddress.isSiteLocalAddress() && !ipAddress.isLoopbackAddress() && !isV6IpAddress(ipAddress);
}
private static boolean isV6IpAddress(InetAddress ipAddress) {
return ipAddress.getHostAddress().contains(":");
}
public static String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException var1) {
throw new HostException(var1);
}
}
}