Skip to content

Commit fc66f7a

Browse files
committed
Adding hooks for DDLogFormatter's to help with thread-safety issues surrounding log formatters (e.g. thread-unsafe classes such as NSDateFormatter)
1 parent 49a2003 commit fc66f7a

File tree

6 files changed

+124
-38
lines changed

6 files changed

+124
-38
lines changed

Lumberjack/DDLog.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,9 +427,22 @@ NSString *DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy);
427427
* The formatter may also optionally filter the log message by returning nil,
428428
* in which case the logger will not log the message.
429429
**/
430-
431430
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage;
432431

432+
@optional
433+
434+
/**
435+
* A single formatter instance can be added to multiple loggers.
436+
* These methods provides hooks to notify the formatter of when it's added/removed.
437+
*
438+
* This is primarily for thread-safety.
439+
* If a formatter is explicitly not thread-safe, it may wish to throw an exception if added to multiple loggers.
440+
* Or if a formatter has potentially thread-unsafe code (e.g. NSDateFormatter),
441+
* it could possibly use these hooks to switch to thread-safe versions of the code.
442+
**/
443+
- (void)didAddToLogger:(id <DDLogger>)logger;
444+
- (void)willRemoveFromLogger:(id <DDLogger>)logger;
445+
433446
@end
434447

435448
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Lumberjack/DDLog.m

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,11 +1022,21 @@ - (void)setLogFormatter:(id <DDLogFormatter>)logFormatter
10221022
{
10231023
// The design of this method is documented extensively in the logFormatter message (above in code).
10241024

1025-
dispatch_block_t block = ^{
1026-
if (formatter != logFormatter) {
1025+
dispatch_block_t block = ^{ @autoreleasepool {
1026+
1027+
if (formatter != logFormatter)
1028+
{
1029+
if ([formatter respondsToSelector:@selector(willRemoveFromLogger:)]) {
1030+
[formatter willRemoveFromLogger:self];
1031+
}
1032+
10271033
formatter = logFormatter;
1034+
1035+
if ([formatter respondsToSelector:@selector(didAddToLogger:)]) {
1036+
[formatter didAddToLogger:self];
1037+
}
10281038
}
1029-
};
1039+
}};
10301040

10311041
dispatch_queue_t currentQueue = dispatch_get_current_queue();
10321042
if (currentQueue == loggerQueue)

Lumberjack/Extensions/DispatchQueueLogFormatter.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,13 @@
5353
{
5454
@protected
5555

56-
NSDateFormatter *dateFormatter;
56+
NSString *dateFormatString;
5757

5858
@private
5959

60+
int32_t atomicLoggerCount;
61+
NSDateFormatter *threadUnsafeDateFormatter; // Use [self stringFromDate]
62+
6063
OSSpinLock lock;
6164

6265
NSUInteger _minQueueLength; // _prefix == Only access via atomic property

Lumberjack/Extensions/DispatchQueueLogFormatter.m

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#import "DispatchQueueLogFormatter.h"
2+
#import <libkern/OSAtomic.h>
23

34
/**
45
* Welcome to Cocoa Lumberjack!
@@ -21,9 +22,10 @@ - (id)init
2122
{
2223
if ((self = [super init]))
2324
{
24-
dateFormatter = [[NSDateFormatter alloc] init];
25-
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
26-
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss:SSS"];
25+
dateFormatString = @"yyyy-MM-dd HH:mm:ss:SSS";
26+
27+
atomicLoggerCount = 0;
28+
threadUnsafeDateFormatter = nil;
2729

2830
_minQueueLength = 0;
2931
_maxQueueLength = 0;
@@ -73,6 +75,46 @@ - (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)lo
7375
#pragma mark DDLogFormatter
7476
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
7577

78+
- (NSString *)stringFromDate:(NSDate *)date
79+
{
80+
int32_t loggerCount = OSAtomicAdd32(0, &atomicLoggerCount);
81+
82+
if (loggerCount <= 1)
83+
{
84+
// Single-threaded mode.
85+
86+
if (threadUnsafeDateFormatter == nil)
87+
{
88+
threadUnsafeDateFormatter = [[NSDateFormatter alloc] init];
89+
[threadUnsafeDateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
90+
[threadUnsafeDateFormatter setDateFormat:dateFormatString];
91+
}
92+
93+
return [threadUnsafeDateFormatter stringFromDate:date];
94+
}
95+
else
96+
{
97+
// Multi-threaded mode.
98+
// NSDateFormatter is NOT thread-safe.
99+
100+
NSString *key = @"DispatchQueueLogFormatter_NSDateFormatter";
101+
102+
NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
103+
NSDateFormatter *dateFormatter = [threadDictionary objectForKey:key];
104+
105+
if (dateFormatter == nil)
106+
{
107+
dateFormatter = [[NSDateFormatter alloc] init];
108+
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
109+
[dateFormatter setDateFormat:dateFormatString];
110+
111+
[threadDictionary setObject:dateFormatter forKey:key];
112+
}
113+
114+
return [dateFormatter stringFromDate:date];
115+
}
116+
}
117+
76118
- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage
77119
{
78120
// As per the DDLogFormatter contract, this method is always invoked on the same thread/dispatch_queue
@@ -180,10 +222,20 @@ - (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage
180222

181223
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage
182224
{
183-
NSString *timestamp = [dateFormatter stringFromDate:(logMessage->timestamp)];
225+
NSString *timestamp = [self stringFromDate:(logMessage->timestamp)];
184226
NSString *queueThreadLabel = [self queueThreadLabelForLogMessage:logMessage];
185227

186228
return [NSString stringWithFormat:@"%@ [%@] %@", timestamp, queueThreadLabel, logMessage->logMsg];
187229
}
188230

231+
- (void)didAddToLogger:(id <DDLogger>)logger
232+
{
233+
OSAtomicIncrement32(&atomicLoggerCount);
234+
}
235+
236+
- (void)willRemoveFromLogger:(id <DDLogger>)logger
237+
{
238+
OSAtomicDecrement32(&atomicLoggerCount);
239+
}
240+
189241
@end

Xcode/ContextFilter/ContextFilter.xcodeproj/project.pbxproj

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
DC2E3340129AD073009F096E /* DDTTYLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2E333C129AD073009F096E /* DDTTYLogger.m */; };
1919
DC2E3347129ADC68009F096E /* MyContextFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2E3346129ADC68009F096E /* MyContextFilter.m */; };
2020
DC2E334A129ADCE0009F096E /* ThirdPartyFramework.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2E3349129ADCE0009F096E /* ThirdPartyFramework.m */; };
21+
DCF89E9515A3604500112881 /* DDAbstractDatabaseLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF89E9415A3604500112881 /* DDAbstractDatabaseLogger.m */; };
22+
DCF89E9D15A3605200112881 /* ContextFilterLogFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF89E9915A3605200112881 /* ContextFilterLogFormatter.m */; };
23+
DCF89E9E15A3605200112881 /* DispatchQueueLogFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = DCF89E9B15A3605200112881 /* DispatchQueueLogFormatter.m */; };
24+
DCF89E9F15A3605200112881 /* README.txt in Resources */ = {isa = PBXBuildFile; fileRef = DCF89E9C15A3605200112881 /* README.txt */; };
2125
/* End PBXBuildFile section */
2226

