diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e99864b3db4a188731f6637ebf61863e5eb67cb..a5cbe12f7f48f59dcee387191a2daeeda7f11fd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ohos 第二个版本: 1. 修改findbugs 2. 修改SDK更新后适配配置 +3. so包移植 +4. 实现日志存储到本地文件 ## 0.0.1-SNAPSHOT ohos 第一个版本 diff --git a/README.md b/README.md index dea946ac6644b597f7eb219af12d3911b6c90678..0be81016a37bfd3c4f3f2102bb34b417b1e1f191 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ CodeCheck代码测试无异常 CloudTest代码测试无异常 -火绒安全病毒安全检测通过 +病毒安全检测通过 当前版本demo功能与原组件基本无差异 diff --git a/entry/build.gradle b/entry/build.gradle index 86814d85566cffe6a41e491fd3b112af50eb31b1..70b39e9425a97d686c571c90c0da920edfe2cf6e 100644 --- a/entry/build.gradle +++ b/entry/build.gradle @@ -13,12 +13,13 @@ ohos { } } } - + } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar', '*.har']) - implementation('com.gitee.chinasoft_ohos:LogUtils:0.0.1-SNAPSHOT') + implementation fileTree(dir: 'libs', include: ['*.jar', '*.har','*.so']) + // implementation('com.gitee.chinasoft_ohos:LogUtils:0.0.1-SNAPSHOT') + compile project(path: ':library') compile project(path: ':export_api') implementation project(path: ':export_api') testImplementation 'junit:junit:4.13' @@ -26,5 +27,5 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:3.2.0' } decc { - supportType = ['html','xml'] + supportType = ['html', 'xml'] } diff --git a/entry/libs/arm64-v8a/liblog4a-lib.so b/entry/libs/arm64-v8a/liblog4a-lib.so new file mode 100644 index 0000000000000000000000000000000000000000..8e8eed75ff93bae7c6204316882f4588a66ba25f Binary files /dev/null and b/entry/libs/arm64-v8a/liblog4a-lib.so differ diff --git a/entry/libs/log2file-debug.aar b/entry/libs/log2file-debug.aar deleted file mode 100644 index 069138e1ca61ff8a87490fb5296a3b79176927d2..0000000000000000000000000000000000000000 Binary files a/entry/libs/log2file-debug.aar and /dev/null differ diff --git a/entry/src/main/config.json b/entry/src/main/config.json index 6f547c77f59578308dd4750012319b60d95b903e..456cccb7fb77aea72acd497e33b862d56a54194b 100644 --- a/entry/src/main/config.json +++ b/entry/src/main/config.json @@ -23,6 +23,26 @@ "moduleName": "entry", "moduleType": "entry" }, + "reqPermissions": [ + { + "name": "ohos.permission.READ_USER_STORAGE", + "reason": "$string:permreason_read", + "usedScene": + { + "ability": ["com.apkfuns.demo.Ability"], + "when": "always" + } + }, + { + "name": "ohos.permission.WRITE_USER_STORAGE", + "reason": "$string:permreason_write", + "usedScene": + { + "ability": ["com.apkfuns.demo.Ability"], + "when": "always" + } + } + ], "abilities": [ { "skills": [ diff --git a/entry/src/main/java/com/apkfuns/demo/MainAbility.java b/entry/src/main/java/com/apkfuns/demo/MainAbility.java index fde71881a25ecba25fb4a6d35434331e34bba28b..a4bbac04ffa3920a01c74daaf0c5c0d5b1592481 100644 --- a/entry/src/main/java/com/apkfuns/demo/MainAbility.java +++ b/entry/src/main/java/com/apkfuns/demo/MainAbility.java @@ -9,7 +9,8 @@ public class MainAbility extends Ability { public void onStart(Intent intent) { super.onStart(intent); super.setMainRoute(MainAbilitySlice.class.getName()); - + requestPermissionsFromUser( + new String[] { "ohos.permission.READ_USER_STORAGE","ohos.permission.WRITE_USER_STORAGE" } , 0); } } diff --git a/entry/src/main/java/com/apkfuns/demo/MyApplication.java b/entry/src/main/java/com/apkfuns/demo/MyApplication.java index 38318b6191e0b38a246359ec018581c8505ea2a0..a3e9327e95b6450cf77bab951976104a8172a451 100644 --- a/entry/src/main/java/com/apkfuns/demo/MyApplication.java +++ b/entry/src/main/java/com/apkfuns/demo/MyApplication.java @@ -1,7 +1,10 @@ package com.apkfuns.demo; +import com.apkfuns.log2file.LogFileEngineFactory; import com.apkfuns.logutils.LogLevel; import com.apkfuns.logutils.LogUtils; +import com.apkfuns.logutils.file.LogFileFilter; import ohos.aafwk.ability.AbilityPackage; +import ohos.app.Environment; public class MyApplication extends AbilityPackage { @Override @@ -13,5 +16,23 @@ public class MyApplication extends AbilityPackage { .configFormatTag("%d{HH:mm:ss:SSS} %t %c{-5}")// 首行显示信息(可配置日期,线程等等) .configShowBorders(true) // 是否显示边框 .configLevel(LogLevel.TYPE_VERBOSE); // 配置可展示日志等级 + + + // 支持输入日志到文件 + String filePath = getExternalCacheDir().getPath() + "/LogUtils/logs/"; + System.out.println("=================="+filePath); + LogUtils.getLog2FileConfig() + .configLog2FileEnable(true) // 是否输出日志到文件 + .configLogFileEngine(new LogFileEngineFactory(this)) // 日志文件引擎实现 + .configLog2FilePath(filePath) // 日志路径 + .configLog2FileNameFormat("app-%d{yyyyMMdd}.txt") // 日志文件名称 + .configLog2FileLevel(LogLevel.TYPE_VERBOSE) // 文件日志等级 + .configLogFileFilter(new LogFileFilter() { // 文件日志过滤 + @Override + public boolean accept(int level, String tag, String logContent) { + return true; + } + }); } + } \ No newline at end of file diff --git a/entry/src/main/java/com/apkfuns/demo/slice/MainAbilitySlice.java b/entry/src/main/java/com/apkfuns/demo/slice/MainAbilitySlice.java index ed9bbf94b9d25f55d5bbd3c94918823e92091859..6f1067ee9973eacefe56094c557bf3f17747d782 100644 --- a/entry/src/main/java/com/apkfuns/demo/slice/MainAbilitySlice.java +++ b/entry/src/main/java/com/apkfuns/demo/slice/MainAbilitySlice.java @@ -59,7 +59,7 @@ public class MainAbilitySlice extends AbilitySlice implements Component.ClickedL logThred(); break; case 3: - LogUtils.getLog2FileConfig().flushAsync(); + flushAsync(); break; case 4: logIntent(); @@ -78,6 +78,10 @@ public class MainAbilitySlice extends AbilitySlice implements Component.ClickedL LogUtils.i(DataHelper.getBigString(this)); } + private void flushAsync() { + LogUtils.getLog2FileConfig().flushAsync(); + } + private void logThred() { new ThreadLog("thread1", "msg from 1").start(); new ThreadLog("thread2", "msg from 2").start(); diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json index 5ce84fc5e4517dd69f4d9bfd5826f6fa18ea5c0e..6370ef865ebafbbcc59eb24b2795bd1425d68026 100644 --- a/entry/src/main/resources/base/element/string.json +++ b/entry/src/main/resources/base/element/string.json @@ -11,6 +11,14 @@ { "name": "HelloWorld", "value": "Hello World" + }, + { + "name": "permreason_read", + "value": "permreason_read" + }, + { + "name": "permreason_write", + "value": "permreason_write" } ] } \ No newline at end of file diff --git a/entry/src/ohosTest/java/com/apkfuns/logutils/ExampleOhosTest.java b/entry/src/ohosTest/java/com/apkfuns/logutils/ExampleOhosTest.java index 1462dd7f04d398e725acb7f7ff6045a59513ec33..c1597be584654d9d4b5f1a073f3400468c190571 100644 --- a/entry/src/ohosTest/java/com/apkfuns/logutils/ExampleOhosTest.java +++ b/entry/src/ohosTest/java/com/apkfuns/logutils/ExampleOhosTest.java @@ -97,6 +97,26 @@ public class ExampleOhosTest { e.printStackTrace(); } } + /** + * 文件刷新 + */ + @Test + public void flushAsync() { + try { + Class mainAbilitySlice = Class.forName("com.apkfuns.demo.slice.MainAbilitySlice"); + Method logDebug = mainAbilitySlice.getMethod("flushAsync"); + Object obj = mainAbilitySlice.getConstructor().newInstance(); + logDebug.invoke(obj); + } catch (ClassNotFoundException | NoSuchMethodException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } /** * intent打印 diff --git a/log2file/.gitignore b/log2file/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..796b96d1c402326528b4ba3c12ee9d92d0e212e9 --- /dev/null +++ b/log2file/.gitignore @@ -0,0 +1 @@ +/build diff --git a/log2file/build.gradle b/log2file/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..31ddcd43cbb88869a393410b342c9902fc271cc3 --- /dev/null +++ b/log2file/build.gradle @@ -0,0 +1,23 @@ +apply plugin: 'com.huawei.ohos.library' +//For instructions on signature configuration, see https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ide_debug_device-0000001053822404#section1112183053510 +ohos { + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 5 + } + buildTypes { + release { + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar','*.so']) + implementation project(path: ':export_api') + implementation project(path: ':library') + testImplementation 'junit:junit:4.13' +} diff --git a/log2file/consumer-rules.pro b/log2file/consumer-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..9dccc613bc71b04b83531f550bdab2fb667ecfc9 --- /dev/null +++ b/log2file/consumer-rules.pro @@ -0,0 +1 @@ +# Add har specific ProGuard rules for consumer here. \ No newline at end of file diff --git a/log2file/libs/arm64-v8a/liblog4a-lib.so b/log2file/libs/arm64-v8a/liblog4a-lib.so new file mode 100644 index 0000000000000000000000000000000000000000..8e8eed75ff93bae7c6204316882f4588a66ba25f Binary files /dev/null and b/log2file/libs/arm64-v8a/liblog4a-lib.so differ diff --git a/log2file/proguard-rules.pro b/log2file/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..f7666e47561d514b2a76d5a7dfbb43ede86da92a --- /dev/null +++ b/log2file/proguard-rules.pro @@ -0,0 +1 @@ +# config module specific ProGuard rules here. \ No newline at end of file diff --git a/log2file/src/cpp/AsyncFileFlush.cpp b/log2file/src/cpp/AsyncFileFlush.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9091f2c52c9e7c4fc2bcb4c2be6938423d0df734 --- /dev/null +++ b/log2file/src/cpp/AsyncFileFlush.cpp @@ -0,0 +1,56 @@ +// +// Created by pqpo on 2017/11/23. +// + +#include "AsyncFileFlush.h" + +AsyncFileFlush::AsyncFileFlush() { + async_thread = std::thread(&AsyncFileFlush::async_log_thread, this); +} + +AsyncFileFlush::~AsyncFileFlush() { + stopFlush(); +} + +void AsyncFileFlush::async_log_thread() { + while (true) { + std::unique_lock lck_async_log_thread(async_mtx); + while (!async_buffer.empty()) { + FlushBuffer* data = async_buffer.back(); + async_buffer.pop_back(); + flush(data); + } + if (exit) { + return; + } + async_condition.wait(lck_async_log_thread); + } +} + +ssize_t AsyncFileFlush::flush(FlushBuffer* flushBuffer) { + ssize_t written = 0; + FILE* log_file = flushBuffer->logFile(); + if(log_file != nullptr && flushBuffer->length() > 0) { + written = fwrite(flushBuffer->ptr(), flushBuffer->length(), 1, log_file); + fflush(log_file); + } + delete flushBuffer; + return written; +} + +bool AsyncFileFlush::async_flush(FlushBuffer* flushBuffer) { + std::unique_lock lck_async_flush(async_mtx); + if (exit) { + delete flushBuffer; + return false; + } + async_buffer.push_back(flushBuffer); + async_condition.notify_all(); + return true; +} + +void AsyncFileFlush::stopFlush() { + exit = true; + async_condition.notify_all(); + async_thread.join(); +} diff --git a/log2file/src/cpp/FlushBuffer.cpp b/log2file/src/cpp/FlushBuffer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..51b884e600b8bb99e4e261e997618493e0330cc0 --- /dev/null +++ b/log2file/src/cpp/FlushBuffer.cpp @@ -0,0 +1,72 @@ +// +// Created by pqpo on 2018/2/10. +// +#include + +FlushBuffer::FlushBuffer(FILE* log_file, size_t size) : capacity(size), log_file(log_file) {} + +FlushBuffer::~FlushBuffer() { + if (data_ptr != nullptr) { + delete[] data_ptr; + } + if (release != nullptr) { + delete release; + } +} + +size_t FlushBuffer::length() { + if (data_ptr != nullptr && write_ptr != nullptr) { + return write_ptr - data_ptr; + } + return 0; +} + +void *FlushBuffer::ptr() { + return data_ptr; +} + +size_t FlushBuffer::emptySize() { + return capacity - length(); +} + +void FlushBuffer::write(void *data, size_t len) { + + if (data_ptr == nullptr) { + capacity = (size_t)fmax(capacity, len); + data_ptr = new char[capacity]{0}; + write_ptr = data_ptr; + } + + size_t empty_size = emptySize(); + if (len < empty_size) { + memcpy(write_ptr, data, len); + write_ptr += len; + } else { + size_t now_len = length(); + size_t new_capacity = now_len + len; + char* data_tmp = new char[new_capacity]{0}; + memcpy(data_tmp, data_ptr, now_len); + memcpy(data_tmp + now_len, data, len); + char* old_data = data_ptr; + data_ptr = data_tmp; + write_ptr = data_ptr + new_capacity; + delete[] old_data; + } +} + +void FlushBuffer::reset() { + if (data_ptr != nullptr) { + memset(data_ptr, 0, capacity); + write_ptr = data_ptr; + } +} + +FILE *FlushBuffer::logFile() { + return log_file; +} + +void FlushBuffer::releaseThis(void *release) { + this->release = release; +} + + diff --git a/log2file/src/cpp/LogBuffer.cpp b/log2file/src/cpp/LogBuffer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..78c34e4dd17af8e685661feeab9eba5bf38cff18 --- /dev/null +++ b/log2file/src/cpp/LogBuffer.cpp @@ -0,0 +1,185 @@ +// +// Created by pqpo on 2017/11/16. +// + +#include "includes/LogBuffer.h" + +LogBuffer::LogBuffer(char *ptr, size_t buffer_size): + buffer_ptr(ptr), + buffer_size(buffer_size), + logHeader(buffer_ptr, buffer_size) { + if (logHeader.isAvailable()) { + data_ptr = (char *) logHeader.ptr(); + write_ptr = (char *) logHeader.write_ptr(); + if(logHeader.getIsCompress()) { + initCompress(true); + } + char* log_path = getLogPath(); + if(log_path != nullptr) { + openSetLogFile(log_path); + delete[] log_path; + } + } + memset(&zStream, 0, sizeof(zStream)); +} + +LogBuffer::~LogBuffer() { + release(); +} + +size_t LogBuffer::length() { + return write_ptr - data_ptr; +} + +void LogBuffer::setLength(size_t len) { + logHeader.setLogLen(len); +} + +size_t LogBuffer::append(const char *log, size_t len) { + std::lock_guard lck_append(log_mtx); + + if (length() == 0) { + initCompress(is_compress); + } + + size_t freeSize = emptySize(); + size_t writeSize = 0; + if (is_compress) { + zStream.avail_in = (uInt)len; + zStream.next_in = (Bytef*)log; + + zStream.avail_out = (uInt)freeSize; + zStream.next_out = (Bytef*)write_ptr; + + if (Z_OK != deflate(&zStream, Z_SYNC_FLUSH)) { + return 0; + } + + writeSize = freeSize - zStream.avail_out; + } else { + writeSize = len <= freeSize ? len : freeSize; + memcpy(write_ptr, log, writeSize); + } + write_ptr += writeSize; + setLength(length()); + return writeSize; +} + +void LogBuffer::setAsyncFileFlush(AsyncFileFlush *_fileFlush) { + fileFlush = _fileFlush; +} + +void LogBuffer::async_flush() { + async_flush(fileFlush); +} + +void LogBuffer::async_flush(AsyncFileFlush *fileFlush) { + async_flush(fileFlush, nullptr); +} + +void LogBuffer::async_flush(AsyncFileFlush *fileFlush, void *releaseThis) { + if(fileFlush == nullptr) { + if (releaseThis != nullptr) { + delete releaseThis; + } + return; + } + std::lock_guard lck_clear(log_mtx); + if (length() > 0) { + if (is_compress && Z_NULL != zStream.state) { + deflateEnd(&zStream); + } + FlushBuffer* flushBuffer = new FlushBuffer(log_file); + flushBuffer->write(data_ptr, length()); + flushBuffer->releaseThis(releaseThis); + clear(); + fileFlush->async_flush(flushBuffer); + } else if (releaseThis != nullptr) { + delete releaseThis; + } +} + +void LogBuffer::clear() { + std::lock_guard lck_clear(log_mtx); + write_ptr = data_ptr; + memset(write_ptr, '\0', emptySize()); + setLength(length()); +} + +void LogBuffer::release() { + std::lock_guard lck_release(log_mtx); + if (is_compress && Z_NULL != zStream.state) { + deflateEnd(&zStream); + } + if(map_buffer) { + munmap(buffer_ptr, buffer_size); + } else { + delete[] buffer_ptr; + } + if(log_file != nullptr) { + fclose(log_file); + } +} + +size_t LogBuffer::emptySize() { + return buffer_size - (write_ptr - buffer_ptr); +} + +void LogBuffer::initData(char *log_path, size_t log_path_len, bool is_compress) { + std::lock_guard lck_release(log_mtx); + memset(buffer_ptr, '\0', buffer_size); + + log_header::Header header; + header.magic = kMagicHeader; + header.log_path_len = log_path_len; + header.log_path = log_path; + header.log_len = 0; + header.isCompress = is_compress; + + logHeader.initHeader(header); + initCompress(is_compress); + + data_ptr = (char *) logHeader.ptr(); + write_ptr = (char *) logHeader.write_ptr(); + + openSetLogFile(log_path); +} + +char *LogBuffer::getLogPath() { + return logHeader.getLogPath(); +} + +bool LogBuffer::initCompress(bool compress) { + is_compress = compress; + if (is_compress) { + zStream.zalloc = Z_NULL; + zStream.zfree = Z_NULL; + zStream.opaque = Z_NULL; + return Z_OK == deflateInit2(&zStream, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); + } + return false; +} + +bool LogBuffer::openSetLogFile(const char *log_path) { + if (log_path != nullptr) { + FILE* _file_log = fopen(log_path, "ab+"); + if(_file_log != NULL) { + log_file = _file_log; + return true; + } + } + return false; +} + +void LogBuffer::changeLogPath(char *log_path) { + if(log_file != nullptr) { + async_flush(); + } + initData(log_path, strlen(log_path), is_compress); +} + + + + + + diff --git a/log2file/src/cpp/LogBufferHeader.cpp b/log2file/src/cpp/LogBufferHeader.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2827aa96fa77bb885c029813e1823510081a604a --- /dev/null +++ b/log2file/src/cpp/LogBufferHeader.cpp @@ -0,0 +1,139 @@ +// +// Created by pqpo on 2018/2/10. +// + +#include +#include "LogBufferHeader.h" + +using namespace log_header; + +//struct Header { +// char magic; +// size_t log_len; +// size_t log_path_len; +// char* log_path; +// char isCompress; +//}; + +LogBufferHeader::LogBufferHeader(void *data, size_t size) : data_ptr((char *) data), data_size(size) { +} + +LogBufferHeader::~LogBufferHeader() { +} + +void *LogBufferHeader::originPtr() { + return data_ptr; +} + +Header* LogBufferHeader::getHeader() { + Header* header = new Header(); + if (isAvailable()) { + header->magic = kMagicHeader; + size_t log_len = 0; + memcpy(&log_len, data_ptr + sizeof(char), sizeof(size_t)); + header->log_len = log_len; + size_t log_path_len = 0; + memcpy(&log_path_len, data_ptr + sizeof(char) + sizeof(size_t), sizeof(size_t)); + header->log_path_len = log_path_len; + char *log_path = new char[log_path_len + 1]; + memset(log_path, 0, log_path_len + 1); + memcpy(log_path, data_ptr + sizeof(char) + sizeof(size_t) + sizeof(size_t), log_path_len); + header->log_path = log_path; + char isCompress = (data_ptr + sizeof(char) + sizeof(size_t) + sizeof(size_t) + log_path_len)[0]; + header->isCompress = isCompress == 1; + } + return header; +} + +size_t LogBufferHeader::getHeaderLen() { + if (isAvailable()) { + size_t log_path_len = 0; + memcpy(&log_path_len, data_ptr + sizeof(char) + sizeof(size_t), sizeof(size_t)); + return calculateHeaderLen(log_path_len); + } + return 0; +} + +void *LogBufferHeader::ptr() { + return data_ptr + getHeaderLen(); +} + +void *LogBufferHeader::write_ptr() { + return data_ptr + getHeaderLen() + getLogLen(); +} + +void LogBufferHeader::initHeader(Header &header) { + if ((sizeof(char) + sizeof(size_t) + sizeof(size_t) + header.log_path_len) > data_size) { + return; + } + memcpy(data_ptr, &header.magic, sizeof(char)); + memcpy(data_ptr + sizeof(char), &header.log_len, sizeof(size_t)); + memcpy(data_ptr + sizeof(char) + sizeof(size_t), &header.log_path_len, sizeof(size_t)); + memcpy(data_ptr + sizeof(char) + sizeof(size_t) + sizeof(size_t), header.log_path, header.log_path_len); + char isCompress = 0; + if (header.isCompress) { + isCompress = 1; + } + memcpy(data_ptr + sizeof(char) + sizeof(size_t) + sizeof(size_t) + header.log_path_len, &isCompress, + sizeof(char)); + +} + +size_t LogBufferHeader::getLogLen() { + if (isAvailable()) { + size_t log_len = 0; + memcpy(&log_len, data_ptr + sizeof(char), sizeof(size_t)); + return log_len; + } + return 0; +} + +size_t LogBufferHeader::getLogPathLen() { + if (isAvailable()) { + size_t log_path_len = 0; + memcpy(&log_path_len, data_ptr + sizeof(char) + sizeof(size_t), sizeof(size_t)); + return log_path_len; + } + return 0; +} + +char *LogBufferHeader::getLogPath() { + if (isAvailable()) { + size_t log_path_len = getLogPathLen(); + if (log_path_len > 0) { + char *log_path = new char[log_path_len + 1]; + memset(log_path, 0, log_path_len + 1); + memcpy(log_path, data_ptr + sizeof(char) + sizeof(size_t) + sizeof(size_t), log_path_len); + return log_path; + } + } + return nullptr; +} + +void LogBufferHeader::setLogLen(size_t log_len) { + if (isAvailable()) { + memcpy(data_ptr + sizeof(char), &log_len, sizeof(size_t)); + } +} + +bool LogBufferHeader::isAvailable() { + return data_ptr[0] == kMagicHeader; +} + +bool LogBufferHeader::getIsCompress() { + if (isAvailable()) { + char isCompress = (data_ptr + sizeof(char) + sizeof(size_t) + sizeof(size_t) + getLogPathLen())[0]; + return isCompress == 1; + } + return false; +} + +size_t LogBufferHeader::calculateHeaderLen(size_t log_path_len) { + return sizeof(char) + sizeof(size_t) + sizeof(size_t) + log_path_len + sizeof(char); +} + + + + + + diff --git a/log2file/src/cpp/includes/AsyncFileFlush.h b/log2file/src/cpp/includes/AsyncFileFlush.h new file mode 100644 index 0000000000000000000000000000000000000000..1a5018f8f70bfd641ebabb9e85d5ad2cab86af5a --- /dev/null +++ b/log2file/src/cpp/includes/AsyncFileFlush.h @@ -0,0 +1,36 @@ +// +// Created by pqpo on 2017/11/23. +// + +#ifndef LOG4A_FILEFLUSH_H +#define LOG4A_FILEFLUSH_H + +#include +#include +#include +#include +#include +#include +#include "FlushBuffer.h" + +class AsyncFileFlush { + +public: + AsyncFileFlush(); + ~AsyncFileFlush(); + bool async_flush(FlushBuffer* flushBuffer); + void stopFlush(); + +private: + void async_log_thread(); + ssize_t flush(FlushBuffer* flushBuffer); + + bool exit = false; + std::vector async_buffer; + std::thread async_thread; + std::condition_variable async_condition; + std::mutex async_mtx; +}; + + +#endif //LOG4A_FILEFLUSH_H diff --git a/log2file/src/cpp/includes/FlushBuffer.h b/log2file/src/cpp/includes/FlushBuffer.h new file mode 100644 index 0000000000000000000000000000000000000000..af7a7adcf40a31a1b7c541d018ff3363cf7faec9 --- /dev/null +++ b/log2file/src/cpp/includes/FlushBuffer.h @@ -0,0 +1,38 @@ +// +// Created by pqpo on 2018/2/10. +// + +#ifndef LOG4A_FLUSHBUFFER_H +#define LOG4A_FLUSHBUFFER_H + +#include +#include +#include +#include + +class FlushBuffer { + + public: + FlushBuffer(FILE* log_file, size_t size = 128); + ~FlushBuffer(); + void write(void* data, size_t len); + void reset(); + size_t length(); + void* ptr(); + FILE* logFile(); + + void releaseThis(void *release); + +private: + FILE* log_file = nullptr; + void* release = nullptr; + char* data_ptr = nullptr; + char* write_ptr = nullptr; + size_t capacity; + + size_t emptySize(); + +}; + + +#endif //LOG4A_FLUSHBUFFER_H diff --git a/log2file/src/cpp/includes/LogBuffer.h b/log2file/src/cpp/includes/LogBuffer.h new file mode 100644 index 0000000000000000000000000000000000000000..2dcaa46abbe1580e45a5dd8a9277a252fb3cd63c --- /dev/null +++ b/log2file/src/cpp/includes/LogBuffer.h @@ -0,0 +1,65 @@ +// +// Created by pqpo on 2017/11/16. +// + +#ifndef LOG4A_LOGBUFFER_H +#define LOG4A_LOGBUFFER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "AsyncFileFlush.h" +#include "FlushBuffer.h" +#include "LogBufferHeader.h" +#include + +using namespace log_header; + +class LogBuffer { +public: + LogBuffer(char* ptr, size_t capacity); + ~LogBuffer(); + + void initData(char *log_path, size_t log_path_len, bool is_compress); + size_t length(); + size_t append(const char* log, size_t len); + void release(); + size_t emptySize(); + char *getLogPath(); + void setAsyncFileFlush(AsyncFileFlush *fileFlush); + void async_flush(); + void async_flush(AsyncFileFlush *fileFlush); + void async_flush(AsyncFileFlush *fileFlush, void *releaseThis); + void changeLogPath(char *log_path); + +public: + bool map_buffer = true; + +private: + void clear(); + void setLength(size_t len); + bool initCompress(bool compress); + bool openSetLogFile(const char *log_path); + + FILE* log_file = nullptr; + AsyncFileFlush *fileFlush = nullptr; + char* const buffer_ptr = nullptr; + char* data_ptr = nullptr; + char* write_ptr = nullptr; + + size_t buffer_size = 0; + std::recursive_mutex log_mtx; + + LogBufferHeader logHeader; + z_stream zStream; + bool is_compress = false; + +}; + + +#endif //LOG4A_LOGBUFFER_H diff --git a/log2file/src/cpp/includes/LogBufferHeader.h b/log2file/src/cpp/includes/LogBufferHeader.h new file mode 100644 index 0000000000000000000000000000000000000000..31bc2d9c5151012ffca3803a78c975c8ca0c26e8 --- /dev/null +++ b/log2file/src/cpp/includes/LogBufferHeader.h @@ -0,0 +1,48 @@ +// +// Created by admin on 2018/2/10. +// + +#ifndef LOG4A_LOGBUFFERHEADER_H +#define LOG4A_LOGBUFFERHEADER_H + +#include + +namespace log_header{ + static const char kMagicHeader = '\x11'; + + struct Header { + char magic; + size_t log_len; + size_t log_path_len; + char* log_path; + bool isCompress; + }; + + class LogBufferHeader { + + public: + LogBufferHeader(void* data, size_t size); + ~LogBufferHeader(); + void initHeader(Header& header); + void* originPtr(); + void* ptr(); + void* write_ptr(); + Header* getHeader(); + size_t getHeaderLen(); + size_t getLogLen(); + size_t getLogPathLen(); + char* getLogPath(); + void setLogLen(size_t log_len); + bool getIsCompress(); + bool isAvailable(); + + static size_t calculateHeaderLen(size_t log_path_len); + + private: + char* data_ptr; + size_t data_size; + }; +} + + +#endif //LOG4A_LOGBUFFERHEADER_H diff --git a/log2file/src/cpp/log4a-lib.cpp b/log2file/src/cpp/log4a-lib.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ce6be6d14ffb21cff2327dc1fb5267af0ebd8ed6 --- /dev/null +++ b/log2file/src/cpp/log4a-lib.cpp @@ -0,0 +1,163 @@ +#include + +#include +#include +#include + +#include "includes/LogBuffer.h" + +static const char* const kClassDocScanner = "me/pqpo/librarylog4a/LogBuffer"; + +static char* openMMap(int buffer_fd, size_t buffer_size); + +static void writeDirtyLogToFile(int buffer_fd); + +static AsyncFileFlush *fileFlush = nullptr; + +static jlong initNative(JNIEnv *env, jclass type, jstring buffer_path_, + jint capacity, jstring log_path_, jboolean compress_) { + const char *buffer_path = env->GetStringUTFChars(buffer_path_, 0); + const char *log_path = env->GetStringUTFChars(log_path_, 0); + size_t buffer_size = static_cast(capacity); + int buffer_fd = open(buffer_path, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + // buffer 的第一个字节会用于存储日志路径名称长度,后面紧跟日志路径,之后才是日志信息 + if (fileFlush == nullptr) { + fileFlush = new AsyncFileFlush(); + } + // 加上头占用的大小 + buffer_size = buffer_size + LogBufferHeader::calculateHeaderLen(strlen(log_path)); + char *buffer_ptr = openMMap(buffer_fd, buffer_size); + bool map_buffer = true; + //如果打开 mmap 失败,则降级使用内存缓存 + if(buffer_ptr == nullptr) { + buffer_ptr = new char[buffer_size]; + map_buffer = false; + } + LogBuffer* logBuffer = new LogBuffer(buffer_ptr, buffer_size); + logBuffer->setAsyncFileFlush(fileFlush); + //将buffer内的数据清0, 并写入日志文件路径 + logBuffer->initData((char *) log_path, strlen(log_path), compress_); + logBuffer->map_buffer = map_buffer; + + env->ReleaseStringUTFChars(buffer_path_, buffer_path); + env->ReleaseStringUTFChars(log_path_, log_path); + return reinterpret_cast(logBuffer); +} + +static char* openMMap(int buffer_fd, size_t buffer_size) { + char* map_ptr = nullptr; + if (buffer_fd != -1) { + // 写脏数据 + writeDirtyLogToFile(buffer_fd); + // 根据 buffer size 调整 buffer 文件大小 + ftruncate(buffer_fd, static_cast(buffer_size)); + lseek(buffer_fd, 0, SEEK_SET); + map_ptr = (char *) mmap(0, buffer_size, PROT_WRITE | PROT_READ, MAP_SHARED, buffer_fd, 0); + if (map_ptr == MAP_FAILED) { + map_ptr = nullptr; + } + } + return map_ptr; +} + +static void writeDirtyLogToFile(int buffer_fd) { + struct stat fileInfo; + if(fstat(buffer_fd, &fileInfo) >= 0) { + size_t buffered_size = static_cast(fileInfo.st_size); + if(buffered_size > 0) { + char *buffer_ptr_tmp = (char *) mmap(0, buffered_size, PROT_WRITE | PROT_READ, MAP_SHARED, buffer_fd, 0); + if (buffer_ptr_tmp != MAP_FAILED) { + LogBuffer *tmp = new LogBuffer(buffer_ptr_tmp, buffered_size); + size_t data_size = tmp -> length(); + if (data_size > 0) { + tmp -> async_flush(fileFlush, tmp); + } else { + delete tmp; + } + } + } + } +} + +static void writeNative(JNIEnv *env, jobject instance, jlong ptr, + jstring log_) { + const char *log = env->GetStringUTFChars(log_, 0); + jsize log_len = env->GetStringUTFLength(log_); + LogBuffer* logBuffer = reinterpret_cast(ptr); + // 缓存写不下时异步刷新 + if (log_len >= logBuffer->emptySize()) { + logBuffer->async_flush(fileFlush); + } + logBuffer->append(log, (size_t)log_len); + env->ReleaseStringUTFChars(log_, log); +} + +static void releaseNative(JNIEnv *env, jobject instance, jlong ptr) { + LogBuffer* logBuffer = reinterpret_cast(ptr); + logBuffer->async_flush(fileFlush, logBuffer); + if (fileFlush != nullptr) { + delete fileFlush; + } + fileFlush = nullptr; +} + +static void changeLogPathNative(JNIEnv *env, jobject instance, jlong ptr, + jstring logFilePath) { + const char *log_path = env->GetStringUTFChars(logFilePath, 0); + LogBuffer* logBuffer = reinterpret_cast(ptr); + logBuffer->changeLogPath(const_cast(log_path)); + env->ReleaseStringUTFChars(logFilePath, log_path); +} + +static void flushAsyncNative(JNIEnv *env, jobject instance, jlong ptr) { + LogBuffer* logBuffer = reinterpret_cast(ptr); + logBuffer->async_flush(fileFlush); +} + +static JNINativeMethod gMethods[] = { + + { + "initNative", + "(Ljava/lang/String;ILjava/lang/String;Z)J", + (void*)initNative + }, + + { + "writeNative", + "(JLjava/lang/String;)V", + (void*)writeNative + }, + + { + "flushAsyncNative", + "(J)V", + (void*)flushAsyncNative + }, + + { + "changeLogPathNative", + "(JLjava/lang/String;)V", + (void*)changeLogPathNative + }, + + { + "releaseNative", + "(J)V", + (void*)releaseNative + } + +}; + +extern "C" +JNIEXPORT jint JNICALL +JNI_OnLoad(JavaVM* vm, void* reserved) { + JNIEnv *env = NULL; + if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) { + return JNI_FALSE; + } + jclass classDocScanner = env->FindClass(kClassDocScanner); + if(env -> RegisterNatives(classDocScanner, gMethods, sizeof(gMethods)/ sizeof(gMethods[0])) < 0) { + return JNI_FALSE; + } + return JNI_VERSION_1_4; +} \ No newline at end of file diff --git a/log2file/src/main/config.json b/log2file/src/main/config.json new file mode 100644 index 0000000000000000000000000000000000000000..4e68db29918fb8fe7ec332ed5708c7b2fd3e79fc --- /dev/null +++ b/log2file/src/main/config.json @@ -0,0 +1,23 @@ +{ + "app": { + "bundleName": "com.apkfuns.logutils", + "vendor": "apkfuns", + "version": { + "code": 1000000, + "name": "1.0.0" + } + }, + "deviceConfig": { + }, + "module": { + "package": "com.apkfuns.log2file", + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "log2file", + "moduleType": "har" + } + } +} \ No newline at end of file diff --git a/log2file/src/main/java/com/apkfuns/log2file/LogFileEngineFactory.java b/log2file/src/main/java/com/apkfuns/log2file/LogFileEngineFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..5713871257310d25d6ead9b8817bf86d0c3b9fc1 --- /dev/null +++ b/log2file/src/main/java/com/apkfuns/log2file/LogFileEngineFactory.java @@ -0,0 +1,102 @@ +package com.apkfuns.log2file; + + +import com.apkfuns.logutils.LogLevel; +import com.apkfuns.logutils.file.LogFileEngine; +import com.apkfuns.logutils.file.LogFileParam; +import librarylog4a.LogBuffer; +import ohos.app.Context; + +import java.io.File; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + + +/** + * Created by pengwei on 2017/3/30. + * 日志写入文件的默认引擎 + */ + +public class LogFileEngineFactory implements LogFileEngine { + + private static final String LOG_CONTENT_FORMAT = "[%s][%s][%s:%s]%s\n"; + private static final String LOG_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss:SSS"; + private DateFormat dateFormat; + private volatile LogBuffer buffer; + private Context context; + + public LogFileEngineFactory(Context context) { + if (context == null) { + throw new NullPointerException("Context must not null!"); + } + this.context = context.getApplicationContext(); + dateFormat = new SimpleDateFormat(LOG_DATE_FORMAT, Locale.getDefault()); + } + + @Override + public void writeToFile(File logFile, String logContent, LogFileParam params) { + if (buffer == null) { + synchronized (LogFileEngine.class) { + if (buffer == null) { + File bufferFile = new File(context.getFilesDir(), ".log4aCache"); + buffer = new LogBuffer(bufferFile.getAbsolutePath(), 4096, + logFile.getAbsolutePath(), false); + } + } + } + buffer.write(getWriteString(logContent, params)); + } + + /** + * 写入文件的内容 + * + * @param logContent log value + * @param params LogFileParam + * @return file log content + */ + private String getWriteString(String logContent, LogFileParam params) { + String time = dateFormat.format(new Date(params.getTime())); + return String.format(LOG_CONTENT_FORMAT, time, getLogLevelString(params.getLogLevel()), + params.getThreadName(), params.getTagName(), logContent); + } + + /** + * 日志等级 + * + * @param level level + * @return level string + */ + private String getLogLevelString(int level) { + switch (level) { + case LogLevel.TYPE_VERBOSE: + return "V"; + case LogLevel.TYPE_ERROR: + return "E"; + case LogLevel.TYPE_INFO: + return "I"; + case LogLevel.TYPE_WARM: + return "W"; + case LogLevel.TYPE_WTF: + return "Wtf"; + } + return "D"; + } + + @Override + public void flushAsync() { + if (buffer != null) { + buffer.flushAsync(); + } + } + + @Override + public void release() { + if (buffer != null) { + buffer.release(); + buffer = null; + } + } + +} diff --git a/log2file/src/main/java/librarylog4a/LogBuffer.java b/log2file/src/main/java/librarylog4a/LogBuffer.java new file mode 100644 index 0000000000000000000000000000000000000000..59546d1fd9161b22094bf663db6806565628bcdf --- /dev/null +++ b/log2file/src/main/java/librarylog4a/LogBuffer.java @@ -0,0 +1,106 @@ +package librarylog4a; + + +import com.apkfuns.logutils.LogUtils; + +/** + * Created by pqpo on 2017/11/16. + */ +public class LogBuffer { + + private static final String TAG = "LogBuffer"; + + private long ptr = 0; + private String logPath; + private String bufferPath; + private int bufferSize; + private boolean compress; + + public LogBuffer(String bufferPath, int capacity, String logPath, boolean compress) { + this.bufferPath = bufferPath; + this.bufferSize = capacity; + this.logPath = logPath; + this.compress = compress; + try { + ptr = initNative(bufferPath, capacity, logPath, compress); + } catch (Exception e) { + LogUtils.e(TAG, "LogBuffer Initialization Exception", e); + } + } + + public void changeLogPath(String logPath) { + if (ptr != 0) { + try { + changeLogPathNative(ptr, logPath); + + + + this.logPath = logPath; + } catch (Exception e) { + LogUtils.e(TAG, e.getMessage(), e); + } + } + } + + public boolean isCompress() { + return compress; + } + + public String getLogPath() { + return logPath; + } + + public String getBufferPath() { + return bufferPath; + } + + public int getBufferSize() { + return bufferSize; + } + + public void write(String log) { + if (ptr != 0) { + try { + writeNative(ptr, log); + } catch (Exception e) { + LogUtils.e(TAG, e.getMessage(), e); + } + } + } + + public void flushAsync() { + if (ptr != 0) { + try { + flushAsyncNative(ptr); + } catch (Exception e) { + LogUtils.e(TAG, e.getMessage(), e); + } + } + } + + public void release() { + if (ptr != 0) { + try { + releaseNative(ptr); + } catch (Exception e) { + LogUtils.e(TAG, e.getMessage(), e); + } + ptr = 0; + } + } + + static { + System.loadLibrary("log4a-lib"); + } + + private native static long initNative(String bufferPath, int capacity, String logPath, boolean compress); + + private native void writeNative(long ptr, String log); + + private native void flushAsyncNative(long ptr); + + private native void releaseNative(long ptr); + + private native void changeLogPathNative(long ptr, String logPath); + +} diff --git a/log2file/src/main/resources/base/element/string.json b/log2file/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..7b167074cf68ab46047b1b91cdcd198cf2315264 --- /dev/null +++ b/log2file/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "log2file_library", + "value": "log2file_library" + } + ] +} diff --git a/log2file/src/test/java/com/apkfuns/log2file/ExampleTest.java b/log2file/src/test/java/com/apkfuns/log2file/ExampleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f75ae8392be6ce45ea6918eb6b7e4f693ac2051a --- /dev/null +++ b/log2file/src/test/java/com/apkfuns/log2file/ExampleTest.java @@ -0,0 +1,9 @@ +package com.apkfuns.log2file; + +import org.junit.Test; + +public class ExampleTest { + @Test + public void onStart() { + } +} diff --git a/settings.gradle b/settings.gradle index 02778765c27ff1e4056dfc4e8b5af39515e1e30c..b1e556635831ab971709b58239c00e4159e0af9a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':entry', ':export_api', ':library', ':logutils_no_op' +include ':entry', ':export_api', ':library', ':logutils_no_op', ':log2file'