AOPLogger
切面日志作用
說到日志,切面的實現最大的好處也就是分離出來,單獨開發,包括埋點,記錄輸出log都可以在不影響項目內邏輯的情況下完成,形成完全的一個獨立模塊。
這里的切面日志,只是作為分離業務而用,所以它的作用只是記錄,至于上傳刪除,這些邏輯同樣可以分離出來單獨寫,存的過程解決了滲透入工程內,上傳刪除包括分級這些邏輯其實本身就很獨立。
在實現上用了Aspects這個庫,主要就是hook方法,用它主要是對一些例外情況處理不錯,自己寫簡單實現其實就只寫寫方法交換剩下的就不管了。。。
下面說一下.h
#import <Foundation/Foundation.h>
extern NSString * const AOPLoggerMethod;//要統計的日志方法Key
extern NSString * const AOPLoggerLogInfo;//要統計的日志信息Key
extern NSString * const AOPLoggerPositionAfter;//方法執行后統計日志
extern NSString * const AOPLoggerPositionBefore;//方法執行前統計日志
extern NSString * const AOPLoggerPositionType;//執行日志統計的類型Key
@protocol AOPLoggerGetConfigInfoProtocol <NSObject>
@required
/**
創建類擴展如果使用此協議必須實現此方法
此方法返回統計的配置信息,可以從網絡取也可以從本地取
@return 統計配置字典
*/
-(NSDictionary*)al_getConfigInfo;
@end
@protocol AOPLoggerBLLProtocol <NSObject>
@required
/**
創建類擴展如果使用此協議必須實現此方法
此方法主要來處理切面方法后的log信息處理可以存本地也可以使用其他任意第三方輸出
@param log 配置文件里定義的AOPLoggerLogInfo信息
@param originAOP AspectInfo的方法信息,第三方庫Aspect返回的切面方法的所有信息
*/
-(void)al_logger:(id)log originAOP:(id)originAOP;
@end
@interface AOPLogger : NSObject
/**
開始讀取日志Plist配置文件
*/
+(void)startAOPLoggerWithPlist;
/**
統計日志的調用方法
(如果不想增加開機時間可以采取每個模塊創建一個日志統計類適時調用,在該類里提供一個初始化方法,內部調用此即可)
@param classString 類名
@param methodString 方法名
@param log 相當于AOPLoggerLogInfo信息
*/
+(void)AOPLoggerWithClassString:(NSString*)classString methodString:(NSString*)methodString log:(id)log;
/**
統計日志的調用方法
(如果不想增加開機時間可以采取每個模塊創建一個日志統計類適時調用,在該類里提供一個初始化方法,內部調用此即可)
@param classString 類名
@param methodString 方法名
@param log 相當于AOPLoggerLogInfo信息
@param logPosition 日志統計時位置,可放在方法運行前或運行后(默認運行后執行日志統計)
*/
+(void)AOPLoggerWithClassString:(NSString *)classString methodString:(NSString *)methodString log:(id)log logPosition:(NSString*)logPosition;
@end
這里提供了兩種方式一種直接讀取自定義的Plist,一種就是調用類方法,而即使調用類方法,也是單獨建一個類,某個模塊的日志類負責記錄,傳入類名方法名統計信息即可,而plist得形式在初期還好,后期統計曾多可就真的太扯了畢竟要在初始化的時候加載遍歷執行
QQ20170306-012628.png
定制擴展
由于每個項目想要做的事情或邏輯都會有不同,這里就可以根據我的協議實現對應的方法,來完成自己的業務需求
如下:
import "AOPLogger+Custom.h"
#import "Aspects.h"
#import <objc/runtime.h>
@implementation AOPLogger (Custom)
-(void)al_logger:(id)log originAOP:(id)originAOP{
if ([log isKindOfClass:[NSString class]]) {
NSLog(@"event:%@",log);
}
if ([log isKindOfClass:[NSDictionary class]]) {
NSLog(@"eventName:%@\neventLabel:%@\neventTime:%@",log[@"EventName"],log[@"EventLabel"],[log[@"EventTime"] boolValue]?[NSDate date]:@"不用獲取");
}
if (originAOP&&[originAOP conformsToProtocol:objc_getProtocol("AspectInfo")]) {
id<AspectInfo> aspectInfo=originAOP;
NSLog(@"originClass:%@\noriginSel:%@",NSStringFromClass([aspectInfo.originalInvocation.target class]),NSStringFromSelector(aspectInfo.originalInvocation.selector));
for (NSInteger i=0; i<aspectInfo.arguments.count; i++) {
NSLog(@"argument:%@",aspectInfo.arguments[i]);
}
}
}
@end
這里只是做了簡單的NSLog操作,而當某個業務模塊下需要不同處理的時候,不妨就直接hook這個方法添加邏輯,用法完全靠個人想像吧,其實用起來經常能處理很多神奇的邏輯,里面的originAOP如果用過Aspects這個庫的話可能會很快明白為什么要選這個庫不是自己寫了,因為它的這塊封裝可以讓我拿到對應方法執行完后返回的值,當然你得指定這個log是在原方法執行完成后執行,同理如果放在執行前執行log我們通過這個對象還可以拿到方法傳遞的參數。
再看.m的源碼
#import "AOPLogger.h"
#import "Aspects.h"
#import <objc/runtime.h>
NSString * const AOPLoggerMethod=@"AOPLoggerMethod";
NSString * const AOPLoggerLogInfo=@"AOPLoggerLogInfo";
NSString * const AOPLoggerPositionAfter=@"AOPLoggerPositionAfter";
NSString * const AOPLoggerPositionBefore=@"AOPLoggerPositionBefore";
NSString * const AOPLoggerPositionType=@"AOPLoggerPositionType";
@implementation AOPLogger
+ (AOPLogger *)sharedAOPLogger {
static AOPLogger *sharedAOPLogger = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedAOPLogger = [[self alloc] init];
});
return sharedAOPLogger;
}
+(void)startAOPLoggerWithPlist{
NSDictionary *loggerConfigInfo=nil;
if ([[AOPLogger sharedAOPLogger] conformsToProtocol:objc_getProtocol("AOPLoggerGetConfigInfoProtocol")]) {
loggerConfigInfo=[(AOPLogger<AOPLoggerGetConfigInfoProtocol>*)[AOPLogger sharedAOPLogger] al_getConfigInfo];
}
else{
loggerConfigInfo=[NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"AOPLoggerConfig" ofType:@"plist"]];
}
for (NSString *className in loggerConfigInfo) {
for (NSDictionary *eventInfo in loggerConfigInfo[className]) {
Class clazz = NSClassFromString(className);
SEL selector = NSSelectorFromString(eventInfo[AOPLoggerMethod]);
AspectOptions positionOptions=AspectPositionAfter;
if ([loggerConfigInfo[AOPLoggerPositionType] isEqualToString:AOPLoggerPositionAfter]) {
positionOptions=AspectPositionAfter;
}
if ([loggerConfigInfo[AOPLoggerPositionType] isEqualToString:AOPLoggerPositionBefore]) {
positionOptions=AspectPositionBefore;
}
[clazz aspect_hookSelector:selector
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> aspectInfo) {
id log=eventInfo[AOPLoggerLogInfo];
if ([[AOPLogger sharedAOPLogger] conformsToProtocol:objc_getProtocol("AOPLoggerBLLProtocol")]) {
[(AOPLogger<AOPLoggerBLLProtocol>*)[AOPLogger sharedAOPLogger] al_logger:log originAOP:aspectInfo];
}
else{
if ([log isKindOfClass:[NSString class]]) {
NSLog(@"AOPLogger:%@",log);
}
}
} error:NULL];
}
}
}
+(void)AOPLoggerWithClassString:(NSString *)classString methodString:(NSString *)methodString log:(id)log{
[self AOPLoggerWithClassString:classString methodString:methodString log:log logPosition:nil];
}
+(void)AOPLoggerWithClassString:(NSString *)classString methodString:(NSString *)methodString log:(id)log logPosition:(NSString*)logPosition{
Class clazz = NSClassFromString(classString);
SEL selector = NSSelectorFromString(methodString);
AspectOptions positionOptions=AspectPositionAfter;
if ([logPosition isEqualToString:AOPLoggerPositionAfter]) {
positionOptions=AspectPositionAfter;
}
if ([logPosition isEqualToString:AOPLoggerPositionBefore]) {
positionOptions=AspectPositionBefore;
}
[clazz aspect_hookSelector:selector
withOptions:positionOptions
usingBlock:^(id<AspectInfo> aspectInfo) {
if ([[AOPLogger sharedAOPLogger] conformsToProtocol:objc_getProtocol("AOPLoggerBLLProtocol")]) {
[(AOPLogger<AOPLoggerBLLProtocol>*)[AOPLogger sharedAOPLogger] al_logger:log originAOP:aspectInfo];
}
else{
if ([log isKindOfClass:[NSString class]]) {
NSLog(@"AOPLogger:%@",log);
}
}
} error:NULL];
}
核心處理.m直接不到百行,主要邏輯就是 單例初始化, 讀取plist并格式化出執行的類 ·方法·log信息,最后就是利用AOP,切面hook對應方法插入我們統計邏輯。
有了這個庫完全可以日志動態話統計,前提你要做好熱更新或使用plist形式。