最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Log4j2代碼方式配置實現(xiàn)線程級動態(tài)控制

 更新時間:2021年12月22日 14:45:08   作者:檸檬睡客  
這篇文章主要介紹了使用Log4j2代碼方式配置實現(xiàn)線程級動態(tài)控制,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

一 需求

最近平臺進行升級,要求日志工具從Log4j升級到Log4j2,以求性能上的提升。之前我寫過以代碼方式的配置Log4j,來實現(xiàn)線程級日志對象的管理,今天把版本升級到Log4j2,依然采用原有思路來做,但是實現(xiàn)上有諸多區(qū)別,這是因為Log4j2的實現(xiàn)較老版本改動太多。

至于以配置文件方式配置的方法,我不做介紹,對于Log4j2的詳細實現(xiàn)亦然,這些部分有興趣的朋友可以自己網(wǎng)絡(luò)搜索,也可以自行跟蹤源碼查閱。

主要需求為每個線程單獨一個日志對象處理,可動態(tài)控制日志輸出級別等參數(shù),可控制的同步及異步輸出模式,標準的日志頭格式等。

大體思路同我之前寫的Log4j配置,只不過日志對象的創(chuàng)建管理等功能實現(xiàn)上略有區(qū)別。

二 對外暴露的接口

設(shè)計的重中之重,這部分一旦設(shè)計完成便不可輕易改動,后續(xù)的維護只能盡其可能的向前兼容。

首先我們需要提供對外可用的預(yù)設(shè)參數(shù)值,包括日志輸出等級、日志的輸出目標(文件、控制臺、網(wǎng)絡(luò)及數(shù)據(jù)庫等)等,這些值設(shè)定我們均以static final來修飾。

第二部分是全局參數(shù)的設(shè)置,這些參數(shù)不隨日志對象的狀態(tài)而發(fā)生變動,反而是作為日志對象構(gòu)造的屬性值來源,比如說日志文件的路徑、日志文件的最大容量、日志文件的備份數(shù)量,以及每個線程的日志對象初始的日志輸出等級等等。這部分參數(shù)可以從外部配置讀取,當然也需要有默認值設(shè)定,屬全局參數(shù)配置,以static修飾。

第三部分為日志輸出的接口定義,這部分可有可無,但是對于大型項目來說極為重要,以規(guī)范的接口進行日志輸出,可以為日志的采集、分析帶來巨大便利,如通訊日志、異常日志等內(nèi)容的格式化輸出。

所以,按總體思路預(yù)先定義日志工具的外對暴露接口,如下:

public class LogUtil
{
	public static final int LevelTrace = 0;
	public static final int LevelDebug = 1;
	public static final int LevelInfo = 2;
	public static final int LevelWarn = 3;
	public static final int LevelError = 4;
	public static final String[] LevelName = new String[]
	{ "TRACE", "DEBUG", "INFO", "WARN", "ERROR" };
	public static final String TypeCommunication = "comm";
	public static final String TypeProcess = "proc";
	public static final String TypeException = "exce";
	public static final int AppenderConsole = 1;
	public static final int AppenderFile = 2;
	private static int DefaultLogLevel = LevelDebug;
	private static String FilePath = null;
	private static String FileSize = "100MB";
	private static int BackupIndex = -1;
	private static int BufferSize = 0;
	private static String LinkChar = "-";
	private static int LogAppender = AppenderFile;	
	public static void log(int logLevel, String logType, String logText)
	{
		getThreadLogger().log(logLevel, logType, logText);
	}
	public static void logCommunication()
	{
		// TODO
	}
	
	public static void logException()
	{
		// TODO
	}	
	……
}

這里我暫定了一個公用的日志輸出接口名為log(),參數(shù)列表為日志的輸出等級、日志的類型以及日志內(nèi)容。而通訊日志的輸出接口為logCommunication(),其參數(shù)列表暫時空著,按各位讀者的需求自行填寫,異常日志的輸出亦然。后文我會在測試部分對其填寫。

為了描述方便,我僅定義了兩個日志輸出目標,一為控制臺,二為文件。

三 代碼方式配置Log4j2日志對象

