@logbrew/react-native 0.1.23 → 0.1.25

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 (35) hide show
  1. package/README.md +62 -5
  2. package/android/CMakeLists.txt +7 -0
  3. package/android/build.gradle +12 -0
  4. package/android/src/main/cpp/android_diagnostics.cpp +363 -0
  5. package/android/src/main/java/co/logbrew/reactnative/AndroidDiagnosticsRuntime.java +214 -0
  6. package/android/src/main/java/co/logbrew/reactnative/AndroidNativeDiagnostics.java +368 -0
  7. package/android/src/main/java/co/logbrew/reactnative/AndroidNativeSignalStore.java +233 -0
  8. package/android/src/main/java/co/logbrew/reactnative/AndroidParentDirectorySync.java +12 -12
  9. package/android/src/main/java/co/logbrew/reactnative/EventRecordStore.java +15 -5
  10. package/android/src/main/java/co/logbrew/reactnative/FatalStoreModuleImpl.java +111 -156
  11. package/android/src/newarch/java/co/logbrew/reactnative/FatalStoreModule.java +15 -20
  12. package/android/src/oldarch/java/co/logbrew/reactnative/FatalStoreModule.java +15 -20
  13. package/android-native-diagnostics.d.ts +29 -0
  14. package/android-native-diagnostics.js +176 -0
  15. package/fatal-replay.cjs +35 -3
  16. package/global-errors.d.cts +3 -3
  17. package/global-errors.d.ts +3 -3
  18. package/global-errors.native.js +1 -12
  19. package/index.cjs +1 -1
  20. package/index.native.d.ts +4 -0
  21. package/index.native.js +21 -3
  22. package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +1 -1
  23. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewLogger.swift +2 -10
  24. package/ios/GeneratedAppleDiagnostics/LogBrew/OperationTrace.swift +56 -0
  25. package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTracer.swift +48 -56
  26. package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +6 -0
  27. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCorrelation.swift +0 -1
  28. package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +5 -4
  29. package/ios/LBRNFatalStoreModule.mm +15 -86
  30. package/package.json +18 -6
  31. package/persistent-delivery.native.js +4 -3
  32. package/src/NativeLogBrewFatalStore.ts +5 -4
  33. package/android/src/main/java/co/logbrew/reactnative/FatalRecordStore.java +0 -623
  34. package/ios/LBRNFatalRecordStore.h +0 -25
  35. package/ios/LBRNFatalRecordStore.m +0 -542
