Classes/TLSDeclarations.m (229 lines of code) (raw):
//
// TLSDeclarations.m
// TwitterLoggingService
//
// Created on 12/11/13.
// Copyright (c) 2016 Twitter, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <sys/sysctl.h>
#import <TwitterLoggingService/TLS_Project.h>
#import <TwitterLoggingService/TLSDeclarations.h>
#import <TwitterLoggingService/TLSLog.h>
NSErrorDomain const TLSErrorDomain = @"TLSErrorDomain";
@implementation TLSLogMessageInfo
{
NSDictionary<NSNumber *, NSString *> *_formattedMessages;
NSString *_fileFunctionLineString;
}
- (instancetype)initWithLevel:(TLSLogLevel)level
file:(NSString *)file
function:(NSString *)function
line:(NSInteger)line
channel:(NSString *)channel
timestamp:(NSDate *)timestamp
logLifespan:(NSTimeInterval)logLifespan
threadId:(unsigned int)threadId
threadName:(NSString *)threadName
contextObject:(id)contextObject
message:(NSString *)message
{
if (self = [super init]) {
_level = level;
_file = [file copy];
_function = [function copy];
_line = line;
_channel = [channel copy];
_contextObject = contextObject;
_timestamp = timestamp;
_logLifespan = logLifespan;
_threadId = threadId;
_threadName = [threadName copy];
_message = [message copy];
}
return self;
}
- (instancetype)init
{
[self doesNotRecognizeSelector:_cmd];
abort();
}
- (NSString *)composeFormattedMessage
{
return [self composeFormattedMessageWithOptions:TLSComposeLogMessageInfoDefaultOptions];
}
- (NSString *)composeFormattedMessageWithOptions:(TLSComposeLogMessageInfoOptions)options
{
NSNumber *optionsKey = @(options);
NSString *composedMessage = _formattedMessages[optionsKey];
if (!composedMessage) {
// wrap work in autorelease pool so that on exit memory impact
// is not different than a property access
@autoreleasepool {
const TLSLogLevel level = self.level;
NSMutableString *mComposedMessage = [[NSMutableString alloc] init];
// TIMESTAMP
{
NSString *logTimestamp = nil;
if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogTimestampAsTimeSinceLoggingStarted)) {
NSTimeInterval logLifespan = self.logLifespan;
const BOOL negative = logLifespan < 0.0;
if (negative) {
logLifespan *= -1.0;
}
unsigned long seconds = (unsigned long)logLifespan;
unsigned long minutes = seconds / 60;
const unsigned long msecs = (logLifespan - (NSTimeInterval)seconds) * 1000;
const unsigned long hours = minutes / 60;
seconds -= minutes * 60;
minutes -= hours * 60;
logTimestamp = [NSString stringWithFormat:((negative) ? @"-%02lu:%02lu:%02lu.%03lu" : @"%03lu:%02lu:%02lu.%03lu"),
hours,
minutes,
seconds,
msecs];
} else if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogTimestampAsLocalTime)) {
static NSDateFormatter *sFormatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sFormatter = [[NSDateFormatter alloc] init];
sFormatter.dateFormat = @"HH':'mm':'ss'.'SSS";
sFormatter.timeZone = [NSTimeZone localTimeZone];
});
logTimestamp = [sFormatter stringFromDate:self.timestamp];
} else if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogTimestampAsUTCTime)) {
static NSDateFormatter *sFormatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sFormatter = [[NSDateFormatter alloc] init];
sFormatter.dateFormat = @"HH':'mm':'ss'.'SSS";
sFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; // UTC == GMT
});
logTimestamp = [sFormatter stringFromDate:self.timestamp];
}
if (logTimestamp) {
[mComposedMessage appendFormat:@"[%@]", logTimestamp];
}
}
// THREAD
if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogThreadId | TLSComposeLogMessageInfoLogThreadName)) {
[mComposedMessage appendString:@"["];
NSString *threadName = self.threadName;
const BOOL hasName = TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogThreadName)
&& (threadName != nil);
if (hasName) {
[mComposedMessage appendString:threadName];
}
if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogThreadId)) {
[mComposedMessage appendFormat:(hasName) ? @"(0x%x)" : @"0x%x", self.threadId];
}
[mComposedMessage appendString:@"]"];
}
// CHANNEL
if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogChannel)) {
[mComposedMessage appendFormat:@"[%@]", self.channel];
}
// LEVEL
if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogLevel)) {
[mComposedMessage appendFormat:@"[%@]", TLSLogLevelToString(level)];
}
// Call Site Info
if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogCallsiteInfoAlways | TLSComposeLogMessageInfoLogCallsiteInfoForWarnings) && level <= TLSLogLevelWarning) {
[mComposedMessage appendString:[self composeFileFunctionLineString]];
}
[mComposedMessage appendFormat:@" : %@", self.message];
composedMessage = [mComposedMessage copy];
if (TLS_BITMASK_EXCLUDES_FLAGS(options, TLSComposeLogMessageInfoDoNotCache)) {
if (!_formattedMessages) {
_formattedMessages = @{ optionsKey : composedMessage };
} else {
NSMutableDictionary<NSNumber *, NSString *> *mMessages = [_formattedMessages mutableCopy];
mMessages[optionsKey] = composedMessage;
_formattedMessages = [mMessages copy];
}
}
} // autoreleasepool
}
return composedMessage;
}
- (NSString *)composeFileFunctionLineString
{
if (!_fileFunctionLineString) {
_fileFunctionLineString = [NSString stringWithFormat:@"(%@:%li %@)", self.file.lastPathComponent, (long)self.line, self.function];
}
return _fileFunctionLineString;
}
@end
NSString *TLSLogLevelToString(TLSLogLevel level)
{
static NSString * const sLevelStrings[] = {
@"OMG",
@"ALR",
@"CRI",
@"ERR",
@"WRN",
@"not",
@"inf",
@"dbg"
};
TLS_COMPILER_ASSERT(((sizeof(sLevelStrings) / sizeof(NSString *)) == TLSLogLevelCount), sLevelStrings_NOT_EQUAL_TO_TLSLogLevelCount);
if (level >= TLSLogLevelCount) {
#if DEBUG
NSCAssert(false, @"Unknown logging level!");
#endif
return [NSString stringWithFormat:@"???[%tu]", level];
}
return sLevelStrings[level];
}
NSString *TLSLogChannelApplicationDefault()
{
static NSString *sAppChannel = nil;
static dispatch_once_t sOnceToken;
dispatch_once(&sOnceToken, ^{
CFBundleRef bundle = CFBundleGetMainBundle();
if (bundle) {
sAppChannel = (__bridge NSString *)(CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleNameKey));
if (!sAppChannel) {
sAppChannel = (__bridge NSString *)(CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleExecutableKey));
}
}
if (!sAppChannel) {
sAppChannel = TLSGetProcessBinaryName();
}
if (!sAppChannel) {
sAppChannel = @"Default";
}
});
return sAppChannel;
}
NSString *TLSGetProcessBinaryName()
{
NSString *name = nil;
// This will retrieve the executing argument of the process,
// which could be an absolute or relative path.
// It (unfortunately) could also be a link.
// Set up our sysctl variables
int mib[3];
size_t argMax = 0;
size_t sizeArgMax = sizeof(argMax);
char *args = NULL;
mib[0] = CTL_KERN;
mib[1] = KERN_ARGMAX;
mib[2] = 0;
// determine the max size we'd need to allocate
if (sysctl(mib, 2, &argMax, &sizeArgMax, NULL, 0) != -1) {
if (sizeArgMax > 0 && argMax > 0) {
args = (char*)malloc(argMax);
}
}
if (args) {
mib[1] = KERN_PROCARGS2;
mib[2] = getpid();
// get the kernal process arguments
if (sysctl(mib, 3, args, &argMax, NULL, 0) != -1) {
// get to the second argument by finding the first NULL character
char *strP;
char *termP = &args[argMax];
for (strP = args; strP < termP; strP++) {
if ('\0' == *strP) {
break;
}
}
// strip out the leading NULL characters so we get to the process launch path
while (strP < termP && *strP == '\0') {
strP++;
}
if (strP < termP) {
// Ensure we don't go out of bounds in case there is malicious code
// that doesn't NULL terminate it's arguments.
int len = 0;
for (char* strP2 = strP; *strP2 != '\0' && strP2 < termP; strP2++) {
len++;
}
char cPath[PATH_MAX+1] = { 0 };
if (PATH_MAX < len) {
// strip leading characters if necessary to fit in our buffer
strP += len - PATH_MAX;
}
memcpy(cPath, strP, len);
name = [@(cPath) lastPathComponent];
}
}
free(args);
}
return name;
}