@onekeyfe/react-native-native-logger 1.1.20 → 1.1.21

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.
@@ -92,6 +92,7 @@ android {
92
92
  buildTypes {
93
93
  release {
94
94
  minifyEnabled false
95
+ consumerProguardFiles 'consumer-rules.pro'
95
96
  }
96
97
  }
97
98
 
@@ -0,0 +1,5 @@
1
+ # Logback-android: keep classes required by logback XML configuration
2
+ -keep class ch.qos.logback.** { *; }
3
+ -keep class org.slf4j.** { *; }
4
+ -dontwarn ch.qos.logback.**
5
+ -dontwarn org.slf4j.**
@@ -1 +1,8 @@
1
- <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <application>
3
+ <provider
4
+ android:name=".OneKeyLogInitProvider"
5
+ android:authorities="${applicationId}.onekeylog-init"
6
+ android:exported="false" />
7
+ </application>
8
+ </manifest>
@@ -7,27 +7,82 @@ import java.io.File
7
7
  @DoNotStrip
8
8
  class NativeLogger : HybridNativeLoggerSpec() {
9
9
 
10
+ companion object {
11
+ /** Patterns that should never be written to log files */
12
+ private val sensitivePatterns = listOf(
13
+ // Hex-encoded private keys (64 hex chars), with optional 0x prefix
14
+ Regex("(?:0x)?[0-9a-fA-F]{64}"),
15
+ // WIF private keys (base58, starting with 5, K, or L)
16
+ Regex("\\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\\b"),
17
+ // Extended keys (xprv/xpub/zprv/zpub/yprv/ypub)
18
+ Regex("\\b[xyzXYZ](?:prv|pub)[1-9A-HJ-NP-Za-km-z]{107,108}\\b"),
19
+ // BIP39 mnemonic-like sequences (12+ words of 3-8 lowercase letters)
20
+ Regex("(?:\\b[a-z]{3,8}\\b[\\s,]+){11,}\\b[a-z]{3,8}\\b"),
21
+ // Bearer/API tokens
22
+ Regex("(?:Bearer|token[=:]?)\\s*[A-Za-z0-9_.\\-+/=]{20,}"),
23
+ // Base64 encoded data that looks like keys (44+ chars)
24
+ Regex("(?:eyJ|AAAA)[A-Za-z0-9+/=]{40,}"),
25
+ )
26
+
27
+ /** Rate limiting: max messages per second */
28
+ private const val MAX_MESSAGES_PER_SECOND = 100
29
+ @Volatile private var messageCount = 0
30
+ @Volatile private var windowStartMs = System.currentTimeMillis()
31
+ private val rateLimitLock = Any()
32
+
33
+ private fun isRateLimited(): Boolean {
34
+ synchronized(rateLimitLock) {
35
+ val now = System.currentTimeMillis()
36
+ if (now - windowStartMs >= 1000L) {
37
+ windowStartMs = now
38
+ messageCount = 0
39
+ }
40
+ messageCount++
41
+ return messageCount > MAX_MESSAGES_PER_SECOND
42
+ }
43
+ }
44
+
45
+ fun sanitize(message: String): String {
46
+ var result = message
47
+ for (pattern in sensitivePatterns) {
48
+ result = pattern.replace(result, "[REDACTED]")
49
+ }
50
+ // Strip newlines to prevent log injection
51
+ result = result.replace("\n", " ").replace("\r", " ")
52
+ return result
53
+ }
54
+ }
55
+
10
56
  override fun write(level: Double, msg: String) {
57
+ if (isRateLimited()) return
58
+ val sanitized = sanitize(msg)
11
59
  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)
60
+ 0 -> OneKeyLog.debug("JS", sanitized)
61
+ 1 -> OneKeyLog.info("JS", sanitized)
62
+ 2 -> OneKeyLog.warn("JS", sanitized)
63
+ 3 -> OneKeyLog.error("JS", sanitized)
64
+ else -> OneKeyLog.info("JS", sanitized)
17
65
  }
18
66
  }
19
67
 