@@ -0,0 +1,368 @@
1
+ package co.logbrew.reactnative;
2
+
3
+ import java.nio.charset.StandardCharsets;
4
+ import java.security.SecureRandom;
5
+ import java.text.SimpleDateFormat;
6
+ import java.util.Date;
7
+ import java.util.Locale;
8
+ import java.util.TimeZone;
9
+ import java.util.concurrent.atomic.AtomicLong;
10
+ import java.util.function.LongConsumer;
11
+ import java.util.function.Supplier;
12
+ import java.util.regex.Pattern;
13
+
14
+ final class AndroidNativeDiagnostics {
15
+ interface Scheduler {
16
+ void start(long thresholdMs, LongConsumer report);
17
+
18
+ void stop();
19
+ }
20
+
21
+ static final class Configuration {
22
+ private static final Pattern PROJECT_ID =
23
+ Pattern.compile("^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$");
24
+ final String projectId;
25
+ final String release;
26
+ final String environment;
27
+ final String service;
28
+ final String operatingSystemVersion;
29
+ final String deviceModel;
30
+ final String architecture;
31
+ final long anrThresholdMs;
32
+
33
+ Configuration(
34
+ String projectId,
35
+ String release,
36
+ String environment,
37
+ String service,
38
+ String operatingSystemVersion,
39
+ String deviceModel,
40
+ String architecture,
41
+ long anrThresholdMs) {
42
+ if (!PROJECT_ID.matcher(projectId).matches()
43
+ || !bounded(release, 256)
44
+ || !bounded(environment, 128)
45
+ || !bounded(service, 128)
46
+ || !bounded(operatingSystemVersion, 128)
47
+ || !bounded(deviceModel, 128)
48
+ || !isArchitecture(architecture)
49
+ || anrThresholdMs < 2_000
50
+ || anrThresholdMs > 60_000) {
51
+ throw new IllegalArgumentException("invalid Android diagnostics configuration");
52
+ }
53
+ this.projectId = projectId;
54
+ this.release = release;
55
+ this.environment = environment;
56
+ this.service = service;
57
+ this.operatingSystemVersion = operatingSystemVersion;
58
+ this.deviceModel = deviceModel;
59
+ this.architecture = architecture;
60
+ this.anrThresholdMs = anrThresholdMs;
61
+ }
62
+
63
+ private static boolean bounded(String value, int maximum) {
64
+ return value != null
65
+ && !value.isEmpty()
66
+ && value.length() <= maximum
67
+ && value.equals(value.trim())
68
+ && value.chars().noneMatch(
69
+ character -> character <= 31 || character >= 127 && character <= 159);
70
+ }
71
+
72
+ private static boolean isArchitecture(String value) {
73
+ return "arm".equals(value)
74
+ || "arm64".equals(value)
75
+ || "x86".equals(value)
76
+ || "x86_64".equals(value);
77
+ }
78
+
79
+ boolean matches(Configuration other) {
80
+ return other != null
81
+ && projectId.equals(other.projectId)
82
+ && release.equals(other.release)
83
+ && environment.equals(other.environment)
84
+ && service.equals(other.service)
85
+ && operatingSystemVersion.equals(other.operatingSystemVersion)
86
+ && deviceModel.equals(other.deviceModel)
87
+ && architecture.equals(other.architecture)
88
+ && anrThresholdMs == other.anrThresholdMs;
89
+ }
90
+ }
91
+
92
+ private static final int MAX_FRAMES = 32;
93
+ private static final AtomicLong SEQUENCE = new AtomicLong();
94
+
95
+ final EventRecordStore eventStore;
96
+ final AndroidNativeSignalStore signalStore;
97
+ private final Configuration configuration;
98
+ private final Scheduler scheduler;
99
+ private final Runnable afterPersist;
100
+ private final Thread.UncaughtExceptionHandler previousHandler;
101
+ private final Supplier<StackTraceElement[]> mainFrames;
102
+ private final String processNonce;
103
+ boolean installed;
104
+
105
+ AndroidNativeDiagnostics(
106
+ EventRecordStore eventStore,
107
+ AndroidNativeSignalStore signalStore,
108
+ Configuration configuration,
109
+ Scheduler scheduler,
110
+ Runnable afterPersist,
111
+ Thread.UncaughtExceptionHandler previousHandler,
112
+ Supplier<StackTraceElement[]> mainFrames) {
113
+ this.eventStore = eventStore;
114
+ this.signalStore = signalStore;
115
+ this.configuration = configuration;
116
+ this.scheduler = scheduler;
117
+ this.afterPersist = afterPersist;
118
+ this.previousHandler = previousHandler;
119
+ this.mainFrames = mainFrames;
120
+ processNonce = randomHex(8);
121
+ }
122
+
123
+ synchronized String install() {
124
+ if (installed) {
125
+ return "already_installed";
126
+ }
127
+ replaySignalRecord();
128
+ scheduler.start(configuration.anrThresholdMs, this::reportHang);
129
+ installed = true;
130
+ return "installed";
131
+ }
132
+
133
+ synchronized String uninstall() {
134
+ if (!installed) {
135
+ return "not_installed";
136
+ }
137
+ installed = false;
138
+ scheduler.stop();
139
+ return "uninstalled";
140
+ }
141
+
142
+ void handleUncaught(Thread thread, Throwable error) {
143
+ try {
144
+ append(javaCrashEvent(error));
145
+ } finally {
146
+ previousHandler.uncaughtException(thread, error);
147
+ }
148
+ }
149
+
150
+ private synchronized void reportHang(long durationMs) {
151
+ if (!installed) {
152
+ return;
153
+ }
154
+ long boundedDuration =
155
+ Math.max(configuration.anrThresholdMs, Math.min(durationMs, 300_000));
156
+ append(hangEvent(mainFrames.get(), boundedDuration));
157
+ }
158
+
159
+ static boolean shouldReportHang(
160
+ long elapsedMs, long thresholdMs, boolean debugging, boolean processNotResponding) {
161
+ return !debugging && processNotResponding && elapsedMs >= thresholdMs;
162
+ }
163
+
164
+ private void replaySignalRecord() {
165
+ AndroidNativeSignalStore.Record record = signalStore.read();
166
+ if (record != null && append(nativeCrashEvent(record))) {
167
+ signalStore.clear();
168
+ }
169
+ }
170
+
171
+ private boolean append(String serializedEvent) {
172
+ int bytes = serializedEvent.getBytes(StandardCharsets.UTF_8).length;
173
+ EventRecordStore.Result result = eventStore.append(serializedEvent, bytes);
174
+ if (!"appended".equals(result.status)) {
175
+ return false;
176
+ }
177
+ afterPersist.run();
178
+ return true;
179
+ }
180
+
181
+ private String javaCrashEvent(Throwable error) {
182
+ StackTraceElement[] frames = error == null ? new StackTraceElement[0] : error.getStackTrace();
183
+ return issueEvent(
184
+ configuration,
185
+ nextId("java"),
186
+ System.currentTimeMillis(),
187
+ "Native application crash",
188
+ "critical",
189
+ safeSymbol(error == null ? null : error.getClass().getName(), 256, "AndroidJavaCrash"),
190
+ "uncaught_exception",
191
+ 0,
192
+ frames,
193
+ null);
194
+ }
195
+
196
+ private String hangEvent(StackTraceElement[] frames, long durationMs) {
197
+ return issueEvent(
198
+ configuration,
199
+ nextId("hang"),
200
+ System.currentTimeMillis(),
201
+ "Native application hang",
202
+ "error",
203
+ "AndroidAppHang",
204
+ "anr_watchdog",
205
+ durationMs,
206
+ frames,
207
+ null);
208
+ }
209
+
210
+ private String nativeCrashEvent(AndroidNativeSignalStore.Record record) {
211
+ return issueEvent(
212
+ new Configuration(
213
+ record.projectId,
214
+ record.release,
215
+ record.environment,
216
+ record.service,
217
+ configuration.operatingSystemVersion,
218
+ configuration.deviceModel,
219
+ record.architecture,
220
+ configuration.anrThresholdMs),
221
+ record.id,
222
+ record.timestampMs,
223
+ "Native application crash",
224
+ "critical",
225
+ "AndroidNativeCrash",
226
+ "signal",
227
+ 0,
228
+ new StackTraceElement[0],
229
+ record);
230
+ }
231
+
232
+ private String issueEvent(
233
+ Configuration eventConfiguration,
234
+ String id,
235
+ long timestampMs,
236
+ String title,
237
+ String level,
238
+ String exceptionType,
239
+ String mechanism,
240
+ long durationMs,
241
+ StackTraceElement[] frames,
242
+ AndroidNativeSignalStore.Record nativeFrame) {
243
+ StringBuilder output = new StringBuilder(2048);
244
+ output.append("{\"type\":\"issue\",\"id\":\"").append(id)
245
+ .append("\",\"timestamp\":\"").append(timestamp(timestampMs))
246
+ .append("\",\"attributes\":{\"title\":\"").append(title)
247
+ .append("\",\"level\":\"").append(level).append('"');
248
+ appendJavaFrames(output, frames);
249
+ if (nativeFrame != null) {
250
+ output.append(",\"nativeStackFrames\":[{\"imageUuid\":\"")
251
+ .append(nativeFrame.imageUuid).append("\",\"architecture\":\"")
252
+ .append(nativeFrame.architecture).append("\",\"instructionOffset\":\"")
253
+ .append(nativeFrame.instructionOffset).append("\"}]");
254
+ }
255
+ output.append(",\"exception\":{\"type\":\"").append(exceptionType)
256
+ .append("\",\"mechanism\":{\"type\":\"").append(mechanism)
257
+ .append("\",\"handled\":false}},\"metadata\":{")
258
+ .append("\"crash.mechanism\":\"").append(mechanism)
259
+ .append("\",\"crash.replayed\":true,\"crash.handled\":false,")
260
+ .append("\"projectId\":\"").append(eventConfiguration.projectId)
261
+ .append("\",\"release\":\"").append(json(eventConfiguration.release))
262
+ .append("\",\"environment\":\"").append(json(eventConfiguration.environment))
263
+ .append("\",\"service\":\"").append(json(eventConfiguration.service)).append('"');
264
+ if (durationMs > 0) {
265
+ output.append(",\"durationMs\":").append(durationMs);
266
+ }
267
+ if (nativeFrame != null) {
268
+ output.append(",\"crash.signal\":").append(nativeFrame.signal);
269
+ }
270
+ output.append('}');
271
+ appendContext(output, eventConfiguration);
272
+ return output.append("}}").toString();
273
+ }
274
+
275
+ private static void appendJavaFrames(StringBuilder output, StackTraceElement[] frames) {
276
+ int start = output.length();
277
+ output.append(",\"stackFrames\":[");
278
+ int count = 0;
279
+ for (StackTraceElement frame : frames) {
280
+ if (frame == null || frame.getLineNumber() <= 0 || count == MAX_FRAMES) {
281
+ continue;
282
+ }
283
+ String filename = safeFilename(frame.getFileName());
284
+ if (filename == null) {
285
+ continue;
286
+ }
287
+ if (count > 0) {
288
+ output.append(',');
289
+ }
290
+ output.append("{\"filename\":\"").append(json(filename))
291
+ .append("\",\"line\":").append(frame.getLineNumber())
292
+ .append(",\"column\":1,\"function\":\"")
293
+ .append(json(safeSymbol(frame.getMethodName(), 256, "unknown")))
294
+ .append("\",\"module\":\"")
295
+ .append(json(safeSymbol(frame.getClassName(), 512, "unknown"))).append("\"}");
296
+ count += 1;
297
+ }
298
+ if (count == 0) {
299
+ output.setLength(start);
300
+ } else {
301
+ output.append(']');
302
+ }
303
+ }
304
+
305
+ private static void appendContext(StringBuilder output, Configuration value) {
306
+ output.append(",\"context\":{\"schemaVersion\":1,\"resource\":{\"service\":{\"name\":\"")
307
+ .append(json(value.service)).append("\"},\"deployment\":{\"environment\":\"")
308
+ .append(json(value.environment)).append("\",\"release\":\"")
309
+ .append(json(value.release)).append("\"},\"operatingSystem\":{\"name\":\"Android\",\"version\":\"")
310
+ .append(json(value.operatingSystemVersion)).append("\"},\"device\":{\"model\":\"")
311
+ .append(json(value.deviceModel)).append("\",\"architecture\":\"")
312
+ .append(value.architecture).append("\"}}}");
313
+ }
314
+
315
+ private String nextId(String kind) {
316
+ return "evt_android_" + kind + '_' + processNonce + '_' + Long.toString(SEQUENCE.incrementAndGet(), 36);
317
+ }
318
+
319
+ String nextNativeEventId() {
320
+ return nextId("native");
321
+ }
322
+
323
+ boolean configurationMatches(Configuration candidate) {
324
+ return configuration.matches(candidate);
325
+ }
326
+
327
+ private static String safeFilename(String value) {
328
+ String safe = safeSymbol(value, 256, null);
329
+ return safe == null || safe.contains("/") || safe.contains("\\") || safe.contains("?") || safe.contains("#") ? null : safe;
330
+ }
331
+
332
+ private static String safeSymbol(String value, int maximum, String fallback) {
333
+ return value == null
334
+ || value.trim().isEmpty()
335
+ || value.length() > maximum
336
+ || value.chars().anyMatch(character -> character <= 31 || character == 127)
337
+ ? fallback
338
+ : value.trim();
339
+ }
340
+
341
+ private static String json(String value) {
342
+ StringBuilder output = new StringBuilder(value.length());
343
+ for (int index = 0; index < value.length(); index += 1) {
344
+ char character = value.charAt(index);
345
+ if (character == '"' || character == '\\') {
346
+ output.append('\\');
347
+ }
348
+ output.append(character);
349
+ }
350
+ return output.toString();
351
+ }
352
+
353
+ private static String timestamp(long timestampMs) {
354
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
355
+ format.setTimeZone(TimeZone.getTimeZone("UTC"));
356
+ return format.format(new Date(timestampMs));
357
+ }
358
+
359
+ private static String randomHex(int bytes) {
360
+ byte[] value = new byte[bytes];
361
+ new SecureRandom().nextBytes(value);
362
+ StringBuilder output = new StringBuilder(bytes * 2);
363
+ for (byte item : value) {
364
+ output.append(String.format(Locale.ROOT, "%02x", item & 0xff));
365
+ }
366
+ return output.toString();
367
+ }
368
+ }
@@ -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 FatalRecordStore.ParentDirectorySync {
9
+ final class AndroidParentDirectorySync implements EventRecordStore.ParentDirectorySync {
10
10
  @Override
11
- public FatalRecordStore.ParentDirectorySyncResult sync(File directory) {
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 FatalRecordStore.ParentDirectorySyncResult.FAILED;
20
+ return EventRecordStore.ParentDirectorySyncResult.FAILED;
21
21
  }
22
22
 
23
- FatalRecordStore.ParentDirectorySyncResult result;
23
+ EventRecordStore.ParentDirectorySyncResult result;
24
24
  try {
25
25
  if (!OsConstants.S_ISDIR(Os.fstat(descriptor).st_mode)) {
26
- result = FatalRecordStore.ParentDirectorySyncResult.FAILED;
26
+ result = EventRecordStore.ParentDirectorySyncResult.FAILED;
27
27
  } else {
28
28
  result = syncDescriptor(descriptor);
29
29
  }
30
30
  } catch (ErrnoException | RuntimeException error) {
31
- result = FatalRecordStore.ParentDirectorySyncResult.FAILED;
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 FatalRecordStore.ParentDirectorySyncResult.FAILED;
37
+ return EventRecordStore.ParentDirectorySyncResult.FAILED;
38
38
  }
39
39
  return result;
40
40
  }
41
41
 
42
- private static FatalRecordStore.ParentDirectorySyncResult syncDescriptor(
42
+ private static EventRecordStore.ParentDirectorySyncResult syncDescriptor(
43
43
  FileDescriptor descriptor) {
44
44
  try {
45
45
  Os.fsync(descriptor);
46
- return FatalRecordStore.ParentDirectorySyncResult.SYNCHRONIZED;
46
+ return EventRecordStore.ParentDirectorySyncResult.SYNCHRONIZED;
47
47
  } catch (ErrnoException error) {
48
48
  return unsupportedDirectorySync(error.errno)
49
- ? FatalRecordStore.ParentDirectorySyncResult.UNSUPPORTED
50
- : FatalRecordStore.ParentDirectorySyncResult.FAILED;
49
+ ? EventRecordStore.ParentDirectorySyncResult.UNSUPPORTED
50
+ : EventRecordStore.ParentDirectorySyncResult.FAILED;
51
51
  } catch (RuntimeException error) {
52
- return FatalRecordStore.ParentDirectorySyncResult.FAILED;
52
+ return EventRecordStore.ParentDirectorySyncResult.FAILED;
53
53
  }
54
54
  }
55
55