@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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -0
  3. package/ReactNativeNativeLogger.podspec +29 -0
  4. package/android/CMakeLists.txt +24 -0
  5. package/android/build.gradle +132 -0
  6. package/android/gradle.properties +4 -0
  7. package/android/src/main/AndroidManifest.xml +1 -0
  8. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  9. package/android/src/main/java/com/margelo/nitro/nativelogger/NativeLogger.kt +51 -0
  10. package/android/src/main/java/com/margelo/nitro/nativelogger/NativeLoggerPackage.kt +24 -0
  11. package/android/src/main/java/com/margelo/nitro/nativelogger/OneKeyLog.kt +131 -0
  12. package/ios/NativeLogger.swift +54 -0
  13. package/ios/OneKeyLog.swift +148 -0
  14. package/lib/module/NativeLogger.nitro.js +4 -0
  15. package/lib/module/NativeLogger.nitro.js.map +1 -0
  16. package/lib/module/index.js +6 -0
  17. package/lib/module/index.js.map +1 -0
  18. package/lib/module/package.json +1 -0
  19. package/lib/typescript/package.json +1 -0
  20. package/lib/typescript/src/NativeLogger.nitro.d.ts +10 -0
  21. package/lib/typescript/src/NativeLogger.nitro.d.ts.map +1 -0
  22. package/lib/typescript/src/index.d.ts +4 -0
  23. package/lib/typescript/src/index.d.ts.map +1 -0
  24. package/nitro.json +17 -0
  25. package/nitrogen/generated/android/c++/JHybridNativeLoggerSpec.cpp +94 -0
  26. package/nitrogen/generated/android/c++/JHybridNativeLoggerSpec.hpp +67 -0
  27. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelogger/HybridNativeLoggerSpec.kt +66 -0
  28. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelogger/nativeloggerOnLoad.kt +35 -0
  29. package/nitrogen/generated/android/nativelogger+autolinking.cmake +81 -0
  30. package/nitrogen/generated/android/nativelogger+autolinking.gradle +27 -0
  31. package/nitrogen/generated/android/nativeloggerOnLoad.cpp +44 -0
  32. package/nitrogen/generated/android/nativeloggerOnLoad.hpp +25 -0
  33. package/nitrogen/generated/ios/ReactNativeNativeLogger+autolinking.rb +60 -0
  34. package/nitrogen/generated/ios/ReactNativeNativeLogger-Swift-Cxx-Bridge.cpp +57 -0
  35. package/nitrogen/generated/ios/ReactNativeNativeLogger-Swift-Cxx-Bridge.hpp +175 -0
  36. package/nitrogen/generated/ios/ReactNativeNativeLogger-Swift-Cxx-Umbrella.hpp +46 -0
  37. package/nitrogen/generated/ios/ReactNativeNativeLoggerAutolinking.mm +33 -0
  38. package/nitrogen/generated/ios/ReactNativeNativeLoggerAutolinking.swift +25 -0
  39. package/nitrogen/generated/ios/c++/HybridNativeLoggerSpecSwift.cpp +11 -0
  40. package/nitrogen/generated/ios/c++/HybridNativeLoggerSpecSwift.hpp +92 -0
  41. package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
  42. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
  43. package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +47 -0
  44. package/nitrogen/generated/ios/swift/HybridNativeLoggerSpec.swift +58 -0
  45. package/nitrogen/generated/ios/swift/HybridNativeLoggerSpec_cxx.swift +174 -0
  46. package/nitrogen/generated/shared/c++/HybridNativeLoggerSpec.cpp +23 -0
  47. package/nitrogen/generated/shared/c++/HybridNativeLoggerSpec.hpp +66 -0
  48. package/package.json +169 -0
  49. package/src/NativeLogger.nitro.ts +8 -0
  50. 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,4 @@
1
+ NativeLogger_kotlinVersion=1.9.25
2
+ NativeLogger_compileSdkVersion=35
3
+ NativeLogger_targetSdkVersion=35
4
+ NativeLogger_minSdkVersion=24
@@ -0,0 +1 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,6 @@
1
+ #include <jni.h>
2
+ #include "nativeloggerOnLoad.hpp"
3
+
4
+ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
5
+ return margelo::nitro::nativelogger::initialize(vm);
6
+ }
@@ -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,4 @@
1
+ "use strict";
2
+
3
+ export {};
4
+ //# sourceMappingURL=NativeLogger.nitro.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["NativeLogger.nitro.ts"],"mappings":"","ignoreList":[]}