1 |
efrain |
1 |
// Copyright 2019 Google
|
|
|
2 |
//
|
|
|
3 |
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
4 |
// you may not use this file except in compliance with the License.
|
|
|
5 |
// You may obtain a copy of the License at
|
|
|
6 |
//
|
|
|
7 |
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
8 |
//
|
|
|
9 |
// Unless required by applicable law or agreed to in writing, software
|
|
|
10 |
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
11 |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
12 |
// See the License for the specific language governing permissions and
|
|
|
13 |
// limitations under the License.
|
|
|
14 |
|
|
|
15 |
#include "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h"
|
|
|
16 |
|
|
|
17 |
#include <sys/time.h>
|
|
|
18 |
|
|
|
19 |
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
|
|
|
20 |
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
|
|
|
21 |
|
|
|
22 |
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportManager_Private.h"
|
|
|
23 |
|
|
|
24 |
NSString *const FIRCLSStartTimeKey = @"com.crashlytics.kit-start-time";
|
|
|
25 |
NSString *const FIRCLSFirstRunloopTurnTimeKey = @"com.crashlytics.first-run-loop-time";
|
|
|
26 |
NSString *const FIRCLSInBackgroundKey = @"com.crashlytics.in-background";
|
|
|
27 |
#if TARGET_OS_IPHONE
|
|
|
28 |
NSString *const FIRCLSDeviceOrientationKey = @"com.crashlytics.device-orientation";
|
|
|
29 |
NSString *const FIRCLSUIOrientationKey = @"com.crashlytics.ui-orientation";
|
|
|
30 |
#endif
|
|
|
31 |
NSString *const FIRCLSUserIdentifierKey = @"com.crashlytics.user-id";
|
|
|
32 |
NSString *const FIRCLSDevelopmentPlatformNameKey = @"com.crashlytics.development-platform-name";
|
|
|
33 |
NSString *const FIRCLSDevelopmentPlatformVersionKey =
|
|
|
34 |
@"com.crashlytics.development-platform-version";
|
|
|
35 |
NSString *const FIRCLSOnDemandRecordedExceptionsKey =
|
|
|
36 |
@"com.crashlytics.on-demand.recorded-exceptions";
|
|
|
37 |
NSString *const FIRCLSOnDemandDroppedExceptionsKey =
|
|
|
38 |
@"com.crashlytics.on-demand.dropped-exceptions";
|
|
|
39 |
|
|
|
40 |
// Empty string object synchronized on to prevent a race condition when accessing AB file path
|
|
|
41 |
NSString *const FIRCLSSynchronizedPathKey = @"";
|
|
|
42 |
|
|
|
43 |
const uint32_t FIRCLSUserLoggingMaxKVEntries = 64;
|
|
|
44 |
|
|
|
45 |
#pragma mark - Prototypes
|
|
|
46 |
static void FIRCLSUserLoggingWriteKeysAndValues(NSDictionary *keysAndValues,
|
|
|
47 |
FIRCLSUserLoggingKVStorage *storage,
|
|
|
48 |
uint32_t *counter,
|
|
|
49 |
BOOL containsNullValue);
|
|
|
50 |
static void FIRCLSUserLoggingCheckAndSwapABFiles(FIRCLSUserLoggingABStorage *storage,
|
|
|
51 |
const char **activePath,
|
|
|
52 |
off_t fileSize);
|
|
|
53 |
void FIRCLSLogInternal(FIRCLSUserLoggingABStorage *storage,
|
|
|
54 |
const char **activePath,
|
|
|
55 |
NSString *message);
|
|
|
56 |
|
|
|
57 |
#pragma mark - Setup
|
|
|
58 |
void FIRCLSUserLoggingInit(FIRCLSUserLoggingReadOnlyContext *roContext,
|
|
|
59 |
FIRCLSUserLoggingWritableContext *rwContext) {
|
|
|
60 |
rwContext->activeUserLogPath = roContext->logStorage.aPath;
|
|
|
61 |
rwContext->activeErrorLogPath = roContext->errorStorage.aPath;
|
|
|
62 |
rwContext->activeCustomExceptionPath = roContext->customExceptionStorage.aPath;
|
|
|
63 |
|
|
|
64 |
rwContext->userKVCount = 0;
|
|
|
65 |
rwContext->internalKVCount = 0;
|
|
|
66 |
rwContext->errorsCount = 0;
|
|
|
67 |
|
|
|
68 |
roContext->userKVStorage.maxIncrementalCount = FIRCLSUserLoggingMaxKVEntries;
|
|
|
69 |
roContext->internalKVStorage.maxIncrementalCount = roContext->userKVStorage.maxIncrementalCount;
|
|
|
70 |
}
|
|
|
71 |
|
|
|
72 |
#pragma mark - KV Logging
|
|
|
73 |
void FIRCLSUserLoggingRecordInternalKeyValue(NSString *key, id value) {
|
|
|
74 |
FIRCLSUserLoggingRecordKeyValue(key, value, &_firclsContext.readonly->logging.internalKVStorage,
|
|
|
75 |
&_firclsContext.writable->logging.internalKVCount);
|
|
|
76 |
}
|
|
|
77 |
|
|
|
78 |
void FIRCLSUserLoggingWriteInternalKeyValue(NSString *key, NSString *value) {
|
|
|
79 |
// Unsynchronized - must be run on the correct queue
|
|
|
80 |
NSDictionary *keysAndValues = key ? @{key : value ?: [NSNull null]} : nil;
|
|
|
81 |
FIRCLSUserLoggingWriteKeysAndValues(keysAndValues,
|
|
|
82 |
&_firclsContext.readonly->logging.internalKVStorage,
|
|
|
83 |
&_firclsContext.writable->logging.internalKVCount, NO);
|
|
|
84 |
}
|
|
|
85 |
|
|
|
86 |
void FIRCLSUserLoggingRecordUserKeyValue(NSString *key, id value) {
|
|
|
87 |
FIRCLSUserLoggingRecordKeyValue(key, value, &_firclsContext.readonly->logging.userKVStorage,
|
|
|
88 |
&_firclsContext.writable->logging.userKVCount);
|
|
|
89 |
}
|
|
|
90 |
|
|
|
91 |
void FIRCLSUserLoggingRecordUserKeysAndValues(NSDictionary *keysAndValues) {
|
|
|
92 |
FIRCLSUserLoggingRecordKeysAndValues(keysAndValues,
|
|
|
93 |
&_firclsContext.readonly->logging.userKVStorage,
|
|
|
94 |
&_firclsContext.writable->logging.userKVCount);
|
|
|
95 |
}
|
|
|
96 |
|
|
|
97 |
static id FIRCLSUserLoggingGetComponent(NSDictionary *entry,
|
|
|
98 |
NSString *componentName,
|
|
|
99 |
bool decodeHex) {
|
|
|
100 |
id value = [entry objectForKey:componentName];
|
|
|
101 |
|
|
|
102 |
return (decodeHex && value != [NSNull null]) ? FIRCLSFileHexDecodeString([value UTF8String])
|
|
|
103 |
: value;
|
|
|
104 |
}
|
|
|
105 |
|
|
|
106 |
static NSString *FIRCLSUserLoggingGetKey(NSDictionary *entry, bool decodeHex) {
|
|
|
107 |
return FIRCLSUserLoggingGetComponent(entry, @"key", decodeHex);
|
|
|
108 |
}
|
|
|
109 |
|
|
|
110 |
static id FIRCLSUserLoggingGetValue(NSDictionary *entry, bool decodeHex) {
|
|
|
111 |
return FIRCLSUserLoggingGetComponent(entry, @"value", decodeHex);
|
|
|
112 |
}
|
|
|
113 |
|
|
|
114 |
NSDictionary *FIRCLSUserLoggingGetCompactedKVEntries(FIRCLSUserLoggingKVStorage *storage,
|
|
|
115 |
bool decodeHex) {
|
|
|
116 |
if (!FIRCLSIsValidPointer(storage)) {
|
|
|
117 |
FIRCLSSDKLogError("storage invalid\n");
|
|
|
118 |
return nil;
|
|
|
119 |
}
|
|
|
120 |
|
|
|
121 |
NSArray *incrementalKVs = FIRCLSUserLoggingStoredKeyValues(storage->incrementalPath);
|
|
|
122 |
NSArray *compactedKVs = FIRCLSUserLoggingStoredKeyValues(storage->compactedPath);
|
|
|
123 |
|
|
|
124 |
NSMutableDictionary *finalKVSet = [NSMutableDictionary new];
|
|
|
125 |
|
|
|
126 |
// These should all be unique, so there might be a more efficient way to
|
|
|
127 |
// do this
|
|
|
128 |
for (NSDictionary *entry in compactedKVs) {
|
|
|
129 |
NSString *key = FIRCLSUserLoggingGetKey(entry, decodeHex);
|
|
|
130 |
NSString *value = FIRCLSUserLoggingGetValue(entry, decodeHex);
|
|
|
131 |
|
|
|
132 |
if (!key || !value) {
|
|
|
133 |
FIRCLSSDKLogError("compacted key/value contains a nil and must be dropped\n");
|
|
|
134 |
continue;
|
|
|
135 |
}
|
|
|
136 |
|
|
|
137 |
[finalKVSet setObject:value forKey:key];
|
|
|
138 |
}
|
|
|
139 |
|
|
|
140 |
// Now, assign the incremental values, in file order, so we overwrite any older values.
|
|
|
141 |
for (NSDictionary *entry in incrementalKVs) {
|
|
|
142 |
NSString *key = FIRCLSUserLoggingGetKey(entry, decodeHex);
|
|
|
143 |
NSString *value = FIRCLSUserLoggingGetValue(entry, decodeHex);
|
|
|
144 |
|
|
|
145 |
if (!key || !value) {
|
|
|
146 |
FIRCLSSDKLogError("incremental key/value contains a nil and must be dropped\n");
|
|
|
147 |
continue;
|
|
|
148 |
}
|
|
|
149 |
|
|
|
150 |
if ([value isEqual:[NSNull null]]) {
|
|
|
151 |
[finalKVSet removeObjectForKey:key];
|
|
|
152 |
} else {
|
|
|
153 |
[finalKVSet setObject:value forKey:key];
|
|
|
154 |
}
|
|
|
155 |
}
|
|
|
156 |
|
|
|
157 |
return finalKVSet;
|
|
|
158 |
}
|
|
|
159 |
|
|
|
160 |
static void FIRCLSUserLoggingWriteKVEntriesToFile(
|
|
|
161 |
NSDictionary<NSString *, NSString *> *keysAndValues, BOOL shouldHexEncode, FIRCLSFile *file) {
|
|
|
162 |
for (NSString *key in keysAndValues) {
|
|
|
163 |
NSString *valueObject = [keysAndValues objectForKey:key];
|
|
|
164 |
|
|
|
165 |
// map `NSNull` into nil
|
|
|
166 |
const char *value = (valueObject == (NSString *)[NSNull null] ? nil : [valueObject UTF8String]);
|
|
|
167 |
|
|
|
168 |
FIRCLSFileWriteSectionStart(file, "kv");
|
|
|
169 |
FIRCLSFileWriteHashStart(file);
|
|
|
170 |
|
|
|
171 |
if (shouldHexEncode) {
|
|
|
172 |
FIRCLSFileWriteHashEntryHexEncodedString(file, "key", [key UTF8String]);
|
|
|
173 |
FIRCLSFileWriteHashEntryHexEncodedString(file, "value", value);
|
|
|
174 |
} else {
|
|
|
175 |
FIRCLSFileWriteHashEntryString(file, "key", [key UTF8String]);
|
|
|
176 |
FIRCLSFileWriteHashEntryString(file, "value", value);
|
|
|
177 |
}
|
|
|
178 |
|
|
|
179 |
FIRCLSFileWriteHashEnd(file);
|
|
|
180 |
FIRCLSFileWriteSectionEnd(file);
|
|
|
181 |
}
|
|
|
182 |
}
|
|
|
183 |
|
|
|
184 |
void FIRCLSUserLoggingCompactKVEntries(FIRCLSUserLoggingKVStorage *storage) {
|
|
|
185 |
if (!FIRCLSIsValidPointer(storage)) {
|
|
|
186 |
FIRCLSSDKLogError("Error: storage invalid\n");
|
|
|
187 |
return;
|
|
|
188 |
}
|
|
|
189 |
|
|
|
190 |
NSDictionary *finalKVs = FIRCLSUserLoggingGetCompactedKVEntries(storage, false);
|
|
|
191 |
|
|
|
192 |
if (unlink(storage->compactedPath) != 0) {
|
|
|
193 |
FIRCLSSDKLog("Error: Unable to remove compacted KV store before compaction %s\n",
|
|
|
194 |
strerror(errno));
|
|
|
195 |
}
|
|
|
196 |
|
|
|
197 |
FIRCLSFile file;
|
|
|
198 |
|
|
|
199 |
if (!FIRCLSFileInitWithPath(&file, storage->compactedPath, true)) {
|
|
|
200 |
FIRCLSSDKLog("Error: Unable to open compacted k-v file\n");
|
|
|
201 |
return;
|
|
|
202 |
}
|
|
|
203 |
|
|
|
204 |
uint32_t maxCount = storage->maxCount;
|
|
|
205 |
if ([finalKVs count] > maxCount) {
|
|
|
206 |
// We need to remove keys, to avoid going over the max.
|
|
|
207 |
// This is just about the worst way to go about doing this. There are lots of smarter ways,
|
|
|
208 |
// but it's very uncommon to go down this path.
|
|
|
209 |
NSArray *keys = [finalKVs allKeys];
|
|
|
210 |
|
|
|
211 |
FIRCLSSDKLogInfo("Truncating %d keys from KV set, which is above max %d\n",
|
|
|
212 |
(uint32_t)(finalKVs.count - maxCount), maxCount);
|
|
|
213 |
|
|
|
214 |
finalKVs =
|
|
|
215 |
[finalKVs dictionaryWithValuesForKeys:[keys subarrayWithRange:NSMakeRange(0, maxCount)]];
|
|
|
216 |
}
|
|
|
217 |
|
|
|
218 |
FIRCLSUserLoggingWriteKVEntriesToFile(finalKVs, false, &file);
|
|
|
219 |
FIRCLSFileClose(&file);
|
|
|
220 |
|
|
|
221 |
if (unlink(storage->incrementalPath) != 0) {
|
|
|
222 |
FIRCLSSDKLog("Error: Unable to remove incremental KV store after compaction %s\n",
|
|
|
223 |
strerror(errno));
|
|
|
224 |
}
|
|
|
225 |
}
|
|
|
226 |
|
|
|
227 |
void FIRCLSUserLoggingRecordKeyValue(NSString *key,
|
|
|
228 |
id value,
|
|
|
229 |
FIRCLSUserLoggingKVStorage *storage,
|
|
|
230 |
uint32_t *counter) {
|
|
|
231 |
if (!FIRCLSIsValidPointer(key)) {
|
|
|
232 |
FIRCLSSDKLogWarn("User provided bad key\n");
|
|
|
233 |
return;
|
|
|
234 |
}
|
|
|
235 |
|
|
|
236 |
NSDictionary *keysAndValues = @{key : (value ?: [NSNull null])};
|
|
|
237 |
FIRCLSUserLoggingRecordKeysAndValues(keysAndValues, storage, counter);
|
|
|
238 |
}
|
|
|
239 |
|
|
|
240 |
void FIRCLSUserLoggingRecordKeysAndValues(NSDictionary *keysAndValues,
|
|
|
241 |
FIRCLSUserLoggingKVStorage *storage,
|
|
|
242 |
uint32_t *counter) {
|
|
|
243 |
if (!FIRCLSContextIsInitialized()) {
|
|
|
244 |
return;
|
|
|
245 |
}
|
|
|
246 |
|
|
|
247 |
if (keysAndValues.count == 0) {
|
|
|
248 |
FIRCLSSDKLogWarn("User provided empty key/value dictionary\n");
|
|
|
249 |
return;
|
|
|
250 |
}
|
|
|
251 |
|
|
|
252 |
if (!FIRCLSIsValidPointer(keysAndValues)) {
|
|
|
253 |
FIRCLSSDKLogWarn("User provided bad key/value dictionary\n");
|
|
|
254 |
return;
|
|
|
255 |
}
|
|
|
256 |
|
|
|
257 |
NSMutableDictionary *sanitizedKeysAndValues = [keysAndValues mutableCopy];
|
|
|
258 |
BOOL containsNullValue = NO;
|
|
|
259 |
|
|
|
260 |
for (NSString *key in keysAndValues) {
|
|
|
261 |
if (!FIRCLSIsValidPointer(key)) {
|
|
|
262 |
FIRCLSSDKLogWarn("User provided bad key\n");
|
|
|
263 |
return;
|
|
|
264 |
}
|
|
|
265 |
|
|
|
266 |
id value = keysAndValues[key];
|
|
|
267 |
|
|
|
268 |
// ensure that any invalid pointer is actually set to nil
|
|
|
269 |
if (!FIRCLSIsValidPointer(value) && value != nil) {
|
|
|
270 |
FIRCLSSDKLogWarn("Bad value pointer being clamped to nil\n");
|
|
|
271 |
sanitizedKeysAndValues[key] = [NSNull null];
|
|
|
272 |
}
|
|
|
273 |
|
|
|
274 |
if ([value respondsToSelector:@selector(description)] && ![value isEqual:[NSNull null]]) {
|
|
|
275 |
sanitizedKeysAndValues[key] = [value description];
|
|
|
276 |
} else {
|
|
|
277 |
// passing nil will result in a JSON null being written, which is deserialized as [NSNull
|
|
|
278 |
// null], signaling to remove the key during compaction
|
|
|
279 |
sanitizedKeysAndValues[key] = [NSNull null];
|
|
|
280 |
containsNullValue = YES;
|
|
|
281 |
}
|
|
|
282 |
}
|
|
|
283 |
|
|
|
284 |
dispatch_sync(FIRCLSGetLoggingQueue(), ^{
|
|
|
285 |
FIRCLSUserLoggingWriteKeysAndValues(sanitizedKeysAndValues, storage, counter,
|
|
|
286 |
containsNullValue);
|
|
|
287 |
});
|
|
|
288 |
}
|
|
|
289 |
|
|
|
290 |
static void FIRCLSUserLoggingWriteKeysAndValues(NSDictionary *keysAndValues,
|
|
|
291 |
FIRCLSUserLoggingKVStorage *storage,
|
|
|
292 |
uint32_t *counter,
|
|
|
293 |
BOOL containsNullValue) {
|
|
|
294 |
FIRCLSFile file;
|
|
|
295 |
|
|
|
296 |
if (!FIRCLSIsValidPointer(storage) || !FIRCLSIsValidPointer(counter)) {
|
|
|
297 |
FIRCLSSDKLogError("Bad parameters\n");
|
|
|
298 |
return;
|
|
|
299 |
}
|
|
|
300 |
|
|
|
301 |
if (!FIRCLSFileInitWithPath(&file, storage->incrementalPath, true)) {
|
|
|
302 |
FIRCLSSDKLogError("Unable to open k-v file\n");
|
|
|
303 |
return;
|
|
|
304 |
}
|
|
|
305 |
|
|
|
306 |
FIRCLSUserLoggingWriteKVEntriesToFile(keysAndValues, true, &file);
|
|
|
307 |
FIRCLSFileClose(&file);
|
|
|
308 |
|
|
|
309 |
*counter += keysAndValues.count;
|
|
|
310 |
if (*counter >= storage->maxIncrementalCount || containsNullValue) {
|
|
|
311 |
dispatch_async(FIRCLSGetLoggingQueue(), ^{
|
|
|
312 |
FIRCLSUserLoggingCompactKVEntries(storage);
|
|
|
313 |
*counter = 0;
|
|
|
314 |
});
|
|
|
315 |
}
|
|
|
316 |
}
|
|
|
317 |
|
|
|
318 |
NSArray *FIRCLSUserLoggingStoredKeyValues(const char *path) {
|
|
|
319 |
if (!FIRCLSContextIsInitialized()) {
|
|
|
320 |
return nil;
|
|
|
321 |
}
|
|
|
322 |
|
|
|
323 |
return FIRCLSFileReadSections(path, true, ^NSObject *(id obj) {
|
|
|
324 |
return [obj objectForKey:@"kv"];
|
|
|
325 |
});
|
|
|
326 |
}
|
|
|
327 |
|
|
|
328 |
#pragma mark - NSError Logging
|
|
|
329 |
static void FIRCLSUserLoggingRecordErrorUserInfo(FIRCLSFile *file,
|
|
|
330 |
const char *fileKey,
|
|
|
331 |
NSDictionary<NSString *, id> *userInfo) {
|
|
|
332 |
if ([userInfo count] == 0) {
|
|
|
333 |
return;
|
|
|
334 |
}
|
|
|
335 |
|
|
|
336 |
FIRCLSFileWriteHashKey(file, fileKey);
|
|
|
337 |
FIRCLSFileWriteArrayStart(file);
|
|
|
338 |
|
|
|
339 |
for (id key in userInfo) {
|
|
|
340 |
id value = [userInfo objectForKey:key];
|
|
|
341 |
if (![value respondsToSelector:@selector(description)]) {
|
|
|
342 |
continue;
|
|
|
343 |
}
|
|
|
344 |
|
|
|
345 |
FIRCLSFileWriteArrayStart(file);
|
|
|
346 |
FIRCLSFileWriteArrayEntryHexEncodedString(file, [key UTF8String]);
|
|
|
347 |
FIRCLSFileWriteArrayEntryHexEncodedString(file, [[value description] UTF8String]);
|
|
|
348 |
FIRCLSFileWriteArrayEnd(file);
|
|
|
349 |
}
|
|
|
350 |
|
|
|
351 |
FIRCLSFileWriteArrayEnd(file);
|
|
|
352 |
}
|
|
|
353 |
|
|
|
354 |
static void FIRCLSUserLoggingWriteError(FIRCLSFile *file,
|
|
|
355 |
NSError *error,
|
|
|
356 |
NSDictionary<NSString *, id> *additionalUserInfo,
|
|
|
357 |
NSArray *addresses,
|
|
|
358 |
uint64_t timestamp) {
|
|
|
359 |
FIRCLSFileWriteSectionStart(file, "error");
|
|
|
360 |
FIRCLSFileWriteHashStart(file);
|
|
|
361 |
FIRCLSFileWriteHashEntryHexEncodedString(file, "domain", [[error domain] UTF8String]);
|
|
|
362 |
FIRCLSFileWriteHashEntryInt64(file, "code", [error code]);
|
|
|
363 |
FIRCLSFileWriteHashEntryUint64(file, "time", timestamp);
|
|
|
364 |
|
|
|
365 |
// addresses
|
|
|
366 |
FIRCLSFileWriteHashKey(file, "stacktrace");
|
|
|
367 |
FIRCLSFileWriteArrayStart(file);
|
|
|
368 |
for (NSNumber *address in addresses) {
|
|
|
369 |
FIRCLSFileWriteArrayEntryUint64(file, [address unsignedLongLongValue]);
|
|
|
370 |
}
|
|
|
371 |
FIRCLSFileWriteArrayEnd(file);
|
|
|
372 |
|
|
|
373 |
// user-info
|
|
|
374 |
FIRCLSUserLoggingRecordErrorUserInfo(file, "info", [error userInfo]);
|
|
|
375 |
FIRCLSUserLoggingRecordErrorUserInfo(file, "extra_info", additionalUserInfo);
|
|
|
376 |
|
|
|
377 |
FIRCLSFileWriteHashEnd(file);
|
|
|
378 |
FIRCLSFileWriteSectionEnd(file);
|
|
|
379 |
}
|
|
|
380 |
|
|
|
381 |
void FIRCLSUserLoggingRecordError(NSError *error,
|
|
|
382 |
NSDictionary<NSString *, id> *additionalUserInfo) {
|
|
|
383 |
if (!error) {
|
|
|
384 |
return;
|
|
|
385 |
}
|
|
|
386 |
|
|
|
387 |
if (!FIRCLSContextIsInitialized()) {
|
|
|
388 |
return;
|
|
|
389 |
}
|
|
|
390 |
|
|
|
391 |
// record the stacktrace and timestamp here, so we
|
|
|
392 |
// are as close as possible to the user's log statement
|
|
|
393 |
NSArray *addresses = [NSThread callStackReturnAddresses];
|
|
|
394 |
uint64_t timestamp = time(NULL);
|
|
|
395 |
|
|
|
396 |
FIRCLSUserLoggingWriteAndCheckABFiles(
|
|
|
397 |
&_firclsContext.readonly->logging.errorStorage,
|
|
|
398 |
&_firclsContext.writable->logging.activeErrorLogPath, ^(FIRCLSFile *file) {
|
|
|
399 |
FIRCLSUserLoggingWriteError(file, error, additionalUserInfo, addresses, timestamp);
|
|
|
400 |
});
|
|
|
401 |
}
|
|
|
402 |
|
|
|
403 |
#pragma mark - CLSLog Support
|
|
|
404 |
void FIRCLSLog(NSString *format, ...) {
|
|
|
405 |
// If the format is nil do nothing just like NSLog.
|
|
|
406 |
if (!format) {
|
|
|
407 |
return;
|
|
|
408 |
}
|
|
|
409 |
|
|
|
410 |
va_list args;
|
|
|
411 |
va_start(args, format);
|
|
|
412 |
NSString *msg = [[NSString alloc] initWithFormat:format arguments:args];
|
|
|
413 |
va_end(args);
|
|
|
414 |
|
|
|
415 |
FIRCLSUserLoggingABStorage *currentStorage = &_firclsContext.readonly->logging.logStorage;
|
|
|
416 |
const char **activePath = &_firclsContext.writable->logging.activeUserLogPath;
|
|
|
417 |
FIRCLSLogInternal(currentStorage, activePath, msg);
|
|
|
418 |
}
|
|
|
419 |
|
|
|
420 |
void FIRCLSLogToStorage(FIRCLSUserLoggingABStorage *storage,
|
|
|
421 |
const char **activePath,
|
|
|
422 |
NSString *format,
|
|
|
423 |
...) {
|
|
|
424 |
// If the format is nil do nothing just like NSLog.
|
|
|
425 |
if (!format) {
|
|
|
426 |
return;
|
|
|
427 |
}
|
|
|
428 |
|
|
|
429 |
va_list args;
|
|
|
430 |
va_start(args, format);
|
|
|
431 |
NSString *msg = [[NSString alloc] initWithFormat:format arguments:args];
|
|
|
432 |
va_end(args);
|
|
|
433 |
|
|
|
434 |
FIRCLSLogInternal(storage, activePath, msg);
|
|
|
435 |
}
|
|
|
436 |
|
|
|
437 |
#pragma mark - Properties
|
|
|
438 |
uint32_t FIRCLSUserLoggingMaxLogSize(void) {
|
|
|
439 |
// don't forget that the message encoding overhead is 2x, and we
|
|
|
440 |
// wrap everything in a json structure with time. So, there is
|
|
|
441 |
// quite a penalty
|
|
|
442 |
|
|
|
443 |
uint32_t size = 1024 * 64;
|
|
|
444 |
|
|
|
445 |
return size * 2;
|
|
|
446 |
}
|
|
|
447 |
|
|
|
448 |
uint32_t FIRCLSUserLoggingMaxErrorSize(void) {
|
|
|
449 |
return FIRCLSUserLoggingMaxLogSize();
|
|
|
450 |
}
|
|
|
451 |
|
|
|
452 |
#pragma mark - AB Logging
|
|
|
453 |
void FIRCLSUserLoggingCheckAndSwapABFiles(FIRCLSUserLoggingABStorage *storage,
|
|
|
454 |
const char **activePath,
|
|
|
455 |
off_t fileSize) {
|
|
|
456 |
if (!activePath || !storage) {
|
|
|
457 |
return;
|
|
|
458 |
}
|
|
|
459 |
|
|
|
460 |
if (!*activePath) {
|
|
|
461 |
return;
|
|
|
462 |
}
|
|
|
463 |
|
|
|
464 |
if (storage->restrictBySize) {
|
|
|
465 |
if (fileSize <= storage->maxSize) {
|
|
|
466 |
return;
|
|
|
467 |
}
|
|
|
468 |
} else {
|
|
|
469 |
if (!FIRCLSIsValidPointer(storage->entryCount)) {
|
|
|
470 |
FIRCLSSDKLogError("Error: storage has invalid pointer, but is restricted by entry count\n");
|
|
|
471 |
return;
|
|
|
472 |
}
|
|
|
473 |
|
|
|
474 |
if (*storage->entryCount < storage->maxEntries) {
|
|
|
475 |
return;
|
|
|
476 |
}
|
|
|
477 |
|
|
|
478 |
// Here we have rolled over, so we have to reset our counter.
|
|
|
479 |
*storage->entryCount = 0;
|
|
|
480 |
}
|
|
|
481 |
|
|
|
482 |
// if it is too big:
|
|
|
483 |
// - reset the other log
|
|
|
484 |
// - make it active
|
|
|
485 |
const char *otherPath = NULL;
|
|
|
486 |
|
|
|
487 |
if (*activePath == storage->aPath) {
|
|
|
488 |
otherPath = storage->bPath;
|
|
|
489 |
} else {
|
|
|
490 |
// take this path if the pointer is invalid as well, to reset
|
|
|
491 |
otherPath = storage->aPath;
|
|
|
492 |
}
|
|
|
493 |
|
|
|
494 |
// guard here against path being nil or empty
|
|
|
495 |
NSString *pathString = [NSString stringWithUTF8String:otherPath];
|
|
|
496 |
|
|
|
497 |
if ([pathString length] > 0) {
|
|
|
498 |
// ignore the error, because there is nothing we can do to recover here, and its likely
|
|
|
499 |
// any failures would be intermittent
|
|
|
500 |
|
|
|
501 |
[[NSFileManager defaultManager] removeItemAtPath:pathString error:nil];
|
|
|
502 |
}
|
|
|
503 |
|
|
|
504 |
@synchronized(FIRCLSSynchronizedPathKey) {
|
|
|
505 |
*activePath = otherPath;
|
|
|
506 |
}
|
|
|
507 |
}
|
|
|
508 |
|
|
|
509 |
void FIRCLSUserLoggingWriteAndCheckABFiles(FIRCLSUserLoggingABStorage *storage,
|
|
|
510 |
const char **activePath,
|
|
|
511 |
void (^openedFileBlock)(FIRCLSFile *file)) {
|
|
|
512 |
if (!storage || !activePath || !openedFileBlock) {
|
|
|
513 |
return;
|
|
|
514 |
}
|
|
|
515 |
|
|
|
516 |
@synchronized(FIRCLSSynchronizedPathKey) {
|
|
|
517 |
if (!*activePath) {
|
|
|
518 |
return;
|
|
|
519 |
}
|
|
|
520 |
}
|
|
|
521 |
|
|
|
522 |
if (storage->restrictBySize) {
|
|
|
523 |
if (storage->maxSize == 0) {
|
|
|
524 |
return;
|
|
|
525 |
}
|
|
|
526 |
} else {
|
|
|
527 |
if (storage->maxEntries == 0) {
|
|
|
528 |
return;
|
|
|
529 |
}
|
|
|
530 |
}
|
|
|
531 |
|
|
|
532 |
dispatch_sync(FIRCLSGetLoggingQueue(), ^{
|
|
|
533 |
FIRCLSFile file;
|
|
|
534 |
|
|
|
535 |
if (!FIRCLSFileInitWithPath(&file, *activePath, true)) {
|
|
|
536 |
FIRCLSSDKLog("Unable to open log file\n");
|
|
|
537 |
return;
|
|
|
538 |
}
|
|
|
539 |
|
|
|
540 |
openedFileBlock(&file);
|
|
|
541 |
|
|
|
542 |
off_t fileSize = 0;
|
|
|
543 |
FIRCLSFileCloseWithOffset(&file, &fileSize);
|
|
|
544 |
|
|
|
545 |
// increment the count before calling FIRCLSUserLoggingCheckAndSwapABFiles, so the value
|
|
|
546 |
// reflects the actual amount of stuff written
|
|
|
547 |
if (!storage->restrictBySize && FIRCLSIsValidPointer(storage->entryCount)) {
|
|
|
548 |
*storage->entryCount += 1;
|
|
|
549 |
}
|
|
|
550 |
|
|
|
551 |
dispatch_async(FIRCLSGetLoggingQueue(), ^{
|
|
|
552 |
FIRCLSUserLoggingCheckAndSwapABFiles(storage, activePath, fileSize);
|
|
|
553 |
});
|
|
|
554 |
});
|
|
|
555 |
}
|
|
|
556 |
|
|
|
557 |
void FIRCLSLogInternalWrite(FIRCLSFile *file, NSString *message, uint64_t time) {
|
|
|
558 |
FIRCLSFileWriteSectionStart(file, "log");
|
|
|
559 |
FIRCLSFileWriteHashStart(file);
|
|
|
560 |
FIRCLSFileWriteHashEntryHexEncodedString(file, "msg", [message UTF8String]);
|
|
|
561 |
FIRCLSFileWriteHashEntryUint64(file, "time", time);
|
|
|
562 |
FIRCLSFileWriteHashEnd(file);
|
|
|
563 |
FIRCLSFileWriteSectionEnd(file);
|
|
|
564 |
}
|
|
|
565 |
|
|
|
566 |
void FIRCLSLogInternal(FIRCLSUserLoggingABStorage *storage,
|
|
|
567 |
const char **activePath,
|
|
|
568 |
NSString *message) {
|
|
|
569 |
if (!message) {
|
|
|
570 |
return;
|
|
|
571 |
}
|
|
|
572 |
|
|
|
573 |
if (!FIRCLSContextIsInitialized()) {
|
|
|
574 |
FIRCLSWarningLog(@"WARNING: FIRCLSLog has been used before (or concurrently with) "
|
|
|
575 |
@"Crashlytics initialization and cannot be recorded. The message was: \n%@",
|
|
|
576 |
message);
|
|
|
577 |
return;
|
|
|
578 |
}
|
|
|
579 |
struct timeval te;
|
|
|
580 |
|
|
|
581 |
NSUInteger messageLength = [message length];
|
|
|
582 |
int maxLogSize = storage->maxSize;
|
|
|
583 |
|
|
|
584 |
if (messageLength > maxLogSize) {
|
|
|
585 |
FIRCLSWarningLog(
|
|
|
586 |
@"WARNING: Attempted to write %zd bytes, but %d is the maximum size of the log. "
|
|
|
587 |
@"Truncating to %d bytes.\n",
|
|
|
588 |
messageLength, maxLogSize, maxLogSize);
|
|
|
589 |
message = [message substringToIndex:maxLogSize];
|
|
|
590 |
}
|
|
|
591 |
|
|
|
592 |
// unable to get time - abort
|
|
|
593 |
if (gettimeofday(&te, NULL) != 0) {
|
|
|
594 |
return;
|
|
|
595 |
}
|
|
|
596 |
|
|
|
597 |
const uint64_t time = te.tv_sec * 1000LL + te.tv_usec / 1000;
|
|
|
598 |
|
|
|
599 |
FIRCLSUserLoggingWriteAndCheckABFiles(storage, activePath, ^(FIRCLSFile *file) {
|
|
|
600 |
FIRCLSLogInternalWrite(file, message, time);
|
|
|
601 |
});
|
|
|
602 |
}
|