我们在项目中日志记录这块也算是比较重要的,有时候用户程序出什么问题,光靠服务器的日志还不能准确的找到问题
现在一般记录日志有几种方式:
1、使用第三方工具来记录日志,如腾讯的bugly,它是只把程序的异常日志,程序崩溃日志,以及一些自定义的操作日志上传到bugly的后台
2、我们把日志记录到本地,在适合的时候再上传到服务器
这里我要介绍的是第二种方法,第一种和第二种可以一起用。
假如现在有下面这样的日志记录要求
1、日志记录在本地
2、日志最多记录n天,n天之前的都需要清理掉
3、日志可以上传到服务器,由服务器控制是否需要上传
4、上传的日志应该压缩后再上传
实现思路
1、日志记录在本地
也就是把字符串保存到本地,我们可以用 将nsstring转换成nsdata然后写入本地,但是nsdata写入本地会对本地的文件进入覆盖,所以我们只有当文件不存在的时候第一次写入的时候用这种方式,如果要将日志内容追加到日志文件里面,我们可以用nsflehandle来处理
2、日志最多记录n天,n天之前的都需要清理掉
这个就比较容易了,我们可以将本地日志文件名定成当天日期,每天一个日志文件,这样我们在程序启动后,可以去检测并清理掉过期的日志文件
3、日志可以上传到服务器,由服务器控制是否需要上传
这个功能我们需要后台的配合,后台需要提供两个接口,一个是app去请求时返回当前应用是否需要上传日志,根据参数来判断,第二个接口就是上传日志的接口
4、上传的日志应该压缩后再上传
一般压缩的功能我们可以使用zip压缩,oc中有开源的插件 ziparchive 地址: http://code.google.com/p/ziparchive/(需要fq)
具体实现代码
我们先将ziparchive引入到项目中,注意还需要引入系统的 libz.tbd 动态库,如下:
由于ziparchive是使用c++编写的,是不支持arc的,所以我们需要在项目中把这个类的arc关闭掉,不然会编译不通过,如下:
给ziparchive.mm文件添加一个 -fno-objc-arc 标签就可以了
然后就是代码部分了,创建一个日志工具类,logmanager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
// // logmanager.h // logfiledemo // // created by xgao on 17/3/9. // copyright © 2017年 xgao. all rights reserved. // #import <foundation/foundation.h> @interface logmanager : nsobject /** * 获取单例实例 * * @return 单例实例 */ + (instancetype) sharedinstance; #pragma mark - method /** * 写入日志 * * @param module 模块名称 * @param logstr 日志信息,动态参数 */ - ( void )loginfo:(nsstring*)module logstr:(nsstring*)logstr, ...; /** * 清空过期的日志 */ - ( void )clearexpiredlog; /** * 检测日志是否需要上传 */ - ( void )checklogneedupload; @end |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
|
// // logmanager.m // logfiledemo // // created by xgao on 17/3/9. // copyright © 2017年 xgao. all rights reserved. // #import "logmanager.h" #import "ziparchive.h" #import "xgnetworking.h" // 日志保留最大天数 static const int logmaxsaveday = 7; // 日志文件保存目录 static const nsstring* logfilepath = @ "/documents/otklog/" ; // 日志压缩包文件名 static nsstring* zipfilename = @ "otklog.zip" ; @interface logmanager() // 日期格式化 @property (nonatomic,retain) nsdateformatter* dateformatter; // 时间格式化 @property (nonatomic,retain) nsdateformatter* timeformatter; // 日志的目录路径 @property (nonatomic,copy) nsstring* basepath; @end @implementation logmanager /** * 获取单例实例 * * @return 单例实例 */ + (instancetype) sharedinstance{ static logmanager* instance = nil; static dispatch_once_t oncetoken; dispatch_once(&oncetoken, ^{ if (!instance) { instance = [[logmanager alloc]init]; } }); return instance; } // 获取当前时间 + (nsdate*)getcurrdate{ nsdate *date = [nsdate date]; nstimezone *zone = [nstimezone systemtimezone]; nsinteger interval = [zone secondsfromgmtfordate: date]; nsdate *localedate = [date datebyaddingtimeinterval: interval]; return localedate; } #pragma mark - init - (instancetype)init{ self = [super init]; if (self) { // 创建日期格式化 nsdateformatter* dateformatter = [[nsdateformatter alloc]init]; [dateformatter setdateformat:@ "yyyy-mm-dd" ]; // 设置时区,解决8小时 [dateformatter settimezone:[nstimezone timezonewithabbreviation:@ "utc" ]]; self.dateformatter = dateformatter; // 创建时间格式化 nsdateformatter* timeformatter = [[nsdateformatter alloc]init]; [timeformatter setdateformat:@ "hh:mm:ss" ]; [timeformatter settimezone:[nstimezone timezonewithabbreviation:@ "utc" ]]; self.timeformatter = timeformatter; // 日志的目录路径 self.basepath = [nsstring stringwithformat:@ "%@%@" ,nshomedirectory(),logfilepath]; } return self; } #pragma mark - method /** * 写入日志 * * @param module 模块名称 * @param logstr 日志信息,动态参数 */ - ( void )loginfo:(nsstring*)module logstr:(nsstring*)logstr, ...{ #pragma mark - 获取参数 nsmutablestring* parmastr = [nsmutablestring string]; // 声明一个参数指针 va_list paramlist; // 获取参数地址,将paramlist指向logstr va_start (paramlist, logstr); id arg = logstr; @ try { // 遍历参数列表 while (arg) { [parmastr appendstring:arg]; // 指向下一个参数,后面是参数类似 arg = va_arg (paramlist, nsstring*); } } @ catch (nsexception *exception) { [parmastr appendstring:@ "【记录日志异常】" ]; } @finally { // 将参数列表指针置空 va_end (paramlist); } #pragma mark - 写入日志 // 异步执行 dispatch_async(dispatch_queue_create( "writelog" , nil), ^{ // 获取当前日期做为文件名 nsstring* filename = [self.dateformatter stringfromdate:[nsdate date]]; nsstring* filepath = [nsstring stringwithformat:@ "%@%@" ,self.basepath,filename]; // [时间]-[模块]-日志内容 nsstring* timestr = [self.timeformatter stringfromdate:[logmanager getcurrdate]]; nsstring* writestr = [nsstring stringwithformat:@ "[%@]-[%@]-%@\n" ,timestr,module,parmastr]; // 写入数据 [self writefile:filepath stringdata:writestr]; nslog(@ "写入日志:%@" ,filepath); }); } /** * 清空过期的日志 */ - ( void )clearexpiredlog{ // 获取日志目录下的所有文件 nsarray* files = [[nsfilemanager defaultmanager] contentsofdirectoryatpath:self.basepath error:nil]; for (nsstring* file in files) { nsdate* date = [self.dateformatter datefromstring:file]; if (date) { nstimeinterval oldtime = [date timeintervalsince1970]; nstimeinterval currtime = [[logmanager getcurrdate] timeintervalsince1970]; nstimeinterval second = currtime - oldtime; int day = ( int )second / (24 * 3600); if (day >= logmaxsaveday) { // 删除该文件 [[nsfilemanager defaultmanager] removeitematpath:[nsstring stringwithformat:@ "%@/%@" ,self.basepath,file] error:nil]; nslog(@ "[%@]日志文件已被删除!" ,file); } } } } /** * 检测日志是否需要上传 */ - ( void )checklogneedupload{ __block nserror* error = nil; // 获取实体字典 __block nsdictionary* resultdic = nil; // 请求的url,后台功能需要自己做 nsstring* url = [nsstring stringwithformat:@ "%@/common/phone/logs" ,servierurl]; // 发起请求,从服务器上获取当前应用是否需要上传日志 [[xgnetworking sharedinstance] get:url success:^(nsstring* jsondata) { // 获取实体字典 nsdictionary* datadic = [utilities getdatastring:jsondata error:&error]; resultdic = datadic.count > 0 ? [datadic objectforkey:@ "data" ] : nil; if ([resultdic isequal:[nsnull null]]){ error = [nserror errorwithdomain:[nsstring stringwithformat:@ "请求失败,data没有数据!" ] code:500 userinfo:nil]; } // 完成后的处理 if (error == nil) { // 处理上传日志 [self uploadlog:resultdic]; } else { logerror(@ "检测日志返回结果有误!data没有数据!" ); } } faild:^(nsstring *errorinfo) { logerror(([nsstring stringwithformat:@ "检测日志失败!%@" ,errorinfo])); }]; } #pragma mark - private /** * 处理是否需要上传日志 * * @param resultdic 包含获取日期的字典 */ - ( void )uploadlog:(nsdictionary*)resultdic{ if (!resultdic) { return ; } // 0不拉取,1拉取n天,2拉取全部 int type = [resultdic[@ "type" ] intvalue]; // 压缩文件是否创建成功 bool created = no; if (type == 1) { // 拉取指定日期的 // "dates": ["2017-03-01", "2017-03-11"] nsarray* dates = resultdic[@ "dates" ]; // 压缩日志 created = [self compresslog:dates]; } else if (type == 2){ // 拉取全部 // 压缩日志 created = [self compresslog:nil]; } if (created) { // 上传 [self uploadlogtoserver:^( bool boolvalue) { if (boolvalue) { loginfo(@ "日志上传成功---->>" ); // 删除日志压缩文件 [self deletezipfile]; } else { logerror(@ "日志上传失败!!" ); } } errorblock:^(nsstring *errorinfo) { logerror(([nsstring stringwithformat:@ "日志上传失败!!error:%@" ,errorinfo])); }]; } } /** * 压缩日志 * * @param dates 日期时间段,空代表全部 * * @return 执行结果 */ - ( bool )compresslog:(nsarray*)dates{ // 先清理几天前的日志 [self clearexpiredlog]; // 获取日志目录下的所有文件 nsarray* files = [[nsfilemanager defaultmanager] contentsofdirectoryatpath:self.basepath error:nil]; // 压缩包文件路径 nsstring * zipfile = [self.basepath stringbyappendingstring:zipfilename] ; ziparchive* zip = [[ziparchive alloc] init]; // 创建一个zip包 bool created = [zip createzipfile2:zipfile]; if (!created) { // 关闭文件 [zip closezipfile2]; return no; } if (dates) { // 拉取指定日期的 for (nsstring* filename in files) { if ([dates containsobject:filename]) { // 将要被压缩的文件 nsstring *file = [self.basepath stringbyappendingstring:filename]; // 判断文件是否存在 if ([[nsfilemanager defaultmanager] fileexistsatpath:file]) { // 将日志添加到zip包中 [zip addfiletozip:file newname:filename]; } } } } else { // 全部 for (nsstring* filename in files) { // 将要被压缩的文件 nsstring *file = [self.basepath stringbyappendingstring:filename]; // 判断文件是否存在 if ([[nsfilemanager defaultmanager] fileexistsatpath:file]) { // 将日志添加到zip包中 [zip addfiletozip:file newname:filename]; } } } // 关闭文件 [zip closezipfile2]; return yes; } /** * 上传日志到服务器 * * @param returnblock 成功回调 * @param errorblock 失败回调 */ - ( void )uploadlogtoserver:(boolblock)returnblock errorblock:(errorblock)errorblock{ __block nserror* error = nil; // 获取实体字典 __block nsdictionary* resultdic; // 访问url nsstring* url = [nsstring stringwithformat:@ "%@/fileupload/fileupload/logs" ,servierurl_file]; // 发起请求,这里是上传日志到服务器,后台功能需要自己做 [[xgnetworking sharedinstance] upload:url filedata:nil filename:zipfilename mimetype:@ "application/zip" parameters:nil success:^(nsstring *jsondata) { // 获取实体字典 resultdic = [utilities getdatastring:jsondata error:&error]; // 完成后的处理 if (error == nil) { // 回调返回数据 returnblock([resultdic[@ "state" ] boolvalue]); } else { if (errorblock){ errorblock(error.domain); } } } faild:^(nsstring *errorinfo) { returnblock(errorinfo); }]; } /** * 删除日志压缩文件 */ - ( void )deletezipfile{ nsstring* zipfilepath = [self.basepath stringbyappendingstring:zipfilename]; if ([[nsfilemanager defaultmanager] fileexistsatpath:zipfilepath]) { [[nsfilemanager defaultmanager] removeitematpath:zipfilepath error:nil]; } } /** * 写入字符串到指定文件,默认追加内容 * * @param filepath 文件路径 * @param stringdata 待写入的字符串 */ - ( void )writefile:(nsstring*)filepath stringdata:(nsstring*)stringdata{ // 待写入的数据 nsdata* writedata = [stringdata datausingencoding:nsutf8stringencoding]; // nsfilemanager 用于处理文件 bool createpathok = yes; if (![[nsfilemanager defaultmanager] fileexistsatpath:[filepath stringbydeletinglastpathcomponent] isdirectory:&createpathok]) { // 目录不存先创建 [[nsfilemanager defaultmanager] createdirectoryatpath:[filepath stringbydeletinglastpathcomponent] withintermediatedirectories:yes attributes:nil error:nil]; } if (![[nsfilemanager defaultmanager] fileexistsatpath:filepath]){ // 文件不存在,直接创建文件并写入 [writedata writetofile:filepath atomically:no]; } else { // nsfilehandle 用于处理文件内容 // 读取文件到上下文,并且是更新模式 nsfilehandle* filehandler = [nsfilehandle filehandleforupdatingatpath:filepath]; // 跳到文件末尾 [filehandler seektoendoffile]; // 追加数据 [filehandler writedata:writedata]; // 关闭文件 [filehandler closefile]; } } @end |
日志工具的使用
1、记录日志
[[logmanager sharedinstance] loginfo:@"首页" logstr:@"这是日志信息!",@"可以多参数",nil];
2、我们在程序启动后,进行一次检测,看要不要上传日志
1
2
|
// 几秒后检测是否有需要上传的日志 [[logmanager sharedinstance] performselector:@selector(checklogneedupload) withobject:nil afterdelay:3]; |
这里可能有人发现我们在记录日志的时候为什么最后面要加上nil,因为这个是oc中动态参数的结束后缀,不加上nil,程序就不知道你有多少个参数,可能有人又要说了,nsstring的 stringwithformat 方法为什么不需要加 nil 也可以呢,那是因为stringwithformat里面用到了占位符,就是那些 %@ %i 之类的,这样程序就能判断你有多少个参数了,所以就不用加 nil 了
看到这里,可能大家觉得这个记录日志的方法有点长,后面还加要nil,不方便,那能不能再优化一些,让它更简单的调用呢?我可以用到宏来优化,我们这样定义一个宏,如下:
1
2
|
// 记录本地日志 #define llog(module,...) [[logmanager sharedinstance] loginfo:module logstr:__va_args__,nil] |
这样我们使用的时候就方便了,这样调用就行了。
llog(@"首页", @"这是日志信息!",@"可以多参数");
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持服务器之家!
原文链接:http://www.cnblogs.com/xgao/p/6553334.html