在游戏测试过程中我们可能会遇到有些偶发性的Bug不能复现,不能定位的问题,这就很尴尬了。
所以我们在这里实现一下实时记录游戏日志的功能。
记录日志,具体记些什么呢?不可能把每一步都记的很清楚,但是我们在开发过程中,每一步可能会打印一些特定的输出,我们就在输出上做一些手脚,把它的每一行都记录下来。
环境:Cocos2d-x quick
LogFile.h:
很清爽的一段代码。我把lua_upload这个方法抛到Lua里了,直接调用就好。
所以我们在这里实现一下实时记录游戏日志的功能。
记录日志,具体记些什么呢?不可能把每一步都记的很清楚,但是我们在开发过程中,每一步可能会打印一些特定的输出,我们就在输出上做一些手脚,把它的每一行都记录下来。
环境:Cocos2d-x quick
if DEBUG_MODE then
--把打印的log写入文件
LOG_FILE_NAME = "log_file.txt"
BACKUP_LOG_FILE_NAME = "backup_log_file.txt"
--如果之前写过文件就把之前的替换成备份
if io.exists(device.writablePath..LOG_FILE_NAME) then
--检查之前是否有备份
if io.exists(device.writablePath..BACKUP_LOG_FILE_NAME) then
--如果有备份则删掉备份
os.execute("rm "..device.writablePath..BACKUP_LOG_FILE_NAME)
end
--把之前的文件替换成备份文件
io.open(device.writablePath..BACKUP_LOG_FILE_NAME,'a+')
os.execute("mv " .. device.writablePath..LOG_FILE_NAME .. " " .. device.writablePath..BACKUP_LOG_FILE_NAME)
end
--新建一个新的空文件
print(os.execute("touch "..device.writablePath..LOG_FILE_NAME))
LOG_FILE_PATH = io.open(device.writablePath..LOG_FILE_NAME,'a+')
--重写print 让打印的东西能同时写入到文件里
old_print = print
print = function (...)
old_print(...)
--写入
if ... then
local args = {...}
local s = args[1]
if #args > 1 then
for i=2,#args do
s = s .. " | " .. args[i]
end
end
LOG_FILE_PATH:write(tostring(s).."\n")
LOG_FILE_PATH:flush()
end
end
end
记录Log的方法就到此为止了,DEBUG_MODE在开启的时候没写一行print都会向文件里写入一行。
下面说一下上传。
上传我们用了libcurl,因为在iOS和Android的系统中没有集成,所以不能直接调用os.execute(curl xxxxx),改在C++层进行上传的操作。
新建LogFile.cpp和LogFile.h两个文件
LogFile.cpp
//
// LogFile.cpp
// vegasnights
//
// Created by DonLiu on 16/7/6.
//
//
#include "LogFile.h"
/* parse headers for Content-Length */
size_t LogFile::getcontentlengthfunc(void *ptr, size_t size, size_t nmemb, void *stream)
{
int r;
long len = 0;
/* _snscanf() is Win32 specific */
//r = _snscanf(ptr, size * nmemb, "Content-Length: %ld\n", &len);
r = sscanf((const char*)ptr, "Content-Length: %ld\n", &len);
if (r) /* Microsoft: we don't read the specs */
*((long *) stream) = len;
return size * nmemb;
}
/* discard downloaded data */
size_t LogFile::discardfunc(void *ptr, size_t size, size_t nmemb, void *stream)
{
return size * nmemb;
}
//write data to upload
size_t LogFile::writefunc(void *ptr, size_t size, size_t nmemb, void *stream)
{
return fwrite(ptr, size, nmemb, (FILE*)stream);
}
/* read data to upload */
size_t LogFile::readfunc(void *ptr, size_t size, size_t nmemb, void *stream)
{
FILE *f = (FILE*)stream;
size_t n;
if (ferror(f))
return CURL_READFUNC_ABORT;
n = fread(ptr, size, nmemb, f) * size;
return n;
}
int LogFile::upload(CURL *curlhandle, const char * remotepath, const char * localpath,const char * userpwd, long timeout, long tries)
{
FILE *f;
long uploaded_len = 0;
CURLcode r = CURLE_GOT_NOTHING;
int c;
f = fopen(localpath, "rb");
if (f == NULL) {
perror(NULL);
return 0;
}
curl_easy_setopt(curlhandle, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curlhandle, CURLOPT_URL, remotepath);
curl_easy_setopt(curlhandle, CURLOPT_USERPWD, userpwd);
curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, 1);
if (timeout)
curl_easy_setopt(curlhandle, CURLOPT_FTP_RESPONSE_TIMEOUT, timeout);
curl_easy_setopt(curlhandle, CURLOPT_HEADERFUNCTION, getcontentlengthfunc);
curl_easy_setopt(curlhandle, CURLOPT_HEADERDATA, &uploaded_len);
curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, discardfunc);
curl_easy_setopt(curlhandle, CURLOPT_READFUNCTION, readfunc);
curl_easy_setopt(curlhandle, CURLOPT_READDATA, f);
curl_easy_setopt(curlhandle, CURLOPT_FTPPORT, "-"); /* disable passive mode */
curl_easy_setopt(curlhandle, CURLOPT_FTP_CREATE_MISSING_DIRS, 1L);
curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, 1L);
for (c = 0; (r != CURLE_OK) && (c < tries); c++) {
/* are we resuming? */
if (c) { /* yes */
/* determine the length of the file already written */
/*
* With NOBODY and NOHEADER, libcurl will issue a SIZE
* command, but the only way to retrieve the result is
* to parse the returned Content-Length header. Thus,
* getcontentlengthfunc(). We need discardfunc() above
* because HEADER will dump the headers to stdout
* without it.
*/
curl_easy_setopt(curlhandle, CURLOPT_NOBODY, 1L);
curl_easy_setopt(curlhandle, CURLOPT_HEADER, 1L);
r = curl_easy_perform(curlhandle);
if (r != CURLE_OK)
continue;
curl_easy_setopt(curlhandle, CURLOPT_NOBODY, 0L);
curl_easy_setopt(curlhandle, CURLOPT_HEADER, 0L);
fseek(f, uploaded_len, SEEK_SET);
curl_easy_setopt(curlhandle, CURLOPT_APPEND, 1L);
}
else { /* no */
curl_easy_setopt(curlhandle, CURLOPT_APPEND, 0L);
}
r = curl_easy_perform(curlhandle);
}
fclose(f);
if (r == CURLE_OK)
return 1;
else {
fprintf(stderr, "%s\n", curl_easy_strerror(r));
return 0;
}
}
// 下载
int LogFile::download(CURL *curlhandle, const char * remotepath, const char * localpath,const char * userpwd, long timeout, long tries)
{
FILE *f;
curl_off_t local_file_len = -1 ;
long filesize =0 ;
CURLcode r = CURLE_GOT_NOTHING;
struct stat file_info;
int use_resume = 0;
//获取本地文件大小信息
if(stat(localpath, &file_info) == 0)
{
local_file_len = file_info.st_size;
use_resume = 1;
}
//追加方式打开文件,实现断点续传
f = fopen(localpath, "ab+");
if (f == NULL) {
perror(NULL);
return 0;
}
curl_easy_setopt(curlhandle, CURLOPT_URL, remotepath);
curl_easy_setopt(curlhandle, CURLOPT_USERPWD, userpwd);
//连接超时设置
curl_easy_setopt(curlhandle, CURLOPT_CONNECTTIMEOUT, timeout);
//设置头处理函数
curl_easy_setopt(curlhandle, CURLOPT_HEADERFUNCTION, getcontentlengthfunc);
curl_easy_setopt(curlhandle, CURLOPT_HEADERDATA, &filesize);
// 设置断点续传
curl_easy_setopt(curlhandle, CURLOPT_RESUME_FROM_LARGE, use_resume?local_file_len:0);
curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, writefunc);
curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, f);
curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, 1L);
r = curl_easy_perform(curlhandle);
fclose(f);
if (r == CURLE_OK)
return 1;
else {
fprintf(stderr, "%s\n", curl_easy_strerror(r));
return 0;
}
}
void LogFile::lua_upload(const char * remotepath, const char *localpath,const char * userpwd){
CURL *curlhandle = curl_easy_init();
upload(curlhandle, remotepath, localpath, userpwd, 1, 3);
curl_easy_cleanup(curlhandle);
curl_global_cleanup();
}
LogFile.h:
//
// LogFile.hpp
// vegasnights
//
// Created by DonLiu on 16/7/6.
//
//
#ifndef LogFile_hpp
#define LogFile_hpp
#include <stdlib.h>
#include <stdio.h>
#include <curl/curl.h>
#include <sys/stat.h>
class LogFile {
public:
static size_t getcontentlengthfunc(void *ptr, size_t size, size_t nmemb, void *stream);
static size_t discardfunc(void *ptr, size_t size, size_t nmemb, void *stream);
static size_t writefunc(void *ptr, size_t size, size_t nmemb, void *stream);
static size_t readfunc(void *ptr, size_t size, size_t nmemb, void *stream);
static int upload(CURL *curlhandle, const char * remotepath, const char * localpath,const char * userpwd, long timeout, long tries);
static int download(CURL *curlhandle, const char * remotepath, const char * localpath,const char * userpwd, long timeout, long tries);
static void lua_upload(const char * remotepath, const char * localpath,const char * userpwd);
};
#endif /* LogFile_hpp */
很清爽的一段代码。我把lua_upload这个方法抛到Lua里了,直接调用就好。