68
+ override fun getLogDirectory(): String {
69
+ return OneKeyLog.logsDirectory
70
+ }
71
+
20
72
  override fun getLogFilePaths(): Promise<Array<String>> {
21
73
  return Promise.async {
74
+ // Flush buffered log data so the active file reflects all written logs
75
+ OneKeyLog.flush()
22
76
  val dir = OneKeyLog.logsDirectory
23
77
  if (dir.isEmpty()) return@async arrayOf<String>()
24
78
  val files = File(dir).listFiles { _, name -> name.endsWith(".log") }
25
79
  if (files == null) {
26
- OneKeyLog.warn("NativeLogger", "Failed to list log directory: $dir")
80
+ OneKeyLog.warn("NativeLogger", "Failed to list log directory")
27
81
  return@async arrayOf<String>()
28
82
  }
83
+ // Return filenames only, not absolute paths
29
84
  files.sortedBy { it.name }
30
- .map { it.absolutePath }.toTypedArray()
85
+ .map { it.name }.toTypedArray()
31
86
  }
32
87
  }
33
88
 
@@ -37,13 +92,13 @@ class NativeLogger : HybridNativeLoggerSpec() {
37
92
  if (dir.isEmpty()) return@async
38
93
  val files = File(dir).listFiles { _, name -> name.endsWith(".log") }
39
94
  if (files == null) {
40
- OneKeyLog.warn("NativeLogger", "Failed to list log directory for deletion: $dir")
95
+ OneKeyLog.warn("NativeLogger", "Failed to list log directory for deletion")
41
96
  return@async
42
97
  }
43
98
  // Skip the active log file to avoid breaking logback's open file handle
44
99
  files.filter { it.name != "app-latest.log" }.forEach { file ->
45
100
  if (!file.delete()) {
46
- OneKeyLog.warn("NativeLogger", "Failed to delete log file: ${file.name}")
101
+ OneKeyLog.warn("NativeLogger", "Failed to delete log file")
47
102
  }
48
103
  }
49
104
  }
@@ -12,8 +12,8 @@ import ch.qos.logback.core.util.FileSize
12
12
  import com.margelo.nitro.NitroModules
13
13
  import java.io.File
14
14
  import java.nio.charset.Charset
15
- import java.text.SimpleDateFormat
16
- import java.util.Date
15
+ import java.time.LocalTime
16
+ import java.time.format.DateTimeFormatter
17
17
  import java.util.Locale
18
18
 