接下來是重頭戲,如果不采取配置文件的方式進行Log4j2的配置,那么Log4j2會自行采用默認的配置,這不是我們想要的。雖然這個過程我們無法選擇,也規(guī)避不了,但是我們最后都是使用Logger對象來進行日志輸出的,所以我們只需要按需構(gòu)造Logger對象,并將其管理起來即可。

這里提供一個思路,首先LogUtil維護一組線程級日志對象,然后這一組線程級日志對象共同訪問同一組Logger對象。

跟蹤源碼我發(fā)現(xiàn)Log4j2對Logger對象的構(gòu)造還是較為復雜的,使用了大量的Builder,其實較為早期的版本中也提供了構(gòu)造函數(shù)方式來初始化對象,但是后期的版本卻都被標記了@depreciation。對于Builder模式大家自己查閱其他信息了解吧。

好消息是Log4j2的核心組件依然是Appender、Logger,只不過提供了更多的可配置內(nèi)容,包括日志同時按日期和文件大小進行備份,這才是我想要的,之前寫Log4j的時候我可是自己派生了一個Appender類才實現(xiàn)的同時備份。

向控制臺輸出的Appender具體類型為ConsoleAppender,我使用默認的構(gòu)造函數(shù)來處理,這是唯一一個被公開出來,且沒有被@depreciation修飾的構(gòu)造函數(shù),想來只要是能通過默認配置實現(xiàn)的都是被Log4j2認可的吧,不然為啥要弄這么多Builder嘞。

向文件輸出的Appender我采用RollingFileAppender,無他,就是為了能夠?qū)崿F(xiàn)同時按日期及文件大小進行備份,而且該Appender可適用全局異步模式,性能較AsyncAppender高了不少。它的創(chuàng)建方式要麻煩許多,因為我需要設(shè)置觸發(fā)器來控制何時觸發(fā)備份,以及備份策略。

整體設(shè)計如下:

private Logger createLogger(String loggerName)
{
		Appender appender = null;
		PatternLayout.Builder layoutBuilder = PatternLayout.newBuilder();
		layoutBuilder.withCharset(Charset.forName(DefaultCharset));
		layoutBuilder.withConfiguration(loggerConfiguration);
		Layout<String> layout = layoutBuilder.build();
		if (LogUtil.AppenderConsole == LogUtil.getAppender())
		{
			appender = ConsoleAppender.createDefaultAppenderForLayout(layout);
		}
		if (LogUtil.AppenderFile == LogUtil.getAppender())
		{
			RollingFileAppender.Builder<?> loggerBuilder = RollingFileAppender.newBuilder();
			if (LogUtil.getBufferSize() > 0)
			{
				loggerBuilder.withImmediateFlush(false);
				loggerBuilder.withBufferedIo(true);
				loggerBuilder.withBufferSize(LogUtil.getBufferSize());
				System.setProperty(AsyncPropKey, AsyncPropVal);
			}
			else
			{
				loggerBuilder.withImmediateFlush(true);
				loggerBuilder.withBufferedIo(false);
				loggerBuilder.withBufferSize(0);
				System.getProperties().remove(AsyncPropKey);
			}
			loggerBuilder.withAppend(true);
			loggerBuilder.withFileName(getFilePath(loggerName));
			loggerBuilder.withFilePattern(spellBackupFileName(loggerName));
			loggerBuilder.withLayout(layout);
			loggerBuilder.withName(loggerName);
			loggerBuilder.withPolicy(CompositeTriggeringPolicy.createPolicy(
					SizeBasedTriggeringPolicy.createPolicy(LogUtil.getFileSize()),
					TimeBasedTriggeringPolicy.createPolicy("1", "true")));
			loggerBuilder.withStrategy(DefaultRolloverStrategy.createStrategy(
					LogUtil.getBackupIndex() > 0 ? String.valueOf(LogUtil.getBackupIndex()) : "-1", "1",
					LogUtil.getBackupIndex() > 0 ? null : "nomax", null, null, true, loggerConfiguration));
			appender = loggerBuilder.build();
		}
		appender.start();
		loggerConfiguration.addAppender(appender);
		AppenderRef appenderRef = AppenderRef.createAppenderRef(loggerName, Level.ALL, null);
		AppenderRef[] appenderRefs = new AppenderRef[]
		{ appenderRef };
		LoggerConfig loggerConfig = LoggerConfig.createLogger(false, Level.ALL, loggerName, "false", appenderRefs, null,
				loggerConfiguration, null);
		loggerConfig.addAppender(appender, null, null);
		loggerConfiguration.addLogger(loggerName, loggerConfig);
		loggerContext.updateLoggers();
		loggerConfiguration.start();
		Logger logger = LogManager.getLogger(loggerName);
		return logger;
}

