基本的方案
日志管理采用第三方库CocoaLumberjack/CocoaLumberjack
后台采用阿里云lujiajing1126/AliyunLogObjc
发送失败的日志,存本地缓存,手机启动或者从后台进入前台的时候再发一次。本地缓存采用第三方库ibireme/YYCache
接口函数
采用类似NSLog();
的形式,只是对第三方库的一层简单封装
// 默认的宏,方便使用
#define WJSLog(frmt, ...) WJSLogInfo(frmt, ##__VA_ARGS__)
// 提供不同的宏,对应到特定参数的对外接口
#define WJSLogError(frmt, ...) DDLogError(frmt, ##__VA_ARGS__)
#define WJSLogWarning(frmt, ...) DDLogWarn(frmt, ##__VA_ARGS__)
#define WJSLogInfo(frmt, ...) DDLogInfo(frmt, ##__VA_ARGS__)
#define WJSLogDebug(frmt, ...) DDLogDebug(frmt, ##__VA_ARGS__)
#define WJSLogVerbose(frmt, ...) DDLogVerbose(frmt, ##__VA_ARGS__)
日志等级
- 按照第三方库的分法,分为5个等级
typedef NS_OPTIONS(NSUInteger, DDLogFlag){
/**
* 0...00001 DDLogFlagError
*/
DDLogFlagError = (1 << 0),
/**
* 0...00010 DDLogFlagWarning
*/
DDLogFlagWarning = (1 << 1),
/**
* 0...00100 DDLogFlagInfo
*/
DDLogFlagInfo = (1 << 2),
/**
* 0...01000 DDLogFlagDebug
*/
DDLogFlagDebug = (1 << 3),
/**
* 0...10000 DDLogFlagVerbose
*/
DDLogFlagVerbose = (1 << 4)
};
-
DEBUG
模式全部输出,其他模式只输出DDLogFlagInfo
以上的日志
#ifdef DEBUG
static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
#else
static const DDLogLevel ddLogLevel = DDLogLevelWarning;
#endif
- 不论是
DEBUG
模式还是其他什么模式,DDLogLevelVerbose
和DDLogLevelDebug
两种级别的日志都不发后台
// 对于Debug和verbose级别的日志,不发送到后台
// 但我们依然要告诉 DDLog 这个存进去了。
if ((DDLogLevelDebug == logMessage.flag) || (DDLogLevelVerbose == logMessage.flag)) {
return YES;
}
对第三方库CocoaLumberjack
功能选择
输出到
XCode
控制台的DDTTYLogger
需要保留XCode8
之后,插件基本上都被封了,所以颜色的插件不引入robbiehanson/XcodeColors输出到控制台的
DDASLLogger
是给MAC
开发用的,iOS
开发不适用,不导入。文件系统在用户手机上,除了占用户手机空间,基本没什么大用,所以
DDFileLogger
也不引入。DDAbstractDatabaseLogger
,是写入本地数据库的一个类,不能直接用(具体的读写数据库的类只定义了方法名,需要子类覆盖),适合作为基类,自定义实现。实际使用中继承这个类,进行自定义。DDAbstractDatabaseLogger
有个很好的特性,就是可以定义当日志达到多少条或者间隔时间达到多久,(这两个是或的关系),他就调用函数- (void)db_save;
在这个函数里,我们可以自定义覆盖,在这里将日志批量发送给阿里云后台。是一个数组,数组的成员是一个字典,一个字典代表一条日志。
#define kLogNumberThreshold 50 // 达到多少条就保存传后台
#define kLogTimeThreshold (5 * 60) // 间隔多少时间(单位:秒)就保存传后台
我们定义的是日志数量达到
50
条或者间隔时间达到5
分钟,就往阿里云批量发送一次。
- (instancetype)init {
self = [super init];
if (self) {
// 自定义的log,直接处理格式,不用代理。阿里云要求的是字典,不是字符串
XXXLogger *logger = [[XXXLogger alloc] init];
[DDLog addLogger:logger];
// XCode的log,用自定义的输出格式,采用代理的格式
XXXLogFormatter *formatter = [[XXXLogFormatter alloc] init];
[[DDTTYLogger sharedInstance] setLogFormatter:formatter];
[DDLog addLogger:[DDTTYLogger sharedInstance]]; // TTY = Xcode console
}
return self;
}
日志格式
没有默认提供的日志格式
提供了一个协议,来自定义日志的格式
自定义一个类,实现协议函数,添加一些必要的信息:比如日志等级,文件名,函数名,行数等等
这个格式仅仅用在
XCode
的调试输出,传到阿里云后台的格式在自定义类中直接写。那里要求的是一个字典,不是一个字符串。
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
NSString *logLevel = nil;
switch (logMessage->_flag) {
case DDLogFlagError:
logLevel = @"[ERROR] > ";
break;
case DDLogFlagWarning:
logLevel = @"[WARN] > ";
break;
case DDLogFlagInfo:
logLevel = @"[INFO] > ";
break;
case DDLogFlagDebug:
logLevel = @"[DEBUG] > ";
break;
default:
logLevel = @"[VBOSE] > ";
break;
}
NSString *formatLog = [NSString stringWithFormat:@"%@ %@ %@ [line: %ld] %@",
logLevel, logMessage->_fileName, logMessage->_function,
logMessage->_line, logMessage->_message];
return formatLog;
}
日志发送到阿里云
#import <AliyunLogObjc/AliyunLogObjc.h>
LogClient *client = [[LogClient alloc] initWithApp: @"endpoint" accessKeyID:@"" accessKeySecret:@"" projectName:@""];
LogGroup *logGroup = [[LogGroup alloc] initWithTopic: @"" andSource:@""];
Log *log1 = [[Log alloc] init];
[log1 PutContent: @"Value" withKey: @"Key"];
[logGroup PutLog:log1];
[client PostLog:logGroup logStoreName: @"" call:^(NSURLResponse* _Nullable response,NSError* _Nullable error) {
if (error != nil) {
}
}];
LogClient
一般只要一个就可以了,可以考虑用一个单例包装一下。那些参数可以问运维拿,买了阿里云的服务之后,一般都是有的。LogGroup
本质上是将一群log放在一个数组中,一次性发送。所以可以考虑以字符数组为入参,用他包装一下,然后调用LogClient
发送LogGroup
创建时需要两个固定字段__topic__
和__source
。__topic__
代表主题,对日志进行分类。这里的日志都是集中处理的,主要通过日志级别区分,主题很难给,所以统一给了个ios-log-native
。__source
代表日志来源,PC
上可以给ip
地址,手机上可以考虑给广告id
,能标识设备就可以了。当然,这两个字段给空字符串也没什么问题Log
代表一条日志,是一个字典。有些字段是固定的,比如时间,对应的key
是__time__
。其他的字段,跟运维商量,这里填进去就好了。比如文件名file_name
,函数名function
,行数line
等等约定一个名字,到时候方便归类查找就可以了。
#define kLogKeyLevel @"level" // 日志等级
#define kLogKeyFileName @"file_name" // 文件名或者说是类名
#define kLogKeyFunction @"function" // 函数名或者说是方法名
#define kLogKeyLine @"line" // 行数
#define kLogKeyContent @"content" // 日志内容
发送函数就是
PostLog
函数,成功和失败的回调函数都在errorCallback
中。可以通过判断参数error
是否为nil
来区分成功和失败。这种设计真的很烂。函数接口,现在都要求显示指定是否为
nil
,如果用Carthage
管理,有warning
也不用管,反正有framework
进行隔离。不过,我们这次是直接引入源文件的(要支持iOS7
),所以为了消除warning
,需要加上_Nullable
- (id _Nonnull )initWithApp:(NSString*_Nonnull) endPoint accessKeyID:(NSString *_Nonnull)ak accessKeySecret: (NSString *_Nonnull)as projectName: (NSString *_Nonnull)name;
- (void)PostLog:(LogGroup*_Nonnull)logGroup logStoreName:(NSString*_Nullable)name call:(void (^_Nullable)(NSURLResponse* _Nullable response,NSError* _Nullable error) )errorCallback;
-
关于证书:阿里云的接口,固定是
https
的,不过我们的证书过期了,需要改成http
的。这个就要改阿里云接口的源码LogClient.m
。最好是用https
的,不要改源码
- (void)PostLog:(LogGroup*)logGroup logStoreName:(NSString*)name call:(void (^)(NSURLResponse* _Nullable response,NSError* _Nullable error) )errorCallback {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Force to use https api interface
// Due to the requirement of Apple Security Policy after Jan. 1st, 2017
// NSString *httpUrl = [NSString stringWithFormat:@"https://%@.%@/logstores/%@/shards/lb",_mProject,_mEndPoint,name];
// 我们的阿里云服务不支持证书,这里改为http可以把log传到后台,否则用https就是证书不通过。
NSString *httpUrl = [NSString stringWithFormat:@"http://%@.%@/logstores/%@/shards/lb",_mProject,_mEndPoint,name];
NSData *httpPostBody = [[logGroup GetJsonPackage] dataUsingEncoding:NSUTF8StringEncoding];
NSData *httpPostBodyZipped = [httpPostBody gzippedData];
NSDictionary<NSString*,NSString*>* httpHeaders = [self GetHttpHeadersFrom:name url:httpUrl body:httpPostBody bodyZipped:httpPostBodyZipped];
[self HttpPostRequest: httpUrl withHeaders:httpHeaders andBody:httpPostBodyZipped callback:errorCallback];
});
}
关于本地缓存
CocoaLumberjack
中有DDFileLogger
做手机的本地缓存,引入也很方便。不过他没有提供读取和存储文件日志的接口函数,日志格式也不符合阿里云的需求,所以不准备用。本地缓存,采用
YYCache
来自定义实现,key-value
的格式,存取都很方便,并且还是id
类型值,NSString *
的key
,非常合理的设计一般情况下,将
log
存用户手机并没有什么用,因为你总不能让用户的手机拿过来给你导一下日志吧?所以大多数情况下只要将日志发送后台就可以了。阿里云的接口,就是将一个数组发送出去,没有涉及本地缓存的内容在网络异常情况下产生的日志,无法发送阿里云。这种情况下,本地缓存是有价值的。在网络好的时候,再把这部分日志发送到后台
本地缓存是
key-value
格式的,对于日志来说,很难定义这个key
,要能方便地定义这个key
是比较麻烦的,存取是分开的,彼此并不知道对应的key
。这里的方法是固定提供一定数量的位置,命名规律化比如log0 ~ log999
,每一个位置对应一个数组的日志。同时也只是正常发送失败时的缓存,应该差不多了。可以考虑做一个定时器,定时将本地缓存发送到阿里云。这里定义的时间间隔是
1
小时。存
log
到本地缓存的方式是先查log0 ~ log999
哪个有空,哪个有空就用哪个key
,将内容缓存到本地。如果一圈下来都满了,就把最后一个覆盖掉。
- (void)saveLogsToLocalCache:(NSArray *)logs {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized (self) {
NSString *key = nil;
// 从0开始轮询,直到查到一个有空的;如果填满了,就覆盖最后一个
for (NSInteger i = 0; i < 1000; i++) {
key = [NSString stringWithFormat:@"Log%ld", (long)i];
if (![self.localCache containsObjectForKey:key]) {
break;
}
}
[self.localCache setObject:logs forKey:key];
}
});
}
- 取
log
的方式是定时轮询(1
小时),log0 ~ log999
都查一遍,只要有内容,就取出来,发送到阿里云。如果发送成功就把对应的logi
删除掉。如果失败,就什么也不做(本来就已经在本地缓存中)。等下一个周期来再试一次。
- (void)sendLocalCacheLogsToAliyun {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized (self) {
for (NSInteger i = 0; i < 1000; i++) {
NSString *key = [NSString stringWithFormat:@"WJSLog%ld", i];
NSArray *logs = (NSArray *)[self.localCache objectForKey:key];
if ((nil == logs) || (0 == logs.count)) {
continue;
}
[self sendLogsToAliyun:logs success:^(NSURLResponse * _Nullable response) {
[self.localCache removeObjectForKey:key];
} fail:nil];
}
}
});
}
日志发送
- 外面包一层,给出简单的接口,方便使用
@interface XXXLogSender : NSObject
// 将logs数组发送到阿里云,如果失败,会把这个logs存储在本地缓存中(内存缓存和磁盘缓存中都有)
+ (void)sendLogs:(NSArray *)logs;
@end
- 内部调用了阿里云的接口进行日志发送;调用
YYCache
的API
进行发送失败时的本地缓存。这些都隐藏在实现文件内部。隐藏的方式是“类方法+单例”,对外提供简洁的调用接口。
#import "XXXLogSender.h"
#import "LogClient.h"
#import "LogGroup.h"
#import "Log.h"
#import <YYCache/YYCache.h>
#import <AdSupport/AdSupport.h>
#define kEndpoint @"" // 问运维要
#define kAccessKeyID @"" // 问运维要
#define kAccessKeySecret @"" // 问运维要
#define kProjectName @"" // 问运维要
#define kLogStoreName @"" // 问运维要
#define kLocalCacheName @"XXXLogCache" // 本地缓存的名字,YYCache要求的
// 留Log0 ~ Log999位置,填满就覆盖最后一个。每一个位置是一个logs数组。发送失败暂存本地
#define kMaxLocalCacheNumber 1000
// 轮询本地缓存,向阿里云发送日志的时间间隔
#define kSendLocalCacheLogsInterval (1 * 60 * 60)
@interface XXXLogSender ()
@property (nonatomic, strong) LogClient *client; // 阿里云日志发送对象
@property (nonatomic, strong) YYCache *localCache; // 本地缓存对象
@property (nonatomic, strong) dispatch_source_t timer; // 本地缓存发送定时器
@end
@implementation XXXLogSender
# pragma mark interface
+ (void)sendLogs:(NSArray *)logs {
[[XXXLogSender sharedInstance] sendLogsToAliyun:logs];
}
# pragma mark lifecycle
+ (instancetype)sharedInstance{
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (void)dealloc {
if (nil != self.timer) {
dispatch_cancel(self.timer);
self.timer = nil;
}
}
- (instancetype)init {
self = [super init];
if (self) {
self.client = [[LogClient alloc] initWithApp:kEndpoint accessKeyID:kAccessKeyID accessKeySecret:kAccessKeySecret projectName:kProjectName];
[self createTimer];
self.localCache = [YYCache cacheWithName:kLocalCacheName];
}
return self;
}
# pragma mark private
- (void)sendLogsToAliyun:(NSArray *)logs {
__weak __typeof(self)weakSelf = self;
[self sendLogsToAliyun:logs success:nil fail:^(NSError * _Nullable error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
[strongSelf saveLogsToLocalCache:logs];
}];
}
- (void)sendLogsToAliyun:(NSArray *)logs success:(void(^)(NSURLResponse * _Nullable response))successCallback fail:(void(^)(NSError * _Nullable error))failCallback {
if ((nil == logs) || (0 == logs.count)) {
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *topic = @"ios-log-native"; // 自定义的,跟运维商量好
NSString *source = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]; // 广告id,用来标识来源手机,
LogGroup *group = [[LogGroup alloc] initWithTopic:topic andSource:source];
for (NSDictionary *sourceLog in logs) {
Log *log = [[Log alloc] init];
[log PutContent:sourceLog[kLogKeyLevel] withKey:kLogKeyLevel];
[log PutContent:sourceLog[kLogKeyFileName] withKey:kLogKeyFileName];
[log PutContent:sourceLog[kLogKeyFunction] withKey:kLogKeyFunction];
[log PutContent:sourceLog[kLogKeyLine] withKey:kLogKeyLine];
[log PutContent:sourceLog[kLogKeyContent] withKey:kLogKeyContent];
[group PutLog:log];
}
[self.client PostLog:group logStoreName:kLogStoreName call:^(NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (nil == error) {
if (nil != successCallback) {
successCallback(response);
}
} else {
if (nil != failCallback) {
failCallback(error);
}
}
}];
});
}
- (void)saveLogsToLocalCache:(NSArray *)logs {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized (self) {
NSString *key = nil;
// 从0开始轮询,直到查到一个有空的;如果填满了,就覆盖最后一个
for (NSInteger i = 0; i < kMaxLocalCacheNumber; i++) {
key = [NSString stringWithFormat:@"WJSLog%ld", (long)i];
if (![self.localCache containsObjectForKey:key]) {
break;
}
}
[self.localCache setObject:logs forKey:key];
}
});
}
- (void)sendLocalCacheLogsToAliyun {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized (self) {
for (NSInteger i = 0; i < kMaxLocalCacheNumber; i++) {
NSString *key = [NSString stringWithFormat:@"WJSLog%ld", i];
NSArray *logs = (NSArray *)[self.localCache objectForKey:key];
if ((nil == logs) || (0 == logs.count)) {
continue;
}
[self sendLogsToAliyun:logs success:^(NSURLResponse * _Nullable response) {
[self.localCache removeObjectForKey:key];
} fail:nil];
}
}
});
}
- (void)createTimer {
// 获得队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建一个定时器
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置开始时间: 1秒之后
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC));
// 设置时间间隔
uint64_t interval = (uint64_t)(kSendLocalCacheLogsInterval * NSEC_PER_SEC);
// 设置定时器
dispatch_source_set_timer(self.timer, start, interval, 0);
// 设置回调
__weak __typeof(self)weakSelf = self;
dispatch_source_set_event_handler(self.timer, ^{
__strong __typeof(weakSelf)strongSelf = weakSelf;
[strongSelf sendLocalCacheLogsToAliyun];
});
//由于定时器默认是暂停的所以我们启动一下
//启动定时器
dispatch_resume(self.timer);
}
@end
自定义logger
- 从
CocoaLumberjack
的类DDAbstractDatabaseLogger
继承而来。这个类不能直接使用,只能继承。关于数据库的存取操作,需要子类覆盖。
#import <CocoaLumberjack/DDAbstractDatabaseLogger.h>
@interface XXXLogger : DDAbstractDatabaseLogger
@end
- 这种通过“虚基类”的方式来提供功能的方法在
C++
中比较常见,在Object-C
中比较少见
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Override Me
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 每次打log的时候都会进来,一般在这里把log搜集到一个数组中,等达到一定条件再统一发送
- (BOOL)db_log:(DDLogMessage *)logMessage {
// Override me and add your implementation.
//
// Return YES if an item was added to the buffer.
// Return NO if the logMessage was ignored.
return NO;
}
// 等条件满足,默认的条件是日志书达到500条,或者,时间间隔超过1分钟,这个函数都会调用一下
// 这里是将log数组发送到阿里云的地方
- (void)db_save {
// Override me and add your implementation.
}
// 下面两个是关于本地数据库删除的实现函数。我们其实并不需要本地数据库缓存,这两个函数可以不实现。
- (void)db_delete {
// Override me and add your implementation.
}
- (void)db_saveAndDelete {
// Override me and add your implementation.
}
- 监听消息
UIApplicationWillResignActiveNotification
,在程序进入后台之前保存一下当前的日志。
#import "XXXLogger.h"
#import "XXXLogSender.h"
#define kLogNumberThreshold 50 // 达到多少条就保存传后台
#define kLogTimeThreshold (5 * 60) // 间隔多少时间(秒)就保存传后台
// 数组容量达到这个最大值的话,说明网络出了问题
#define kLogMaxCapacity (kLogNumberThreshold * 10)
@interface XXXLogger ()
@property (nonatomic, strong) NSMutableArray *logs;
@end
@implementation XXXLogger
// 生命周期函数
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (instancetype)init {
self = [super init];
if (self) {
self.logs = [NSMutableArray array];
// 使用默认的配置。达到500条或者间隔1分钟就保存;磁盘数据库保留7天,删除操作间隔5分钟,这两个数据不关心,用基类的就可以了
self.saveThreshold = kLogNumberThreshold;
self.saveInterval = kLogTimeThreshold;
// 监听UIApplicationWillResignActiveNotification消息,在程序进入后台前保存log
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
}
return self;
}
// 重写父类函数
- (BOOL)db_log:(DDLogMessage *)logMessage {
if ([self.logs count] > kLogMaxCapacity) {
// 如果段时间内进入大量log,并且迟迟发不到服务器上,我们可以判断哪里出了问题,在这之后的log暂时不处理了。
// 但我们依然要告诉DDLog这个存进去了。
return YES;
}
// 对于Debug和verbose级别的日志,不发送到后台
// 但我们依然要告诉 DDLog 这个存进去了。
if ((DDLogLevelDebug == logMessage.flag) || (DDLogLevelVerbose == logMessage.flag)) {
return YES;
}
// 将log放在缓存的数组中
@synchronized (self) {
// 阿里云要求字典格式的日志;而_logFormatter返回的是字符串,不符合;所以这里直接设置日志的格式
// 自定义的log要传到阿里云后台,值处理3级log
NSMutableDictionary *log = [NSMutableDictionary dictionary];
switch (logMessage.flag) {
case DDLogFlagError:
log[kLogKeyLevel] = @"ERROR";
break;
case DDLogFlagWarning:
log[kLogKeyLevel] = @"WARNING";
break;
default:
log[kLogKeyLevel] = @"INFO";
break;
}
log[kLogKeyFileName] = logMessage.fileName;
log[kLogKeyFunction] = logMessage.function;
log[kLogKeyLine] = [NSString stringWithFormat:@"%ld", (unsigned long)logMessage.line];
log[kLogKeyContent] = logMessage.message;
[self.logs addObject:[log copy]];
}
return YES;
}
- (void)db_save {
//如果缓存内没数据,啥也不做
if (0 == [self.logs count]) {
return;
}
// 将缓存在数组中的logs传给后台,并清空缓存数组
[XXXLogSender sendLogs:[self.logs copy]];
@synchronized (self) {
[self.logs removeAllObjects];
}
}
// selector
// 手机退到后台,不管条件是否满足,都保存一次,也就是向阿里云发送一次
- (void)onWillResignActive:(NSNotification *)notification {
dispatch_async(self.loggerQueue, ^{
[self db_save];
});
}
@end
实际效果
日志库
CocoaLumberjack
基本还可以,提前了解坑,功能上根据实际情况做取舍,没有遇到大的问题,相对比较顺利。本地缓存
YYCache
用下来比较顺手,没有遇到坑,功能满足要求,接口也比较人性化,个人比较推荐使用。这个比自己写数据库,文件,或者序列化什么的都要方便。阿里云接口
AliyunLogObjc
,感觉比较差。问题主要有以下急个:
(1)接口设计很差,不好用。格式要求比较复杂。
(2)加密算法导致程序崩溃,虽然情况比较特殊。
(3)还有几个warning
,比如(long)
强转之类的,接口少nullable
修饰之类的,虽然是小问题,但感觉不好。
(4)Swift
版本提供cathage
是支持的,但是Object-C
版本,CocoaPods
都不提供,感觉有点不合适。
(5)至于Http
还是https
,这个还是我们这里的证书问题。日志,还是先发自己的后台,经过一轮搜集之后,由自己的后台再转阿里云,这个方案比较好一点。将终端的日志先收集起来是最重要的。至于转阿里云后台,自己的后台可以统一控制。