• 经营你的iOS应用日志(一):开始编写日志组件


    http://www.cnblogs.com/alario/archive/2012/03/27/2419710.html

    对于那些做后端开发的工程师来说,看LOG解Bug应该是理所当然的事,但我接触 到的移动应用开发的工程师里面,很多人并没有这个意识,查Bug时总是一遍一遍的试图重现,试图调试,特别是对一些不太容易重现的Bug经常焦头烂额。而 且iOS的异常机制比较复杂,Objective-C的语言驾驭也需要一定的功力,做出来的应用有时候挺容易产生崩溃闪退。一遍一遍的用XCode取应用 崩溃记录、解析符号,通常不胜其烦,有时还对着解析出来的调用栈发呆,因为程序当时的内部状态常常难以看明白,只能去猜测。

    好了,先从一个自制的日志组件开始吧。我们需要一个专门的后台线程去输出日志,线程根函数如下:

    - ( void ) threadProc
    {
    do
    {
    NSAutoreleasePool* pool = [ [ NSAutoreleasePool alloc ] init ];
    for ( int i = 0; i < 20; i++ )
    {
    [ _signal lock ];
    while ( [ _queue count ] == 0 ) // NSMutableArray* _queue,其它线程将日志加入_queue,日志线程负责输出到文件和控制台
    [ _signal wait ]; // NSCondition* _signal
    NSArray* items = [ NSArray arrayWithArray: _queue ];
    [ _queue removeAllObjects ];
    [ _signal unlock ];
    if ( [ items count ] > 0 && [ self checkFileCreated ] /* 检查日志文件是否已创建 */ )
    [ self logToFile: items ]; // 输出到文件以及控制台
    }

    // 每20次输出日志执行一次NSAutoreleasePool的release
    // 保证既不太频繁也不太滞后
    [ pool release ];

    } while ( YES );
    }

    再上记录日志的入口函数。注意Objective-C作为一门动态语言,要以动态语言的思维去使用,比如习惯去用NSDictionary,而不是自己定义一个数据类。好处很多,后面再说

    void writeCinLog( const char* function,        // 记录日志所在的函数名称
    CinLogLevel level, // 日志级别,Debug、Info、Warn、Error
    NSString* format, // 日志内容,格式化字符串
    ... ) // 格式化字符串的参数
    {
    CinLoggerManager* manager = instanceOfLoggerManager(); // CinLoggerManager是单件的日志管理器

    if ( manager.mLogLevel > level || ! format ) // 先检查当前程序设置的日志输出级别。如果这条日志不需要输出,就不用做字符串格式化
    return;

    va_list args;
    va_start( args, format );
    NSString* str = [ [ NSString alloc ] initWithFormat: format arguments: args ];
    va_end( args );
    NSThread* currentThread = [ NSThread currentThread ];
    NSString* threadName = [ currentThread name ];
    NSString* functionName = [ NSString stringWithUTF8String: function ];
    if ( ! threadName )
    threadName = @"";
    if ( ! functionName )
    functionName = @"";
    if ( ! str )
    str = @"";

    // NSDictionary中加入所有需要记录到日志中的信息
    NSDictionary* entry = [ [ NSDictionary alloc ] initWithObjectsAndKeys:
    @"LogEntry", @"Type",
    str, @"Message", // 日志内容
    [ NSDate date ], @"Date", // 日志生成时间
    [ NSNumber numberWithUnsignedInteger: level ], @"Level", // 本条日志级别
    threadName, @"ThreadName", // 本条日志所在的线程名称
    functionName, @"FunctionName", // 本条日志所在的函数名称
    nil ];
    [ str release ];
    [ manager appendLogEntry: entry ];
    [ entry release ];
    }


    appendLogEntry实现如下:

    - ( void ) appendLogEntry: ( NSDictionary* )entry
    {
    [ _signal lock ];
    [ _queue addObject: entry ];
    [ _signal signal ];
    [ _signal unlock ];
    }


    日志文件的管理也是必须考虑的。我现在日志文件的文件名形如:“03月27日 09:57:25 (0).txt”;
    其中前面是本次程序启动的时间,括号内默认是0。如果同一次的运行进程输出的日志文件超过1M,就创建新文件“03月27日 09:57:25 (1).txt”。这样文件不会太大,也有利于在时间点上与测试报上的Bug对应起来。

    另外为了调用writeCinLog时能将当前所在的函数名传进来,我们需要借助宏,使用__FUNCTION__预定义宏在编译期将函数名转换为字符串

    #define FeLogDebug(format,...)        writeCinLog(__FUNCTION__,CinLogLevelDebug,format,##__VA_ARGS__)
    #define FeLogInfo(format,...) writeCinLog(__FUNCTION__,CinLogLevelInfo,format,##__VA_ARGS__)
    #define FeLogWarn(format,...) writeCinLog(__FUNCTION__,CinLogLevelWarning,format,##__VA_ARGS__)
    #define FeLogError(format,...) writeCinLog(__FUNCTION__,CinLogLevelError,format,##__VA_ARGS__)

    这样,如果在didFinishLaunchingWithOptions函数中写一句日志

    FeLogInfo( @"========= 应用已经启动成功了 =========" );

    输出的日志可能是这样的

    <- 03-27 10:44:59 INFO -> [UI] -[myAppDelegate application:didFinishLaunchingWithOptions:]
    ========= 应用已经启动成功了 =========

    其中前面是时间,INFO是日志级别,UI是线程名 称,myAppDelegate是记录日志的类的名称,application:didFinishLaunchingWithOptions:是所在的 函数名称。还有其它可利用的预定义宏,比如__FILE__、__LINE__,能将代码文件名和行号也加入到日志中,就看有没有必要了。

    稍后继续写

  • 相关阅读:
    pip 安装
    「csp模拟」模拟测试15
    某些博客的优化
    晚间测试6
    「csp模拟」模拟测试15
    「csp模拟」模拟测试14
    线段树维护单调栈
    晚间测试 2
    晚间测试 1
    晚间测试4
  • 原文地址:https://www.cnblogs.com/ligun123/p/2426416.html
Copyright © 2020-2023  润新知