注意!!我在初始化Logger對象的時候,是根據(jù)LogUtil是否開啟了異步輸出模式來判定是否需要開啟全局異步模式的,這里簡單說一些Log4j2的異步模式。

Log4j2提供了兩種異步模式,第一種是使用AsyncAppender,這種方式跟我以前寫的Log4j的異步輸出模式一樣,都是單獨開啟一個線程來輸出日志。第二種方式是AsychLogger,這個就厲害了,官推,而且官方提供了兩種使用模式,一個是混合異步,一個是全局異步,全局異步時不需要對配置文件進行任何改動,盡在應(yīng)用啟動時添加一個jvm參數(shù)即可,并且據(jù)壓測數(shù)據(jù)顯示,全局異步模式下性能飆升。

更多的配置信息建議讀者朋友自行查閱官方文檔。

最后還有一點需要注意的是,Log4j2的全局異步是要依賴隊列緩存的,其實現(xiàn)采用的是disruptor,所以需要依賴這個外部jar,不然在初始化Logger對象的時候,你會看到相關(guān)異常。

貼一下依賴的Maven:

<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-core</artifactId>
	<version>2.8.2</version>
</dependency>
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-api</artifactId>
	<version>2.8.2</version>
</dependency>
<dependency>
	<groupId>com.lmax</groupId>
	<artifactId>disruptor</artifactId>
	<version>3.3.6</version>
</dependency>

嚴重強調(diào):AsyncAppender、AsyncLogger以及全局異步的System.property設(shè)置,不要同時出現(xiàn)?。?!

最最后,我特么還是要嘴賤啰嗦一下,不見得“異步”就是好的,你想想看異步模式無非是額外線程或者緩存來實現(xiàn),這些也是要吃資源的,日志量大的場景下其帶來的收益很高,但小量日志場景下其對性能資源的消耗很可能大于其帶來的性能收益,請酌情使用。

四 線程級日志對象的設(shè)計

按上文中的設(shè)計思路,LogUtil持有一組線程級日志對象,而這一組日志對象又共享一組Logger對象。延續(xù)Log4j版本的設(shè)計,線程級日志對象類型依然為ThreadLogger,其基礎(chǔ)屬性為線程ID、日志類型以及日志的輸出級別等。

為了解決多并發(fā)問題,使用ConcurrentHashMap來存儲Logger對象,其Key值為線程ID,這里需要注意的是ConcurrentHashMap的put操作盡管能保證可見性,但是不能保證操作的原子性,所以在其put操作上需要額外加鎖。

class ThreadLogger
{
	private static final String AsyncPropKey = "log4j2.contextSelector";
	private static final String AsyncPropVal = "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector";
	private static final String DefaultCharset = "UTF-8";
	private static final String DefaultDateFormat = "yyyy-MM-dd HH:mm:ss.SSSSSS";
	private static final Object LoggerMapLock = new Object();
	private static final ConcurrentHashMap<String, Logger> LoggerMap = new ConcurrentHashMap<>();
	private static LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
	private static Configuration loggerConfiguration = loggerContext.getConfiguration();
	public static void cleanLoggerMap()
	{
		synchronized (LoggerMapLock)
		{
			LoggerMap.clear();
		}
	}
	
	private Logger getLogger(String loggerName)
	{
		if (StringUtil.isEmpty(loggerName))
			return null;
		Logger logger = LoggerMap.get(loggerName);
		if (logger == null)
		{
			synchronized (LoggerMapLock)
			{
				logger = createLogger(loggerName);
				LoggerMap.put(loggerName, logger);
			}
		}
		return logger;
	}
}

五 標準日志頭

頭部內(nèi)容應(yīng)包含當前的日志輸出級別,日志打印所在的類名、行號,當前的線程號、進程號等信息。這里簡單介紹下進程號及類名行號的獲取。

每個應(yīng)用進程的進程號唯一,所以僅在第一次獲取該信息時獲取即可,類名行號則通過方法棧的棧信息獲取,如下:

