Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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
}