精华内容
下载资源
问答
  • IOS日志记录

    万次阅读 2014-08-19 11:30:31
    在开发过程中,众所周知,日志记录调试的关键部分,尤其是当产品发布的时候,有用户feedback一些崩溃问题或者是其他问题时,日志就显得尤其重要,通过分析日志可以很快地找出问题的症结所在并快速解决问题。...

    日志

    在开发过程中,众所周知,日志记录调试的关键部分,尤其是当产品发布的时候,有用户feedback一些崩溃问题或者是其他问题时,日志就显得尤其重要,通过分析日志可以很快地找出问题的症结所在并快速解决问题。

    恰当的记录用户日志是一门艺术。什么样的信息应该写入日志(通常包括用户行为和错误信息,分开记录),写入日志的信息太少不利于调试,而频繁地记录日志则会影响系统的性能,还会使得日志文件迅速膨胀导致难以查找到需要的信息。对于不同的应用,应该记录的信息是不用的,不过还是有一些通用的规则的。关于日志引擎,有以下几点需要注意:

    1、在开发环境中,应该将日志写入控制台;而在生产环境中,应该将日志写入文件。在调试代码的时候,不输出到控制台就无法在XCode中看到日志。当最好的方式是同时写入控制台和日志文件。

    2、应该分为多种不同的日志级别(错误、警告、信息、详细)。

    3、当某个日志级别被禁用时,相应日志函数的调用开销要非常小。

    4、向控制台或者文件写日志的时候,不可以阻塞调用者线程。

    5、要定期删除日志文件以避免占满磁盘。

    6、日志函数的调用要非常方便,通常使用支持变参的C语法,不建议使用Object-C语法。NSLog的调用凡是非常简单,这一点就值得学习。


    在加入一条日志的时候,应该想一下这条日志有什么用,这条语句记录的数据是否已经在其他地方被记录过来了。对于不是肯定会被记录的内容,不要浪费计算机资源。

    无需多言,错误信息肯定是要被记录进日志文件的。这里要强调的一点是,断言(NSAssert)也要记录进日志文件中而不是直接让程序崩溃(断言应该位于程序崩溃代码之前)

    例子:

    这里会导致断言失败,那么即使关闭断言程序依然会崩溃。所以要将代码改为下面这样:


    这样就好多了,在 NSAssert 崩溃之前先记录下日志。当然,你可以重写一下 NSAssert ,如:MyNSAssert ,把日志记录代码和 NSAssert 封装在一起使用,这样更加方便,推荐使用。

    关于记录敏感信息

    记录日志通常会牵扯到隐私问题,要谨慎考虑哪些日志信息是不该被记录进日志的,例如用户的用户名和密码或者是信用卡号和密码等。不要忘了记录日志的目的只是为了在程序出现错误的时候很方便的重现和定位到错误位置,仅此而已。

    获取日志文件

    如果拿不到日志文件,那记录日志也是白搭。获取日志可以通过网络协议让用户上传日志到服务器。另外要注意一点,日志文件可能会比较大,在上传之前应该进行压缩以减少大小。考虑到用户流量情况,最好是在 WIFI 情况下静默上传日志文件。

    何时上传日志

    非程序崩溃情况下地日志,最好是选择在用户闲暇时间段且 WIFI 情况下上传;而崩溃情况下的日志,考虑到程序在崩溃的时候会处于奇怪且未知的状态,最好是选择在程序重启的时候(而不是崩溃期间)上传 crash 报告。在程序崩溃期间尽量什么都不要做。


    项目中用到的 Bee 框架的 Log 日志写得非常不错,就直接贴出来给大家学习学习:

    //
    //	 ______    ______    ______
    //	/\  __ \  /\  ___\  /\  ___\
    //	\ \  __<  \ \  __\_ \ \  __\_
    //	 \ \_____\ \ \_____\ \ \_____\
    //	  \/_____/  \/_____/  \/_____/
    //
    //
    //	Copyright (c) 2013-2014, {Bee} open source community
    //	http://www.bee-framework.com
    //
    //
    //	Permission is hereby granted, free of charge, to any person obtaining a
    //	copy of this software and associated documentation files (the "Software"),
    //	to deal in the Software without restriction, including without limitation
    //	the rights to use, copy, modify, merge, publish, distribute, sublicense,
    //	and/or sell copies of the Software, and to permit persons to whom the
    //	Software is furnished to do so, subject to the following conditions:
    //
    //	The above copyright notice and this permission notice shall be included in
    //	all copies or substantial portions of the Software.
    //
    //	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    //	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    //	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    //	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    //	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    //	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    //	IN THE SOFTWARE.
    //
    
    #import "Bee_Precompile.h"
    #import "Bee_Singleton.h"
    
    #pragma mark -
    
    typedef enum
    {
    	BeeLogLevelNone			= 0,
    	BeeLogLevelInfo			= 100,
    	BeeLogLevelPerf			= 100 + 1,
    	BeeLogLevelProgress		= 100 + 2,
    	BeeLogLevelWarn			= 200,
    	BeeLogLevelError		= 300
    } BeeLogLevel;
    
    #pragma mark -
    
    #undef	CC
    #define CC( ... )			[[BeeLogger sharedInstance] level:BeeLogLevelNone format:__VA_ARGS__];
    
    #undef	INFO
    #define INFO( ... )			[[BeeLogger sharedInstance] level:BeeLogLevelInfo format:__VA_ARGS__];
    
    #undef	PERF
    #define PERF( ... )			[[BeeLogger sharedInstance] level:BeeLogLevelPerf format:__VA_ARGS__];
    
    #undef	WARN
    #define WARN( ... )			[[BeeLogger sharedInstance] level:BeeLogLevelWarn format:__VA_ARGS__];
    
    #undef	ERROR
    #define ERROR( ... )		[[BeeLogger sharedInstance] level:BeeLogLevelError format:__VA_ARGS__];
    
    #undef	PROGRESS
    #define PROGRESS( ... )		[[BeeLogger sharedInstance] level:BeeLogLevelProgress format:__VA_ARGS__];
    
    #undef	VAR_DUMP
    #define VAR_DUMP( __obj )	[[BeeLogger sharedInstance] level:BeeLogLevelNone format:[__obj description]];
    
    #undef	OBJ_DUMP
    #define OBJ_DUMP( __obj )	[[BeeLogger sharedInstance] level:BeeLogLevelNone format:[__obj objectToDictionary]];
    
    #undef	TODO
    #define TODO( desc, ... )
    
    #pragma mark -
    
    @interface BeeBacklog : NSObject
    @property (nonatomic, assign) BeeLogLevel		level;
    @property (nonatomic, retain) NSDate *			time;
    @property (nonatomic, retain) NSString *		text;
    @end
    
    #pragma mark -
    
    @interface BeeLogger : NSObject
    
    AS_SINGLETON( BeeLogger );
    
    @property (nonatomic, assign) BOOL				enabled;
    @property (nonatomic, assign) BOOL				backlog;
    @property (nonatomic, retain) NSMutableArray *	backlogs;
    @property (nonatomic, assign) NSUInteger		indentTabs;
    
    - (void)toggle;
    - (void)enable;
    - (void)disable;
    
    - (void)indent;
    - (void)indent:(NSUInteger)tabs;
    - (void)unindent;
    - (void)unindent:(NSUInteger)tabs;
    
    - (void)level:(BeeLogLevel)level format:(NSString *)format, ...;
    - (void)level:(BeeLogLevel)level format:(NSString *)format args:(va_list)args;
    
    @end
    
    #pragma mark -
    
    #if __cplusplus
    extern "C" {
    #endif
    
    	void BeeLog( NSString * format, ... );
    	
    #if __cplusplus
    };
    #endif
    


    //
    //	 ______    ______    ______
    //	/\  __ \  /\  ___\  /\  ___\
    //	\ \  __<  \ \  __\_ \ \  __\_
    //	 \ \_____\ \ \_____\ \ \_____\
    //	  \/_____/  \/_____/  \/_____/
    //
    //
    //	Copyright (c) 2013-2014, {Bee} open source community
    //	http://www.bee-framework.com
    //
    //
    //	Permission is hereby granted, free of charge, to any person obtaining a
    //	copy of this software and associated documentation files (the "Software"),
    //	to deal in the Software without restriction, including without limitation
    //	the rights to use, copy, modify, merge, publish, distribute, sublicense,
    //	and/or sell copies of the Software, and to permit persons to whom the
    //	Software is furnished to do so, subject to the following conditions:
    //
    //	The above copyright notice and this permission notice shall be included in
    //	all copies or substantial portions of the Software.
    //
    //	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    //	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    //	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    //	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    //	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    //	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    //	IN THE SOFTWARE.
    //
    
    #import "Bee_Log.h"
    #import "Bee_UnitTest.h"
    #import "Bee_Sandbox.h"
    #import "NSArray+BeeExtension.h"
    
    // ----------------------------------
    // Source code
    // ----------------------------------
    
    #pragma mark -
    
    #undef	MAX_BACKLOG
    #define MAX_BACKLOG	(50)
    
    #pragma mark -
    
    @implementation BeeBacklog
    
    @synthesize level = _level;
    @synthesize time = _time;
    @synthesize text = _text;
    
    - (id)init
    {
    	self = [super init];
    	if ( self )
    	{
    		self.level = BeeLogLevelNone;
    		self.time = [NSDate date];
    		self.text = nil;
    	}
    	return self;
    }
    
    - (void)dealloc
    {
    	self.time = nil;
    	self.text = nil;
    	
    	[super dealloc];
    }
    
    @end
    
    #pragma mark -
    
    @interface BeeLogger()
    {
    	BOOL				_enabled;
    	BOOL				_backlog;
    	NSMutableArray *	_backlogs;
    	NSUInteger			_indentTabs;
    }
    @end
    
    #pragma mark -
    
    @implementation BeeLogger
    
    DEF_SINGLETON( BeeLogger );
    
    @synthesize enabled = _enabled;
    @synthesize backlog = _backlog;
    @synthesize backlogs = _backlogs;
    @synthesize indentTabs = _indentTabs;
    
    - (id)init
    {
    	self = [super init];
    	if ( self )
    	{
    		self.enabled = YES;
    		self.backlog = YES;
    		self.backlogs = [NSMutableArray array];
    		self.indentTabs = 0;
    	}
    	return self;
    }
    
    - (void)dealloc
    {
    	self.backlogs = nil;
    	
    	[super dealloc];
    }
    
    - (void)toggle
    {
    	_enabled = _enabled ? NO : YES;
    }
    
    - (void)enable
    {
    	_enabled = YES;
    }
    
    - (void)disable
    {
    	_enabled = YES;
    }
    
    - (void)indent
    {
    	_indentTabs += 1;
    }
    
    - (void)indent:(NSUInteger)tabs
    {
    	_indentTabs += tabs;
    }
    
    - (void)unindent
    {
    	if ( _indentTabs > 0 )
    	{
    		_indentTabs -= 1;
    	}
    }
    
    - (void)unindent:(NSUInteger)tabs
    {
    	if ( _indentTabs < tabs )
    	{
    		_indentTabs = 0;
    	}
    	else
    	{
    		_indentTabs -= tabs;
    	}
    }
    
    - (void)level:(BeeLogLevel)level format:(NSString *)format, ...
    {
    #if (__ON__ == __BEE_LOG__)
    	
    	if ( nil == format || NO == [format isKindOfClass:[NSString class]] )
    		return;
    
    	va_list args;
    	va_start( args, format );
    	
    	[self level:level format:format args:args];
    
    	va_end( args );
    	
    #endif	// #if (__ON__ == __BEE_LOG__)
    }
    
    - (void)level:(BeeLogLevel)level format:(NSString *)format args:(va_list)args
    {
    #if (__ON__ == __BEE_LOG__)
    	
    	if ( NO == _enabled )
    		return;
    	
    	// formatting
    	
    	NSString * prefix = nil;
    	
    	if ( BeeLogLevelInfo == level )
    	{
    		prefix = @"INFO";
    	}
    	else if ( BeeLogLevelPerf == level )
    	{
    		prefix = @"PERF";
    	}
    	else if ( BeeLogLevelWarn == level )
    	{
    		prefix = @"WARN";
    	}
    	else if ( BeeLogLevelError == level )
    	{
    		prefix = @"ERROR";
    	}
    	
    	if ( prefix )
    	{
    		prefix = [NSString stringWithFormat:@"[%@]", prefix];
    		prefix = [prefix stringByPaddingToLength:8 withString:@" " startingAtIndex:0];
    	}
    	
    	NSMutableString * tabs = nil;
    	NSMutableString * text = nil;
    	
    	if ( _indentTabs > 0 )
    	{
    		tabs = [NSMutableString string];
    		
    		for ( int i = 0; i < _indentTabs; ++i )
    		{
    			[tabs appendString:@"\t"];
    		}
    	}
    	
    	text = [NSMutableString string];
    	
    	if ( prefix && prefix.length )
    	{
    		[text appendString:prefix];
    	}
    	
    	if ( tabs && tabs.length )
    	{
    		[text appendString:tabs];
    	}
    	
    	if ( BeeLogLevelProgress == level )
    	{
    		NSString *	name = [format stringByPaddingToLength:32 withString:@" " startingAtIndex:0];
    		NSString *	state = va_arg( args, NSString * );
    		
    		[text appendFormat:@"%@\t\t\t\t[%@]", name, state];
    	}
    	else
    	{
    		NSString * content = [[[NSString alloc] initWithFormat:(NSString *)format arguments:args] autorelease];
    		if ( content && content.length )
    		{
    			[text appendString:content];
    		}
    	}
    	
    	if ( [text rangeOfString:@"\n"].length )
    	{
    		[text replaceOccurrencesOfString:@"\n"
    							  withString:[NSString stringWithFormat:@"\n%@", tabs ? tabs : @"\t\t"]
    								 options:NSCaseInsensitiveSearch
    								   range:NSMakeRange( 0, text.length )];
    	}
    	
    	// print to console
    	
    	fprintf( stderr, [text UTF8String], NULL );
    	fprintf( stderr, "\n", NULL );
    	
    	// back log
    	
    	if ( _backlog )
    	{
    		BeeBacklog * log = [[[BeeBacklog alloc] init] autorelease];
    		log.level = level;
    		log.text = text;
    		
    		[_backlogs pushTail:log];
    		[_backlogs keepTail:MAX_BACKLOG];
    	}
    	
    #endif	// #if (__ON__ == __BEE_LOG__)
    }
    
    @end
    
    extern "C" void BeeLog( NSString * format, ... )
    {
    #if (__ON__ == __BEE_LOG__)
    	
    	if ( nil == format || NO == [format isKindOfClass:[NSString class]] )
    		return;
    	
    	va_list args;
    	va_start( args, format );
    	
    	[[BeeLogger sharedInstance] level:BeeLogLevelInfo format:format args:args];
    	
    	va_end( args );
    	
    #endif	// #if (__ON__ == __BEE_LOG__)
    }
    
    // ----------------------------------
    // Unit test
    // ----------------------------------
    
    #if defined(__BEE_UNITTEST__) && __BEE_UNITTEST__
    
    TEST_CASE( BeeLog )
    {
    	TIMES( 3 )
    	{
    		HERE( "output log", {
    			CC( nil );
    			CC( @"" );
    			CC( @"format %@", @"" );
    		});
    		
    		HERE( "test info", {
    			INFO( nil );
    			INFO( nil, nil );
    			INFO( nil, @"" );
    			INFO( nil, @"format %@", @"" );
    			
    			INFO( @"a", nil );
    			INFO( @"a", @"" );
    			INFO( @"a", @"format %@", @"" );
    		});
    
    		HERE( "test warn", {
    			WARN( nil );
    			WARN( nil, nil );
    			WARN( nil, @"" );
    			WARN( nil, @"format %@", @"" );
    
    			WARN( @"a", nil );
    			WARN( @"a", @"" );
    			WARN( @"a", @"format %@", @"" );
    		});
    		
    		HERE( "test error", {
    			ERROR( nil );
    			ERROR( nil, nil );
    			ERROR( nil, @"" );
    			ERROR( nil, @"format %@", @"" );
    			
    			ERROR( @"a", nil );
    			ERROR( @"a", @"" );
    			ERROR( @"a", @"format %@", @"" );
    		});
    	}
    }
    TEST_CASE_END
    
    #endif	// #if defined(__BEE_UNITTEST__) && __BEE_UNITTEST__
    

    有点可惜的是,Bee 框架的 log 日志并没有直接写入沙盒,我们自己添加一下就好了。强调一点,记得定期清除没用或者已过期的日志文件(可以选择选择先上传到服务器后删掉沙盒中的日志),这点很重要。

    日志分析

    为了即时拿到用户的崩溃日志并且记录用户的行为,第三方统计会是一个不错的选择。这里就以友盟统计为例:



    由上面图片可以看到,日志记录的崩溃信息很是详细,包括错误摘要,版本信息、错误次数和发生时间,最重要的是有记录下 crash 时的堆栈信息,这样找错误就方便很多了。

    对于一些比较难懂的错误摘要,如:Application received signal SIGSEGV ,就要用到发生该错误的版本代码和 .DYSM 文件符号化定位到发生错误的位置,具体方法就不赘述了,可以看一下这里,或者是自己Google一下关键词。

    展开全文
  • 一文搞懂Java日志级别,重复记录、丢日志问题

    万次阅读 多人点赞 2020-12-06 21:33:46
    13 | 日志:日志记录真没你想象的那么简单 2020-04-07 朱晔 你好,我是朱晔。今天,我和你分享的是,记录日志可能会踩的坑。 一些同学可能要说了,记录日志还不简单,无非是几个常用的API方法,比如debug、info、...

    1 新手村试炼

    1.1 框架烦多!

    不同类库可能使用不同日志框架,兼容难!

    1.2 配置复杂

    由于配置文件烦杂!很多同学喜欢从其他项目或网上直接闭眼复制一份。

    1.3 随意度高

    因为不会直接导致代码 bug,测试人员也难及时发现问题,开发就没仔细考虑日志内容获取的性能开销、随意选用日志级别。

    2 SLF4J

    Logback、Log4j、Log4j2、commons-logging、JDK自带的java.util.logging等,都是Java体系的日志框架。
    不同的类库,还可能选择使用不同的日志框架,导致日志统一管理困难。

    • SLF4J(Simple Logging Facade For Java)就为解决该问题

    • 提供统一的日志门面API,即图中紫色部分,实现中立的日志记录API
    • 桥接功能,蓝色部分,把各种日志框架API(绿色部分)桥接到SLF4J API。这样即便你的程序中使用各种日志API记录日志,最终都可桥接到SLF4J门面API。
    • 适配功能,红色部分,可实现SLF4J API和实际日志框架(灰色部分)绑定。
      SLF4J只是日志标准,还是需要实际日志框架。日志框架本身未实现SLF4J API,所以需前置转换。Logback就是按SLF4J API标准实现,所以才无需绑定模块做转换。

    虽然可用log4j-over-slf4j实现Log4j桥接到SLF4J,也可使用slf4j-log4j12实现SLF4J适配到Log4j,也把它们画到了一列,但是它不能同时使用它们,否则就会产生死循环。jcl和jul同理。

    虽然图中有4个灰色的日志实现框架,但日常业务使用最多的还是Logback和Log4j,都是同一人开发的。Logback可认为是Log4j改进版,更推荐使用,基本已是主流。

    Spring Boot的日志框架也是Logback。那为什么我们没有手动引入Logback包,就可直接使用Logback?

    spring-boot-starter模块依赖spring-boot-starter-logging模块
    spring-boot-starter-logging模块自动引入logback-classic(包含SLF4J和Logback日志框架)和SLF4J的一些适配器。其中,log4j-to-slf4j用于实现Log4j2 API到SLF4J的桥接,jul-to-slf4j则是实现java.util.logging API到SLF4J的桥接。

    4 异步日志提高性能?

    知道了到底如何正确将日志输出到文件后,就该考虑如何避免日志记录成为系统性能瓶颈。这可解决,磁盘(比如机械磁盘)IO性能较差、日志量又很大的情况下,如何记录日志问题。

    定义如下的日志配置,一共有两个Appender:

    FILE是一个FileAppender,用于记录所有的日志;
    CONSOLE是一个ConsoleAppender,用于记录带有time标记的日志。

    把大量日志输出到文件中,日志文件会非常大,如果性能测试结果也混在其中的话,就很难找到那条日志。所以,这里使用EvaluatorFilter对日志按照标记进行过滤,并将过滤出的日志单独输出到控制台上。该案例中给输出测试结果的那条日志上做了time标记。

    配合使用标记和EvaluatorFilter,实现日志的按标签过滤

    • 测试代码:实现记录指定次数的大日志,每条日志包含1MB字节的模拟数据,最后记录一条以time为标记的方法执行耗时日志:

    执行程序后可以看到,记录1000次日志和10000次日志的调用耗时,分别是5.1秒和39秒

    对只记录文件日志的代码,这耗时过长。

    源码解析

    FileAppender继承自OutputStreamAppender

    在追加日志时,是直接把日志写入OutputStream中,属同步记录日志

    所以日志大量写入才会旷日持久。如何才能实现大量日志写入时,不会过多影响业务逻辑执行耗时而影响吞吐量呢?

    AsyncAppender

    使用Logback的AsyncAppender

    即可实现异步日志记录。AsyncAppender类似装饰模式,在不改变类原有基本功能情况下为其增添新功能。这便可把AsyncAppender附加在其他Appender,将其变为异步。

    定义一个异步Appender ASYNCFILE,包装之前的同步文件日志记录的FileAppender, 即可实现异步记录日志到文件

    • 记录1000次日志和10000次日志的调用耗时,分别是537毫秒和1019毫秒

    异步日志真的如此高性能?并不,因为这并没有记录下所有日志。

    AsyncAppender异步日志坑

    • 记录异步日志撑爆内存
    • 记录异步日志出现日志丢失
    • 记录异步日志出现阻塞。

    案例

    模拟慢日志记录场景:
    首先,自定义一个继承自ConsoleAppenderMySlowAppender,作为记录到控制台的输出器,写入日志时休眠1秒。

    • 配置文件中使用AsyncAppender,将MySlowAppender包装为异步日志记录

    • 测试代码

    • 耗时很短但出现日志丢失:要记录1000条日志,最终控制台只能搜索到215条日志,而且日志行号变问号。

    • 原因分析
      AsyncAppender提供了一些配置参数,而当前没用对。

    源码解析

    • includeCallerData
      默认false:方法行号、方法名等信息不显示
    • queueSize
      控制阻塞队列大小,使用的ArrayBlockingQueue阻塞队列,默认容量256:内存中最多保存256条日志
    • discardingThreshold
      丢弃日志的阈值,为防止队列满后发生阻塞。默认队列剩余容量 < 队列长度的20%,就会丢弃TRACE、DEBUG和INFO级日志
    • neverBlock
      控制队列满时,加入的数据是否直接丢弃,不会阻塞等待,默认是false
      • 队列满时:offer不阻塞,而put会阻塞
      • neverBlock为true时,使用offer
    public class AsyncAppender extends AsyncAppenderBase<ILoggingEvent> {
    	// 是否收集调用方数据
        boolean includeCallerData = false;
        protected boolean isDiscardable(ILoggingEvent event) {
            Level level = event.getLevel();
            // 丢弃 ≤ INFO级日志
            return level.toInt() <= Level.INFO_INT;
        }
        protected void preprocess(ILoggingEvent eventObject) {
            eventObject.prepareForDeferredProcessing();
            if (includeCallerData)
                eventObject.getCallerData();
        }
    }
    public class AsyncAppenderBase<E> extends UnsynchronizedAppenderBase<E> implements AppenderAttachable<E> {
    
    	// 阻塞队列:实现异步日志的核心
        BlockingQueue<E> blockingQueue;
        // 默认队列大小
        public static final int DEFAULT_QUEUE_SIZE = 256;
        int queueSize = DEFAULT_QUEUE_SIZE;
        static final int UNDEFINED = -1;
        int discardingThreshold = UNDEFINED;
        // 当队列满时:加入数据时是否直接丢弃,不会阻塞等待
        boolean neverBlock = false;
    
        @Override
        public void start() {
           	...
            blockingQueue = new ArrayBlockingQueue<E>(queueSize);
            if (discardingThreshold == UNDEFINED)
            //默认丢弃阈值是队列剩余量低于队列长度的20%,参见isQueueBelowDiscardingThreshold方法
                discardingThreshold = queueSize / 5;
            ...
        }
    
        @Override
        protected void append(E eventObject) {
            if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) { //判断是否可以丢数据
                return;
            }
            preprocess(eventObject);
            put(eventObject);
        }
    
        private boolean isQueueBelowDiscardingThreshold() {
            return (blockingQueue.remainingCapacity() < discardingThreshold);
        }
    
        private void put(E eventObject) {
            if (neverBlock) { //根据neverBlock决定使用不阻塞的offer还是阻塞的put方法
                blockingQueue.offer(eventObject);
            } else {
                putUninterruptibly(eventObject);
            }
        }
        //以阻塞方式添加数据到队列
        private void putUninterruptibly(E eventObject) {
            boolean interrupted = false;
            try {
                while (true) {
                    try {
                        blockingQueue.put(eventObject);
                        break;
                    } catch (InterruptedException e) {
                        interrupted = true;
                    }
                }
            } finally {
                if (interrupted) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }  
    

    默认队列大小256,达到80%后开始丢弃<=INFO级日志后,即可理解日志中为什么只有两百多条INFO日志了。

    queueSize 过大

    可能导致OOM

    queueSize 较小

    默认值256就已经算很小了,且discardingThreshold设置为大于0(或为默认值),队列剩余容量少于discardingThreshold的配置就会丢弃<=INFO日志。这里的坑点有两个:

    1. 因为discardingThreshold,所以设置queueSize时容易踩坑。
      比如本案例最大日志并发1000,即便置queueSize为1000,同样会导致日志丢失
    2. discardingThreshold参数容易有歧义,它不是百分比,而是日志条数。对于总容量10000队列,若希望队列剩余容量少于1000时丢弃,需配置为1000

    neverBlock 默认false

    意味总可能会出现阻塞。

    • discardingThreshold = 0,那么队列满时再有日志写入就会阻塞
    • discardingThreshold != 0,也只丢弃≤INFO级日志,出现大量错误日志时,还是会阻塞

    queueSize、discardingThreshold和neverBlock三参密不可分,务必按业务需求设置:

    • 若优先绝对性能,设置neverBlock = true,永不阻塞
    • 若优先绝不丢数据,设置discardingThreshold = 0,即使≤INFO级日志也不会丢。但最好把queueSize设置大一点,毕竟默认的queueSize显然太小,太容易阻塞。
    • 若兼顾,可丢弃不重要日志,把queueSize设置大点,再设置合理的discardingThreshold

    以上日志配置最常见两个误区

    再看日志记录本身的误区。

    使用日志占位符就无需判断日志级别?

    SLF4J的{}占位符语法,到真正记录日志时才会获取实际参数,因此解决了日志数据获取的性能问题。
    这说法对吗?

    • 验证代码:返回结果耗时1秒

    若记录DEBUG日志,并设置只记录>=INFO级日志,程序是否也会耗时1秒?
    三种方法测试:

    • 拼接字符串方式记录slowString
    • 使用占位符方式记录slowString
    • 先判断日志级别是否启用DEBUG。


    前俩方式都调用slowString,所以都耗时1s。且方式二就是使用占位符记录slowString,这种方式虽允许传Object,不显式拼接String,但也只是延迟(若日志不记录那就是省去)日志参数对象.toString()字符串拼接的耗时。

    本案例除非事先判断日志级别,否则必调用slowString。
    所以使用{}占位符不能通过延迟参数值获取,来解决日志数据获取的性能问题。

    除事先判断日志级别,还可通过lambda表达式延迟参数内容获取。但SLF4J的API还不支持lambda,因此需使用Log4j2日志API,把Lombok的@Slf4j注解替换为**@Log4j2**注解,即可提供lambda表达式参数的方法:

    这样调用debug,签名Supplier<?>,参数就会延迟到真正需要记录日志时再获取:



    所以debug4并不会调用slowString方法

    只是换成Log4j2 API,真正的日志记录还是走的Logback,这就是SLF4J适配的好处。

    总结

    • SLF4J统一了Java日志框架。在使用SLF4J时,要理清楚其桥接API和绑定。若程序启动时出现SLF4J错误提示,那可能是配置问题,可使用Maven的dependency:tree命令梳理依赖关系。
    • 异步日志解决性能问题,是用空间换时间。但空间毕竟有限,当空间满,要考虑阻塞等待or丢弃日志。如果更希望不丢弃重要日志,那么选择阻塞等待;如果更希望程序不要因为日志记录而阻塞,那么就需要丢弃日志。
    • 日志框架提供的参数化日志记录方式不能完全取代日志级别判断。若你的日志量很大,获取日志参数代价也很大,就要判断日志级别,避免不记录日志也要耗时获取日志参数。
    展开全文
  • Java中使用log4j记录日志

    千次阅读 2016-02-04 09:35:03
    在项目开发中,记录错误日志是一个很有必要功能。一是方便调试;二是便于发现系统运行过程中的错误;三是存储业务数据,便于后期分析; 在java中,记录日志,有很多种方式。 比如,自己实现。 自己写类,将...

    在项目开发中,记录错误日志是一个很有必要功能。一是方便调试;二是便于发现系统运行过程中的错误;三是存储业务数据,便于后期分析;

    在java中,记录日志,有很多种方式。

    比如,自己实现。

    自己写类,将日志数据,以io操作方式,写数据到文本文件。或者是写到数据库中。

    比如,使用log4j。

    log4j,这也是此文要记录的。log4j,以前在用.Net做web form时,用log4net。平台通吃。而且,log4j可以将日志,输出到console窗口,输出到文本文件,输出到数据库等,功能还是很强大的!

    比如,使用jdk自带的logging.jar中的方法。

    比如,使用slfj。

    slfj,是也是一个很强大的功能。slfj旨在一统天下,也就是slfj提供了logging.jar 和 log4j的接口,可以通过slfj来调用log4j,也可以调用jdk的logging。

     

    从需求出发,记录log4j

    1、log4j引入哪些包?

    去官网看看吧

    2、添加配置文件

    在src下,也就是class下,把log4j的配置文件添加进去log4j.properties,标准的properties文件(Properties文件的操作,参考此文)

    3、建一个类文件,建主函数,下图为结构

    QQ截图20150210163842

    4、修改配置文件,使用log4j将日志输出到console窗口

    我们经常使用System.out.println(“日志内容”),来将内容输出。今天不用这个了。用log4j的。

    log4J配置文件为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ### 设置级别和目的地(这里多个目的地) ###
    log4j.rootLogger = DEBUG,CONSOLE
    ### 这里的me是包,也就是在这个包记录日志时,是只记录debug及以上级别的日志
    log4j.logger.me=DEBUG
    ### 输出到控制台 ###
    log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender
    log4j.appender.CONSOLE.Target = System.out
    log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
    log4j.appender.CONSOLE.layout.ConversionPattern =  %d{ABSOLUTE} %5p %c{1}:%L - %m%n

    main主函数调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import org.apache.log4j.Logger;
     
    public class Log4jTest {
        public static Logger logger1 = Logger.getLogger(Log4jTest.class);
        public static void main(String[] args) {
            //logger1
            logger1.trace("我是logger1,trace");
            logger1.debug("我是logger1,debug");
            logger1.info("我是logger1,info");
            logger1.warn("我是logger1,warn");
            logger1.error("我是logger1,error");
            logger1.fatal("我是logger1,fatal");
        }
    }

    可以看到console中输出内容:

    1
    2
    3
    4
    5
    16:51:16,575 DEBUG Log4jTest:15 [main:0]- 我是logger1,debug
    16:51:16,578  INFO Log4jTest:16 [main:3]- 我是logger1,info
    16:51:16,578  WARN Log4jTest:17 [main:3]- 我是logger1,warn
    16:51:16,578 ERROR Log4jTest:18 [main:3]- 我是logger1,error
    16:51:16,578 FATAL Log4jTest:19 [main:3]- 我是logger1,fatal

    5、log4j将日志输出到console窗口,说说输出的格式

    在标题4中,看到console的输出内容,是按一定格式输出。格式的配置还是来自于配置文件log4j.properties

    log4j.appender.CONSOLE.layout.ConversionPattern =  %d{ABSOLUTE} %5p %c{1}:%L - %m%n

    日志输出格式,所用到的参数如下,按需添加:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    %p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,
    %d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921
    %r: 输出自应用启动到输出该log信息耗费的毫秒数
    %c: 输出日志信息所属的类目,通常就是所在类的全名
    %t: 输出产生该日志事件的线程名
    %l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
    %x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
    %%: 输出一个”%”字符
    %F: 输出日志消息产生时所在的文件名称
    %L: 输出代码中的行号
    %m: 输出代码中指定的消息,产生的日志具体信息
    %n: 输出一个回车换行符,Windows平台为”\r\n”,Unix平台为”\n”输出日志信息换行
    可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如:
    1)%20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,默认的情况下右对齐。
    2)%-20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,”-”号指定左对齐。
    3)%.30c:指定输出category的名称,最大的宽度是30,如果category的名称大于30的话,就会将左边多出的字符截掉,但小于30的话也不会有空格。
    4)%20.30c:如果category的名称小于20就补空格,并且右对齐,如果其名称长于30字符,就从左边交远销出的字符截掉。

    6、log4j将日志输出到文本文件

    log4j配置文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    ### 设置级别和目的地(这里多个目的地) ###
    log4j.rootLogger = trace,CONSOLE,zhangsanLog
    log4j.logger.me=DEBUG
     
    ### 输出到控制台 ###
    log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender
    log4j.appender.CONSOLE.Target = System.out
    log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
    log4j.appender.CONSOLE.layout.ConversionPattern =  %d{ABSOLUTE} %5p %c{1}:%L [%t:%r]- %m%n
     
    ### 输出到日志文件 ###
    log4j.appender.zhangsanLog = org.apache.log4j.DailyRollingFileAppender
    log4j.appender.zhangsanLog.File =G\:\\var\\alldata\\zhenduan\\debug.log
    #log4j.appender.zhangsanLog.File =/var/alldata/zhenduan/debug.log
    log4j.appender.zhangsanLog.Append = true
    ## 只输出DEBUG级别以上的日志
    log4j.appender.zhangsanLog.Threshold = DEBUG
    #'.'yyyy-MM-dd: 每天产生一个新的文件
    log4j.appender.zhangsanLog.DatePattern = '.'yyyy-MM-dd
    log4j.appender.zhangsanLog.layout = org.apache.log4j.PatternLayout
    log4j.appender.zhangsanLog.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] [%c{1}:%L] [%M] %m%n
    log4j.additivity.zhangsanLog = false

    main函数调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static Logger logger1 = Logger.getLogger(Log4jTest.class);
    public static void main(String[] args) {
        //logger1
        logger1.trace("我是logger1,trace");
        logger1.debug("我是logger1,debug");
        logger1.info("我是logger1,info");
        logger1.warn("我是logger1,warn");
        logger1.error("我是logger1,error");
        logger1.fatal("我是logger1,fatal");
    }

    暂时不做说明,具体配置说明,见后续文章

    7、log4j将日志输出到文本文件,并且,每一小时生成一个文件,每天一个文件,每半天一个文件

    继续使用标题6的配置,但是需要修改几个地方。

    在6中,有这句话

    log4j.appender.zhangsanLog = org.apache.log4j.DailyRollingFileAppender

    这句话的意思是,写到文件中,并且追加,那么多久生成一个文件呢?

    需要修改

    log4j.appender.zhangsanLog.DatePattern = '.'yyyy-MM-dd

    DatePattern=’.’yyyy-ww:每周滚动一次文件,即每周产生一个新的文件。当然也可以指定按月、周、天、时和分。即对应的格式如下:
    1)’.’yyyy-MM: 每月
    2)’.’yyyy-ww: 每周
    3)’.’yyyy-MM-dd: 每天
    4)’.’yyyy-MM-dd-a: 每天两次
    5)’.’yyyy-MM-dd-HH: 每小时
    6)’.’yyyy-MM-dd-HH-mm: 每分钟

    8、log4j将日志输出到文本文件,并且,每当文本文件为3KB大时,新建一个文件。

    同样使用6的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    log4j.appender.zhangsanLog = org.apache.log4j.RollingFileAppender
    log4j.appender.zhangsanLog.File =G\:\\var\\alldata\\zhenduan\\debug.log
    #log4j.appender.zhangsanLog.File =/var/alldata/zhenduan/debug.log
    log4j.appender.zhangsanLog.Append = true
    ## 输出DEBUG级别以上的日志
    log4j.appender.zhangsanLog.Threshold = DEBUG
    #'.'yyyy-MM-dd: 每天产生一个新的文件
    log4j.appender.zhangsanLog.MaxFileSize = 2KB
    log4j.appender.zhangsanLog.MaxBackupIndex = 5
    log4j.appender.zhangsanLog.layout = org.apache.log4j.PatternLayout
    log4j.appender.zhangsanLog.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] [%c{1}:%L] [%M] %m%n
    log4j.additivity.zhangsanLog = false

    这里修改的是

    log4j.appender.zhangsanLog = org.apache.log4j.RollingFileAppender

    MaxFileSize,当文件达到多大存储空间时,就新建一个文件

    MaxBackupIndex,最多会新建几个文件(如果设置的小了,当文件个数较多时,后续就不再新建文件了)

    9、log4j将日志输出到数据库

    暂无

    10、log4j将日志输出到hadoop框架中的hbase上

    暂无

     

    Log4j的日志级别

    (由低到高。可以联想到windows或unix的错误级别,都是类似的):

    trace: 是追踪,就是程序推进以下,你就可以写个trace输出,所以trace应该会特别多,不过没关系,我们可以设置最低日志级别不让他输出。

    debug: 调试么,我一般就只用这个作为最低级别,trace压根不用。是在没办法就用eclipse或者idea的debug功能就好了么。

    info: 输出一下你感兴趣的或者重要的信息,这个用的最多了。

    warn: 有些信息不是错误信息,但是也要给程序员的一些提示,类似于eclipse中代码的验证不是有error 和warn(不算错误但是也请注意,比如以下depressed的方法)。

    error: 错误信息。用的也比较多。

    fatal: 级别比较高了。重大错误,这种级别你可以直接停止程序了,是不应该出现的错误么!不用那么紧张,其实就是一个程度的问题。

     

    log4j的配置,说明:

    Log4j支持两种配置文件格式,一种是XML格式的文件,一种是properties格式的文件。一般我常用的是properties文件

    1、log4j.rootLogger = [ level ] , appenderName, appenderName, …

    level:是log4j的日志级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。

    appenderName:就是指定日志信息输出到哪个地方。可同时指定多个输出目的地。

     

    2、 配置日志信息输出目的地Appender,其语法为:

    log4j.appender.appenderName = fully.qualified.name.of.appender.class

    log4j.appender.appenderName.option1 = value1

    log4j.appender.appenderName.option = valueN
    其中,Log4j提供的appender有以下几种:
    org.apache.log4j.ConsoleAppender(控制台),
    org.apache.log4j.FileAppender(文件),
    org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件),
    org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件),
    org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)

     

    3、设置好appender后,针对appender的配置

    也就是设置好要输出到什么地方后,其它配置选项

    (1).ConsoleAppender选项
    Threshold=WARN:指定日志消息的输出最低层次。
    ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
    Target=System.err:默认情况下是:System.out,指定输出控制台

    (2).FileAppender 选项
    Threshold=WARN:指定日志消息的输出最低层次。
    ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
    File=mylog.txt:指定消息输出到mylog.txt文件。
    Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。

    (3).DailyRollingFileAppender 选项
    Threshold=WARN:指定日志消息的输出最低层次。
    ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
    File=mylog.txt:指定消息输出到mylog.txt文件。
    Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。
    DatePattern=’.’yyyy-ww:每周滚动一次文件,即每周产生一个新的文件。当然也可以指定按月、周、天、时和分。即对应的格式如下:
    1)’.’yyyy-MM: 每月
    2)’.’yyyy-ww: 每周
    3)’.’yyyy-MM-dd: 每天
    4)’.’yyyy-MM-dd-a: 每天两次
    5)’.’yyyy-MM-dd-HH: 每小时
    6)’.’yyyy-MM-dd-HH-mm: 每分钟

    (4).RollingFileAppender 选项
    Threshold=WARN:指定日志消息的输出最低层次。
    ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
    File=mylog.txt:指定消息输出到mylog.txt文件。
    Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。
    MaxFileSize=100KB: 后缀可以是KB, MB 或者是 GB. 在日志文件到达该大小时,将会自动滚动,即将原来的内容移到mylog.log.1文件。
    MaxBackupIndex=2:指定可以产生的滚动文件的最大数。

     

    4. 配置日志信息的布局,其语法为:

    log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class
    log4j.appender.appenderName.layout.option1 = value1

    log4j.appender.appenderName.layout.option = valueN
    其中,Log4j提供的layout有以下几种:
    org.apache.log4j.HTMLLayout(以HTML表格形式布局),
    org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
    org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
    org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)

     

    关于additivity

    log4j.additivity.zhangsanLog = false

    这个additivity也是很有意思的选项。

    它是 子Logger 是否继承 父Logger 的 输出源(appender) 的标志位。具体说,默认情况下子Logger会继承父Logger的appender,也就是说子Logger会在父Logger的appender里输出。若是additivity设为false,则子Logger只会在自己的appender里输出,而不会在父Logger的appender里输出。
    效果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    log4j.rootLogger=WARN,A2,A3
    log4j.logger.test=DEBUG
     
    log4j.appender.A2=org.apache.log4j.RollingFileAppender
    log4j.appender.A2.File=../logs/test.log
    log4j.appender.A2.Encoding=UTF-8
    log4j.appender.A2.Append=true
    log4j.appender.A2.MaxFileSize=2MB
    log4j.appender.A2.MaxBackupIndex=5
    log4j.appender.A2.layout=org.apache.log4j.PatternLayout
    log4j.appender.A2.layout.ConversionPattern=%-5p %d [%t] %c{3}.%M - %m%n
     
    log4j.additivity.test.xml=false
    log4j.logger.test.xml=DEBUG,A3
     
    log4j.appender.A3=org.apache.log4j.RollingFileAppender
    log4j.appender.A3.File=../logs/test-xml.log
    log4j.appender.A3.Encoding=UTF-8
    log4j.appender.A3.Append=true
    log4j.appender.A3.MaxFileSize=2MB
    log4j.appender.A3.MaxBackupIndex=5
    log4j.appender.A3.layout=org.apache.log4j.PatternLayout
    log4j.appender.A3.layout.ConversionPattern=%-5p %d [%t] %c{3}.%M - %m%n

    配置文件中有

    log4j.logger.test=DEBUG

    log4j.additivity.test.xml=false

    log4j.logger.test.xml=DEBUG,A3

    这个实例中,通过log4j.additivity.test.xml=false这一句取消了继承关系,这样XML的log就只会在A3中输出;而不会在再

     

    还有一个需求,

    不考虑日志级别,a.java, b.java, c.java 分别调用自己的logger,将日志分别写入到不同的文件中。

    这个需求主要是在配置文件中,给appender取个名字

    然后在调用时,使用名称的方式调用,就可以了。

    public static Logger loggerA = Logger.getLogger(“Alog”);

    public static Logger loggerB = Logger.getLogger(“Blog”);

    public static Logger loggerC = Logger.getLogger(“Clog”);




    文章来源http://www.weixuehao.com/archives/385

    展开全文
  • 日志框架Nlog之异步记录

    千次阅读 热门讨论 2015-09-25 15:18:26
    没关系,本篇博客咱们不仅来解决这个问题,咱们更要眼见为实,带带真相让你用的明明白白。 那好,那如何让日志实现异步功能呢。其实呢就一句话。。  配置文件targets中配置async="true"即为异步。默认或写false...

         日志功能本来是一件非常好的事情,能帮助我们分析和解决很多问题。但是,如果让日志影响到性能,这就需要权衡一下了。没关系,本篇博客咱们不仅来解决这个问题,咱们更要眼见为实,带图带真相让你用的明明白白。


    日志实现异步功能

               其实呢就一句话。。

               配置文件targets中配置async="true"即为异步。默认或写false都为同步。


    准备

         接下来让我们开始测试。我还是用了上篇博客中将日志输出到数据库中的例子。不过为了效果明显,客户端的代码稍有改动,添加了循环。是这样的:

    <span style="font-family:KaiTi_GB2312;font-size:18px;">using NLog;
    using System;
    using System.Text;
    
    namespace NLogToSql
    {
        /// <summary>
        ///用来测试 日志记录同步和异步-马丹妹-2015年9月25日
        /// </summary>
        class Program
        {
            private static Logger logger = LogManager.GetCurrentClassLogger();  //Logger对象代表与当前类相关联的日志消息的来源    
            static void Main(string[] args)
            {
                for (int i = 0; i < 5000; i++)
                {
                    logger.Debug("同步异步测试!");//记录到数据库 
                }
                 string time = System.DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fff");
                 Console.WriteLine(time);//打印到控制台语句
            }  
        }
    }
    </span>


    判断依据

        如果记录日志是同步的话,那程序必然是先执行1000次输出到数据库的语句,然后执行下面一句打印到控制台。而,如果程序是异步的话,那很有可能在日志记录到数据库还没有全部执行完的时候,下面打印到控制台的语句就已经执行了。也就是我们通过各个语句的标记时间就能看出是同步还是异步。


    测试一

           默认不配置,或配置成async="false"  -----------同步情况。看图:

               

         我们发现打印到控制台的语句是在16秒571输出的,而循环写日志最后的时间是15秒213。   可见确是循环所有日志全部记录之后才打印到控制台的。


    测试二

         配置成async="true"------------异步情况,看图:

              

         如此,一张图胜过千言万语。


    最后,再给大家带一个福利。


          

          我查询了一秒钟记录日志的条数有700多。所以如果量不大的话,也基本是毫毫秒的事。如果对性能要求没有那么苛刻的话,使用同步记录还是没有任何影响的。当然,如果真的日志记录量非常大,采用 异步也是不错的选择。不过这时候,你要考虑到日志的记录时间可能就和你要记录的上下文中的语句执行时间有所不同咯。


          



    展开全文
  • 在多线程应用程序中进行日志记录

    千次阅读 2012-12-06 11:16:41
    要分析并找出导致这些问题的原因,程序员所广泛使用的一种方法就是日志记录。在本文中,您将了解如何使用循环缓冲区通过内存操作(而不是文件操作)高效地进行日志记录。为该缓冲区选择合适的大小,从而确保转储相关...
  • Spring Boot 各种日志框架记录方式

    千次阅读 2020-06-28 15:09:49
    1、常用日志框架比较 ...结构如: 门面模式的核心为Facade即门面对象,门面对象核心为: 知道所有子角色的功能和责任 将客户端发来的请求委派到子系统中,没有实际业务逻辑 不参与子系统内
  • Log4net日志记录组件的使用详解

    千次阅读 2010-06-08 15:18:00
    首先我们来分析一下Log4net的工作原理:  Log4net是基于.NET开发的一款非常著名的记录日志开源组件。它通过一套XML配置的日志引擎,将日志分不同的等级,分别是:FATAL 、 ERROR、 WARN、 INFO 、 DEBUG...
  • 在多线程应用程序中使用循环缓冲区高效地进行日志记录 在关键的计算机应用程序的生存期中,日志记录是一件非常重要的活动,特别是当故障的症状并不十分明显时。日志记录提供了故障前应用程序状态的详细信息,...
  • 出品丨Docker公司(ID:docker-cn)编译丨小东每周一、三、五晚6点10分 与您不见不散简 介在传统上,设计和实现集中的日志记录总是成为马后炮。要等到...
  • Intellij idea中使用SLF4J+log4j做日志记录 2 应用程序中使用方法及配置 3 spring+mvc web项目使用及配置方法 4 SLF4J对比Log4J,logback和java.util.Logging的优势
  • Windows XML Event Log (EVTX)单条日志清除系列文章的第三篇,介绍第一种删除当前系统evtx日志文件单条日志记录的方法:关闭服务对应的进程,释放文件句柄,解除文件占用,删除日志,重启服务   0x01简介   ...
  • JAVA中使用LOG4J记录日志

    千次阅读 2018-01-25 18:20:45
    在项目开发中,记录错误日志是一个很有必要功能。一是方便调试;二是便于发现系统运行过程中的错误;三是存储业务数据,便于后期分析; 在java中,记录日志,有很多种方式。 比如,自己实现。 自己写类,将...
  • 日志组件既支持输出到文本文件,也支持输出到数据库文件,但同时又要求支持不同的加密方式。加密和输出方式两者应该不太相干,用怎样的加密方法不应该影响到用什么输出方式,同样用怎样的输出方式也不应该影响到用...
  • 考虑题4所示的日志记录,假设开始时A、B、C的值都是0 1. 实验 Transaction 性质 2. 实验结论 3. 题目 3.1 如果系统故障发生在14之后,写出系统恢复后A、B、C的值 3.2. 如果系统故障发生在12之后,写出系统恢复...
  • 一篇文章教你如何用 Python 记录日志

    万次阅读 2018-02-02 00:00:00
    (点击上方公众号,可快速关注)编译: Python开发者 - 李趴趴要化身女超人,英文:Mario Corcherohttp://python.jobbole.com/89007/对一名开发者来说最糟糕的情况,莫过于要弄清楚一个不...记录日志允许我们在开发
  • Ruth Zamorano (ruth.zamorano@orange-soft.com),软件架构师,Orange SoftRafael Luque (rafael.luque@orange-soft.com), CTO,Orange Soft 2003年 9 月日志记录不仅是开发和测试周期中的一个重要元素——提供...
  • 日志记录不仅对于我们开发的应用,还是对于ASP.NET Core框架功能都是一项非常重要的功能特性。我们知道ASP.NET Core使用的是一个极具扩展性的日志系统,该系统由Logger、LoggerFactory和LoggerProvider这三个核心...
  • 记录跟踪日志【r.r2d】

    千次阅读 2012-11-05 08:52:13
    TIPTOP ERP系统 GP 3.0 记录跟踪日志[r.r2d]  Debug命令 r.d2+ 此命令用得比较多,不详细介绍,可以...不同之处就是在相应目录下会产生一个cooc200.log的文件,如下图所示:   文件说明:在.log的文件里面会
  • 由于在工作中,gitlab或github中,对于每一条的操作记录,都有绝对时间与相对时间两种显示,考虑到使用者的习惯也是不同的,故git log同样提供了转换绝对时间为相对时间的功能,若使用者有需要,可以使用该命令调整...
  • Windows日志机制

    千次阅读 2011-09-25 15:23:27
    对于日志,大部分人的想法应当和我起初的想法一致,只要写个函数,在这个函数中打开一个文件...但此时需要考虑的问题有很多,包括文件名的定义,是每次写日志都创建一个新文件还是在一个文件中写所有的信息。如果是同一
  • 服务端和客户端所有信息通过独立日志系统记录可以使用单独的服务器存储和管理,实现权限分离,增强生产服务器的安全性,使用通用的格式方便分析日志,所有日志统一处理,客户端可以直接和日志服务器对话。...
  • 基于SLF4J的日志系统需要考虑的细节

    千次阅读 2014-04-27 22:12:19
    项目中依赖的不同第三方包会依赖这些不同的日志系统,为了解决这些日志系统带来的混乱,出现了日志框架,这些日志框架不提供日志记录功能,只为使用者提供日志调用接口,这些接口的实现依赖于日志系统。常用的日志...
  • 部署Zipkin环境的操作记录: 部署Zipkin,比较麻烦的是前期环境的准备,只有先把前期环境安装好了,后面的部署就顺利多了。(部署机ip为192.168.1.102) 一、环境准备:  1)java环境安装(Centos中yu
  • 不会产生过多日志问题,但是发现tomcat由于是每天生成一个日志文件,导致一天所有的日志都打印到同一个文件中,导致日志文件过大,并且日志文件中的内容包括很多东西,如下图,直接导致磁盘被占满。1、解决方法首先...
  • 使用 Java 实现日志框架,关键的技术在于前面提及的Java日志框架特性的内部实现,特别是:日志的分类和级别、日志分发框架的设计、日志记录器的设计以及在设计中的高性能和高稳定性的考虑。 2.1系统架构 Java日志...
  • 一、整体架构部署, 如下:   本只是一个大概的描述,真实的情况会有所差异,后台部署采用二级负载均衡:一级lvs,二级nginx。日志框架采用flume(两种source:syslogtcp和avro),日志分析采用hadoop。 nginx...
  • Centos7配置系统日志备份策略

    千次阅读 2017-02-28 14:07:09
    Centos配置系统日志备份策略 1、 引发问题 keepalived运行时,往往会将心跳信息记录到系统日志/var/log/messages中,每隔2-3秒记录一条日志信息,长久以往,messages文件将永远增长,从而占满内存空间。   2...
  • 6.1.6 日志压缩

    千次阅读 2021-04-21 22:10:07
    不管是传统的RDBMS还是分布式的NoSQL,存储在数据库中的数据总会更新。更新数据有两种方式:直接更新(找到数据库中的已有位置,以最新的值替换旧的值)、以追加方式更新...如6-35所示,Kafka的消息由键值组成,在.
  • 在C++中使用Apache的Log4cxx记录日志

    千次阅读 2008-10-27 09:41:00
    项目环境设置请先在IDE中打开一个需要加入日志功能的工程,或者出于实验目的,新建一个工程,以便对其进行设置。首先需要设置log4cxx的include文件。这些文件位于log4cxx软件包的include/log4cxx目录内。请查看你的...
  • 最佳日志数据实践

    千次阅读 2017-07-13 11:24:38
    原文链接: https://zhuanlan.zhihu.com/p/273634840. 缘起大约在三年前,我曾经写过一篇 最佳日志实践,还被码农周刊选为那年的 最受欢迎技术干货 之一。当时我任职于网易杭州研究院的存储平台组,主要做网易...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 133,434
精华内容 53,373
关键字:

考虑下图的日志记录