private static String ProcessID = null;
private String logLocation = null;
public static String getProcessID()
{
	if (ProcessID == null)
	{
		ProcessID = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
	}
	return ProcessID;
}
private static String getLocation()
{
	StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
	StringBuilder builder = new StringBuilder(stackTraceElements[3].getClassName());
	builder.append(":");
	builder.append(stackTraceElements[3].getLineNumber());
	return builder.toString();
}

這里需要注意,類名行號信息對于每一條日志都不盡相同,所以不能聲明為static,并且需要在LogUtil中獲取,并通過ThreadLogger的setLocation()方法傳入到ThreadLogger對象,最后由ThreadLogger的日志頭拼裝函數(shù)拼接到日志頭部信息中:

private String spellLogText(int logLevel, String logText)
{
	StringBuilder builder = new StringBuilder();
	SimpleDateFormat sdf = new SimpleDateFormat(DefaultDateFormat);
	builder.append(sdf.format(Calendar.getInstance().getTime()));
	builder.append("|");
	builder.append(LogUtil.LevelName[logLevel]);
	builder.append("|");
	builder.append(getProcessID());
	builder.append("|");
	builder.append(this.threadID);
	builder.append("|");
	builder.append(this.threadUUID);
	builder.append("|");
	builder.append(this.logLocation);
	builder.append("|");
	builder.append(logText);
	return builder.toString();
}

六 異常日志的堆棧信息打印

需要注意咯,異常的日志輸出略微復雜些,這也是我經(jīng)常被人問起的一個問題,很多從事400或大機開發(fā)的同事轉(zhuǎn)入java開發(fā)后,最常問的就是異常堆棧的問題,看不懂,不知道怎么來的。

這里我只提一個事情,Exception的構(gòu)造,其成員有兩個,其一為cause,Throwable類型,其二為message,String類型,構(gòu)造函數(shù)提供較多,請大家自己做一個測試,看看不同構(gòu)造其輸出的內(nèi)容有何不同,cause和message成員又有何關(guān)系。

如果你弄明白了Exception的構(gòu)造,那么下面的邏輯不難理解:

private static String getExceptionStackTrace(Exception e)
{
	StringBuilder builder = new StringBuilder();
	builder.append(e.toString());
	StackTraceElement[] stackTraceElements = e.getStackTrace();
	for (int i = 0; i < stackTraceElements.length; i++)
	{
		builder.append("\r\nat ");
		builder.append(stackTraceElements[i].toString());
	}
	Throwable throwable = e.getCause();
	while (throwable != null)
	{
		builder.append("\r\nCaused by:");
		builder.append(throwable.toString());
		stackTraceElements = throwable.getStackTrace();
		for (int i = 0; i < stackTraceElements.length; i++)
		{
			builder.append("\r\nat ");
			builder.append(stackTraceElements[i].toString());
		}
		throwable = throwable.getCause();
	}
	return builder.toString();
}

七 測試

補充一個邏輯實現(xiàn),這里我以異常日志的打印作為測試接口,并在多線程并發(fā)場景下實現(xiàn)異步輸出。

異常日志輸出接口補充完整:

public static void logException(String desc, Exception exception)
{
	getThreadLogger().setLogLocation(getLocation());
	StringBuilder builder = new StringBuilder("Description=");
	builder.append(StringUtil.isEmpty(desc) ? "" : desc);
	builder.append(",Exception=");
	builder.append(getExceptionStackTrace(exception));
	log(LevelError, TypeException, builder.toString());
}

測試代碼:

public static void main(String[] args)
{
	LogUtil.setAppender(LogUtil.AppenderFile);
	LogUtil.setBufferSize(1024); // 1KB緩存
	LogUtil.setFileSize("1MB");
	LogUtil.setBackupIndex(10);
	LogUtil.setFilePath("C:\\log");
	for (int i = 0; i < 4; i++)
	{
		Thread thread = new Thread(new Runnable()
		{
			@Override
			public void run()
			{
				LogUtil.setModule(Thread.currentThread().getId() + "");
				for (int j = 0; j < 100000; j++)
				{
					LogUtil.logException("test", new Exception("my test"));
				}
			}
		});
		thread.start();
	}
}

測試結(jié)果:

日志目錄