2327
/* Begin PBXFileReference section */
@@ -45,6 +49,13 @@
4549
DC2E3346129ADC68009F096E /* MyContextFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MyContextFilter.m; sourceTree = "<group>"; };
4650
DC2E3348129ADCE0009F096E /* ThirdPartyFramework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThirdPartyFramework.h; sourceTree = "<group>"; };
4751
DC2E3349129ADCE0009F096E /* ThirdPartyFramework.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThirdPartyFramework.m; sourceTree = "<group>"; };
52+
DCF89E9315A3604500112881 /* DDAbstractDatabaseLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DDAbstractDatabaseLogger.h; path = ../../Lumberjack/DDAbstractDatabaseLogger.h; sourceTree = "<group>"; };
53+
DCF89E9415A3604500112881 /* DDAbstractDatabaseLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DDAbstractDatabaseLogger.m; path = ../../Lumberjack/DDAbstractDatabaseLogger.m; sourceTree = "<group>"; };
54+
DCF89E9815A3605200112881 /* ContextFilterLogFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContextFilterLogFormatter.h; sourceTree = "<group>"; };
55+
DCF89E9915A3605200112881 /* ContextFilterLogFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContextFilterLogFormatter.m; sourceTree = "<group>"; };
56+
DCF89E9A15A3605200112881 /* DispatchQueueLogFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DispatchQueueLogFormatter.h; sourceTree = "<group>"; };
57+
DCF89E9B15A3605200112881 /* DispatchQueueLogFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DispatchQueueLogFormatter.m; sourceTree = "<group>"; };
58+
DCF89E9C15A3605200112881 /* README.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.txt; sourceTree = "<group>"; };
4859
/* End PBXFileReference section */
4960

