@logbrew/react-native 0.1.22 → 0.1.24
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/LogBrewReactNative.podspec +1 -0
- package/README.md +70 -7
- package/android/CMakeLists.txt +7 -0
- package/android/build.gradle +12 -0
- package/android/src/main/cpp/android_diagnostics.cpp +363 -0
- package/android/src/main/java/co/logbrew/reactnative/AndroidDiagnosticsRuntime.java +214 -0
- package/android/src/main/java/co/logbrew/reactnative/AndroidNativeDiagnostics.java +363 -0
- package/android/src/main/java/co/logbrew/reactnative/AndroidNativeSignalStore.java +233 -0
- package/android/src/main/java/co/logbrew/reactnative/AndroidParentDirectorySync.java +12 -12
- package/android/src/main/java/co/logbrew/reactnative/EventRecordStore.java +15 -5
- package/android/src/main/java/co/logbrew/reactnative/FatalStoreModuleImpl.java +127 -156
- package/android/src/newarch/java/co/logbrew/reactnative/FatalStoreModule.java +17 -17
- package/android/src/oldarch/java/co/logbrew/reactnative/FatalStoreModule.java +17 -17
- package/android-native-diagnostics.d.ts +29 -0
- package/android-native-diagnostics.js +176 -0
- package/fatal-replay.cjs +35 -3
- package/global-errors.d.cts +3 -3
- package/global-errors.d.ts +3 -3
- package/global-errors.native.js +1 -12
- package/index.cjs +27 -5
- package/index.native.d.ts +4 -0
- package/index.native.js +29 -4
- package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +1 -1
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewLogger.swift +2 -10
- package/ios/GeneratedAppleDiagnostics/LogBrew/OperationTrace.swift +56 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTracer.swift +48 -56
- package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +6 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCorrelation.swift +0 -1
- package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +5 -4
- package/ios/LBRNFatalStoreModule.mm +34 -86
- package/package.json +17 -5
- package/persistent-delivery.native.js +4 -3
- package/resource-fetch.js +5 -463
- package/src/NativeLogBrewFatalStore.ts +6 -4
- package/android/src/main/java/co/logbrew/reactnative/FatalRecordStore.java +0 -623
- package/ios/LBRNFatalRecordStore.h +0 -25
- package/ios/LBRNFatalRecordStore.m +0 -542
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
package co.logbrew.reactnative;
|
|
2
|
+
|
|
3
|
+
import java.io.File;
|
|
4
|
+
import java.io.FileInputStream;
|
|
5
|
+
import java.io.FileOutputStream;
|
|
6
|
+
import java.nio.ByteBuffer;
|
|
7
|
+
import java.nio.ByteOrder;
|
|
8
|
+
import java.nio.charset.StandardCharsets;
|
|
9
|
+
import java.util.Arrays;
|
|
10
|
+
import java.util.regex.Pattern;
|
|
11
|
+
import java.util.zip.CRC32;
|
|
12
|
+
|
|
13
|
+
final class AndroidNativeSignalStore {
|
|
14
|
+
static final int MAGIC = 0x4c424e53;
|
|
15
|
+
static final int VERSION = 1;
|
|
16
|
+
static final int ID_BYTES = 96;
|
|
17
|
+
static final int UUID_BYTES = 36;
|
|
18
|
+
static final int ARCH_BYTES = 16;
|
|
19
|
+
static final int OFFSET_BYTES = 16;
|
|
20
|
+
static final int PROJECT_BYTES = 36 * 2;
|
|
21
|
+
static final int RELEASE_BYTES = 256 * 2;
|
|
22
|
+
static final int ENVIRONMENT_BYTES = 128 * 2;
|
|
23
|
+
static final int SERVICE_BYTES = 128 * 2;
|
|
24
|
+
static final int RECORD_BYTES =
|
|
25
|
+
4 + 4 + 4 + 8 + ID_BYTES + UUID_BYTES + ARCH_BYTES + OFFSET_BYTES
|
|
26
|
+
+ PROJECT_BYTES + RELEASE_BYTES + ENVIRONMENT_BYTES + SERVICE_BYTES + 4;
|
|
27
|
+
|
|
28
|
+
private static final Pattern ID = Pattern.compile("^evt_android_native_[a-z0-9_]{1,76}$");
|
|
29
|
+
private static final Pattern UUID =
|
|
30
|
+
Pattern.compile("^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$");
|
|
31
|
+
private static final Pattern OFFSET = Pattern.compile("^[0-9a-f]{16}$");
|
|
32
|
+
private static final Pattern ARCHITECTURE = Pattern.compile("^(?:arm|arm64|x86|x86_64)$");
|
|
33
|
+
|
|
34
|
+
private final File file;
|
|
35
|
+
|
|
36
|
+
AndroidNativeSignalStore(File file) {
|
|
37
|
+
this.file = canonicalFile(file);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
synchronized Record read() {
|
|
41
|
+
if (!safeRegularFile(file) || file.length() != RECORD_BYTES) {
|
|
42
|
+
discardInvalid();
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
byte[] bytes = new byte[RECORD_BYTES];
|
|
46
|
+
try (FileInputStream stream = new FileInputStream(file)) {
|
|
47
|
+
if (stream.read(bytes) != RECORD_BYTES || stream.read() != -1) {
|
|
48
|
+
discardInvalid();
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
} catch (Exception error) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
ByteBuffer input = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
|
|
55
|
+
if (input.getInt() != MAGIC || input.getInt() != VERSION) {
|
|
56
|
+
discardInvalid();
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
int signal = input.getInt();
|
|
60
|
+
long timestampMs = input.getLong();
|
|
61
|
+
String id = ascii(input, ID_BYTES);
|
|
62
|
+
String imageUuid = ascii(input, UUID_BYTES);
|
|
63
|
+
String architecture = ascii(input, ARCH_BYTES);
|
|
64
|
+
String instructionOffset = ascii(input, OFFSET_BYTES);
|
|
65
|
+
String projectId = utf16(input, PROJECT_BYTES);
|
|
66
|
+
String release = utf16(input, RELEASE_BYTES);
|
|
67
|
+
String environment = utf16(input, ENVIRONMENT_BYTES);
|
|
68
|
+
String service = utf16(input, SERVICE_BYTES);
|
|
69
|
+
long expectedChecksum = Integer.toUnsignedLong(input.getInt());
|
|
70
|
+
CRC32 checksum = new CRC32();
|
|
71
|
+
checksum.update(bytes, 0, RECORD_BYTES - 4);
|
|
72
|
+
if (expectedChecksum != checksum.getValue()
|
|
73
|
+
|| signal <= 0
|
|
74
|
+
|| timestampMs <= 0
|
|
75
|
+
|| !ID.matcher(id).matches()
|
|
76
|
+
|| !UUID.matcher(imageUuid).matches()
|
|
77
|
+
|| !ARCHITECTURE.matcher(architecture).matches()
|
|
78
|
+
|| !OFFSET.matcher(instructionOffset).matches()
|
|
79
|
+
|| !UUID.matcher(projectId).matches()
|
|
80
|
+
|| !bounded(release, 256)
|
|
81
|
+
|| !bounded(environment, 128)
|
|
82
|
+
|| !bounded(service, 128)) {
|
|
83
|
+
discardInvalid();
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return new Record(
|
|
87
|
+
id,
|
|
88
|
+
signal,
|
|
89
|
+
timestampMs,
|
|
90
|
+
imageUuid,
|
|
91
|
+
architecture,
|
|
92
|
+
instructionOffset,
|
|
93
|
+
projectId,
|
|
94
|
+
release,
|
|
95
|
+
environment,
|
|
96
|
+
service);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
synchronized boolean prepare(EventRecordStore.ParentDirectorySync parentSync) {
|
|
100
|
+
if (file.exists()) {
|
|
101
|
+
if (!safeRegularFile(file)) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
read();
|
|
105
|
+
if (file.exists()) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
try (FileOutputStream output = new FileOutputStream(file, false)) {
|
|
110
|
+
output.write(new byte[RECORD_BYTES]);
|
|
111
|
+
output.getFD().sync();
|
|
112
|
+
return parentSync.sync(file.getParentFile())
|
|
113
|
+
!= EventRecordStore.ParentDirectorySyncResult.FAILED;
|
|
114
|
+
} catch (Exception error) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
synchronized boolean clear() {
|
|
120
|
+
return !file.exists() || (safeRegularFile(file) && file.delete());
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
String path() {
|
|
124
|
+
return file.getAbsolutePath();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private void discardInvalid() {
|
|
128
|
+
if (safeRegularFile(file)) {
|
|
129
|
+
file.delete();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private static File canonicalFile(File candidate) {
|
|
134
|
+
try {
|
|
135
|
+
File parent = candidate.getParentFile();
|
|
136
|
+
return parent == null
|
|
137
|
+
? candidate.getCanonicalFile()
|
|
138
|
+
: new File(parent.getCanonicalFile(), candidate.getName());
|
|
139
|
+
} catch (Exception error) {
|
|
140
|
+
return candidate.getAbsoluteFile();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private static boolean safeRegularFile(File candidate) {
|
|
145
|
+
try {
|
|
146
|
+
return candidate.exists()
|
|
147
|
+
&& candidate.isFile()
|
|
148
|
+
&& !java.nio.file.Files.isSymbolicLink(candidate.toPath())
|
|
149
|
+
&& candidate.getCanonicalFile().equals(candidate.getAbsoluteFile());
|
|
150
|
+
} catch (Exception error) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private static String ascii(ByteBuffer input, int width) {
|
|
156
|
+
byte[] bytes = new byte[width];
|
|
157
|
+
input.get(bytes);
|
|
158
|
+
int end = 0;
|
|
159
|
+
while (end < bytes.length && bytes[end] != 0) {
|
|
160
|
+
if (bytes[end] < 0x20 || bytes[end] > 0x7e) {
|
|
161
|
+
return "";
|
|
162
|
+
}
|
|
163
|
+
end += 1;
|
|
164
|
+
}
|
|
165
|
+
for (int index = end; index < bytes.length; index += 1) {
|
|
166
|
+
if (bytes[index] != 0) {
|
|
167
|
+
return "";
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return new String(Arrays.copyOf(bytes, end), StandardCharsets.US_ASCII);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private static String utf16(ByteBuffer input, int bytes) {
|
|
174
|
+
char[] value = new char[bytes / 2];
|
|
175
|
+
int end = 0;
|
|
176
|
+
boolean terminated = false;
|
|
177
|
+
for (int index = 0; index < value.length; index += 1) {
|
|
178
|
+
char character = input.getChar();
|
|
179
|
+
if (character == 0) {
|
|
180
|
+
terminated = true;
|
|
181
|
+
} else if (terminated) {
|
|
182
|
+
return "";
|
|
183
|
+
} else {
|
|
184
|
+
value[end++] = character;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return new String(value, 0, end);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private static boolean bounded(String value, int maximum) {
|
|
191
|
+
return !value.isEmpty()
|
|
192
|
+
&& value.length() <= maximum
|
|
193
|
+
&& value.equals(value.trim())
|
|
194
|
+
&& value.chars().noneMatch(
|
|
195
|
+
character -> character <= 31 || character >= 127 && character <= 159);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
static final class Record {
|
|
199
|
+
final String id;
|
|
200
|
+
final int signal;
|
|
201
|
+
final long timestampMs;
|
|
202
|
+
final String imageUuid;
|
|
203
|
+
final String architecture;
|
|
204
|
+
final String instructionOffset;
|
|
205
|
+
final String projectId;
|
|
206
|
+
final String release;
|
|
207
|
+
final String environment;
|
|
208
|
+
final String service;
|
|
209
|
+
|
|
210
|
+
Record(
|
|
211
|
+
String id,
|
|
212
|
+
int signal,
|
|
213
|
+
long timestampMs,
|
|
214
|
+
String imageUuid,
|
|
215
|
+
String architecture,
|
|
216
|
+
String instructionOffset,
|
|
217
|
+
String projectId,
|
|
218
|
+
String release,
|
|
219
|
+
String environment,
|
|
220
|
+
String service) {
|
|
221
|
+
this.id = id;
|
|
222
|
+
this.signal = signal;
|
|
223
|
+
this.timestampMs = timestampMs;
|
|
224
|
+
this.imageUuid = imageUuid;
|
|
225
|
+
this.architecture = architecture;
|
|
226
|
+
this.instructionOffset = instructionOffset;
|
|
227
|
+
this.projectId = projectId;
|
|
228
|
+
this.release = release;
|
|
229
|
+
this.environment = environment;
|
|
230
|
+
this.service = service;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -6,9 +6,9 @@ import android.system.OsConstants;
|
|
|
6
6
|
import java.io.File;
|
|
7
7
|
import java.io.FileDescriptor;
|
|
8
8
|
|
|
9
|
-
final class AndroidParentDirectorySync implements
|
|
9
|
+
final class AndroidParentDirectorySync implements EventRecordStore.ParentDirectorySync {
|
|
10
10
|
@Override
|
|
11
|
-
public
|
|
11
|
+
public EventRecordStore.ParentDirectorySyncResult sync(File directory) {
|
|
12
12
|
FileDescriptor descriptor;
|
|
13
13
|
try {
|
|
14
14
|
descriptor =
|
|
@@ -17,39 +17,39 @@ final class AndroidParentDirectorySync implements FatalRecordStore.ParentDirecto
|
|
|
17
17
|
OsConstants.O_RDONLY | OsConstants.O_CLOEXEC | OsConstants.O_NOFOLLOW,
|
|
18
18
|
0);
|
|
19
19
|
} catch (ErrnoException | RuntimeException error) {
|
|
20
|
-
return
|
|
20
|
+
return EventRecordStore.ParentDirectorySyncResult.FAILED;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
EventRecordStore.ParentDirectorySyncResult result;
|
|
24
24
|
try {
|
|
25
25
|
if (!OsConstants.S_ISDIR(Os.fstat(descriptor).st_mode)) {
|
|
26
|
-
result =
|
|
26
|
+
result = EventRecordStore.ParentDirectorySyncResult.FAILED;
|
|
27
27
|
} else {
|
|
28
28
|
result = syncDescriptor(descriptor);
|
|
29
29
|
}
|
|
30
30
|
} catch (ErrnoException | RuntimeException error) {
|
|
31
|
-
result =
|
|
31
|
+
result = EventRecordStore.ParentDirectorySyncResult.FAILED;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
try {
|
|
35
35
|
Os.close(descriptor);
|
|
36
36
|
} catch (ErrnoException | RuntimeException error) {
|
|
37
|
-
return
|
|
37
|
+
return EventRecordStore.ParentDirectorySyncResult.FAILED;
|
|
38
38
|
}
|
|
39
39
|
return result;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
private static
|
|
42
|
+
private static EventRecordStore.ParentDirectorySyncResult syncDescriptor(
|
|
43
43
|
FileDescriptor descriptor) {
|
|
44
44
|
try {
|
|
45
45
|
Os.fsync(descriptor);
|
|
46
|
-
return
|
|
46
|
+
return EventRecordStore.ParentDirectorySyncResult.SYNCHRONIZED;
|
|
47
47
|
} catch (ErrnoException error) {
|
|
48
48
|
return unsupportedDirectorySync(error.errno)
|
|
49
|
-
?
|
|
50
|
-
:
|
|
49
|
+
? EventRecordStore.ParentDirectorySyncResult.UNSUPPORTED
|
|
50
|
+
: EventRecordStore.ParentDirectorySyncResult.FAILED;
|
|
51
51
|
} catch (RuntimeException error) {
|
|
52
|
-
return
|
|
52
|
+
return EventRecordStore.ParentDirectorySyncResult.FAILED;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -34,11 +34,11 @@ final class EventRecordStore {
|
|
|
34
34
|
Pattern.compile("^event-([0-9]{20})\\.record$");
|
|
35
35
|
private static final Pattern MARKER_PATTERN =
|
|
36
36
|
Pattern.compile("^accepted-([0-9]{20})\\.marker$");
|
|
37
|
-
private static final
|
|
38
|
-
directory ->
|
|
37
|
+
private static final ParentDirectorySync UNSUPPORTED_PARENT_DIRECTORY_SYNC =
|
|
38
|
+
directory -> ParentDirectorySyncResult.UNSUPPORTED;
|
|
39
39
|
|
|
40
40
|
private final File directory;
|
|
41
|
-
private final
|
|
41
|
+
private final ParentDirectorySync parentDirectorySync;
|
|
42
42
|
private boolean poisoned;
|
|
43
43
|
|
|
44
44
|
EventRecordStore(File directory) {
|
|
@@ -46,7 +46,7 @@ final class EventRecordStore {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
EventRecordStore(
|
|
49
|
-
File directory,
|
|
49
|
+
File directory, ParentDirectorySync parentDirectorySync) {
|
|
50
50
|
this.directory = resolveCanonicalParent(directory);
|
|
51
51
|
this.parentDirectorySync = parentDirectorySync;
|
|
52
52
|
}
|
|
@@ -332,7 +332,7 @@ final class EventRecordStore {
|
|
|
332
332
|
return false;
|
|
333
333
|
}
|
|
334
334
|
if (parentDirectorySync.sync(directory)
|
|
335
|
-
==
|
|
335
|
+
== ParentDirectorySyncResult.FAILED) {
|
|
336
336
|
poisoned = true;
|
|
337
337
|
return false;
|
|
338
338
|
}
|
|
@@ -555,6 +555,16 @@ final class EventRecordStore {
|
|
|
555
555
|
&& value.setWritable(true, true);
|
|
556
556
|
}
|
|
557
557
|
|
|
558
|
+
interface ParentDirectorySync {
|
|
559
|
+
ParentDirectorySyncResult sync(File directory);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
enum ParentDirectorySyncResult {
|
|
563
|
+
SYNCHRONIZED,
|
|
564
|
+
UNSUPPORTED,
|
|
565
|
+
FAILED
|
|
566
|
+
}
|
|
567
|
+
|
|
558
568
|
static final class Record {
|
|
559
569
|
final long sequence;
|
|
560
570
|
final String serializedEvent;
|