cordova-plugin-unvired-logger 0.0.10 → 0.0.12
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/README.md +5 -6
- package/package.json +1 -1
- package/plugin.xml +1 -1
- package/src/android/Logger.java +5 -6
- package/src/browser/Logger.js +7 -8
- package/src/electron/Logger.js +96 -90
- package/src/ios/Logger.swift +10 -6
- package/www/logger.js +1 -1
package/README.md
CHANGED
|
@@ -200,12 +200,12 @@ testLogger();
|
|
|
200
200
|
## Platform-Specific Details
|
|
201
201
|
|
|
202
202
|
### Android
|
|
203
|
-
- Logs are stored in the app's internal storage: `/data/data/[package]/files/[userId]
|
|
203
|
+
- Logs are stored in the app's internal storage: `/data/data/[package]/files/[userId]/`
|
|
204
204
|
- Requires storage permissions for external storage access
|
|
205
205
|
- Automatic log rotation when file size exceeds 5MB
|
|
206
206
|
|
|
207
207
|
### iOS
|
|
208
|
-
- Logs are stored in the app's Documents directory: `Documents/[userId]
|
|
208
|
+
- Logs are stored in the app's Documents directory: `Documents/[userId]/`
|
|
209
209
|
- Uses native Swift implementation for optimal performance
|
|
210
210
|
- Automatic log rotation when file size exceeds 5MB
|
|
211
211
|
|
|
@@ -230,14 +230,13 @@ testLogger();
|
|
|
230
230
|
|
|
231
231
|
Logs are stored in the following structure:
|
|
232
232
|
```
|
|
233
|
-
{userDataPath}/{userId}/
|
|
234
|
-
{userDataPath}/{userId}/
|
|
233
|
+
{userDataPath}/{userId}/log.txt
|
|
234
|
+
{userDataPath}/{userId}/log_backup.txt
|
|
235
235
|
```
|
|
236
236
|
|
|
237
237
|
Where:
|
|
238
238
|
- `userDataPath` is the Electron app's user data directory
|
|
239
|
-
- `userId` is the
|
|
240
|
-
- `AppLogs` is the log folder name
|
|
239
|
+
- `userId` is the log folder name
|
|
241
240
|
- `log.txt` is the current log file
|
|
242
241
|
- `log_backup.txt` is the backup file (created during rotation)
|
|
243
242
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cordova-plugin-unvired-logger",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"description": "A logger plugin for Electron, Android, Browser, and iOS that appends logs to log.txt files organized by user ID.",
|
|
5
5
|
"cordova": {
|
|
6
6
|
"id": "cordova-plugin-unvired-logger",
|
package/plugin.xml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<plugin id="cordova-plugin-unvired-logger" version="0.0.
|
|
2
|
+
<plugin id="cordova-plugin-unvired-logger" version="0.0.12"
|
|
3
3
|
xmlns="http://apache.org/cordova/ns/plugins/1.0"
|
|
4
4
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
5
5
|
|
package/src/android/Logger.java
CHANGED
|
@@ -16,12 +16,11 @@ import java.util.Locale;
|
|
|
16
16
|
|
|
17
17
|
public class Logger extends CordovaPlugin {
|
|
18
18
|
|
|
19
|
-
private static final String LOG_FOLDER_NAME = "AppLogs";
|
|
20
19
|
private static final String LOG_FILE_NAME = "log.txt";
|
|
21
20
|
private static final String BACKUP_LOG_FILE_NAME = "log_backup.txt";
|
|
22
21
|
private static final long MAX_LOG_SIZE = 5 * 1024 * 1024; // 5MB
|
|
23
22
|
|
|
24
|
-
private String defaultLogLevel = "
|
|
23
|
+
private String defaultLogLevel = "important";
|
|
25
24
|
|
|
26
25
|
@Override
|
|
27
26
|
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
|
|
@@ -111,8 +110,8 @@ public class Logger extends CordovaPlugin {
|
|
|
111
110
|
private boolean shouldSkipLog(String level) {
|
|
112
111
|
String normLevel = level.toLowerCase();
|
|
113
112
|
String normDefault = defaultLogLevel.toLowerCase();
|
|
114
|
-
if (normDefault.equals("error") && (normLevel.equals("debug") || normLevel.equals("
|
|
115
|
-
if (normDefault.equals("
|
|
113
|
+
if (normDefault.equals("error") && (normLevel.equals("debug") || normLevel.equals("important"))) return true;
|
|
114
|
+
if (normDefault.equals("important") && normLevel.equals("debug")) return true;
|
|
116
115
|
return false;
|
|
117
116
|
}
|
|
118
117
|
|
|
@@ -203,7 +202,7 @@ public class Logger extends CordovaPlugin {
|
|
|
203
202
|
|
|
204
203
|
private File getUserLogDir(String userId) {
|
|
205
204
|
Context ctx = cordova.getActivity().getApplicationContext();
|
|
206
|
-
File userDir = new File(ctx.getFilesDir(), userId
|
|
205
|
+
File userDir = new File(ctx.getFilesDir(), userId);
|
|
207
206
|
if (!userDir.exists()) userDir.mkdirs();
|
|
208
207
|
return userDir;
|
|
209
208
|
}
|
|
@@ -229,7 +228,7 @@ public class Logger extends CordovaPlugin {
|
|
|
229
228
|
|
|
230
229
|
private String getStringFromLevel(String level) {
|
|
231
230
|
switch (level.toLowerCase()) {
|
|
232
|
-
case "
|
|
231
|
+
case "important": return "IMPORTANT";
|
|
233
232
|
case "error": return "ERROR";
|
|
234
233
|
case "debug": return "DEBUG";
|
|
235
234
|
default: return "";
|
package/src/browser/Logger.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const LOG_FOLDER_NAME = 'AppLogs';
|
|
2
1
|
const LOG_FILE_NAME = 'log.txt';
|
|
3
2
|
const BACKUP_LOG_FILE_NAME = 'log_backup.txt';
|
|
4
3
|
const MAX_LOG_SIZE = 5 * 1024 * 1024; // 5MB
|
|
@@ -6,7 +5,7 @@ const MAX_LOG_SIZE = 5 * 1024 * 1024; // 5MB
|
|
|
6
5
|
const LogLevel = {
|
|
7
6
|
Debug: 'debug',
|
|
8
7
|
Error: 'error',
|
|
9
|
-
Info: '
|
|
8
|
+
Info: 'important',
|
|
10
9
|
};
|
|
11
10
|
|
|
12
11
|
let defaultLogLevel = LogLevel.Info;
|
|
@@ -29,12 +28,12 @@ module.exports.logInfo = async function (successCallback, errorCallback, args) {
|
|
|
29
28
|
|
|
30
29
|
module.exports.setLogLevel = function(successCallback, errorCallback, level) {
|
|
31
30
|
// If level is an array (from Cordova), extract the first element
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
successCallback("")
|
|
31
|
+
let levelToSet = Array.isArray(level) ? level[0] : level;
|
|
32
|
+
|
|
33
|
+
// Normalize the level to lowercase for consistency
|
|
34
|
+
defaultLogLevel = (levelToSet || '').toLowerCase();
|
|
35
|
+
|
|
36
|
+
successCallback("");
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
module.exports.getLogLevel = function(successCallback, errorCallback) {
|
package/src/electron/Logger.js
CHANGED
|
@@ -3,14 +3,42 @@ const path = require('path');
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const fsPromises = fs.promises;
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// --- Proper async lock for log file operations ---
|
|
7
|
+
let logLockPromise = Promise.resolve();
|
|
8
|
+
let isLocked = false;
|
|
9
|
+
|
|
10
|
+
async function withLogLock(fn) {
|
|
11
|
+
// Wait for any existing lock to complete
|
|
12
|
+
await logLockPromise;
|
|
13
|
+
|
|
14
|
+
// Create a new promise for this operation
|
|
15
|
+
const operationPromise = (async () => {
|
|
16
|
+
try {
|
|
17
|
+
isLocked = true;
|
|
18
|
+
const result = await fn();
|
|
19
|
+
return result;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error('[Logger] Error in log operation:', error);
|
|
22
|
+
// Return error message instead of throwing to prevent unhandled promise rejections
|
|
23
|
+
return `Error in log operation: ${error.message}`;
|
|
24
|
+
} finally {
|
|
25
|
+
isLocked = false;
|
|
26
|
+
}
|
|
27
|
+
})();
|
|
28
|
+
|
|
29
|
+
// Update the lock promise to wait for this operation
|
|
30
|
+
logLockPromise = operationPromise;
|
|
31
|
+
|
|
32
|
+
return operationPromise;
|
|
33
|
+
}
|
|
34
|
+
|
|
7
35
|
const LOG_FILE_NAME = 'log.txt';
|
|
8
36
|
const BACKUP_LOG_FILE_NAME = 'log_backup.txt';
|
|
9
37
|
|
|
10
38
|
const LogLevel = {
|
|
11
39
|
Debug: 'debug',
|
|
12
40
|
Error: 'error',
|
|
13
|
-
Info: '
|
|
41
|
+
Info: 'important',
|
|
14
42
|
};
|
|
15
43
|
|
|
16
44
|
let defaultLogLevel = LogLevel.Info;
|
|
@@ -29,15 +57,11 @@ function logInfo(args) {
|
|
|
29
57
|
}
|
|
30
58
|
|
|
31
59
|
function setLogLevel(level) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
defaultLogLevel = level;
|
|
38
|
-
}
|
|
39
|
-
resolve();
|
|
40
|
-
});
|
|
60
|
+
// If level is an array (from Cordova), extract the first element
|
|
61
|
+
let levelToSet = Array.isArray(level) ? level[0] : level;
|
|
62
|
+
|
|
63
|
+
// Normalize the level to lowercase for consistency
|
|
64
|
+
defaultLogLevel = (levelToSet || '').toLowerCase();
|
|
41
65
|
}
|
|
42
66
|
|
|
43
67
|
function getLogLevel() {
|
|
@@ -68,14 +92,10 @@ function getLogFileContent(args) {
|
|
|
68
92
|
}
|
|
69
93
|
|
|
70
94
|
function clearLogFile(args) {
|
|
71
|
-
return
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
resolve();
|
|
76
|
-
} catch (err) {
|
|
77
|
-
reject(new Error(`Failed to clear log file: ${err.message}`));
|
|
78
|
-
}
|
|
95
|
+
return withLogLock(async () => {
|
|
96
|
+
const logPath = getLogFileURL(args);
|
|
97
|
+
await fsPromises.writeFile(logPath, '');
|
|
98
|
+
return 'Log file cleared successfully';
|
|
79
99
|
});
|
|
80
100
|
}
|
|
81
101
|
|
|
@@ -101,69 +121,58 @@ function getBackupLogFileContent(args) {
|
|
|
101
121
|
}
|
|
102
122
|
|
|
103
123
|
function copyLogToBackup(args) {
|
|
104
|
-
return
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
resolve();
|
|
110
|
-
} catch (err) {
|
|
111
|
-
reject(new Error(`Failed to copy log file to backup: ${err.message}`));
|
|
112
|
-
}
|
|
124
|
+
return withLogLock(async () => {
|
|
125
|
+
const logPath = getLogFileURL(args);
|
|
126
|
+
const backupPath = getBackupLogFileURL(args);
|
|
127
|
+
await fsPromises.copyFile(logPath, backupPath);
|
|
128
|
+
return 'Log file copied to backup successfully';
|
|
113
129
|
});
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
function loggerWithLevel(args) {
|
|
117
|
-
return
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
(normalizedDefaultLogLevel === LogLevel.Info && normalizedLevel === LogLevel.Debug)
|
|
136
|
-
) {
|
|
137
|
-
return resolve();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
await checkAndRotateLogFile(args);
|
|
133
|
+
return withLogLock(async () => {
|
|
134
|
+
if (!args || !args[0]) throw new Error("Invalid arguments provided");
|
|
135
|
+
const { userId, level, sourceClass, sourceMethod, message } = args[0];
|
|
136
|
+
if (!userId) throw new Error("userId is required");
|
|
137
|
+
if (!level) throw new Error("level is required");
|
|
138
|
+
if (!message) throw new Error("message is required");
|
|
139
|
+
|
|
140
|
+
// Normalize log levels for comparison
|
|
141
|
+
const normalizedDefaultLogLevel = (defaultLogLevel || '').toLowerCase();
|
|
142
|
+
const normalizedLevel = (level || '').toLowerCase();
|
|
143
|
+
|
|
144
|
+
// Log level filtering using normalized values
|
|
145
|
+
if (
|
|
146
|
+
(normalizedDefaultLogLevel === LogLevel.Error && (normalizedLevel === LogLevel.Debug || normalizedLevel === LogLevel.Info)) ||
|
|
147
|
+
(normalizedDefaultLogLevel === LogLevel.Info && normalizedLevel === LogLevel.Debug)
|
|
148
|
+
) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
141
151
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
await fsPromises.appendFile(logPath, data, 'utf8');
|
|
163
|
-
resolve(`Logged to ${logPath}`);
|
|
164
|
-
} catch (err) {
|
|
165
|
-
reject(new Error(`Logging failed: ${err.message}`));
|
|
152
|
+
await checkAndRotateLogFile(args);
|
|
153
|
+
|
|
154
|
+
const logPath = getLogFileURL(args);
|
|
155
|
+
const currentDate = new Date();
|
|
156
|
+
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
157
|
+
day: '2-digit', month: '2-digit', year: 'numeric',
|
|
158
|
+
hour: '2-digit', minute: '2-digit', second: '2-digit'
|
|
159
|
+
});
|
|
160
|
+
const localDateString = formatter.format(currentDate);
|
|
161
|
+
|
|
162
|
+
const dateUtc = new Date(currentDate.toUTCString());
|
|
163
|
+
const utcFormatter = new Intl.DateTimeFormat('en-US', {
|
|
164
|
+
day: '2-digit', month: '2-digit', year: 'numeric',
|
|
165
|
+
hour: '2-digit', minute: '2-digit', second: '2-digit', timeZone: 'UTC'
|
|
166
|
+
});
|
|
167
|
+
const utcDateString = utcFormatter.format(dateUtc);
|
|
168
|
+
|
|
169
|
+
let data = `${localDateString} | UTC:${utcDateString} | ${getStringFromLevel(level)} | ${sourceClass} | ${sourceMethod} | ${message}\n`;
|
|
170
|
+
if (level === LogLevel.Error) {
|
|
171
|
+
data = `⭕️⭕️⭕️ ${data}`;
|
|
166
172
|
}
|
|
173
|
+
console.log(data);
|
|
174
|
+
await fsPromises.appendFile(logPath, data, 'utf8');
|
|
175
|
+
return `Logged to ${logPath}`;
|
|
167
176
|
});
|
|
168
177
|
}
|
|
169
178
|
|
|
@@ -173,7 +182,7 @@ function getLogDirectory(args) {
|
|
|
173
182
|
}
|
|
174
183
|
const userId = args[0].userId;
|
|
175
184
|
const userDataPath = app.getPath('userData');
|
|
176
|
-
const userPath = path.join(userDataPath, userId
|
|
185
|
+
const userPath = path.join(userDataPath, userId);
|
|
177
186
|
if (!fs.existsSync(userPath)) {
|
|
178
187
|
fs.mkdirSync(userPath, { recursive: true });
|
|
179
188
|
}
|
|
@@ -190,21 +199,18 @@ function getStringFromLevel(level) {
|
|
|
190
199
|
}
|
|
191
200
|
|
|
192
201
|
function checkAndRotateLogFile(args) {
|
|
193
|
-
return
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
202
|
+
return withLogLock(async () => {
|
|
203
|
+
const logPath = getLogFileURL(args);
|
|
204
|
+
if (fs.existsSync(logPath)) {
|
|
205
|
+
const stats = await fsPromises.stat(logPath);
|
|
206
|
+
if (stats.size > MAX_LOG_SIZE) {
|
|
207
|
+
const backupPath = getBackupLogFileURL(args);
|
|
208
|
+
await fsPromises.copyFile(logPath, backupPath);
|
|
209
|
+
await fsPromises.writeFile(logPath, '');
|
|
210
|
+
return 'Log file rotated due to size limit';
|
|
203
211
|
}
|
|
204
|
-
resolve();
|
|
205
|
-
} catch (err) {
|
|
206
|
-
reject(new Error(`Failed to rotate log file: ${err.message}`));
|
|
207
212
|
}
|
|
213
|
+
return 'Log file rotation check completed';
|
|
208
214
|
});
|
|
209
215
|
}
|
|
210
216
|
|
package/src/ios/Logger.swift
CHANGED
|
@@ -3,12 +3,11 @@ import UIKit
|
|
|
3
3
|
|
|
4
4
|
@objc(Logger) class Logger : CDVPlugin {
|
|
5
5
|
|
|
6
|
-
private let LOG_FOLDER_NAME = "AppLogs"
|
|
7
6
|
private let LOG_FILE_NAME = "log.txt"
|
|
8
7
|
private let BACKUP_LOG_FILE_NAME = "log_backup.txt"
|
|
9
8
|
private let MAX_LOG_SIZE: Int64 = 5 * 1024 * 1024 // 5MB
|
|
10
9
|
|
|
11
|
-
private var defaultLogLevel: String = "
|
|
10
|
+
private var defaultLogLevel: String = "important"
|
|
12
11
|
|
|
13
12
|
@objc(logDebug:)
|
|
14
13
|
func logDebug(_ command: CDVInvokedUrlCommand) {
|
|
@@ -193,10 +192,10 @@ import UIKit
|
|
|
193
192
|
let normLevel = level.lowercased()
|
|
194
193
|
let normDefault = defaultLogLevel.lowercased()
|
|
195
194
|
|
|
196
|
-
if normDefault == "error" && (normLevel == "debug" || normLevel == "
|
|
195
|
+
if normDefault == "error" && (normLevel == "debug" || normLevel == "important") {
|
|
197
196
|
return true
|
|
198
197
|
}
|
|
199
|
-
if normDefault == "
|
|
198
|
+
if normDefault == "important" && normLevel == "debug" {
|
|
200
199
|
return true
|
|
201
200
|
}
|
|
202
201
|
return false
|
|
@@ -214,7 +213,7 @@ import UIKit
|
|
|
214
213
|
|
|
215
214
|
private func getUserLogDir(userId: String) throws -> URL {
|
|
216
215
|
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
217
|
-
let userDir = documentsPath.appendingPathComponent(userId)
|
|
216
|
+
let userDir = documentsPath.appendingPathComponent(userId)
|
|
218
217
|
|
|
219
218
|
if !FileManager.default.fileExists(atPath: userDir.path) {
|
|
220
219
|
try FileManager.default.createDirectory(at: userDir, withIntermediateDirectories: true, attributes: nil)
|
|
@@ -251,7 +250,7 @@ import UIKit
|
|
|
251
250
|
|
|
252
251
|
private func getStringFromLevel(level: String) -> String {
|
|
253
252
|
switch level.lowercased() {
|
|
254
|
-
case "
|
|
253
|
+
case "important": return "IMPORTANT"
|
|
255
254
|
case "error": return "ERROR"
|
|
256
255
|
case "debug": return "DEBUG"
|
|
257
256
|
default: return ""
|
|
@@ -259,6 +258,11 @@ import UIKit
|
|
|
259
258
|
}
|
|
260
259
|
|
|
261
260
|
private func appendToFile(file: URL, data: String) throws {
|
|
261
|
+
let fileManager = FileManager.default
|
|
262
|
+
if !fileManager.fileExists(atPath: file.path) {
|
|
263
|
+
// Create the file if it doesn't exist
|
|
264
|
+
fileManager.createFile(atPath: file.path, contents: nil, attributes: nil)
|
|
265
|
+
}
|
|
262
266
|
let fileHandle = try FileHandle(forWritingTo: file)
|
|
263
267
|
fileHandle.seekToEndOfFile()
|
|
264
268
|
fileHandle.write(data.data(using: .utf8)!)
|