5061
/* Begin PBXFrameworksBuildPhase section */
@@ -150,10 +161,26 @@
150161
DC2E3336129AD073009F096E /* DDASLLogger.m */,
151162
DC2E3337129AD073009F096E /* DDFileLogger.h */,
152163
DC2E3338129AD073009F096E /* DDFileLogger.m */,
164+
DCF89E9315A3604500112881 /* DDAbstractDatabaseLogger.h */,
165+
DCF89E9415A3604500112881 /* DDAbstractDatabaseLogger.m */,
166+
DCF89E9715A3605200112881 /* Extensions */,
153167
);
154168
name = Logging;
155169
sourceTree = "<group>";
156170
};
171+
DCF89E9715A3605200112881 /* Extensions */ = {
172+
isa = PBXGroup;
173+
children = (
174+
DCF89E9815A3605200112881 /* ContextFilterLogFormatter.h */,
175+
DCF89E9915A3605200112881 /* ContextFilterLogFormatter.m */,
176+
DCF89E9A15A3605200112881 /* DispatchQueueLogFormatter.h */,
177+
DCF89E9B15A3605200112881 /* DispatchQueueLogFormatter.m */,
178+
DCF89E9C15A3605200112881 /* README.txt */,
179+
);
180+
name = Extensions;
181+
path = ../../Lumberjack/Extensions;
182+
sourceTree = "<group>";
183+
};
157184
/* End PBXGroup section */
158185

159186
/* Begin PBXNativeTarget section */
@@ -209,6 +236,7 @@
209236
files = (
210237
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */,
211238
1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */,
239+
DCF89E9F15A3605200112881 /* README.txt in Resources */,
212240
);
213241
runOnlyForDeploymentPostprocessing = 0;
214242
};
@@ -227,6 +255,9 @@
227255
DC2E3340129AD073009F096E /* DDTTYLogger.m in Sources */,
228256
DC2E3347129ADC68009F096E /* MyContextFilter.m in Sources */,
229257
DC2E334A129ADCE0009F096E /* ThirdPartyFramework.m in Sources */,
258+
DCF89E9515A3604500112881 /* DDAbstractDatabaseLogger.m in Sources */,
259+
DCF89E9D15A3605200112881 /* ContextFilterLogFormatter.m in Sources */,
260+
DCF89E9E15A3605200112881 /* DispatchQueueLogFormatter.m in Sources */,
230261
);
231262
runOnlyForDeploymentPostprocessing = 0;
232263
};

Xcode/DispatchQueueLogger/DispatchQueueLogger/AppDelegate.m

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,24 @@ @implementation AppDelegate
1818

1919
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
2020
{
21-
if (YES)
21+
if (NO)
2222
{
23-
// Log statements *AFTER* DispatchQueueLogFormatter
23+
// See what log statements look like *BEFORE* using DispatchQueueLogFormatter :(
24+
}
25+
else
26+
{
27+
// See what log statements look like *AFTER* using DispatchQueueLogFormatter :)
2428

2529
DispatchQueueLogFormatter *formatter = [[DispatchQueueLogFormatter alloc] init];
2630
formatter.minQueueLength = 4;
2731
formatter.maxQueueLength = 0;
2832

29-
[formatter setReplacementString:@"main" forQueueLabel:@"com.apple.main-thread"];
30-
[formatter setReplacementString:@"global-background" forQueueLabel:@"com.apple.root.background-priority"];
31-
[formatter setReplacementString:@"global-low" forQueueLabel:@"com.apple.root.low-priority"];
32-
[formatter setReplacementString:@"global-default" forQueueLabel:@"com.apple.root.default-priority"];
33-
[formatter setReplacementString:@"global-high" forQueueLabel:@"com.apple.root.high-priority"];
34-
3533
[formatter setReplacementString:@"downloading" forQueueLabel:@"downloadingQueue"];
3634
[formatter setReplacementString:@"parsing" forQueueLabel:@"parsingQueue"];
3735
[formatter setReplacementString:@"processing" forQueueLabel:@"processingQueue"];
3836

3937
[[DDTTYLogger sharedInstance] setLogFormatter:formatter];
4038
}
41-
else
42-
{
43-
// Log statements *BEFORE* DispatchQueueLogFormatter
44-
}
4539

4640
[DDLog addLogger:[DDTTYLogger sharedInstance]];
4741

@@ -70,23 +64,6 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
7064
dispatch_async(processingQueue, blockC);
7165
}
7266

73-
dispatch_queue_t bgq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
74-
dispatch_queue_t lgq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
75-
dispatch_queue_t dgq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
76-
dispatch_queue_t hgq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
77-
78-
dispatch_block_t blockD = ^{
79-
DDLogVerbose(@"Some log statement");
80-
};
81-
82-
for (i = 0; i < count; i++)
83-
{
84-
dispatch_async(bgq, blockD);
85-
dispatch_async(lgq, blockD);
86-
dispatch_async(dgq, blockD);
87-
dispatch_async(hgq, blockD);
88-
}
89-
9067
[NSThread detachNewThreadSelector:@selector(backgroundThread:) toTarget:self withObject:nil];
9168
}
9269

0 commit comments

Comments
 (0)