19
19
  object OneKeyLog {
@@ -28,6 +28,18 @@ object OneKeyLog {
28
28
  @Volatile
29
29
  private var cachedLogsDir: String? = null
30
30
 
31
+ /**
32
+ * Initialise OneKeyLog with an Android Context before NitroModules is ready.
33
+ * Call this early in Application.onCreate() so that logs emitted before
34
+ * React Native loads are not silently dropped.
35
+ */
36
+ @JvmStatic
37
+ fun init(context: android.content.Context) {
38
+ if (cachedLogsDir == null) {
39
+ cachedLogsDir = "${context.cacheDir.absolutePath}/logs"
40
+ }
41
+ }
42
+
31
43
  val logsDirectory: String
32
44
  get() {
33
45
  cachedLogsDir?.let { return it }
@@ -99,7 +111,8 @@ object OneKeyLog {
99
111
  }
100
112
  }
101
113
 
102
- private val timeFormatter = SimpleDateFormat("HH:mm:ss", Locale.US)
114
+ // DateTimeFormatter is immutable and thread-safe (unlike SimpleDateFormat)
115
+ private val timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss", Locale.US)
103
116
 
104
117
  private fun truncate(message: String): String {
105
118
  return if (message.length > MAX_MESSAGE_LENGTH) {
@@ -109,23 +122,84 @@ object OneKeyLog {
109
122
  }
110
123
  }
111
124
 
125
+ private fun sanitizeForLog(str: String): String {
126
+ return str.replace("\n", " ").replace("\r", " ")
127
+ }
128
+
129
+ /** Sanitize sensitive data from all log messages (native and JS) */
130
+ private val nativeSensitivePatterns = listOf(
131
+ Regex("(?:0x)?[0-9a-fA-F]{64}"),
132
+ Regex("\\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\\b"),
133
+ Regex("\\b[xyzXYZ](?:prv|pub)[1-9A-HJ-NP-Za-km-z]{107,108}\\b"),
134
+ Regex("(?:\\b[a-z]{3,8}\\b[\\s,]+){11,}\\b[a-z]{3,8}\\b"),
135
+ Regex("(?:Bearer|token[=:]?)\\s*[A-Za-z0-9_.\\-+/=]{20,}"),
136
+ Regex("(?:eyJ|AAAA)[A-Za-z0-9+/=]{40,}"),
137
+ )
138
+
139
+ private fun sanitizeSensitive(message: String): String {
140
+ var result = message
141
+ for (pattern in nativeSensitivePatterns) {
142
+ result = pattern.replace(result, "[REDACTED]")
143
+ }
144
+ return result
145
+ }
146
+
112
147
  private fun formatMessage(tag: String, level: String, message: String): String {
113
- if (tag == "JS") {
114
- return truncate(message)
148
+ val safeTag = sanitizeForLog(tag.take(64))
149
+ val safeMessage = sanitizeSensitive(sanitizeForLog(message))
150
+ if (safeTag == "JS") {
151
+ return truncate(safeMessage)
152
+ }
153
+ val time = LocalTime.now().format(timeFormatter)
154
+ return truncate("$time | $level : [$safeTag] $safeMessage")
155
+ }
156
+
157
+ private fun log(tag: String, level: String, message: String, androidLogLevel: Int) {
158
+ val formatted = formatMessage(tag, level, message)
159
+ val l = logger
160
+ if (l != null) {
161
+ when (androidLogLevel) {
162
+ android.util.Log.DEBUG -> l.debug(formatted)
163
+ android.util.Log.INFO -> l.info(formatted)
164
+ android.util.Log.WARN -> l.warn(formatted)
165
+ android.util.Log.ERROR -> l.error(formatted)
166
+ }
167
+ } else {
168
+ // Fallback to android.util.Log when file logger is unavailable
169
+ // Use formatted (sanitized) message, not raw message
170
+ android.util.Log.println(androidLogLevel, "OneKey/${tag.take(64)}", formatted)
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Flush all buffered log data to disk.
176
+ * Call before reading log files to ensure the active file is up-to-date.
177
+ */
178
+ @JvmStatic
179
+ fun flush() {
180
+ val loggerContext = LoggerFactory.getILoggerFactory()
181
+ if (loggerContext is LoggerContext) {
182
+ val appender = loggerContext.getLogger("OneKey")
183
+ ?.getAppender(APPENDER_NAME)
184
+ if (appender is RollingFileAppender<*>) {
185
+ try {
186
+ appender.outputStream?.flush()
187
+ } catch (_: Exception) {
188
+ // Ignore flush errors
189
+ }
190
+ }
115
191
  }
116
- val time = timeFormatter.format(Date())
117
- return truncate("$time | $level : [$tag] $message")
118
192
  }
119
193
 
120
194
  @JvmStatic
121
- fun debug(tag: String, message: String) { logger?.debug(formatMessage(tag, "DEBUG", message)) }
195
+ fun debug(tag: String, message: String) { log(tag, "DEBUG", message, android.util.Log.DEBUG) }
122
196
 
123
197
  @JvmStatic
124
- fun info(tag: String, message: String) { logger?.info(formatMessage(tag, "INFO", message)) }
198
+ fun info(tag: String, message: String) { log(tag, "INFO", message, android.util.Log.INFO) }
125
199
 
126
200
  @JvmStatic
127
- fun warn(tag: String, message: String) { logger?.warn(formatMessage(tag, "WARN", message)) }
201
+ fun warn(tag: String, message: String) { log(tag, "WARN", message, android.util.Log.WARN) }
128
202
 
129
203
  @JvmStatic
130
- fun error(tag: String, message: String) { logger?.error(formatMessage(tag, "ERROR", message)) }
204
+ fun error(tag: String, message: String) { log(tag, "ERROR", message, android.util.Log.ERROR) }
131
205
  }
@@ -0,0 +1,27 @@
1
+ package com.margelo.nitro.nativelogger
2
+
3
+ import android.content.ContentProvider
4
+ import android.content.ContentValues
5
+ import android.database.Cursor
6
+ import android.net.Uri
7
+
8
+ /**
9
+ * Auto-initialises [OneKeyLog] before Application.onCreate().
10
+ *
11
+ * ContentProvider.onCreate() is invoked by the system between
12
+ * Application.attachBaseContext() and Application.onCreate(),
13
+ * so the logger is ready for the earliest app-level code.
14
+ */
15
+ class OneKeyLogInitProvider : ContentProvider() {
16
+
17
+ override fun onCreate(): Boolean {
18
+ context?.let { OneKeyLog.init(it) }
19
+ return true
20
+ }
21
+
22
+ override fun query(uri: Uri, proj: Array<String>?, sel: String?, selArgs: Array<String>?, sort: String?): Cursor? = null
23
+ override fun getType(uri: Uri): String? = null
24
+ override fun insert(uri: Uri, values: ContentValues?): Uri? = null
25
+ override fun delete(uri: Uri, sel: String?, selArgs: Array<String>?): Int = 0
26
+ override fun update(uri: Uri, values: ContentValues?, sel: String?, selArgs: Array<String>?): Int = 0
27
+ }
@@ -1,31 +1,92 @@
1
1
  import NitroModules
2
+ import CocoaLumberjack
2
3
 
3
4
  class NativeLogger: HybridNativeLoggerSpec {
4
5
 
6
+ /// Patterns that should never be written to log files
7
+ private static let sensitivePatterns: [NSRegularExpression] = {
8
+ let patterns = [
9
+ // Hex-encoded private keys (64 hex chars), with optional 0x prefix
10
+ "(?:0x)?[0-9a-fA-F]{64}",
11
+ // WIF private keys (base58, starting with 5, K, or L)
12
+ "\\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\\b",
13
+ // Extended keys (xprv/xpub/zprv/zpub/yprv/ypub)
14
+ "\\b[xyzXYZ](?:prv|pub)[1-9A-HJ-NP-Za-km-z]{107,108}\\b",
15
+ // BIP39 mnemonic-like sequences (12+ words of 3-8 lowercase letters)
16
+ "(?:\\b[a-z]{3,8}\\b[\\s,]+){11,}\\b[a-z]{3,8}\\b",
17
+ // Bearer/API tokens
18
+ "(?:Bearer|token[=:]?)\\s*[A-Za-z0-9_.\\-+/=]{20,}",
19
+ // Base64 encoded data that looks like keys (44+ chars)
20
+ "(?:eyJ|AAAA)[A-Za-z0-9+/=]{40,}",
21
+ ]
22
+ return patterns.compactMap { try? NSRegularExpression(pattern: $0) }
23
+ }()
24
+
25
+ /// Rate limiting: max messages per second
26
+ private static let maxMessagesPerSecond = 100
27
+ private static var messageCount = 0
28
+ private static var windowStart = Date()
29
+ private static let rateLimitLock = NSLock()
30
+
31
+ private static func isRateLimited() -> Bool {
32
+ rateLimitLock.lock()
33
+ defer { rateLimitLock.unlock() }
34
+ let now = Date()
35
+ if now.timeIntervalSince(windowStart) >= 1.0 {
36
+ windowStart = now
37
+ messageCount = 0
38
+ }
39
+ messageCount += 1
40
+ return messageCount > maxMessagesPerSecond
41
+ }
42
+
43
+ private static func sanitize(_ message: String) -> String {
44
+ var result = message
45
+ for regex in sensitivePatterns {
46
+ result = regex.stringByReplacingMatches(
47
+ in: result,
48
+ range: NSRange(result.startIndex..., in: result),
49
+ withTemplate: "[REDACTED]"
50
+ )
51
+ }
52
+ // Strip newlines to prevent log injection
53
+ result = result.replacingOccurrences(of: "\n", with: " ")
54
+ result = result.replacingOccurrences(of: "\r", with: " ")
55
+ return result
56
+ }
57
+
5
58
  func write(level: Double, msg: String) {
59
+ if NativeLogger.isRateLimited() { return }
60
+ let sanitized = NativeLogger.sanitize(msg)
6
61
  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)
62
+ case 0: OneKeyLog.debug("JS", sanitized)
63
+ case 1: OneKeyLog.info("JS", sanitized)
64
+ case 2: OneKeyLog.warn("JS", sanitized)
65
+ case 3: OneKeyLog.error("JS", sanitized)
66
+ default: OneKeyLog.info("JS", sanitized)
12
67
  }
13
68
  }
14
69
 
70
+ func getLogDirectory() throws -> String {
71
+ return OneKeyLog.logsDirectory
72
+ }
73
+
15
74
  func getLogFilePaths() throws -> Promise<[String]> {
16
75
  return Promise.async {
76
+ // Flush buffered log data so the active file reflects all written logs
77
+ DDLog.flushLog()
17
78
  let dir = OneKeyLog.logsDirectory
18
79
  let fm = FileManager.default
19
80
  let files: [String]
20
81
  do {
21
82
  files = try fm.contentsOfDirectory(atPath: dir)
22
83
  } catch {
23
- OneKeyLog.warn("NativeLogger", "Failed to list log directory: \(error.localizedDescription)")
84
+ OneKeyLog.warn("NativeLogger", "Failed to list log directory")
24
85
  return []
25
86
  }
87
+ // Return filenames only, not absolute paths
26
88
  return files
27
89
  .filter { $0.hasSuffix(".log") }
28
- .map { "\(dir)/\($0)" }
29
90
  .sorted()
30
91
  }
31
92
  }
@@ -38,7 +99,7 @@ class NativeLogger: HybridNativeLoggerSpec {
38
99
  do {
39
100
  files = try fm.contentsOfDirectory(atPath: dir)
40
101
  } catch {
41
- OneKeyLog.warn("NativeLogger", "Failed to list log directory for deletion: \(error.localizedDescription)")
102
+ OneKeyLog.warn("NativeLogger", "Failed to list log directory for deletion")
42
103
  return
43
104
  }
44
105
  // Skip the active log file to avoid breaking CocoaLumberjack's open file handle
@@ -46,7 +107,7 @@ class NativeLogger: HybridNativeLoggerSpec {
46
107
  do {
47
108
  try fm.removeItem(atPath: "\(dir)/\(file)")
48
109
  } catch {
49
- OneKeyLog.warn("NativeLogger", "Failed to delete log file \(file): \(error.localizedDescription)")
110
+ OneKeyLog.warn("NativeLogger", "Failed to delete log file")
50
111
  }
51
112
  }
52
113
  }
@@ -5,13 +5,20 @@ private let ddLogLevel: DDLogLevel = .debug
5
5
  private class OneKeyLogFileManager: DDLogFileManagerDefault {
6
6
  private static let logPrefix = "app"
7
7
  private static let latestFileName = "\(logPrefix)-latest.log"
8
- private static let dateFormatter: DateFormatter = {
8
+ private static let dateFormatterLock = NSLock()
9
+ private static let _dateFormatter: DateFormatter = {
9
10
  let fmt = DateFormatter()
10
11
  fmt.dateFormat = "yyyy-MM-dd"
11
12
  fmt.locale = Locale(identifier: "en_US_POSIX")
12
13
  return fmt
13
14
  }()
14
15
 
16
+ private static func formattedDate(_ date: Date) -> String {
17
+ dateFormatterLock.lock()
18
+ defer { dateFormatterLock.unlock() }
19
+ return _dateFormatter.string(from: date)
20
+ }
21
+
15
22
  /// Always write to app-latest.log (matches Android behavior)
16
23
  override var newLogFileName: String {
17
24
  return Self.latestFileName
@@ -23,33 +30,40 @@ private class OneKeyLogFileManager: DDLogFileManagerDefault {
23
30
 
24
31
  /// When rolled, rename app-latest.log → app-{yyyy-MM-dd}.{i}.log (matches Android pattern)
25
32
  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
- }
33
+ guard wasRolled else {
34
+ super.didArchiveLogFile(atPath: logFilePath, wasRolled: wasRolled)
35
+ return
36
+ }
37
+
38
+ let dir = (logFilePath as NSString).deletingLastPathComponent
39
+ let dateStr = Self.formattedDate(Date())
40
+ let fm = FileManager.default
41
+
42
+ // Find next available index, retry on move failure to handle TOCTOU race
43
+ var index = 0
44
+ var archivedPath = logFilePath
45
+ var moved = false
46
+ while !moved && index < 1000 {
47
+ archivedPath = "\(dir)/\(Self.logPrefix)-\(dateStr).\(index).log"
48
+ if fm.fileExists(atPath: archivedPath) {
49
+ index += 1
50
+ continue
47
51
  }
48
- if !moved {
49
- NSLog("[OneKeyLog] Failed to archive log file after 1000 attempts: %@", logFilePath)
52
+ do {
53
+ try fm.moveItem(atPath: logFilePath, toPath: archivedPath)
54
+ moved = true
55
+ } catch {
56
+ // Another thread may have created this file; try next index
57
+ index += 1
50
58
  }
51
59
  }
52
- super.didArchiveLogFile(atPath: logFilePath, wasRolled: wasRolled)
60
+ if !moved {
61
+ NSLog("[OneKeyLog] Failed to archive log file after 1000 attempts: %@", logFilePath)
62
+ super.didArchiveLogFile(atPath: logFilePath, wasRolled: wasRolled)
63
+ } else {
64
+ // Pass the new path so the super class can find the file for cleanup
65
+ super.didArchiveLogFile(atPath: archivedPath, wasRolled: wasRolled)
66
+ }
53
67
  }
54
68
  }
55
69
 
@@ -86,13 +100,21 @@ private class OneKeyLogFileManager: DDLogFileManagerDefault {
86
100
  return true
87
101
  }()
88
102
 
89
- private static let timeFormatter: DateFormatter = {
103
+ // Use a lock to protect DateFormatter (not thread-safe per Apple docs)
104
+ private static let timeFormatterLock = NSLock()
105
+ private static let _timeFormatter: DateFormatter = {
90
106
  let fmt = DateFormatter()
91
107
  fmt.dateFormat = "HH:mm:ss"
92
108
  fmt.locale = Locale(identifier: "en_US_POSIX")
93
109
  return fmt
94
110
  }()
95
111
 
112
+ private static func formattedTime(_ date: Date) -> String {
113
+ timeFormatterLock.lock()
114
+ defer { timeFormatterLock.unlock() }
115
+ return _timeFormatter.string(from: date)
116
+ }
117
+
96
118
  private static func truncate(_ message: String) -> String {
97
119
  if message.count > maxMessageLength {
98
120
  return String(message.prefix(maxMessageLength)) + "...(truncated)"
@@ -100,12 +122,44 @@ private class OneKeyLogFileManager: DDLogFileManagerDefault {
100
122
  return message
101
123
  }
102
124
 
125
+ private static func sanitizeForLog(_ str: String) -> String {
126
+ return str.replacingOccurrences(of: "\n", with: " ")
127
+ .replacingOccurrences(of: "\r", with: " ")
128
+ }
129
+
130
+ /// Sanitize sensitive data from native-side log messages
131
+ private static let nativeSensitivePatterns: [NSRegularExpression] = {
132
+ let patterns = [
133
+ "(?:0x)?[0-9a-fA-F]{64}",
134
+ "\\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\\b",
135
+ "\\b[xyzXYZ](?:prv|pub)[1-9A-HJ-NP-Za-km-z]{107,108}\\b",
136
+ "(?:\\b[a-z]{3,8}\\b[\\s,]+){11,}\\b[a-z]{3,8}\\b",
137
+ "(?:Bearer|token[=:]?)\\s*[A-Za-z0-9_.\\-+/=]{20,}",
138
+ "(?:eyJ|AAAA)[A-Za-z0-9+/=]{40,}",
139
+ ]
140
+ return patterns.compactMap { try? NSRegularExpression(pattern: $0) }
141
+ }()
142
+
143
+ private static func sanitizeSensitive(_ message: String) -> String {
144
+ var result = message
145
+ for regex in nativeSensitivePatterns {
146
+ result = regex.stringByReplacingMatches(
147
+ in: result,
148
+ range: NSRange(result.startIndex..., in: result),
149
+ withTemplate: "[REDACTED]"
150
+ )
151
+ }
152
+ return result
153
+ }
154
+
103
155
  private static func formatMessage(_ tag: String, _ level: String, _ message: String) -> String {
104
- if tag == "JS" {
105
- return truncate(message)
156
+ let safeTag = sanitizeForLog(String(tag.prefix(64)))
157
+ let safeMessage = sanitizeSensitive(sanitizeForLog(message))
158
+ if safeTag == "JS" {
159
+ return truncate(safeMessage)
106
160
  }
107
- let time = timeFormatter.string(from: Date())
108
- return truncate("\(time) | \(level) : [\(tag)] \(message)")
161
+ let time = formattedTime(Date())
162
+ return truncate("\(time) | \(level) : [\(safeTag)] \(safeMessage)")
109
163
  }
110
164
 
111
165
  @objc public static func debug(_ tag: String, _ message: String) {
@@ -4,6 +4,7 @@ export interface NativeLogger extends HybridObject<{
4
4
  android: 'kotlin';
5
5
  }> {
6
6
  write(level: number, msg: string): void;
7
+ getLogDirectory(): string;
7
8
  getLogFilePaths(): Promise<string[]>;
8
9
  deleteLogFiles(): Promise<void>;
9
10
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NativeLogger.nitro.d.ts","sourceRoot":"","sources":["../../../src/NativeLogger.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,MAAM,WAAW,YACf,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IACzD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACrC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC"}
1
+ {"version":3,"file":"NativeLogger.nitro.d.ts","sourceRoot":"","sources":["../../../src/NativeLogger.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,MAAM,WAAW,YACf,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IACzD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,eAAe,IAAI,MAAM,CAAC;IAC1B,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACrC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC"}
@@ -50,6 +50,11 @@ namespace margelo::nitro::nativelogger {
50
50
  static const auto method = javaClassStatic()->getMethod<void(double /* level */, jni::alias_ref<jni::JString> /* msg */)>("write");
51
51
  method(_javaPart, level, jni::make_jstring(msg));
52
52
  }
53
+ std::string JHybridNativeLoggerSpec::getLogDirectory() {
54
+ static const auto method = javaClassStatic()->getMethod<jni::local_ref<jni::JString>()>("getLogDirectory");
55
+ auto __result = method(_javaPart);
56
+ return __result->toStdString();
57
+ }
53
58
  std::shared_ptr<Promise<std::vector<std::string>>> JHybridNativeLoggerSpec::getLogFilePaths() {
54
59
  static const auto method = javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>()>("getLogFilePaths");
55
60
  auto __result = method(_javaPart);
@@ -55,6 +55,7 @@ namespace margelo::nitro::nativelogger {
55
55
  public:
56
56
  // Methods
57
57
  void write(double level, const std::string& msg) override;
58
+ std::string getLogDirectory() override;
58
59
  std::shared_ptr<Promise<std::vector<std::string>>> getLogFilePaths() override;
59
60
  std::shared_ptr<Promise<void>> deleteLogFiles() override;
60
61
 
@@ -50,6 +50,10 @@ abstract class HybridNativeLoggerSpec: HybridObject() {
50
50
  @Keep
51
51
  abstract fun write(level: Double, msg: String): Unit
52
52
 
53
+ @DoNotStrip
54
+ @Keep
55
+ abstract fun getLogDirectory(): String
56
+
53
57
  @DoNotStrip
54
58
  @Keep
55
59
  abstract fun getLogFilePaths(): Promise<Array<String>>
@@ -154,6 +154,15 @@ namespace margelo::nitro::nativelogger::bridge::swift {
154
154
  return Result<void>::withError(error);
155
155
  }
156
156
 
157
+ // pragma MARK: Result<std::string>
158
+ using Result_std__string_ = Result<std::string>;
159
+ inline Result_std__string_ create_Result_std__string_(const std::string& value) noexcept {
160
+ return Result<std::string>::withValue(value);
161
+ }
162
+ inline Result_std__string_ create_Result_std__string_(const std::exception_ptr& error) noexcept {
163
+ return Result<std::string>::withError(error);
164
+ }
165
+
157
166
  // pragma MARK: Result<std::shared_ptr<Promise<std::vector<std::string>>>>
158
167
  using Result_std__shared_ptr_Promise_std__vector_std__string____ = Result<std::shared_ptr<Promise<std::vector<std::string>>>>;
159
168
  inline Result_std__shared_ptr_Promise_std__vector_std__string____ create_Result_std__shared_ptr_Promise_std__vector_std__string____(const std::shared_ptr<Promise<std::vector<std::string>>>& value) noexcept {
@@ -68,6 +68,14 @@ namespace margelo::nitro::nativelogger {
68
68
  std::rethrow_exception(__result.error());
69
69
  }
70
70
  }
71
+ inline std::string getLogDirectory() override {
72
+ auto __result = _swiftPart.getLogDirectory();
73
+ if (__result.hasError()) [[unlikely]] {
74
+ std::rethrow_exception(__result.error());
75
+ }
76
+ auto __value = std::move(__result.value());
77
+ return __value;
78
+ }
71
79
  inline std::shared_ptr<Promise<std::vector<std::string>>> getLogFilePaths() override {
72
80
  auto __result = _swiftPart.getLogFilePaths();
73
81
  if (__result.hasError()) [[unlikely]] {
@@ -15,6 +15,7 @@ public protocol HybridNativeLoggerSpec_protocol: HybridObject {
15
15
 
16
16
  // Methods
17
17
  func write(level: Double, msg: String) throws -> Void
18
+ func getLogDirectory() throws -> String
18
19
  func getLogFilePaths() throws -> Promise<[String]>
19
20
  func deleteLogFiles() throws -> Promise<Void>
20
21
  }
@@ -128,6 +128,18 @@ open class HybridNativeLoggerSpec_cxx {
128
128
  }
129
129
  }
130
130
 
131
+ @inline(__always)
132
+ public final func getLogDirectory() -> bridge.Result_std__string_ {
133
+ do {
134
+ let __result = try self.__implementation.getLogDirectory()
135
+ let __resultCpp = std.string(__result)
136
+ return bridge.create_Result_std__string_(__resultCpp)
137
+ } catch (let __error) {
138
+ let __exceptionPtr = __error.toCpp()
139
+ return bridge.create_Result_std__string_(__exceptionPtr)
140
+ }
141
+ }
142
+
131
143
  @inline(__always)
132
144
  public final func getLogFilePaths() -> bridge.Result_std__shared_ptr_Promise_std__vector_std__string____ {
133
145
  do {
@@ -15,6 +15,7 @@ namespace margelo::nitro::nativelogger {
15
15
  // load custom methods/properties
16
16
  registerHybrids(this, [](Prototype& prototype) {
17
17
  prototype.registerHybridMethod("write", &HybridNativeLoggerSpec::write);
18
+ prototype.registerHybridMethod("getLogDirectory", &HybridNativeLoggerSpec::getLogDirectory);
18
19
  prototype.registerHybridMethod("getLogFilePaths", &HybridNativeLoggerSpec::getLogFilePaths);
19
20
  prototype.registerHybridMethod("deleteLogFiles", &HybridNativeLoggerSpec::deleteLogFiles);
20
21
  });
@@ -51,6 +51,7 @@ namespace margelo::nitro::nativelogger {
51
51
  public:
52
52
  // Methods
53
53
  virtual void write(double level, const std::string& msg) = 0;
54
+ virtual std::string getLogDirectory() = 0;
54
55
  virtual std::shared_ptr<Promise<std::vector<std::string>>> getLogFilePaths() = 0;
55
56
  virtual std::shared_ptr<Promise<void>> deleteLogFiles() = 0;
56
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-native-logger",
3
- "version": "1.1.20",
3
+ "version": "1.1.21",
4
4
  "description": "react-native-native-logger",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -3,6 +3,7 @@ import type { HybridObject } from 'react-native-nitro-modules';
3
3
  export interface NativeLogger
4
4
  extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
5
5
  write(level: number, msg: string): void;
6
+ getLogDirectory(): string;
6
7
  getLogFilePaths(): Promise<string[]>;
7
8
  deleteLogFiles(): Promise<void>;
8
9
  }