@onekeyfe/react-native-native-logger 1.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/ReactNativeNativeLogger.podspec +29 -0
- package/android/CMakeLists.txt +24 -0
- package/android/build.gradle +132 -0
- package/android/gradle.properties +4 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/nativelogger/NativeLogger.kt +51 -0
- package/android/src/main/java/com/margelo/nitro/nativelogger/NativeLoggerPackage.kt +24 -0
- package/android/src/main/java/com/margelo/nitro/nativelogger/OneKeyLog.kt +131 -0
- package/ios/NativeLogger.swift +54 -0
- package/ios/OneKeyLog.swift +148 -0
- package/lib/module/NativeLogger.nitro.js +4 -0
- package/lib/module/NativeLogger.nitro.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeLogger.nitro.d.ts +10 -0
- package/lib/typescript/src/NativeLogger.nitro.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/nitro.json +17 -0
- package/nitrogen/generated/android/c++/JHybridNativeLoggerSpec.cpp +94 -0
- package/nitrogen/generated/android/c++/JHybridNativeLoggerSpec.hpp +67 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelogger/HybridNativeLoggerSpec.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelogger/nativeloggerOnLoad.kt +35 -0
- package/nitrogen/generated/android/nativelogger+autolinking.cmake +81 -0
- package/nitrogen/generated/android/nativelogger+autolinking.gradle +27 -0
- package/nitrogen/generated/android/nativeloggerOnLoad.cpp +44 -0
- package/nitrogen/generated/android/nativeloggerOnLoad.hpp +25 -0
- package/nitrogen/generated/ios/ReactNativeNativeLogger+autolinking.rb +60 -0
- package/nitrogen/generated/ios/ReactNativeNativeLogger-Swift-Cxx-Bridge.cpp +57 -0
- package/nitrogen/generated/ios/ReactNativeNativeLogger-Swift-Cxx-Bridge.hpp +175 -0
- package/nitrogen/generated/ios/ReactNativeNativeLogger-Swift-Cxx-Umbrella.hpp +46 -0
- package/nitrogen/generated/ios/ReactNativeNativeLoggerAutolinking.mm +33 -0
- package/nitrogen/generated/ios/ReactNativeNativeLoggerAutolinking.swift +25 -0
- package/nitrogen/generated/ios/c++/HybridNativeLoggerSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNativeLoggerSpecSwift.hpp +92 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +47 -0
- package/nitrogen/generated/ios/swift/HybridNativeLoggerSpec.swift +58 -0
- package/nitrogen/generated/ios/swift/HybridNativeLoggerSpec_cxx.swift +174 -0
- package/nitrogen/generated/shared/c++/HybridNativeLoggerSpec.cpp +23 -0
- package/nitrogen/generated/shared/c++/HybridNativeLoggerSpec.hpp +66 -0
- package/package.json +169 -0
- package/src/NativeLogger.nitro.ts +8 -0
- package/src/index.tsx +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 OneKey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# native-logger
|
|
2
|
+
|
|
3
|
+
native-logger
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install native-logger react-native-nitro-modules
|
|
9
|
+
|
|
10
|
+
> `react-native-nitro-modules` is required as this library relies on [Nitro Modules](https://nitro.margelo.com/).
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { NativeLogger } from 'native-logger';
|
|
17
|
+
|
|
18
|
+
// ...
|
|
19
|
+
|
|
20
|
+
const result = await NativeLogger.hello({ message: 'World' });
|
|
21
|
+
console.log(result); // { success: true, data: 'Hello, World!' }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Contributing
|
|
25
|
+
|
|
26
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
27
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
28
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
29
|
+
|
|
30
|
+
## License
|
|
31
|
+
|
|
32
|
+
MIT
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "ReactNativeNativeLogger"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/OneKeyHQ/app-modules/native-logger.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = [
|
|
17
|
+
"ios/**/*.{swift}",
|
|
18
|
+
"cpp/**/*.{hpp,cpp}",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
s.dependency 'React-jsi'
|
|
22
|
+
s.dependency 'React-callinvoker'
|
|
23
|
+
s.dependency 'CocoaLumberjack/Swift', '~> 3.8'
|
|
24
|
+
|
|
25
|
+
load 'nitrogen/generated/ios/ReactNativeNativeLogger+autolinking.rb'
|
|
26
|
+
add_nitrogen_files(s)
|
|
27
|
+
|
|
28
|
+
install_modules_dependencies(s)
|
|
29
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
project(nativelogger)
|
|
2
|
+
cmake_minimum_required(VERSION 3.9.0)
|
|
3
|
+
|
|
4
|
+
set(PACKAGE_NAME nativelogger)
|
|
5
|
+
set(CMAKE_VERBOSE_MAKEFILE ON)
|
|
6
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
7
|
+
|
|
8
|
+
# Define C++ library and add all sources
|
|
9
|
+
add_library(${PACKAGE_NAME} SHARED src/main/cpp/cpp-adapter.cpp)
|
|
10
|
+
|
|
11
|
+
# Add Nitrogen specs :)
|
|
12
|
+
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/nativelogger+autolinking.cmake)
|
|
13
|
+
|
|
14
|
+
# Set up local includes
|
|
15
|
+
include_directories("src/main/cpp" "../cpp")
|
|
16
|
+
|
|
17
|
+
find_library(LOG_LIB log)
|
|
18
|
+
|
|
19
|
+
# Link all libraries together
|
|
20
|
+
target_link_libraries(
|
|
21
|
+
${PACKAGE_NAME}
|
|
22
|
+
${LOG_LIB}
|
|
23
|
+
android # <-- Android core
|
|
24
|
+
)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.getExtOrDefault = {name ->
|
|
3
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['NativeLogger_' + name]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
13
|
+
// noinspection DifferentKotlinGradleVersion
|
|
14
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def reactNativeArchitectures() {
|
|
19
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
20
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
apply plugin: "com.android.library"
|
|
24
|
+
apply plugin: "kotlin-android"
|
|
25
|
+
apply from: '../nitrogen/generated/android/nativelogger+autolinking.gradle'
|
|
26
|
+
|
|
27
|
+
apply plugin: "com.facebook.react"
|
|
28
|
+
|
|
29
|
+
def getExtOrIntegerDefault(name) {
|
|
30
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NativeLogger_" + name]).toInteger()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
android {
|
|
34
|
+
namespace "com.margelo.nitro.nativelogger"
|
|
35
|
+
|
|
36
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
37
|
+
|
|
38
|
+
defaultConfig {
|
|
39
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
40
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
41
|
+
|
|
42
|
+
externalNativeBuild {
|
|
43
|
+
cmake {
|
|
44
|
+
cppFlags "-frtti -fexceptions -Wall -fstack-protector-all"
|
|
45
|
+
arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
|
|
46
|
+
abiFilters (*reactNativeArchitectures())
|
|
47
|
+
|
|
48
|
+
buildTypes {
|
|
49
|
+
debug {
|
|
50
|
+
cppFlags "-O1 -g"
|
|
51
|
+
}
|
|
52
|
+
release {
|
|
53
|
+
cppFlags "-O2"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
externalNativeBuild {
|
|
61
|
+
cmake {
|
|
62
|
+
path "CMakeLists.txt"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
packagingOptions {
|
|
67
|
+
excludes = [
|
|
68
|
+
"META-INF",
|
|
69
|
+
"META-INF/**",
|
|
70
|
+
"**/libc++_shared.so",
|
|
71
|
+
"**/libfbjni.so",
|
|
72
|
+
"**/libjsi.so",
|
|
73
|
+
"**/libfolly_json.so",
|
|
74
|
+
"**/libfolly_runtime.so",
|
|
75
|
+
"**/libglog.so",
|
|
76
|
+
"**/libhermes.so",
|
|
77
|
+
"**/libhermes-executor-debug.so",
|
|
78
|
+
"**/libhermes_executor.so",
|
|
79
|
+
"**/libreactnative.so",
|
|
80
|
+
"**/libreactnativejni.so",
|
|
81
|
+
"**/libturbomodulejsijni.so",
|
|
82
|
+
"**/libreact_nativemodule_core.so",
|
|
83
|
+
"**/libjscexecutor.so"
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
buildFeatures {
|
|
88
|
+
buildConfig true
|
|
89
|
+
prefab true
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
buildTypes {
|
|
93
|
+
release {
|
|
94
|
+
minifyEnabled false
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
lintOptions {
|
|
99
|
+
disable "GradleCompatible"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
compileOptions {
|
|
103
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
104
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
sourceSets {
|
|
108
|
+
main {
|
|
109
|
+
java.srcDirs += [
|
|
110
|
+
"generated/java",
|
|
111
|
+
"generated/jni"
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
repositories {
|
|
118
|
+
mavenCentral()
|
|
119
|
+
google()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
123
|
+
|
|
124
|
+
dependencies {
|
|
125
|
+
implementation "com.facebook.react:react-android"
|
|
126
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
127
|
+
implementation project(":react-native-nitro-modules")
|
|
128
|
+
|
|
129
|
+
// Logging backend — use `api` so consuming modules get SLF4J transitively
|
|
130
|
+
api 'org.slf4j:slf4j-api:1.7.33'
|
|
131
|
+
api 'com.github.tony19:logback-android:2.0.0'
|
|
132
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
package com.margelo.nitro.nativelogger
|
|
2
|
+
|
|
3
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
4
|
+
import com.margelo.nitro.core.Promise
|
|
5
|
+
import java.io.File
|
|
6
|
+
|
|
7
|
+
@DoNotStrip
|
|
8
|
+
class NativeLogger : HybridNativeLoggerSpec() {
|
|
9
|
+
|
|
10
|
+
override fun write(level: Double, msg: String) {
|
|
11
|
+
when (level.toInt()) {
|
|
12
|
+
0 -> OneKeyLog.debug("JS", msg)
|
|
13
|
+
1 -> OneKeyLog.info("JS", msg)
|
|
14
|
+
2 -> OneKeyLog.warn("JS", msg)
|
|
15
|
+
3 -> OneKeyLog.error("JS", msg)
|
|
16
|
+
else -> OneKeyLog.info("JS", msg)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override fun getLogFilePaths(): Promise<Array<String>> {
|
|
21
|
+
return Promise.async {
|
|
22
|
+
val dir = OneKeyLog.logsDirectory
|
|
23
|
+
if (dir.isEmpty()) return@async arrayOf<String>()
|
|
24
|
+
val files = File(dir).listFiles { _, name -> name.endsWith(".log") }
|
|
25
|
+
if (files == null) {
|
|
26
|
+
OneKeyLog.warn("NativeLogger", "Failed to list log directory: $dir")
|
|
27
|
+
return@async arrayOf<String>()
|
|
28
|
+
}
|
|
29
|
+
files.sortedBy { it.name }
|
|
30
|
+
.map { it.absolutePath }.toTypedArray()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun deleteLogFiles(): Promise<Unit> {
|
|
35
|
+
return Promise.async {
|
|
36
|
+
val dir = OneKeyLog.logsDirectory
|
|
37
|
+
if (dir.isEmpty()) return@async
|
|
38
|
+
val files = File(dir).listFiles { _, name -> name.endsWith(".log") }
|
|
39
|
+
if (files == null) {
|
|
40
|
+
OneKeyLog.warn("NativeLogger", "Failed to list log directory for deletion: $dir")
|
|
41
|
+
return@async
|
|
42
|
+
}
|
|
43
|
+
// Skip the active log file to avoid breaking logback's open file handle
|
|
44
|
+
files.filter { it.name != "app-latest.log" }.forEach { file ->
|
|
45
|
+
if (!file.delete()) {
|
|
46
|
+
OneKeyLog.warn("NativeLogger", "Failed to delete log file: ${file.name}")
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package com.margelo.nitro.nativelogger
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
7
|
+
|
|
8
|
+
class NativeLoggerPackage : BaseReactPackage() {
|
|
9
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
14
|
+
return ReactModuleInfoProvider { HashMap() }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
companion object {
|
|
18
|
+
init {
|
|
19
|
+
System.loadLibrary("nativelogger")
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
package com.margelo.nitro.nativelogger
|
|
2
|
+
|
|
3
|
+
import org.slf4j.Logger
|
|
4
|
+
import org.slf4j.LoggerFactory
|
|
5
|
+
import ch.qos.logback.classic.Level
|
|
6
|
+
import ch.qos.logback.classic.LoggerContext
|
|
7
|
+
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
|
|
8
|
+
import ch.qos.logback.classic.spi.ILoggingEvent
|
|
9
|
+
import ch.qos.logback.core.rolling.RollingFileAppender
|
|
10
|
+
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy
|
|
11
|
+
import ch.qos.logback.core.util.FileSize
|
|
12
|
+
import com.margelo.nitro.NitroModules
|
|
13
|
+
import java.io.File
|
|
14
|
+
import java.nio.charset.Charset
|
|
15
|
+
import java.text.SimpleDateFormat
|
|
16
|
+
import java.util.Date
|
|
17
|
+
import java.util.Locale
|
|
18
|
+
|
|
19
|
+
object OneKeyLog {
|
|
20
|
+
private const val APPENDER_NAME = "OneKeyFileAppender"
|
|
21
|
+
private const val LOG_PREFIX = "app"
|
|
22
|
+
private const val MAX_MESSAGE_LENGTH = 4096
|
|
23
|
+
private const val MAX_FILE_SIZE = 20L * 1024 * 1024 // 20 MB
|
|
24
|
+
private const val MAX_HISTORY = 6
|
|
25
|
+
private const val TOTAL_SIZE_CAP = MAX_FILE_SIZE * MAX_HISTORY
|
|
26
|
+
|
|
27
|
+
// Cached value; empty string means context was not yet available (will retry)
|
|
28
|
+
@Volatile
|
|
29
|
+
private var cachedLogsDir: String? = null
|
|
30
|
+
|
|
31
|
+
val logsDirectory: String
|
|
32
|
+
get() {
|
|
33
|
+
cachedLogsDir?.let { return it }
|
|
34
|
+
val context = NitroModules.applicationContext
|
|
35
|
+
if (context == null) {
|
|
36
|
+
android.util.Log.w("OneKeyLog", "applicationContext not yet available")
|
|
37
|
+
return "" // Don't cache — allow retry on next access
|
|
38
|
+
}
|
|
39
|
+
val dir = "${context.cacheDir.absolutePath}/logs"
|
|
40
|
+
cachedLogsDir = dir
|
|
41
|
+
return dir
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Cached logger; null means not yet initialized (will retry).
|
|
45
|
+
// Once successfully initialized, stays non-null.
|
|
46
|
+
@Volatile
|
|
47
|
+
private var cachedLogger: Logger? = null
|
|
48
|
+
|
|
49
|
+
private val logger: Logger?
|
|
50
|
+
get() {
|
|
51
|
+
cachedLogger?.let { return it }
|
|
52
|
+
return synchronized(this) {
|
|
53
|
+
cachedLogger?.let { return@synchronized it }
|
|
54
|
+
val dir = logsDirectory
|
|
55
|
+
if (dir.isEmpty()) return@synchronized null
|
|
56
|
+
|
|
57
|
+
File(dir).mkdirs()
|
|
58
|
+
|
|
59
|
+
val loggerContext = LoggerFactory.getILoggerFactory() as LoggerContext
|
|
60
|
+
|
|
61
|
+
val appender = RollingFileAppender<ILoggingEvent>().apply {
|
|
62
|
+
context = loggerContext
|
|
63
|
+
name = APPENDER_NAME
|
|
64
|
+
file = "$dir/$LOG_PREFIX-latest.log"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
SizeAndTimeBasedRollingPolicy<ILoggingEvent>().apply {
|
|
68
|
+
context = loggerContext
|
|
69
|
+
fileNamePattern = "$dir/$LOG_PREFIX-%d{yyyy-MM-dd}.%i.log"
|
|
70
|
+
setMaxFileSize(FileSize(MAX_FILE_SIZE))
|
|
71
|
+
maxHistory = MAX_HISTORY
|
|
72
|
+
setTotalSizeCap(FileSize(TOTAL_SIZE_CAP))
|
|
73
|
+
setParent(appender)
|
|
74
|
+
start()
|
|
75
|
+
appender.rollingPolicy = this
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
PatternLayoutEncoder().apply {
|
|
79
|
+
context = loggerContext
|
|
80
|
+
charset = Charset.forName("UTF-8")
|
|
81
|
+
pattern = "%msg%n"
|
|
82
|
+
start()
|
|
83
|
+
appender.encoder = this
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
appender.start()
|
|
87
|
+
|
|
88
|
+
// Attach appender to a dedicated named logger only (not ROOT)
|
|
89
|
+
// to avoid capturing third-party library SLF4J output
|
|
90
|
+
val onekeyLogger = loggerContext.getLogger("OneKey")
|
|
91
|
+
onekeyLogger.level = Level.DEBUG
|
|
92
|
+
onekeyLogger.getAppender(APPENDER_NAME)?.stop()
|
|
93
|
+
onekeyLogger.detachAppender(APPENDER_NAME)
|
|
94
|
+
onekeyLogger.addAppender(appender)
|
|
95
|
+
onekeyLogger.isAdditive = false // Do not propagate to ROOT logger
|
|
96
|
+
|
|
97
|
+
cachedLogger = onekeyLogger
|
|
98
|
+
onekeyLogger
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private val timeFormatter = SimpleDateFormat("HH:mm:ss", Locale.US)
|
|
103
|
+
|
|
104
|
+
private fun truncate(message: String): String {
|
|
105
|
+
return if (message.length > MAX_MESSAGE_LENGTH) {
|
|
106
|
+
message.substring(0, MAX_MESSAGE_LENGTH) + "...(truncated)"
|
|
107
|
+
} else {
|
|
108
|
+
message
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private fun formatMessage(tag: String, level: String, message: String): String {
|
|
113
|
+
if (tag == "JS") {
|
|
114
|
+
return truncate(message)
|
|
115
|
+
}
|
|
116
|
+
val time = timeFormatter.format(Date())
|
|
117
|
+
return truncate("$time | $level : [$tag] $message")
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@JvmStatic
|
|
121
|
+
fun debug(tag: String, message: String) { logger?.debug(formatMessage(tag, "DEBUG", message)) }
|
|
122
|
+
|
|
123
|
+
@JvmStatic
|
|
124
|
+
fun info(tag: String, message: String) { logger?.info(formatMessage(tag, "INFO", message)) }
|
|
125
|
+
|
|
126
|
+
@JvmStatic
|
|
127
|
+
fun warn(tag: String, message: String) { logger?.warn(formatMessage(tag, "WARN", message)) }
|
|
128
|
+
|
|
129
|
+
@JvmStatic
|
|
130
|
+
fun error(tag: String, message: String) { logger?.error(formatMessage(tag, "ERROR", message)) }
|
|
131
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import NitroModules
|
|
2
|
+
|
|
3
|
+
class NativeLogger: HybridNativeLoggerSpec {
|
|
4
|
+
|
|
5
|
+
func write(level: Double, msg: String) {
|
|
6
|
+
switch Int(level) {
|
|
7
|
+
case 0: OneKeyLog.debug("JS", msg)
|
|
8
|
+
case 1: OneKeyLog.info("JS", msg)
|
|
9
|
+
case 2: OneKeyLog.warn("JS", msg)
|
|
10
|
+
case 3: OneKeyLog.error("JS", msg)
|
|
11
|
+
default: OneKeyLog.info("JS", msg)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func getLogFilePaths() throws -> Promise<[String]> {
|
|
16
|
+
return Promise.async {
|
|
17
|
+
let dir = OneKeyLog.logsDirectory
|
|
18
|
+
let fm = FileManager.default
|
|
19
|
+
let files: [String]
|
|
20
|
+
do {
|
|
21
|
+
files = try fm.contentsOfDirectory(atPath: dir)
|
|
22
|
+
} catch {
|
|
23
|
+
OneKeyLog.warn("NativeLogger", "Failed to list log directory: \(error.localizedDescription)")
|
|
24
|
+
return []
|
|
25
|
+
}
|
|
26
|
+
return files
|
|
27
|
+
.filter { $0.hasSuffix(".log") }
|
|
28
|
+
.map { "\(dir)/\($0)" }
|
|
29
|
+
.sorted()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func deleteLogFiles() throws -> Promise<Void> {
|
|
34
|
+
return Promise.async {
|
|
35
|
+
let dir = OneKeyLog.logsDirectory
|
|
36
|
+
let fm = FileManager.default
|
|
37
|
+
let files: [String]
|
|
38
|
+
do {
|
|
39
|
+
files = try fm.contentsOfDirectory(atPath: dir)
|
|
40
|
+
} catch {
|
|
41
|
+
OneKeyLog.warn("NativeLogger", "Failed to list log directory for deletion: \(error.localizedDescription)")
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
// Skip the active log file to avoid breaking CocoaLumberjack's open file handle
|
|
45
|
+
for file in files where file.hasSuffix(".log") && file != "app-latest.log" {
|
|
46
|
+
do {
|
|
47
|
+
try fm.removeItem(atPath: "\(dir)/\(file)")
|
|
48
|
+
} catch {
|
|
49
|
+
OneKeyLog.warn("NativeLogger", "Failed to delete log file \(file): \(error.localizedDescription)")
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import CocoaLumberjack
|
|
2
|
+
|
|
3
|
+
private let ddLogLevel: DDLogLevel = .debug
|
|
4
|
+
|
|
5
|
+
private class OneKeyLogFileManager: DDLogFileManagerDefault {
|
|
6
|
+
private static let logPrefix = "app"
|
|
7
|
+
private static let latestFileName = "\(logPrefix)-latest.log"
|
|
8
|
+
private static let dateFormatter: DateFormatter = {
|
|
9
|
+
let fmt = DateFormatter()
|
|
10
|
+
fmt.dateFormat = "yyyy-MM-dd"
|
|
11
|
+
fmt.locale = Locale(identifier: "en_US_POSIX")
|
|
12
|
+
return fmt
|
|
13
|
+
}()
|
|
14
|
+
|
|
15
|
+
/// Always write to app-latest.log (matches Android behavior)
|
|
16
|
+
override var newLogFileName: String {
|
|
17
|
+
return Self.latestFileName
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override func isLogFile(withName fileName: String) -> Bool {
|
|
21
|
+
return fileName.hasPrefix(Self.logPrefix) && fileName.hasSuffix(".log")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// When rolled, rename app-latest.log → app-{yyyy-MM-dd}.{i}.log (matches Android pattern)
|
|
25
|
+
override func didArchiveLogFile(atPath logFilePath: String, wasRolled: Bool) {
|
|
26
|
+
if wasRolled {
|
|
27
|
+
let dir = (logFilePath as NSString).deletingLastPathComponent
|
|
28
|
+
let dateStr = Self.dateFormatter.string(from: Date())
|
|
29
|
+
let fm = FileManager.default
|
|
30
|
+
|
|
31
|
+
// Find next available index, retry on move failure to handle TOCTOU race
|
|
32
|
+
var index = 0
|
|
33
|
+
var moved = false
|
|
34
|
+
while !moved && index < 1000 {
|
|
35
|
+
let archivedPath = "\(dir)/\(Self.logPrefix)-\(dateStr).\(index).log"
|
|
36
|
+
if fm.fileExists(atPath: archivedPath) {
|
|
37
|
+
index += 1
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
do {
|
|
41
|
+
try fm.moveItem(atPath: logFilePath, toPath: archivedPath)
|
|
42
|
+
moved = true
|
|
43
|
+
} catch {
|
|
44
|
+
// Another thread may have created this file; try next index
|
|
45
|
+
index += 1
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if !moved {
|
|
49
|
+
NSLog("[OneKeyLog] Failed to archive log file after 1000 attempts: %@", logFilePath)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
super.didArchiveLogFile(atPath: logFilePath, wasRolled: wasRolled)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@objc public class OneKeyLog: NSObject {
|
|
57
|
+
|
|
58
|
+
private static let maxMessageLength = 4096
|
|
59
|
+
|
|
60
|
+
private static let configured: Bool = {
|
|
61
|
+
let logsDir = logsDirectory
|
|
62
|
+
|
|
63
|
+
// Ensure logs directory exists (matches Android's File(dir).mkdirs())
|
|
64
|
+
try? FileManager.default.createDirectory(
|
|
65
|
+
atPath: logsDir, withIntermediateDirectories: true
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
// Apply file protection: files inaccessible while device is locked before first unlock
|
|
69
|
+
try? FileManager.default.setAttributes(
|
|
70
|
+
[.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication],
|
|
71
|
+
ofItemAtPath: logsDir
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
let fileManager = OneKeyLogFileManager(logsDirectory: logsDir)
|
|
75
|
+
// NOTE: DDLogFileManagerDefault.maximumNumberOfLogFiles counts ALL log files
|
|
76
|
+
// including the current active file (app-latest.log).
|
|
77
|
+
// Set to 7 = 1 active + 6 archived, matching Android MAX_HISTORY=6.
|
|
78
|
+
fileManager.maximumNumberOfLogFiles = 7
|
|
79
|
+
|
|
80
|
+
let logger = DDFileLogger(logFileManager: fileManager)
|
|
81
|
+
logger.rollingFrequency = 86400 // daily rolling
|
|
82
|
+
logger.maximumFileSize = 20_971_520 // 20 MB
|
|
83
|
+
logger.logFormatter = OneKeyLogFormatter()
|
|
84
|
+
|
|
85
|
+
DDLog.add(logger)
|
|
86
|
+
return true
|
|
87
|
+
}()
|
|
88
|
+
|
|
89
|
+
private static let timeFormatter: DateFormatter = {
|
|
90
|
+
let fmt = DateFormatter()
|
|
91
|
+
fmt.dateFormat = "HH:mm:ss"
|
|
92
|
+
fmt.locale = Locale(identifier: "en_US_POSIX")
|
|
93
|
+
return fmt
|
|
94
|
+
}()
|
|
95
|
+
|
|
96
|
+
private static func truncate(_ message: String) -> String {
|
|
97
|
+
if message.count > maxMessageLength {
|
|
98
|
+
return String(message.prefix(maxMessageLength)) + "...(truncated)"
|
|
99
|
+
}
|
|
100
|
+
return message
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private static func formatMessage(_ tag: String, _ level: String, _ message: String) -> String {
|
|
104
|
+
if tag == "JS" {
|
|
105
|
+
return truncate(message)
|
|
106
|
+
}
|
|
107
|
+
let time = timeFormatter.string(from: Date())
|
|
108
|
+
return truncate("\(time) | \(level) : [\(tag)] \(message)")
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@objc public static func debug(_ tag: String, _ message: String) {
|
|
112
|
+
_ = configured
|
|
113
|
+
DDLogDebug(formatMessage(tag, "DEBUG", message))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@objc public static func info(_ tag: String, _ message: String) {
|
|
117
|
+
_ = configured
|
|
118
|
+
DDLogInfo(formatMessage(tag, "INFO", message))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@objc public static func warn(_ tag: String, _ message: String) {
|
|
122
|
+
_ = configured
|
|
123
|
+
DDLogWarn(formatMessage(tag, "WARN", message))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@objc public static func error(_ tag: String, _ message: String) {
|
|
127
|
+
_ = configured
|
|
128
|
+
DDLogError(formatMessage(tag, "ERROR", message))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Returns the logs directory path (for getLogFilePaths / deleteLogFiles)
|
|
132
|
+
static var logsDirectory: String {
|
|
133
|
+
guard let cacheDir = NSSearchPathForDirectoriesInDomains(
|
|
134
|
+
.cachesDirectory, .userDomainMask, true
|
|
135
|
+
).first else {
|
|
136
|
+
// Fallback to tmp directory if Caches is somehow unavailable
|
|
137
|
+
return (NSTemporaryDirectory() as NSString).appendingPathComponent("logs")
|
|
138
|
+
}
|
|
139
|
+
return (cacheDir as NSString).appendingPathComponent("logs")
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Formatter that outputs message only (matches Android's `%msg%n` pattern)
|
|
144
|
+
private class OneKeyLogFormatter: NSObject, DDLogFormatter {
|
|
145
|
+
func format(message logMessage: DDLogMessage) -> String? {
|
|
146
|
+
return logMessage.message
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../src","sources":["NativeLogger.nitro.ts"],"mappings":"","ignoreList":[]}
|