日志內(nèi)容

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Javaweb動態(tài)開發(fā)最重要的Servlet詳解

    Javaweb動態(tài)開發(fā)最重要的Servlet詳解

    動態(tài)web的核心是Servlet,由tomcat解析并執(zhí)行,本質(zhì)是Java中的一個類(面向?qū)ο螅┻@個類的功能十分強大幾乎可以完成全部功能,在Java規(guī)范中只有Servlet實現(xiàn)類實例化的對象才能被瀏覽器訪問,所以掌握Servlet具有重要意義
    2022-08-08
  • SpringBoot自動配置原理詳解

    SpringBoot自動配置原理詳解

    SpringBoot的誕生就是為了簡化Spring中繁瑣的XML配置,其本質(zhì)依然還是Spring框架,使用SpringBoot之后可以不使用任何XML配置來啟動一個服務(wù),使得我們在使用微服務(wù)架構(gòu)時可以更加快速的建立一個應(yīng)用。本文將為具體介紹一下SpringBoot的原理,需要的可以參考一下
    2021-12-12
  • 使用@Validate分組驗證參數(shù)

    使用@Validate分組驗證參數(shù)

    SpringFramework的@Validated注解用于方法級別參數(shù)驗證,結(jié)合BeanValidation規(guī)范,支持分組驗證,通過指定驗證分組,可以在不同場景下執(zhí)行不同的驗證邏輯
    2024-11-11
  • 使用Maven配置Spring的方法步驟

    使用Maven配置Spring的方法步驟

    這篇文章主要介紹了使用Maven配置Spring的方法步驟,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-04-04
  • Java數(shù)據(jù)結(jié)構(gòu)與算法學習之雙向鏈表

    Java數(shù)據(jù)結(jié)構(gòu)與算法學習之雙向鏈表

    雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個數(shù)據(jù)結(jié)點中都有兩個指針,分別指向直接后繼和直接前驅(qū)。所以,從雙向鏈表中的任意一個結(jié)點開始,都可以很方便地訪問它的前驅(qū)結(jié)點和后繼結(jié)點。本文將為大家詳細介紹雙向鏈表的特點與使用,需要的可以參考一下
    2021-12-12
  • springMVC之HandlerExceptionResolver使用

    springMVC之HandlerExceptionResolver使用

    這篇文章主要介紹了springMVC之HandlerExceptionResolver使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • java 跳轉(zhuǎn)搜索的實現(xiàn)示例

    java 跳轉(zhuǎn)搜索的實現(xiàn)示例

    與二分搜索一樣,跳轉(zhuǎn)搜索是一種針對排序數(shù)組的搜索算法,本文主要介紹了java 跳轉(zhuǎn)搜索的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,需要的朋友們下面隨著小編來一起學習學習吧
    2024-04-04
  • Java詳細講解包的作用以及修飾符的介紹

    Java詳細講解包的作用以及修飾符的介紹

    本文主要講述的是包的使用和注意事項和四種訪問修飾符public,protected,默認的,private的訪問范圍及實例,感興趣的朋友一起來看看
    2022-05-05
  • Java ArrayList的底層實現(xiàn)方法

    Java ArrayList的底層實現(xiàn)方法

    今天小編就為大家分享一篇Java ArrayList的底層實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-10-10
  • SpringBoot壓縮png, jpg, jpeg, webp類型圖片的實現(xiàn)代碼

    SpringBoot壓縮png, jpg, jpeg, webp類型圖片的實現(xiàn)代碼

    這篇文章主要介紹了SpringBoot壓縮png, jpg, jpeg, webp類型圖片的實現(xiàn),文中通過代碼示例和圖文結(jié)合的方式給大家講解的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下
    2024-08-08

最新評論

松滋市| 东明县| 瓮安县| 中卫市| 海林市| 吐鲁番市| 咸宁市| 宁武县| 苍南县| 新密市| 平江县| 林州市| 盐源县| 汉阴县| 阿瓦提县| 呼伦贝尔市| 漳平市| 北辰区| 江永县| 弥渡县| 湖州市| 望城县| 安阳市| 武邑县| 平度市| 天津市| 綦江县| 南汇区| 太仓市| 富顺县| 海伦市| 鹤峰县| 尚志市| 陇西县| 肇源县| 峨眉山市| 桦川县| 淳安县| 绩溪县| 泰安市| 金山区|