TwitterLoggingServiceTests/TLSLoggingTests.m (929 lines of code) (raw):
//
// TLSLoggingTests.m
// TLSLoggingTests
//
// Created on 12/11/13.
// Copyright (c) 2016 Twitter, Inc.
//
#include <pthread.h>
#import <TwitterLoggingService/TwitterLoggingService.h>
#import <XCTest/XCTest.h>
static void LogStream(id<TLSOutputStream> stream, TLSLogLevel level, NSString *channel, NSString *file, NSString *function, unsigned int line, NSString *format, ...);
static NSTimeInterval NSTimeIntervalFromTLSFileOutputStreamTimestamp(NSString *timestamp);
static id GenerateLoggingArgument(void);
static void TurnConsoleChannelOn(NSString *channel, BOOL on);
static BOOL IsConsoleChannelOn(NSString *channel);
@interface TestLogger : NSObject <TLSOutputStream>
@property (nonatomic) TLSLogLevelMask permittedLoggingLevels;
@property (nonatomic) BOOL shouldFilterChannelsThatAreOff;
@property (nonatomic, readonly) NSUInteger loggedMessages;
- (void)setChannel:(NSString *)channel on:(BOOL)on;
@end
@interface TestStdErrLogger : TLSStdErrOutputStream
@end
@interface TestNSLogLogger : TLSNSLogOutputStream
@end
@interface TestFileLogger : TLSFileOutputStream
@end
@interface TLSLoggingTests : XCTestCase
@end
@interface TestRollingLogger : NSObject <TLSOutputStream>
@property (nonatomic) TLSLogLevelMask permittedLoggingLevels;
@property (nonatomic) BOOL shouldFilterChannelsThatAreOff;
@property (nonatomic, readonly) NSUInteger loggedMessages;
- (void)setChannel:(NSString *)channel on:(BOOL)on;
@end
@interface TestRollingFileLogger : TLSRollingFileOutputStream
@end
@interface TLSRollingFileTests : XCTestCase
@end
typedef void(^TestLoggingBlock)(NSString *channel);
typedef void(^TestStreamBlock)(id<TLSOutputStream> stream, NSString *channel);
static TLSLoggingService *sLoggingService;
static TLSStdErrOutputStream *sStdErrOut;
static TLSNSLogOutputStream *sNSLogOut;
static TLSFileOutputStream *sFileOut;
static TLSRollingFileOutputStream *sRollingFileOut;
static TestLoggingBlock sTestLoggingBlock;
static TestStreamBlock sTestStreamBlock;
static NSMutableSet *sOnConsoleChannels;
static NSMutableDictionary *sRuntimes;
#define TEST_MX_COUNT (3 * TEST_COUNT)
#define TEST_COUNT (250)
#define TEST_START NSDate *__start__ = [NSDate date];
#define TEST_STOP sRuntimes[NSStringFromSelector(_cmd)] = @([[NSDate date] timeIntervalSinceDate:__start__]);
#define TEST_UPDATE_CHANNEL(channel, on) do { [sLoggingService dispatchAsynchronousTransaction:^{ TurnConsoleChannelOn(channel, on); }]; [sLoggingService updateOutputStream:sStdErrOut]; [sLoggingService updateOutputStream:sNSLogOut]; } while (0)
#define TEST_CHANNEL_ON(channel) TEST_UPDATE_CHANNEL(channel, YES)
#define TEST_CHANNEL_OFF(channel) TEST_UPDATE_CHANNEL(channel, NO)
#define TEST_FILE_NAME @"TLSFileOutputStreamTest.log"
#define TEST_FLUSH_TRANSACTIONS() [sLoggingService dispatchSynchronousTransaction:^{}]
@implementation TLSLoggingTests
+ (void)setUp
{
[super setUp];
sLoggingService = [TLSLoggingService sharedInstance];
sStdErrOut = [[TestStdErrLogger alloc] init];
sNSLogOut = [[TestNSLogLogger alloc] init];
NSString *defaultLogFileDirectoryPath = [TLSFileOutputStream defaultLogFileDirectoryPath];
[[NSFileManager defaultManager] removeItemAtPath:defaultLogFileDirectoryPath error:NULL];
sFileOut = [[TestFileLogger alloc] initWithLogFileName:TEST_FILE_NAME error:NULL];
sRollingFileOut = [[TestRollingFileLogger alloc] initWithLogFileDirectoryPath:defaultLogFileDirectoryPath logFilePrefix:TLSRollingFileOutputStreamDefaultLogFilePrefix maxLogFiles:5 maxBytesPerLogFile:(1024 * 64)];
sTestLoggingBlock = ^(NSString *theChannel) {
TLSLogDebug(theChannel, @"%d %@ %f %@ %@", 1, @2, 3.0f, GenerateLoggingArgument(), nil);
TLSLogInformation(theChannel, @"%d %@ %f %@ %@", 1, @2, 3.0f, GenerateLoggingArgument(), nil);
TLSLogWarning(theChannel, @"%d %@ %f %@ %@", 1, @2, 3.0f, GenerateLoggingArgument(), nil);
TLSLogError(theChannel, @"%d %@ %f %@ %@", 1, @2, 3.0f, GenerateLoggingArgument(), nil);
};
sTestStreamBlock = ^(id<TLSOutputStream> stream, NSString *channel) {
LogStream(stream, TLSLogLevelDebug, channel, @(__FILE__), @(__PRETTY_FUNCTION__), __LINE__, @"%d %@ %f %@ %@", 1, @2, 3.0f, GenerateLoggingArgument(), nil);
LogStream(stream, TLSLogLevelInformation, channel, @(__FILE__), @(__PRETTY_FUNCTION__), __LINE__, @"%d %@ %f %@ %@", 1, @2, 3.0f, GenerateLoggingArgument(), nil);
LogStream(stream, TLSLogLevelWarning, channel, @(__FILE__), @(__PRETTY_FUNCTION__), __LINE__, @"%d %@ %f %@ %@", 1, @2, 3.0f, GenerateLoggingArgument(), nil);
LogStream(stream, TLSLogLevelError, channel, @(__FILE__), @(__PRETTY_FUNCTION__), __LINE__, @"%d %@ %f %@ %@", 1, @2, 3.0f, GenerateLoggingArgument(), nil);
};
sRuntimes = [[NSMutableDictionary alloc] init];
sOnConsoleChannels = [[NSMutableSet alloc] init];
}
- (void)testLoggingStdErr1
{
TEST_START
[sLoggingService addOutputStream:sStdErrOut];
NSString *channel = [TLSLogChannelDefault stringByAppendingString:@"-StdErr"];
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_CHANNEL_ON(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_CHANNEL_OFF(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_STOP
}
- (void)testLoggingStdErr2
{
NSString *channel = [TLSLogChannelDefault stringByAppendingString:@"-StdErr"];
[sLoggingService removeOutputStream:sStdErrOut];
TEST_CHANNEL_ON(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_CHANNEL_OFF(channel);
[sLoggingService flush];
}
- (void)testLoggingStdErr3
{
TEST_START
NSString *channel = [TLSLogChannelDefault stringByAppendingString:@"-StdErrMx"];
TEST_CHANNEL_ON(channel);
[sLoggingService addOutputStream:sStdErrOut];
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t q = dispatch_queue_create("mxQ", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < TEST_MX_COUNT; i++) {
dispatch_group_async(group, q, ^() {
sTestLoggingBlock(channel);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
TEST_CHANNEL_OFF(channel);
TEST_STOP
[sLoggingService flush];
[sLoggingService removeOutputStream:sStdErrOut];
}
- (void)testLoggingNSLog1
{
TEST_START
[sLoggingService addOutputStream:sNSLogOut];
NSString *channel = [TLSLogChannelDefault stringByAppendingString:@"-NSLog"];
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_CHANNEL_ON(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_CHANNEL_OFF(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_STOP
}
- (void)testLoggingNSLog2
{
NSString *channel = [TLSLogChannelDefault stringByAppendingString:@"-NSLog"];
[sLoggingService removeOutputStream:sNSLogOut];
TEST_CHANNEL_ON(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
};
TEST_CHANNEL_OFF(channel);
[sLoggingService flush];
}
- (void)testLoggingNSLog3
{
TEST_START
NSString *channel = [TLSLogChannelDefault stringByAppendingString:@"-NSLogMx"];
TEST_CHANNEL_ON(channel);
[sLoggingService addOutputStream:sNSLogOut];
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t q = dispatch_queue_create("mxQ", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < TEST_MX_COUNT; i++) {
dispatch_group_async(group, q, ^() {
sTestLoggingBlock(channel);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
TEST_CHANNEL_OFF(channel);
TEST_STOP
[sLoggingService flush];
[sLoggingService removeOutputStream:sNSLogOut];
}
- (void)testStdErr1
{
TEST_START
(void)TLSLogChannelDefault;
NSString *channel = @"StdErr";
for (int i = 0; i < TEST_COUNT; i++) {
sTestStreamBlock(sStdErrOut, channel);
}
TEST_CHANNEL_ON(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestStreamBlock(sStdErrOut, channel);
}
TEST_CHANNEL_OFF(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestStreamBlock(sStdErrOut, channel);
}
TEST_STOP
[sStdErrOut tls_flush];
}
- (void)testStdErr2
{
TEST_START
NSString *channel = @"StdErrMx";
TEST_CHANNEL_ON(channel);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t q = dispatch_queue_create("mxQ", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < TEST_MX_COUNT; i++) {
dispatch_group_async(group, q, ^() {
sTestStreamBlock(sStdErrOut, channel);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
TEST_CHANNEL_OFF(channel);
TEST_STOP
}
- (void)testNSLog1
{
TEST_START
(void)TLSLogChannelDefault;
NSString *channel = @"NSLog";
for (int i = 0; i < TEST_COUNT; i++) {
sTestStreamBlock(sNSLogOut, channel);
}
TEST_CHANNEL_ON(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestStreamBlock(sNSLogOut, channel);
}
TEST_CHANNEL_OFF(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestStreamBlock(sNSLogOut, channel);
}
TEST_STOP
}
- (void)testNSLog2
{
TEST_START
NSString *channel = @"NSLogMx";
TEST_CHANNEL_ON(channel);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t q = dispatch_queue_create("mxQ", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < TEST_MX_COUNT; i++) {
dispatch_group_async(group, q, ^() {
sTestStreamBlock(sNSLogOut, channel);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
TEST_CHANNEL_OFF(channel);
TEST_STOP
}
- (void)testLoggingFile1
{
TEST_START
[sLoggingService addOutputStream:sFileOut];
NSString *channel = [TLSLogChannelDefault stringByAppendingString:@"-File"];
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_CHANNEL_ON(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_CHANNEL_OFF(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_STOP
}
- (void)testLoggingFile2
{
NSString *channel = [TLSLogChannelDefault stringByAppendingString:@"-File"];
[sLoggingService removeOutputStream:sFileOut];
TEST_CHANNEL_ON(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_CHANNEL_OFF(channel);
[sLoggingService flush];
}
- (void)testLoggingFile3
{
TEST_START
NSString *channel = [TLSLogChannelDefault stringByAppendingString:@"-FileMx"];
TEST_CHANNEL_ON(channel);
[sLoggingService addOutputStream:sFileOut];
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t q = dispatch_queue_create("mxQ", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < TEST_MX_COUNT; i++) {
dispatch_group_async(group, q, ^() {
sTestLoggingBlock(channel);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
TEST_CHANNEL_OFF(channel);
TEST_STOP
[sLoggingService flush];
[sLoggingService removeOutputStream:sFileOut];
}
- (void)testLoggingFile4
{
// Test Logging File Creation
#if TARGET_OS_SIMULATOR
NSString *restrictedDir = @"/bin";
#else
NSString *restrictedDir = @"/";
#endif
NSError *error = nil;
TLSFileOutputStream *stream = [[TLSFileOutputStream alloc] initWithLogFileDirectoryPath:restrictedDir logFileName:@"restricted.log" error:&error];
XCTAssertNil(stream, @"'%@' directory should always result in an empty stream representing the failure to create a %@ stream! Your machine might have write permissions on '%@', which it shouldn't.", restrictedDir, NSStringFromClass([stream class]), restrictedDir);
XCTAssertNotNil(error, @"'%@' directory should always result in an error describing the failure to create a %@ stream! Your machine might have write permissions on '%@', which it shouldn't.", restrictedDir, NSStringFromClass([stream class]), restrictedDir);
error = nil;
stream = [[TLSFileOutputStream alloc] initWithLogFileName:TEST_FILE_NAME error:&error];
XCTAssertNil(error, @"TLSFileOutputStream should have succeeded");
XCTAssertNotEqual(NULL, stream.logFile, @"TLSLoggingFileOuptutStream.logFile should not be NULL");
XCTAssert([[[TLSFileOutputStream defaultLogFileDirectoryPath] stringByAppendingPathComponent:TEST_FILE_NAME] isEqualToString:stream.logFilePath], @"TLSFileOutputStream.tls_loggedDataEncoding should have been NSUTF8StringEncoding");
XCTAssertEqual(NSUTF8StringEncoding, stream.tls_loggedDataEncoding, @"TLSFileOutputStream.tls_loggedDataEncoding should have been NSUTF8StringEncoding");
NSString *data = @"TLSFileOutputStream data";
[stream outputLogData:[data dataUsingEncoding:stream.tls_loggedDataEncoding]];
XCTAssertEqual(data.length+1, stream.bytesWritten, @"TLSFileOutputStream bytes written should equal %tu", data.length);
}
- (void)testLoggingFile5
{
NSArray *levels = @[ TLSLogLevelToString(TLSLogLevelError), TLSLogLevelToString(TLSLogLevelWarning), TLSLogLevelToString(TLSLogLevelInformation), TLSLogLevelToString(TLSLogLevelDebug) ];
XCTAssertFalse([sFileOut conformsToProtocol:@protocol(TLSDataRetrieval)], @"baseline FileOutputStream should not conform to TLSDataRetrieval");
// Validate on disk logs
@autoreleasepool {
NSString *string = [[NSString alloc] initWithContentsOfFile:sFileOut.logFilePath encoding:sFileOut.tls_loggedDataEncoding error:NULL];
XCTAssertNotNil(string, @"reading log file must result in non-nil string");
NSArray *lines = [string componentsSeparatedByString:@"\n"];
XCTAssertTrue(lines.count > 0, @"each log file must have at least 1 line");
for (NSString *line in lines) {
NSMutableArray *tokens = [[line componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"[]"]] mutableCopy];
[tokens removeObjectsAtIndexes:[tokens indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [(NSString *)obj length] == 0;
}]];
if (tokens.count >= 5) {
NSTimeInterval ti = NSTimeIntervalFromTLSFileOutputStreamTimestamp(tokens[0]);
if (ti > 0.0f) {
XCTAssertTrue([levels containsObject:tokens[3]], @"Log level must be Error, Warning, Information or Debug");
XCTAssertTrue([tokens[2] hasPrefix:TLSLogChannelDefault], @"Logging channel is wrong");
}
}
}
}
}
- (void)testLoggingFile6
{
NSFileManager *fm = [NSFileManager defaultManager];
NSString *logFileDirectoryPath = [TLSFileOutputStream defaultLogFileDirectoryPath];
NSArray *logFiles = [fm contentsOfDirectoryAtPath:logFileDirectoryPath error:NULL];
NSString *logFileOfInterest = logFiles[[logFiles indexOfObject:TEST_FILE_NAME]];
unsigned long long bytes = [fm attributesOfItemAtPath:[logFileDirectoryPath stringByAppendingPathComponent:logFileOfInterest] error:NULL].fileSize;
unsigned long long expectedBytes = [NSString stringWithFormat:@"%@ data\n", NSStringFromClass([TLSFileOutputStream class])].length;
XCTAssertEqual(bytes, expectedBytes, @"file size doesn't match expectations");
}
- (void)testLoggingFileNSLogCombo
{
TEST_START
[sLoggingService addOutputStream:sNSLogOut];
[sLoggingService addOutputStream:sFileOut];
NSString *channel = [TLSLogChannelDefault stringByAppendingString:@"-ComboMx"];
TEST_CHANNEL_ON(channel);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t q = dispatch_queue_create("mxQ", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < TEST_MX_COUNT; i++) {
dispatch_group_async(group, q, ^() {
sTestLoggingBlock(channel);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
TEST_CHANNEL_OFF(channel);
TEST_STOP
[sLoggingService flush];
for (id<TLSOutputStream> stream in sLoggingService.outputStreams) {
[sLoggingService removeOutputStream:stream];
}
}
- (void)testLoggingRollingFile1
{
TEST_START
[sLoggingService addOutputStream:sRollingFileOut];
NSString *channel = [TLSLogChannelDefault stringByAppendingString:@"-Roll"];
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_CHANNEL_ON(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_CHANNEL_OFF(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_STOP
}
- (void)testLoggingRollingFile2
{
NSString *channel = [TLSLogChannelDefault stringByAppendingString:@"-Roll"];
[sLoggingService removeOutputStream:sRollingFileOut];
TEST_CHANNEL_ON(channel);
for (int i = 0; i < TEST_COUNT; i++) {
sTestLoggingBlock(channel);
}
TEST_CHANNEL_OFF(channel);
[sLoggingService flush];
}
- (void)testLoggingRollingFile3
{
TEST_START
NSString *channel = [TLSLogChannelDefault stringByAppendingString:@"-RollMx"];
TEST_CHANNEL_ON(channel);
[sLoggingService addOutputStream:sRollingFileOut];
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t q = dispatch_queue_create("mxQ", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < TEST_MX_COUNT; i++) {
dispatch_group_async(group, q, ^() {
sTestLoggingBlock(channel);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
TEST_CHANNEL_OFF(channel);
TEST_STOP
[sLoggingService flush];
}
- (void)testLoggingRollingFile4
{
// Test Logging File Creation
#if TARGET_OS_SIMULATOR
NSString *restrictedDir = @"/bin";
#else
NSString *restrictedDir = @"/";
#endif
NSError *error = nil;
TLSRollingFileOutputStream *stream = [[TLSRollingFileOutputStream alloc] initWithLogFileDirectoryPath:restrictedDir logFilePrefix:@"log." maxLogFiles:5 maxBytesPerLogFile:1024 error:&error];
XCTAssertNil(stream, @"'%@' directory should always result in a failure to create a %@ stream! Your machine might have write permissions on '%@', which it shouldn't.", restrictedDir, NSStringFromClass([stream class]), restrictedDir);
XCTAssertNotNil(error, @"'%@' directory should always result in a failure to create a %@ stream! Your machine might have write permissions on '%@', which it shouldn't.", restrictedDir, NSStringFromClass([stream class]), restrictedDir);
error = nil;
NSString *path = [[TLSRollingFileOutputStream defaultLogFileDirectoryPath] stringByAppendingPathComponent:@"TLSLogging"];
NSString *prefix = @"twp.";
NSUInteger maxBytesPerLogFile = 256 * 1024;
NSUInteger maxLogFiles = 10;
stream = [[TLSRollingFileOutputStream alloc] initWithLogFileDirectoryPath:path logFilePrefix:prefix maxLogFiles:maxLogFiles maxBytesPerLogFile:maxBytesPerLogFile error:&error];
XCTAssertNil(error, @"TLSRollingFileOutputStream should have succeeded");
XCTAssertEqual(maxBytesPerLogFile, stream.maxBytesPerLogFile, @"maxBytesPerLogFile differs!");
XCTAssertEqual(maxLogFiles, stream.maxLogFiles, @"maxLogFiles differs!");
XCTAssertEqualObjects(path, stream.logFileDirectoryPath, @"logFileDirectoryPath differs!");
XCTAssertEqualObjects(prefix, stream.logFilePrefix, @"logFilePrefix differs!");
NSUInteger maxBytesPerLogFileModified = (NSUInteger)(1024ULL * 1024ULL * 1024ULL); // capped value (1GB)
maxBytesPerLogFile = (NSUInteger)((unsigned long long)maxBytesPerLogFileModified * 2ULL);
maxLogFiles = 100;
NSUInteger maxLogFilesModified = (NSUInteger)((4ULL * 1024ULL * 1024ULL * 1024ULL) / (unsigned long long)maxBytesPerLogFileModified); // capped value
stream = [[TLSRollingFileOutputStream alloc] initWithLogFileDirectoryPath:path logFilePrefix:prefix maxLogFiles:maxLogFiles maxBytesPerLogFile:maxBytesPerLogFile error:&error];
XCTAssertNil(error, @"TLSRollingFileOutputStream should have succeeded");
XCTAssertNotEqual(maxBytesPerLogFile, stream.maxBytesPerLogFile, @"maxBytesPerLogFile should have been capped!");
XCTAssertEqual(maxBytesPerLogFileModified, stream.maxBytesPerLogFile, @"maxBytesPerLogFile differs!");
XCTAssertNotEqual(maxLogFiles, stream.maxLogFiles, @"maxLogFiles should have been capped!");
XCTAssertEqual(maxLogFilesModified, stream.maxLogFiles, @"maxLogFiles differ!");
maxBytesPerLogFile = 0; // 256MB
maxBytesPerLogFileModified = 1024; // min value
maxLogFiles = 0;
maxLogFilesModified = 1; // min value
stream = [[TLSRollingFileOutputStream alloc] initWithLogFileDirectoryPath:path logFilePrefix:prefix maxLogFiles:maxLogFiles maxBytesPerLogFile:maxBytesPerLogFile error:&error];
XCTAssertNil(error, @"TLSRollingFileOutputStream should have succeeded");
XCTAssertNotEqual(maxBytesPerLogFile, stream.maxBytesPerLogFile, @"maxBytesPerLogFile should have been capped!");
XCTAssertEqual(maxBytesPerLogFileModified, stream.maxBytesPerLogFile, @"maxBytesPerLogFile differs!");
XCTAssertNotEqual(maxLogFiles, stream.maxLogFiles, @"maxLogFiles should have been capped!");
XCTAssertEqual(maxLogFilesModified, stream.maxLogFiles, @"maxLogFiles differ!");
}
- (void)testLoggingRollingFile5
{
NSArray *levels = @[ TLSLogLevelToString(TLSLogLevelError), TLSLogLevelToString(TLSLogLevelWarning), TLSLogLevelToString(TLSLogLevelInformation), TLSLogLevelToString(TLSLogLevelDebug) ];
@autoreleasepool {
// Validate received data
NSData *newlineData = [NSData dataWithBytes:"\n" length:1];
NSData *data = [sLoggingService retrieveLoggedDataFromOutputStream:sRollingFileOut maxBytes:0];
NSUInteger dataLength1 = data.length;
XCTAssertLessThanOrEqual(data.length, (sRollingFileOut.maxBytesPerLogFile * 2), @"Logs were not rolled over at the right time!");
NSRange r;
r.length = [data rangeOfData:newlineData options:NSDataSearchBackwards range:NSMakeRange(0, dataLength1 / 2)].location;
r.location = [data rangeOfData:newlineData options:NSDataSearchBackwards range:NSMakeRange(0, r.length - 2)].location + 1;
r.length -= r.location;
NSString *exampleNewer = [[NSString alloc] initWithData:[data subdataWithRange:r] encoding:sRollingFileOut.tls_loggedDataEncoding];
data = [sLoggingService retrieveLoggedDataFromOutputStream:sRollingFileOut maxBytes:sRollingFileOut.maxBytesPerLogFile + (2 * dataLength1)];
NSUInteger dataLength2 = data.length;
XCTAssertLessThanOrEqual(data.length, (sRollingFileOut.maxBytesPerLogFile * 3), @"Logs were not rolled over at the right time!");
r.location = 0;
r.length = dataLength2 - dataLength1;
data = [data subdataWithRange:r];
XCTAssertEqual(data.length, dataLength2 - dataLength1, @"Didn't create subset of data correctly");
XCTAssertLessThanOrEqual(data.length, (sRollingFileOut.maxBytesPerLogFile * 2), @"Logs were not rolled over at the right time!");
r.length = [data rangeOfData:newlineData options:NSDataSearchBackwards range:NSMakeRange(0, dataLength1 / 2)].location;
r.location = [data rangeOfData:newlineData options:NSDataSearchBackwards range:NSMakeRange(0, r.length - 2)].location + 1;
r.length -= r.location;
NSString *exampleOlder = [[NSString alloc] initWithData:[data subdataWithRange:r] encoding:sRollingFileOut.tls_loggedDataEncoding];
XCTAssertNotEqualObjects(exampleNewer, exampleOlder, @"Log lines should differ between files");
NSMutableArray *tokensOlder = [[exampleOlder componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"[]"]] mutableCopy];
NSMutableArray *tokensNewer = [[exampleNewer componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"[]"]] mutableCopy];
[tokensOlder removeObjectsAtIndexes:[tokensOlder indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [(NSString *)obj length] == 0;
}]];
[tokensNewer removeObjectsAtIndexes:[tokensNewer indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [(NSString *)obj length] == 0;
}]];
XCTAssertNotEqualObjects(tokensOlder, tokensNewer, @"Log lines should differ between files");
XCTAssertGreaterThanOrEqual(tokensOlder.count, 5, @"Must have at minimum: timestamp, threadId, channel, level and message in log entries");
XCTAssertGreaterThanOrEqual(tokensNewer.count, 5, @"Must have at minimum: timestamp, threadId, channel, level and message in log entries");
XCTAssertTrue([levels containsObject:tokensNewer[3]], @"Log level must be Error, Warning, Information or Debug");
XCTAssertTrue([levels containsObject:tokensOlder[3]], @"Log level must be Error, Warning, Information or Debug");
XCTAssertTrue([tokensNewer[2] hasPrefix:TLSLogChannelDefault], @"Logging channel is wrong");
XCTAssertTrue([tokensOlder[2] hasPrefix:TLSLogChannelDefault], @"Logging channel is wrong");
NSTimeInterval tiOlder = NSTimeIntervalFromTLSFileOutputStreamTimestamp(tokensOlder[0]);
NSTimeInterval tiNewer = NSTimeIntervalFromTLSFileOutputStreamTimestamp(tokensNewer[0]);
XCTAssertGreaterThan(tiOlder, 0.0, @"Invalid timestamp");
XCTAssertGreaterThan(tiNewer, 0.0, @"Invalid timestamp");
if ([tokensNewer[1] isEqualToString:tokensOlder[1]]) {
XCTAssertGreaterThanOrEqual(tiNewer, tiOlder, @"Newer timestamp must be greater than older timestamp");
} else if (tiOlder > tiNewer) {
XCTAssertGreaterThanOrEqual(tiNewer, tiOlder + 0.1, @"Newer timestamp must be greater than older timestamp: %@ then %@", tokensOlder[0], tokensNewer[0]);
}
}
// Validate on disk logs
NSMutableArray *logs = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:sRollingFileOut.logFileDirectoryPath error:NULL] mutableCopy];
[logs removeObjectsAtIndexes:[logs indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return ![[(NSString *)obj lastPathComponent] hasPrefix:sRollingFileOut.logFilePrefix];
}]];
XCTAssertEqual(logs.count, sRollingFileOut.maxLogFiles, @"Logs must have rolled over and been pruned!");
[logs sortWithOptions:NSSortStable usingComparator:^NSComparisonResult(id obj1, id obj2) {
NSString *path1 = obj1;
NSString *path2 = obj2;
return [path1 compare:path2];
}];
unsigned long inversionCount = 0;
NSTimeInterval lastTimestamp = 0;
__strong NSString *lastLog = nil;
NSMutableDictionary<NSString *, NSMutableArray<NSNumber *> *> *perThreadTimestamps = [[NSMutableDictionary alloc] init];
for (__strong NSString *path in logs) {
@autoreleasepool {
path = [sRollingFileOut.logFileDirectoryPath stringByAppendingPathComponent:path];
NSString *string = [[NSString alloc] initWithContentsOfFile:path encoding:sRollingFileOut.tls_loggedDataEncoding error:NULL];
XCTAssertNotNil(string, @"reading log file must result in nil string");
NSArray *lines = [string componentsSeparatedByString:@"\n"];
string = nil;
XCTAssertGreaterThan(lines.count, 0, @"each log file must have at least 1 line");
for (NSString *line in lines) {
NSMutableArray *tokens = [[line componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"[]"]] mutableCopy];
[tokens removeObjectsAtIndexes:[tokens indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [(NSString *)obj length] == 0;
}]];
if (tokens.count >= 5) {
NSTimeInterval ti = NSTimeIntervalFromTLSFileOutputStreamTimestamp(tokens[0]);
NSString *thread = tokens[1];
if (ti > 0.0) {
XCTAssertTrue([levels containsObject:tokens[3]], @"Log level must be Error, Warning, Information or Debug");
XCTAssertTrue([tokens[2] hasPrefix:TLSLogChannelDefault], @"Logging channel is wrong");
if (ti < lastTimestamp) {
inversionCount++;
// NSLog(@"Timestamps out of order %lu times", inversionCount);
}
lastTimestamp = ti;
lastLog = line;
}
NSMutableArray<NSNumber *> *timestamps = perThreadTimestamps[thread];
if (!timestamps) {
timestamps = [[NSMutableArray alloc] init];
perThreadTimestamps[thread] = timestamps;
}
[timestamps addObject:@(ti)];
}
}
}
}
NSLog(@"Timestamps out of order %lu times", inversionCount);
// Even though timestamps could be out of order in the log, that could be due to multi-threading.
// Test to ensure that, per thread, the timestamps _ARE_ in order.
[perThreadTimestamps enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull thread, NSMutableArray<NSNumber *> * _Nonnull timestamps, BOOL * _Nonnull stop) {
NSTimeInterval previousTimestamp = 0;
for (NSNumber *timestampNum in timestamps) {
const NSTimeInterval ti = [timestampNum doubleValue];
XCTAssertGreaterThanOrEqual(ti, previousTimestamp, @"Incorrect timestamp ordering for thread `%@`!", thread);
previousTimestamp = ti;
}
}];
}
- (void)testLoggingRollingFile6
{
unsigned long long bytes = 0;
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *logFiles = [fm contentsOfDirectoryAtPath:sRollingFileOut.logFileDirectoryPath error:NULL];
XCTAssert(logFiles, @"directory containing log files should exist");
for (NSString *item in logFiles) {
if ([item hasPrefix:sRollingFileOut.logFilePrefix]) {
bytes += [fm attributesOfItemAtPath:[sRollingFileOut.logFileDirectoryPath stringByAppendingPathComponent:item] error:NULL].fileSize;
}
}
NSData *logs = [sLoggingService retrieveLoggedDataFromOutputStream:sRollingFileOut maxBytes:NSUIntegerMax];
XCTAssertTrue(bytes <= logs.length + 128 && bytes >= logs.length - 128, @"retrieved logged data doesn't match expectations");
logs = nil;
logs = [sLoggingService retrieveLoggedDataFromOutputStream:sRollingFileOut maxBytes:0];
XCTAssertTrue(logs.length > 0, @"the minimum bytes you can cap is the maximum bytes per files, not 0");
}
- (void)testLoggingRollingNSLogCombo
{
TEST_START
[sLoggingService addOutputStream:sNSLogOut];
[sLoggingService addOutputStream:sRollingFileOut];
NSString *channel = [TLSLogChannelDefault stringByAppendingString:@"-ComboMx"];
TEST_CHANNEL_ON(channel);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t q = dispatch_queue_create("mxQ", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < TEST_MX_COUNT; i++) {
dispatch_group_async(group, q, ^() {
sTestLoggingBlock(channel);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
TEST_CHANNEL_OFF(channel);
TEST_STOP
[sLoggingService flush];
for (id<TLSOutputStream> stream in sLoggingService.outputStreams) {
[sLoggingService removeOutputStream:stream];
}
}
- (void)testZZLoggingSpeedVerification
{
XCTAssertTrue(sFileOut != nil, @"TLSFileOutputStream must have successfully been created!");
XCTAssertTrue(sRollingFileOut != nil, @"TLSRollingFileOutputStream must have successfully been created!");
NSString *loggingPrefix = @"testLogging";
NSString *nonLoggingPrefix = @"test";
for (NSString *key in sRuntimes.allKeys) {
if ([key hasPrefix:loggingPrefix]) {
NSString *otherKey = [key stringByReplacingOccurrencesOfString:loggingPrefix withString:nonLoggingPrefix];
if (sRuntimes[otherKey]) {
double loggingTime = [sRuntimes[key] doubleValue];
double nonLoggingTime = [sRuntimes[otherKey] doubleValue];
double improvement = ((loggingTime < nonLoggingTime) ? ((nonLoggingTime - loggingTime) / loggingTime) : (((loggingTime - nonLoggingTime) / nonLoggingTime) * -1.0f)) * 100;
NSLog(@"%f%% speed boost going from %@ (%f s) to %@ (%f s)", improvement, otherKey, nonLoggingTime, key, loggingTime);
// TODO: re-enable the following line when a the infrastructure has been added to disable occasionally flaky tests in CI
// XCTAssertTrue(loggingTime < (nonLoggingTime * 2.0), @"%@ must perform as well as %@", key, otherKey);
}
}
}
XCTAssertTrue([[NSFileManager defaultManager] contentsOfDirectoryAtPath:[TLSFileOutputStream defaultLogFileDirectoryPath] error:NULL].count > 0, @"Must have logged something to log file directory");
NSLog(@"Logs files: %@", [TLSFileOutputStream defaultLogFileDirectoryPath]);
// Cleanup
[[NSFileManager defaultManager] removeItemAtPath:[TLSFileOutputStream defaultLogFileDirectoryPath] error:NULL];
}
- (void)testZZZCustomLogger
{
#if DEBUG
const NSUInteger kNumberOfLevelsSupported = 4;
#else
const NSUInteger kNumberOfLevelsSupported = 3;
#endif
#define TEST_CUSTOM \
do { \
XCTAssertTrue(testLogger.loggedMessages == expectedLoggedMessages, @"Should have logged %tu messages not %tu", expectedLoggedMessages, testLogger.loggedMessages); \
XCTAssertTrue(dispatchedLogMessages == expectedDispatchedMessages, @"Should have dispatched %tu messages not %tu", expectedDispatchedMessages, dispatchedLogMessages); \
expectedLoggedMessages = testLogger.loggedMessages; \
expectedDispatchedMessages = dispatchedLogMessages; \
} while (0)
__block NSUInteger dispatchedLogMessages = 0;
TestLoggingBlock block = ^(NSString *theChannel) {
if (TLSCanLog(nil, TLSLogLevelDebug, theChannel, NULL)) {
dispatchedLogMessages++;
[sLoggingService logWithLevel:TLSLogLevelDebug channel:theChannel file:@(__FILE__) function:@(__PRETTY_FUNCTION__) line:__LINE__ contextObject:NULL options:0 message:@"%d %@ %f %@ %@", 1, @2, 3.0f, GenerateLoggingArgument(), nil];
[sLoggingService flush];
}
if (TLSCanLog(nil, TLSLogLevelInformation, theChannel, NULL)) {
dispatchedLogMessages++;
[sLoggingService logWithLevel:TLSLogLevelInformation channel:theChannel file:@(__FILE__) function:@(__PRETTY_FUNCTION__) line:__LINE__ contextObject:NULL options:0 message:@"%d %@ %f %@ %@", 1, @2, 3.0f, GenerateLoggingArgument(), nil];
[sLoggingService flush];
}
if (TLSCanLog(nil, TLSLogLevelWarning, theChannel, NULL)) {
dispatchedLogMessages++;
[sLoggingService logWithLevel:TLSLogLevelWarning channel:theChannel file:@(__FILE__) function:@(__PRETTY_FUNCTION__) line:__LINE__ contextObject:NULL options:0 message:@"%d %@ %f %@ %@", 1, @2, 3.0f, GenerateLoggingArgument(), nil];
[sLoggingService flush];
}
if (TLSCanLog(nil, TLSLogLevelError, theChannel, NULL)) {
dispatchedLogMessages++;
[sLoggingService logWithLevel:TLSLogLevelError channel:theChannel file:@(__FILE__) function:@(__PRETTY_FUNCTION__) line:__LINE__ contextObject:NULL options:0 message:@"%d %@ %f %@ %@", 1, @2, 3.0f, GenerateLoggingArgument(), nil];
[sLoggingService flush];
}
};
#define TEST_FILTER(channel) block(channel)
NSUInteger expectedLoggedMessages = 0;
NSUInteger expectedDispatchedMessages = 0;
NSString *defaultChannel = [TLSLogChannelDefault stringByAppendingString:@"-Custom"];
TestLogger *testLogger = [[TestLogger alloc] init];
[sLoggingService addOutputStream:testLogger];
TEST_FLUSH_TRANSACTIONS();
TEST_CUSTOM;
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += 1; // first log message would have been dispatched to cache the channel as off
expectedLoggedMessages += 0;
TEST_CUSTOM;
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += 0; // channel is cached as off
expectedLoggedMessages += 0;
TEST_CUSTOM;
TEST_CHANNEL_ON(defaultChannel);
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += 0;
expectedLoggedMessages += 0;
TEST_CUSTOM;
[sLoggingService updateOutputStream:testLogger];
TEST_FLUSH_TRANSACTIONS();
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += kNumberOfLevelsSupported;
expectedLoggedMessages += kNumberOfLevelsSupported;
TEST_CUSTOM;
[sLoggingService removeOutputStream:testLogger];
TEST_FLUSH_TRANSACTIONS();
TEST_CUSTOM;
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += 0;
expectedLoggedMessages += 0;
TEST_CUSTOM;
[sLoggingService addOutputStream:[[TLSNSLogOutputStream alloc] init]];
TEST_FLUSH_TRANSACTIONS();
TEST_CUSTOM;
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += kNumberOfLevelsSupported;
expectedLoggedMessages += 0;
TEST_CUSTOM;
[sLoggingService removeOutputStream:sLoggingService.outputStreams.anyObject];
TEST_FLUSH_TRANSACTIONS();
TEST_CUSTOM;
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += 0;
expectedLoggedMessages += 0;
TEST_CUSTOM;
[sLoggingService addOutputStream:testLogger];
TEST_FLUSH_TRANSACTIONS();
TEST_CUSTOM;
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += kNumberOfLevelsSupported;
expectedLoggedMessages += kNumberOfLevelsSupported;
TEST_CUSTOM;
// updated log level changes
testLogger.permittedLoggingLevels = TLSLogLevelMaskInformation;
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += kNumberOfLevelsSupported;
expectedLoggedMessages += 1;
TEST_CUSTOM;
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += 1;
expectedLoggedMessages += 1;
TEST_CUSTOM;
[sLoggingService updateOutputStream:testLogger];
TEST_FLUSH_TRANSACTIONS();
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += kNumberOfLevelsSupported;
expectedLoggedMessages += 1;
TEST_CUSTOM;
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += 1;
expectedLoggedMessages += 1;
TEST_CUSTOM;
testLogger.permittedLoggingLevels = TLSLogLevelMaskAll;
[sLoggingService updateOutputStream:testLogger];
TEST_FLUSH_TRANSACTIONS();
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += kNumberOfLevelsSupported;
expectedLoggedMessages += kNumberOfLevelsSupported;
TEST_CUSTOM;
// update channel filtering
TEST_CHANNEL_OFF(defaultChannel); // resets cache
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += 1; // first log message would have been dispatched to cache the channel as off
expectedLoggedMessages += 0;
TEST_CUSTOM;
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += 0; // cached as off
expectedLoggedMessages += 0;
TEST_CUSTOM;
testLogger.shouldFilterChannelsThatAreOff = NO; // cache not updated
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += 0;
expectedLoggedMessages += 0;
TEST_CUSTOM;
[sLoggingService updateOutputStream:testLogger]; // cache reset
TEST_FLUSH_TRANSACTIONS();
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += kNumberOfLevelsSupported;
expectedLoggedMessages += kNumberOfLevelsSupported;
TEST_CUSTOM;
TEST_CHANNEL_ON(defaultChannel);
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += kNumberOfLevelsSupported;
expectedLoggedMessages += kNumberOfLevelsSupported;
TEST_CUSTOM;
// update stream specific channel filter
[testLogger setChannel:defaultChannel on:NO]; // doesn't update cache
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += 1; // first log message will cache
expectedLoggedMessages += 0;
TEST_CUSTOM;
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += 0;
expectedLoggedMessages += 0;
TEST_CUSTOM;
[testLogger setChannel:defaultChannel on:YES]; // doesn't update cache
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += 0;
expectedLoggedMessages += 0;
TEST_CUSTOM;
[sLoggingService updateOutputStream:testLogger]; // resets cache
TEST_FLUSH_TRANSACTIONS();
TEST_FILTER(defaultChannel);
expectedDispatchedMessages += kNumberOfLevelsSupported;
expectedLoggedMessages += kNumberOfLevelsSupported;
TEST_CUSTOM;
[sLoggingService removeOutputStream:testLogger];
}
@end
@implementation TestLogger
{
NSMutableSet *_channelsToFilter;
}
- (void)setPermittedLoggingLevels:(TLSLogLevelMask)permittedLoggingLevels
{
if (_permittedLoggingLevels != permittedLoggingLevels) {
_permittedLoggingLevels = permittedLoggingLevels;
}
}
- (void)setShouldFilterChannelsThatAreOff:(BOOL)filterChannels
{
if (_shouldFilterChannelsThatAreOff != filterChannels) {
_shouldFilterChannelsThatAreOff = filterChannels;
}
}
- (TLSFilterStatus)tls_shouldFilterLevel:(TLSLogLevel)level channel:(NSString *)channel contextObject:(id)contextObject
{
if (0 == (_permittedLoggingLevels & (1 << level))) {
return TLSFilterStatusCannotLogLevel;
}
if (_shouldFilterChannelsThatAreOff && !IsConsoleChannelOn(channel)) {
return TLSFilterStatusCannotLogChannel;
}
if ([_channelsToFilter containsObject:channel]) {
return TLSFilterStatusCannotLogChannel;
}
return TLSFilterStatusOK;
}
- (void)tls_outputLogInfo:(TLSLogMessageInfo *)logInfo
{
_loggedMessages++;
}
- (void)setChannel:(NSString *)channel on:(BOOL)on
{
if (on) {
[_channelsToFilter removeObject:channel];
} else {
[_channelsToFilter addObject:channel];
}
}
- (instancetype)init
{
if (self = [super init]) {
_shouldFilterChannelsThatAreOff = YES;
_permittedLoggingLevels = TLSLogLevelMaskAll;
_channelsToFilter = [NSMutableSet set];
}
return self;
}
@end
@implementation TestStdErrLogger
- (TLSFilterStatus)tls_shouldFilterLevel:(TLSLogLevel)level channel:(NSString *)channel contextObject:(id)contextObject
{
if (!IsConsoleChannelOn(channel)) {
return TLSFilterStatusCannotLogChannel;
}
return TLSFilterStatusOK;
}
@end
@implementation TestNSLogLogger
- (TLSFilterStatus)tls_shouldFilterLevel:(TLSLogLevel)level channel:(NSString *)channel contextObject:(id)contextObject
{
if (!IsConsoleChannelOn(channel)) {
return TLSFilterStatusCannotLogChannel;
}
return TLSFilterStatusOK;
}
@end
@implementation TestFileLogger
@end
@implementation TestRollingFileLogger
@end
static void LogStream(id<TLSOutputStream> stream, TLSLogLevel level, NSString *channel, NSString *file, NSString *function, unsigned int line, NSString *format, ...)
{
NSDate *timestamp = [NSDate date];
va_list list;
va_start(list, format);
TLSLogMessageInfo *info = [[TLSLogMessageInfo alloc] initWithLevel:level
file:file
function:function
line:line
channel:channel
timestamp:timestamp
logLifespan:[timestamp timeIntervalSinceDate:[[TLSLoggingService sharedInstance] startupTimestamp]]
threadId:pthread_mach_thread_np(pthread_self())
threadName:TLSCurrentThreadName()
contextObject:nil
message:[[NSString alloc] initWithFormat:format arguments:list]];
va_end(list);
__block BOOL isChannelOn = NO;
[[TLSLoggingService sharedInstance] dispatchSynchronousTransaction:^{
isChannelOn = IsConsoleChannelOn(channel);
}];
if (isChannelOn) {
[stream tls_outputLogInfo:info];
}
}
static NSTimeInterval NSTimeIntervalFromTLSFileOutputStreamTimestamp(NSString *timestamp)
{
NSMutableArray<NSString *> *timestampElements = [[timestamp componentsSeparatedByString:@":"] mutableCopy];
if (!timestampElements) {
timestampElements = [NSMutableArray array];
}
if ([timestampElements.lastObject rangeOfString:@"."].location != NSNotFound) {
NSArray<NSString *> *dotSeperatedEnd = [timestampElements.lastObject componentsSeparatedByString:@"."];
[timestampElements removeLastObject];
[timestampElements addObjectsFromArray:dotSeperatedEnd];
}
while (timestampElements.count < 4) {
[timestampElements insertObject:@"0" atIndex:0];
}
NSTimeInterval ti = 0;
ti += [timestampElements[0] integerValue] * 60 * 60;
ti += [timestampElements[1] integerValue] * 60;
ti += [timestampElements[2] integerValue];
ti += ((double)[timestampElements[3] integerValue]) / 1000.0f;
return ti;
}
static id GenerateLoggingArgument()
{
// [NSThread sleepForTimeInterval:0.004]; // <- uncomment this to enable slow arguments in the test
return [NSDate date];
}
static void TurnConsoleChannelOn(NSString *channel, BOOL on)
{
if (on) {
[sOnConsoleChannels addObject:channel];
} else {
[sOnConsoleChannels removeObject:channel];
}
}
static BOOL IsConsoleChannelOn(NSString *channel)
{
return [sOnConsoleChannels containsObject:channel];
}