DDAbstractDatabaseLogger.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. // Software License Agreement (BSD License)
  2. //
  3. // Copyright (c) 2010-2024, Deusty, LLC
  4. // All rights reserved.
  5. //
  6. // Redistribution and use of this software in source and binary forms,
  7. // with or without modification, are permitted provided that the following conditions are met:
  8. //
  9. // * Redistributions of source code must retain the above copyright notice,
  10. // this list of conditions and the following disclaimer.
  11. //
  12. // * Neither the name of Deusty nor the names of its contributors may be used
  13. // to endorse or promote products derived from this software without specific
  14. // prior written permission of Deusty, LLC.
  15. #if !__has_feature(objc_arc)
  16. #error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
  17. #endif
  18. #import <CocoaLumberjack/DDAbstractDatabaseLogger.h>
  19. @interface DDAbstractDatabaseLogger ()
  20. - (void)destroySaveTimer;
  21. - (void)updateAndResumeSaveTimer;
  22. - (void)createSuspendedSaveTimer;
  23. - (void)destroyDeleteTimer;
  24. - (void)updateDeleteTimer;
  25. - (void)createAndStartDeleteTimer;
  26. @end
  27. #pragma mark -
  28. @implementation DDAbstractDatabaseLogger
  29. - (instancetype)init {
  30. if ((self = [super init])) {
  31. _saveThreshold = 500;
  32. _saveInterval = 60; // 60 seconds
  33. _maxAge = (60 * 60 * 24 * 7); // 7 days
  34. _deleteInterval = (60 * 5); // 5 minutes
  35. }
  36. return self;
  37. }
  38. - (void)dealloc {
  39. [self destroySaveTimer];
  40. [self destroyDeleteTimer];
  41. }
  42. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  43. #pragma mark Override Me
  44. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  45. - (BOOL)db_log:(__unused DDLogMessage *)logMessage {
  46. // Override me and add your implementation.
  47. //
  48. // Return YES if an item was added to the buffer.
  49. // Return NO if the logMessage was ignored.
  50. return NO;
  51. }
  52. - (void)db_save {
  53. // Override me and add your implementation.
  54. }
  55. - (void)db_delete {
  56. // Override me and add your implementation.
  57. }
  58. - (void)db_saveAndDelete {
  59. // Override me and add your implementation.
  60. }
  61. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  62. #pragma mark Private API
  63. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  64. - (void)performSaveAndSuspendSaveTimer {
  65. if (_unsavedCount > 0) {
  66. if (_deleteOnEverySave) {
  67. [self db_saveAndDelete];
  68. } else {
  69. [self db_save];
  70. }
  71. }
  72. _unsavedCount = 0;
  73. _unsavedTime = 0;
  74. if (_saveTimer != NULL && _saveTimerSuspended == 0) {
  75. dispatch_suspend(_saveTimer);
  76. _saveTimerSuspended = 1;
  77. }
  78. }
  79. - (void)performDelete {
  80. if (_maxAge > 0.0) {
  81. [self db_delete];
  82. _lastDeleteTime = dispatch_time(DISPATCH_TIME_NOW, 0);
  83. }
  84. }
  85. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  86. #pragma mark Timers
  87. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  88. - (void)destroySaveTimer {
  89. if (_saveTimer != NULL) {
  90. dispatch_source_cancel(_saveTimer);
  91. // Must activate a timer before releasing it (or it will crash)
  92. if (_saveTimerSuspended < 0) {
  93. dispatch_activate(_saveTimer);
  94. } else if (_saveTimerSuspended > 0) {
  95. dispatch_resume(_saveTimer);
  96. }
  97. #if !OS_OBJECT_USE_OBJC
  98. dispatch_release(_saveTimer);
  99. #endif
  100. _saveTimer = NULL;
  101. _saveTimerSuspended = 0;
  102. }
  103. }
  104. - (void)updateAndResumeSaveTimer {
  105. if ((_saveTimer != NULL) && (_saveInterval > 0.0) && (_unsavedTime > 0)) {
  106. __auto_type interval = (uint64_t)(_saveInterval * (NSTimeInterval) NSEC_PER_SEC);
  107. __auto_type startTime = dispatch_time(_unsavedTime, (int64_t)interval);
  108. dispatch_source_set_timer(_saveTimer, startTime, interval, 1ull * NSEC_PER_SEC);
  109. if (_saveTimerSuspended < 0) {
  110. dispatch_activate(_saveTimer);
  111. _saveTimerSuspended = 0;
  112. } else if (_saveTimerSuspended > 0) {
  113. dispatch_resume(_saveTimer);
  114. _saveTimerSuspended = 0;
  115. }
  116. }
  117. }
  118. - (void)createSuspendedSaveTimer {
  119. if ((_saveTimer == NULL) && (_saveInterval > 0.0)) {
  120. _saveTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue);
  121. dispatch_source_set_event_handler(_saveTimer, ^{ @autoreleasepool {
  122. [self performSaveAndSuspendSaveTimer];
  123. } });
  124. _saveTimerSuspended = -1;
  125. }
  126. }
  127. - (void)destroyDeleteTimer {
  128. if (_deleteTimer != NULL) {
  129. dispatch_source_cancel(_deleteTimer);
  130. #if !OS_OBJECT_USE_OBJC
  131. dispatch_release(_deleteTimer);
  132. #endif
  133. _deleteTimer = NULL;
  134. }
  135. }
  136. - (void)updateDeleteTimer {
  137. if ((_deleteTimer != NULL) && (_deleteInterval > 0.0) && (_maxAge > 0.0)) {
  138. __auto_type interval = (int64_t)(_deleteInterval * (NSTimeInterval) NSEC_PER_SEC);
  139. dispatch_time_t startTime;
  140. if (_lastDeleteTime > 0) {
  141. startTime = dispatch_time(_lastDeleteTime, interval);
  142. } else {
  143. startTime = dispatch_time(DISPATCH_TIME_NOW, interval);
  144. }
  145. dispatch_source_set_timer(_deleteTimer, startTime, (uint64_t)interval, 1ull * NSEC_PER_SEC);
  146. }
  147. }
  148. - (void)createAndStartDeleteTimer {
  149. if ((_deleteTimer == NULL) && (_deleteInterval > 0.0) && (_maxAge > 0.0)) {
  150. _deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue);
  151. if (_deleteTimer != NULL) {
  152. dispatch_source_set_event_handler(_deleteTimer, ^{ @autoreleasepool {
  153. [self performDelete];
  154. } });
  155. [self updateDeleteTimer];
  156. // We are sure that -updateDeleteTimer did call dispatch_source_set_timer()
  157. // since it has the same guards on _deleteInterval and _maxAge
  158. dispatch_activate(_deleteTimer);
  159. }
  160. }
  161. }
  162. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  163. #pragma mark Configuration
  164. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  165. - (NSUInteger)saveThreshold {
  166. // The design of this method is taken from the DDAbstractLogger implementation.
  167. // For extensive documentation please refer to the DDAbstractLogger implementation.
  168. // Note: The internal implementation MUST access the colorsEnabled variable directly,
  169. // This method is designed explicitly for external access.
  170. //
  171. // Using "self." syntax to go through this method will cause immediate deadlock.
  172. // This is the intended result. Fix it by accessing the ivar directly.
  173. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  174. DDAbstractLoggerAssertLockedPropertyAccess();
  175. __block NSUInteger result;
  176. dispatch_sync(DDLog.loggingQueue, ^{
  177. dispatch_sync(self.loggerQueue, ^{
  178. result = self->_saveThreshold;
  179. });
  180. });
  181. return result;
  182. }
  183. - (void)setSaveThreshold:(NSUInteger)threshold {
  184. dispatch_block_t block = ^{
  185. @autoreleasepool {
  186. if (self->_saveThreshold != threshold) {
  187. self->_saveThreshold = threshold;
  188. // Since the saveThreshold has changed,
  189. // we check to see if the current unsavedCount has surpassed the new threshold.
  190. //
  191. // If it has, we immediately save the log.
  192. if ((self->_unsavedCount >= self->_saveThreshold) && (self->_saveThreshold > 0)) {
  193. [self performSaveAndSuspendSaveTimer];
  194. }
  195. }
  196. }
  197. };
  198. // The design of the setter logic below is taken from the DDAbstractLogger implementation.
  199. // For documentation please refer to the DDAbstractLogger implementation.
  200. if ([self isOnInternalLoggerQueue]) {
  201. block();
  202. } else {
  203. DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
  204. dispatch_async(DDLog.loggingQueue, ^{
  205. dispatch_async(self.loggerQueue, block);
  206. });
  207. }
  208. }
  209. - (NSTimeInterval)saveInterval {
  210. // The design of this method is taken from the DDAbstractLogger implementation.
  211. // For extensive documentation please refer to the DDAbstractLogger implementation.
  212. // Note: The internal implementation MUST access the colorsEnabled variable directly,
  213. // This method is designed explicitly for external access.
  214. //
  215. // Using "self." syntax to go through this method will cause immediate deadlock.
  216. // This is the intended result. Fix it by accessing the ivar directly.
  217. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  218. DDAbstractLoggerAssertLockedPropertyAccess();
  219. __block NSTimeInterval result;
  220. dispatch_sync(DDLog.loggingQueue, ^{
  221. dispatch_sync(self.loggerQueue, ^{
  222. result = self->_saveInterval;
  223. });
  224. });
  225. return result;
  226. }
  227. - (void)setSaveInterval:(NSTimeInterval)interval {
  228. __auto_type block = ^{
  229. @autoreleasepool {
  230. // C99 recommended floating point comparison macro
  231. // Read: isLessThanOrGreaterThan(floatA, floatB)
  232. if (/* saveInterval != interval */ islessgreater(self->_saveInterval, interval)) {
  233. self->_saveInterval = interval;
  234. // There are several cases we need to handle here.
  235. //
  236. // 1. If the saveInterval was previously enabled and it just got disabled,
  237. // then we need to stop the saveTimer. (And we might as well release it.)
  238. //
  239. // 2. If the saveInterval was previously disabled and it just got enabled,
  240. // then we need to setup the saveTimer. (Plus we might need to do an immediate save.)
  241. //
  242. // 3. If the saveInterval increased, then we need to reset the timer so that it fires at the later date.
  243. //
  244. // 4. If the saveInterval decreased, then we need to reset the timer so that it fires at an earlier date.
  245. // (Plus we might need to do an immediate save.)
  246. if (self->_saveInterval > 0.0) {
  247. if (self->_saveTimer == NULL) {
  248. // Handles #2
  249. //
  250. // Since the saveTimer uses the unsavedTime to calculate it's first fireDate,
  251. // if a save is needed the timer will fire immediately.
  252. [self createSuspendedSaveTimer];
  253. [self updateAndResumeSaveTimer];
  254. } else {
  255. // Handles #3
  256. // Handles #4
  257. //
  258. // Since the saveTimer uses the unsavedTime to calculate it's first fireDate,
  259. // if a save is needed the timer will fire immediately.
  260. [self updateAndResumeSaveTimer];
  261. }
  262. } else if (self->_saveTimer) {
  263. // Handles #1
  264. [self destroySaveTimer];
  265. }
  266. }
  267. }
  268. };
  269. // The design of the setter logic below is taken from the DDAbstractLogger implementation.
  270. // For documentation please refer to the DDAbstractLogger implementation.
  271. if ([self isOnInternalLoggerQueue]) {
  272. block();
  273. } else {
  274. DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
  275. dispatch_async(DDLog.loggingQueue, ^{
  276. dispatch_async(self.loggerQueue, block);
  277. });
  278. }
  279. }
  280. - (NSTimeInterval)maxAge {
  281. // The design of this method is taken from the DDAbstractLogger implementation.
  282. // For extensive documentation please refer to the DDAbstractLogger implementation.
  283. // Note: The internal implementation MUST access the colorsEnabled variable directly,
  284. // This method is designed explicitly for external access.
  285. //
  286. // Using "self." syntax to go through this method will cause immediate deadlock.
  287. // This is the intended result. Fix it by accessing the ivar directly.
  288. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  289. DDAbstractLoggerAssertLockedPropertyAccess();
  290. __block NSTimeInterval result;
  291. dispatch_sync(DDLog.loggingQueue, ^{
  292. dispatch_sync(self.loggerQueue, ^{
  293. result = self->_maxAge;
  294. });
  295. });
  296. return result;
  297. }
  298. - (void)setMaxAge:(NSTimeInterval)interval {
  299. __auto_type block = ^{
  300. @autoreleasepool {
  301. // C99 recommended floating point comparison macro
  302. // Read: isLessThanOrGreaterThan(floatA, floatB)
  303. if (/* maxAge != interval */ islessgreater(self->_maxAge, interval)) {
  304. __auto_type oldMaxAge = self->_maxAge;
  305. __auto_type newMaxAge = interval;
  306. self->_maxAge = interval;
  307. // There are several cases we need to handle here.
  308. //
  309. // 1. If the maxAge was previously enabled and it just got disabled,
  310. // then we need to stop the deleteTimer. (And we might as well release it.)
  311. //
  312. // 2. If the maxAge was previously disabled and it just got enabled,
  313. // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.)
  314. //
  315. // 3. If the maxAge was increased,
  316. // then we don't need to do anything.
  317. //
  318. // 4. If the maxAge was decreased,
  319. // then we should do an immediate delete.
  320. __auto_type shouldDeleteNow = NO;
  321. if (oldMaxAge > 0.0) {
  322. if (newMaxAge <= 0.0) {
  323. // Handles #1
  324. [self destroyDeleteTimer];
  325. } else if (oldMaxAge > newMaxAge) {
  326. // Handles #4
  327. shouldDeleteNow = YES;
  328. }
  329. } else if (newMaxAge > 0.0) {
  330. // Handles #2
  331. shouldDeleteNow = YES;
  332. }
  333. if (shouldDeleteNow) {
  334. [self performDelete];
  335. if (self->_deleteTimer) {
  336. [self updateDeleteTimer];
  337. } else {
  338. [self createAndStartDeleteTimer];
  339. }
  340. }
  341. }
  342. }
  343. };
  344. // The design of the setter logic below is taken from the DDAbstractLogger implementation.
  345. // For documentation please refer to the DDAbstractLogger implementation.
  346. if ([self isOnInternalLoggerQueue]) {
  347. block();
  348. } else {
  349. DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
  350. dispatch_async(DDLog.loggingQueue, ^{
  351. dispatch_async(self.loggerQueue, block);
  352. });
  353. }
  354. }
  355. - (NSTimeInterval)deleteInterval {
  356. // The design of this method is taken from the DDAbstractLogger implementation.
  357. // For extensive documentation please refer to the DDAbstractLogger implementation.
  358. // Note: The internal implementation MUST access the colorsEnabled variable directly,
  359. // This method is designed explicitly for external access.
  360. //
  361. // Using "self." syntax to go through this method will cause immediate deadlock.
  362. // This is the intended result. Fix it by accessing the ivar directly.
  363. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  364. DDAbstractLoggerAssertLockedPropertyAccess();
  365. __block NSTimeInterval result;
  366. dispatch_sync(DDLog.loggingQueue, ^{
  367. dispatch_sync(self.loggerQueue, ^{
  368. result = self->_deleteInterval;
  369. });
  370. });
  371. return result;
  372. }
  373. - (void)setDeleteInterval:(NSTimeInterval)interval {
  374. __auto_type block = ^{
  375. @autoreleasepool {
  376. // C99 recommended floating point comparison macro
  377. // Read: isLessThanOrGreaterThan(floatA, floatB)
  378. if (/* deleteInterval != interval */ islessgreater(self->_deleteInterval, interval)) {
  379. self->_deleteInterval = interval;
  380. // There are several cases we need to handle here.
  381. //
  382. // 1. If the deleteInterval was previously enabled and it just got disabled,
  383. // then we need to stop the deleteTimer. (And we might as well release it.)
  384. //
  385. // 2. If the deleteInterval was previously disabled and it just got enabled,
  386. // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.)
  387. //
  388. // 3. If the deleteInterval increased, then we need to reset the timer so that it fires at the later date.
  389. //
  390. // 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date.
  391. // (Plus we might need to do an immediate delete.)
  392. if (self->_deleteInterval > 0.0) {
  393. if (self->_deleteTimer == NULL) {
  394. // Handles #2
  395. //
  396. // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate,
  397. // if a delete is needed the timer will fire immediately.
  398. [self createAndStartDeleteTimer];
  399. } else {
  400. // Handles #3
  401. // Handles #4
  402. //
  403. // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate,
  404. // if a save is needed the timer will fire immediately.
  405. [self updateDeleteTimer];
  406. }
  407. } else if (self->_deleteTimer) {
  408. // Handles #1
  409. [self destroyDeleteTimer];
  410. }
  411. }
  412. }
  413. };
  414. // The design of the setter logic below is taken from the DDAbstractLogger implementation.
  415. // For documentation please refer to the DDAbstractLogger implementation.
  416. if ([self isOnInternalLoggerQueue]) {
  417. block();
  418. } else {
  419. DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
  420. dispatch_async(DDLog.loggingQueue, ^{
  421. dispatch_async(self.loggerQueue, block);
  422. });
  423. }
  424. }
  425. - (BOOL)deleteOnEverySave {
  426. // The design of this method is taken from the DDAbstractLogger implementation.
  427. // For extensive documentation please refer to the DDAbstractLogger implementation.
  428. // Note: The internal implementation MUST access the colorsEnabled variable directly,
  429. // This method is designed explicitly for external access.
  430. //
  431. // Using "self." syntax to go through this method will cause immediate deadlock.
  432. // This is the intended result. Fix it by accessing the ivar directly.
  433. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  434. DDAbstractLoggerAssertLockedPropertyAccess();
  435. __block BOOL result;
  436. dispatch_sync(DDLog.loggingQueue, ^{
  437. dispatch_sync(self.loggerQueue, ^{
  438. result = self->_deleteOnEverySave;
  439. });
  440. });
  441. return result;
  442. }
  443. - (void)setDeleteOnEverySave:(BOOL)flag {
  444. __auto_type block = ^{
  445. self->_deleteOnEverySave = flag;
  446. };
  447. // The design of the setter logic below is taken from the DDAbstractLogger implementation.
  448. // For documentation please refer to the DDAbstractLogger implementation.
  449. if ([self isOnInternalLoggerQueue]) {
  450. block();
  451. } else {
  452. DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
  453. dispatch_async(DDLog.loggingQueue, ^{
  454. dispatch_async(self.loggerQueue, block);
  455. });
  456. }
  457. }
  458. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  459. #pragma mark Public API
  460. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  461. - (void)savePendingLogEntries {
  462. __auto_type block = ^{
  463. @autoreleasepool {
  464. [self performSaveAndSuspendSaveTimer];
  465. }
  466. };
  467. if ([self isOnInternalLoggerQueue]) {
  468. block();
  469. } else {
  470. dispatch_async(self.loggerQueue, block);
  471. }
  472. }
  473. - (void)deleteOldLogEntries {
  474. __auto_type block = ^{
  475. @autoreleasepool {
  476. [self performDelete];
  477. }
  478. };
  479. if ([self isOnInternalLoggerQueue]) {
  480. block();
  481. } else {
  482. dispatch_async(self.loggerQueue, block);
  483. }
  484. }
  485. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  486. #pragma mark DDLogger
  487. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  488. - (void)didAddLogger {
  489. // If you override me be sure to invoke [super didAddLogger];
  490. [self createSuspendedSaveTimer];
  491. [self createAndStartDeleteTimer];
  492. }
  493. - (void)willRemoveLogger {
  494. // If you override me be sure to invoke [super willRemoveLogger];
  495. [self performSaveAndSuspendSaveTimer];
  496. [self destroySaveTimer];
  497. [self destroyDeleteTimer];
  498. }
  499. - (void)logMessage:(DDLogMessage *)logMessage {
  500. if ([self db_log:logMessage]) {
  501. __auto_type firstUnsavedEntry = (++_unsavedCount == 1);
  502. if ((_unsavedCount >= _saveThreshold) && (_saveThreshold > 0)) {
  503. [self performSaveAndSuspendSaveTimer];
  504. } else if (firstUnsavedEntry) {
  505. _unsavedTime = dispatch_time(DISPATCH_TIME_NOW, 0);
  506. [self updateAndResumeSaveTimer];
  507. }
  508. }
  509. }
  510. - (void)flush {
  511. // This method is invoked by DDLog's flushLog method.
  512. //
  513. // It is called automatically when the application quits,
  514. // or if the developer invokes DDLog's flushLog method prior to crashing or something.
  515. [self performSaveAndSuspendSaveTimer];
  516. }
  517. @end