├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Classes ├── TLSConsoleOutputStreams.h ├── TLSConsoleOutputStreams.m ├── TLSCrashlyticsOutputStream.h ├── TLSCrashlyticsOutputStream.m ├── TLSDeclarations.h ├── TLSDeclarations.m ├── TLSFileOutputStream+Protected.h ├── TLSFileOutputStream.h ├── TLSFileOutputStream.m ├── TLSLog.h ├── TLSLog.swift ├── TLSLoggingService+Advanced.h ├── TLSLoggingService.h ├── TLSLoggingService.m ├── TLSProtocols.h ├── TLSRollingFileOutputStream.h ├── TLSRollingFileOutputStream.m ├── TLS_Project.h └── TwitterLoggingService.h ├── ExampleLogger ├── ExampleAppConsoleViewController.h ├── ExampleAppConsoleViewController.m ├── ExampleAppDelegate.h ├── ExampleAppDelegate.m ├── ExampleConfigureViewController.h ├── ExampleConfigureViewController.m ├── ExampleLogger-Info.plist ├── ExampleMakeLogsViewController.h ├── ExampleMakeLogsViewController.m ├── ExampleTextView.h ├── ExampleTextView.m ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon-1.png │ │ ├── icon-2.png │ │ ├── icon-3.png │ │ ├── icon-4.png │ │ ├── icon-5.png │ │ ├── icon-57.png │ │ ├── icon-57@2x.png │ │ ├── icon-6.png │ │ ├── icon-7.png │ │ ├── icon-72.png │ │ ├── icon-72@2x.png │ │ └── icon.png │ ├── Contents.json │ ├── LaunchImage.launchimage │ │ └── Contents.json │ ├── first.imageset │ │ ├── Contents.json │ │ ├── first.png │ │ └── first@2x.png │ ├── second.imageset │ │ ├── Contents.json │ │ ├── second.png │ │ └── second@2x.png │ └── third.imageset │ │ ├── Contents.json │ │ ├── third.png │ │ └── third@2x.png ├── TLSLoggingService+ExampleAdditions.h ├── TLSLoggingService+ExampleAdditions.m ├── en.lproj │ └── InfoPlist.strings └── main.m ├── LICENSE ├── README.md ├── Resources ├── BuildConfigurations │ ├── ExampleLogger.xcconfig │ ├── TwitterLoggingService.Debug.xcconfig │ ├── TwitterLoggingService.framework.xcconfig │ ├── TwitterLoggingService.xcconfig │ ├── TwitterLoggingServiceLibrary.xcconfig │ └── TwitterLoggingServiceTests.xcconfig └── module.modulemap ├── TwitterLoggingService.podspec ├── TwitterLoggingService.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── ExampleLogger.xcscheme │ ├── TwitterLoggingService macOS.xcscheme │ ├── TwitterLoggingService.framework tvOS.xcscheme │ ├── TwitterLoggingService.framework.xcscheme │ └── TwitterLoggingService.xcscheme ├── TwitterLoggingService └── Info.plist ├── TwitterLoggingServiceExt ├── TLSExt.h ├── TLSExt.m └── TLSExtDefinitions.h ├── TwitterLoggingServiceTests ├── TLSLoggingSwiftTests.swift ├── TLSLoggingTests.m ├── TwitterLoggingServiceTests-Bridging-Header.h └── TwitterLoggingServiceTests-Info.plist └── build.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode11.5 3 | script: 4 | ./build.sh 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Twitter Logging Service Change Log 2 | 3 | ## Info 4 | 5 | **Document version:** 2.9.0 6 | 7 | **Last updated:** 08/06/2020 8 | 9 | **Author:** Nolan O'Brien 10 | 11 | ## History 12 | 13 | ### 2.9.0 (08/06/2020) 14 | 15 | - Drop support for iOS 7, 8 & 9 16 | 17 | ### 2.8.5 (07/21/2020) 18 | 19 | - Improved Swift interface 20 | 21 | ### 2.8.1 (07/01/2020) 22 | 23 | - Convert private static C functions (that take `self` as an argument) to Objective-C `direct` methods 24 | - Same low overhead, better calling syntax 25 | 26 | ### 2.8.0 (06/08/2020) 27 | 28 | - Update `initWithOutError:` to `initAndReturnError:` for `TLSRollingFileOutputStream` 29 | - More idiomatic 30 | 31 | ### 2.7.1 (04/12/2020) 32 | 33 | - Increase use of `@autoreleasepool` to keep memory management tighter 34 | - Improve at launch perf by deferring the pruning of old files for `TLSRollingFileOutputStream` 35 | - Other miscellaneous bug fixes and cleanup 36 | 37 | ### 2.7.0 (06/28/2019) 38 | 39 | - Add support for capturing `os_log` logs executed within a running app. 40 | - __TLSExt__ is provided as the interface and is not a part of __TLS__ proper. This is because __TLSExt__ makes use of private Apple frameworks and would lead to app store rejection. 41 | - So, the consumer is responsible to compiling and linking the `TLSExt*.h|m` files themselves and must only do so with non-production builds. 42 | - This can be of immense help when dogfooding with developers and teammates with an Enterprise distribution (not Test Flight and not production). 43 | 44 | ### 2.6.0 (06/11/2019) 45 | 46 | - Add options to composing a log message string from `TLSLogMessageInfo` 47 | - `TLSComposeLogMessageInfoOptions` provides options for what components to put in the output `composeFormattedMessageWithOptions:` string 48 | 49 | ### 2.5.0 (09/13/2016) 50 | 51 | - Add options to _TLS_ with `TLSLogMessageOptions` 52 | - Provides support to explicitely ignore the maximum message length cap 53 | - Change `maximumSafeMessageLength` to truncate by default instead of discard when message length is too long and there is not delegate 54 | 55 | ### 2.4.0 (07/28/2016) 56 | 57 | - Create `TLSLoggingServiceDelegate` protocol. Currently delegates the decision of what to do when a log message with an unsafe length is encountered. 58 | 59 | ### 2.3.0 (06/30/2016) 60 | 61 | - Add `os_log` support (iOS 10+ and macOS 10.12+) with `TLSOSLogOutputStream` 62 | 63 | ### 2.2.1 (05/31/2016) 64 | 65 | - Add cap to log message sizes. Will also fire a notification that can be observed to identify where the message that was too large was logged from. 66 | 67 | ### 2.2.0 (05/19/2016) 68 | 69 | - Remove `TLSFileFunctionLine` struct since it is too easy to make mistakes such as constructing the struct on the stack with stack C-string values then accessing copies of the struct from other threads that should not have references to the stack C-string values. 70 | 71 | ### 2.1.1 (05/04/2016) 72 | 73 | - Add `threadName` to `TLSLogMessageInfo` for additional context 74 | 75 | ### 2.1.0 (03/23/2016) 76 | 77 | - Refactor coding style/conventions to be better aligned with open source best practices 78 | - Absolute minimal executable code changes 79 | 80 | ### 2.0.0 (02/25/2016) 81 | 82 | - Rename `TFNLogging` to `TwitterLoggingService` 83 | 84 | ### 1.2.5 (02/03/2016) 85 | 86 | - Add Swift support 87 | - Simplify _Crashlytics_ support by delegating responsibility of calling `CLSLog` to the subclass of `TLSCrashlyticsOutputStream` 88 | 89 | ### 1.2.1 (09/11/2015) 90 | 91 | - Optimize log message filtering by moving quick filter checks to a concurrent queue 92 | 93 | ### 1.2.0 (06/12/2014) - Kirk Beitz 94 | 95 | - Made class `TLSFileOutputStream` more abstract as a base + protected implementation 96 | - no longer implements @protocol `TLSDataRetrieval` 97 | - keeps the generic readonly @property 'constants', but makes them @protected 98 | - implements one default initializer taking a logging directory and a file name 99 | - implements one convenience initializer taking a file name and making use of the default logging directory 100 | - makes 'init' NS_UNAVAILABLE 101 | - keeps the public `defaultLogFileDirectory` class method & the `tls_outputLogInfo:` and `tls_flush` methods 102 | - keep the several protected `(void)write` methods 103 | - refactor some portions of `(instancetype)initWithLogFileDirectoryPath:logFilePrefix:maxLogFiles:maxBytesPerLogFile:error:` to new protected `createLogFileDirectoryPath:error:` & `openLogFile:error:` 104 | 105 | - Made new protocol `TLSFileOutputStreamEvent` based on methods that had been "abstract" and "overrideable" in the old TLSFileOutputStream 106 | - makes use of the new typedef `TLSFileOutputEvent` for the first argument of all functions 107 | 108 | - Made new class `TLSRollingFileOutputStream` as a concrete implementation of `TLSFileOutputStream` 109 | - copied over all of the old initializers from `TLSFileOutputStream` 110 | - copied over from `TLSFileOutputStream` the @property items that were specific to rolling file output stream 111 | - interface creates new NS_ENUM `TLSRollingFileOutputEvent` (based upon `TLSFileOutputEvent` and override-able `fileStreamEvent` methods 112 | - takes over implementation of protocol `TLSDataRetrieval` 113 | - takes over implementation of `fileOutputEventBegan/Finished/Failed` methods via implementation of the @protocol `TLSFileOutputStreamEvent` 114 | 115 | ### 1.1.0 (04/09/2014) 116 | 117 | - Remove `permittedLoggingLevels` and `shouldFilterChannelsThatAreOff` to completely decouple the filtering from the `TLSLoggingService`. All output streams control their own destiny now. 118 | 119 | ### 1.0.1 (01/16/2014) 120 | 121 | - Expand maximum bytes per log file from 128MB to 1GB. 122 | - Add `flushAfterEveryWriteEnabled` property to `TLSLoggingService`. 123 | 124 | ### 1.0.0 (01/01/2014) 125 | 126 | - Initial production release 127 | -------------------------------------------------------------------------------- /Classes/TLSConsoleOutputStreams.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLSConsoleOutputStreams.h 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/11/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #import 21 | 22 | /** 23 | concrete implementation of a `TLSOutputStream` that writes to `stderr`. 24 | @note Only use one of `TLSStdErrOutputStream`, `TLSNSLogOutputStream` or `TLSOSLogOutputStream`. 25 | */ 26 | @interface TLSStdErrOutputStream : NSObject 27 | 28 | /** uses `fprintf` to write the *logInfo* to `stderr` */ 29 | - (void)tls_outputLogInfo:(nonnull TLSLogMessageInfo *)logInfo; 30 | 31 | /** flush `stderr` */ 32 | - (void)tls_flush; 33 | 34 | @end 35 | 36 | 37 | /** 38 | concrete implementation of a `TLSOutputStream` that uses `NSLog`. 39 | @note Only use one of `TLSStdErrOutputStream`, `TLSNSLogOutputStream` or `TLSOSLogOutputStream`. 40 | */ 41 | @interface TLSNSLogOutputStream : NSObject 42 | 43 | /** writes the *logInfo* to `NSLog` */ 44 | - (void)tls_outputLogInfo:(nonnull TLSLogMessageInfo *)logInfo; 45 | 46 | @end 47 | 48 | /** 49 | concrete implementation of a `TLSOutputStream` that uses `os_log`. 50 | requires iOS/tvOS 10 and/or macOS 10.12, will have no effect on older OS versions 51 | watchOS is unsupported 52 | @note Only use one of `TLSStdErrOutputStream`, `TLSNSLogOutputStream` or `TLSOSLogOutputStream`. 53 | */ 54 | @interface TLSOSLogOutputStream : NSObject 55 | 56 | /** returns `YES` if this output stream is supported (basically, requires iOS/tvOS 10 or macOS 10.12) */ 57 | + (BOOL)supported; 58 | 59 | /** writes the *logInfo* to `os_log` */ 60 | - (void)tls_outputLogInfo:(nonnull TLSLogMessageInfo *)logInfo; 61 | 62 | /** 63 | subclasses can override to indicate if a *logInfo* is sensitive. 64 | Default == `NO` 65 | */ 66 | - (BOOL)logInfoIsSensitive:(nonnull TLSLogMessageInfo *)logInfo; 67 | 68 | @end 69 | 70 | -------------------------------------------------------------------------------- /Classes/TLSConsoleOutputStreams.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLSConsoleOutputStreams.m 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/11/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #import "TLSConsoleOutputStreams.h" 21 | #import "TLSLog.h" 22 | 23 | #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_MAC 24 | #define OS_LOG_AVAILABLE 1 25 | #elif TARGET_OS_WATCH 26 | #define OS_LOG_AVAILABLE 0 // not on watch 27 | #else 28 | #define OS_LOG_AVAILABLE 0 29 | #endif 30 | 31 | #if OS_LOG_AVAILABLE 32 | #include 33 | #endif 34 | 35 | @implementation TLSStdErrOutputStream 36 | 37 | - (void)tls_outputLogInfo:(TLSLogMessageInfo *)logInfo 38 | { 39 | NSString *message = [logInfo composeFormattedMessageWithOptions: 40 | TLSComposeLogMessageInfoLogTimestampAsLocalTime | 41 | TLSComposeLogMessageInfoLogThreadId | 42 | TLSComposeLogMessageInfoLogChannel | 43 | TLSComposeLogMessageInfoLogLevel | 44 | TLSComposeLogMessageInfoLogCallsiteInfoForWarnings]; 45 | fprintf(stderr, "%s\n", [message UTF8String]); 46 | } 47 | 48 | - (void)tls_flush 49 | { 50 | fflush(stderr); 51 | } 52 | 53 | @end 54 | 55 | @implementation TLSNSLogOutputStream 56 | 57 | - (void)tls_outputLogInfo:(TLSLogMessageInfo *)logInfo 58 | { 59 | NSString *message = [logInfo composeFormattedMessageWithOptions: 60 | TLSComposeLogMessageInfoLogChannel | 61 | TLSComposeLogMessageInfoLogLevel | 62 | TLSComposeLogMessageInfoLogCallsiteInfoForWarnings | 63 | TLSComposeLogMessageInfoDoNotCache]; 64 | NSLog(@"%@", message); 65 | } 66 | 67 | @end 68 | 69 | @implementation TLSOSLogOutputStream 70 | 71 | + (BOOL)supported 72 | { 73 | #if OS_LOG_AVAILABLE 74 | 75 | #if !TARGET_OS_WATCH 76 | return YES; 77 | #endif 78 | 79 | #endif // OS_LOG_AVAILABLE 80 | 81 | return NO; 82 | } 83 | 84 | - (void)tls_outputLogInfo:(TLSLogMessageInfo *)logInfo 85 | { 86 | #if OS_LOG_AVAILABLE 87 | /** 88 | Start off simple and just support os_log, but in the future 89 | we could start using the os_log_create to have a specific 90 | output "log" based on the channel for more advanced filtering support. 91 | */ 92 | 93 | os_log_type_t type = OS_LOG_TYPE_DEFAULT; 94 | TLSLogLevel level = logInfo.level; 95 | if (TLSLogLevelDebug == level) { 96 | type = OS_LOG_TYPE_DEBUG; 97 | } else if (level > TLSLogLevelWarning) { 98 | type = OS_LOG_TYPE_INFO; 99 | } else { 100 | type = OS_LOG_TYPE_ERROR; 101 | } 102 | 103 | NSString *message = [logInfo composeFormattedMessageWithOptions: 104 | TLSComposeLogMessageInfoLogTimestampAsLocalTime | 105 | TLSComposeLogMessageInfoLogThreadId | 106 | TLSComposeLogMessageInfoLogChannel | 107 | TLSComposeLogMessageInfoLogLevel | 108 | TLSComposeLogMessageInfoLogCallsiteInfoForWarnings]; 109 | const BOOL isSensitive = [self logInfoIsSensitive:logInfo]; 110 | if (isSensitive) { 111 | os_log_with_type(OS_LOG_DEFAULT, type, "%s", message.UTF8String); 112 | } else { 113 | os_log_with_type(OS_LOG_DEFAULT, type, "%{public}s", message.UTF8String); 114 | } 115 | #endif // OS_LOG_AVAILABLE 116 | } 117 | 118 | - (BOOL)logInfoIsSensitive:(TLSLogMessageInfo *)logInfo 119 | { 120 | return NO; 121 | } 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /Classes/TLSCrashlyticsOutputStream.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLSCrashlyticsOutputStream.h 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/11/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #import 21 | 22 | /** 23 | Abstract base class of `TLSOutputStream` to output logs to `Crashlytics`. 24 | 25 | This class does not directly deliver the logging message to _Crashlytics_, 26 | but rather delegates that responsibility to subclasses overriding `outputLogMessageToCrashlytics:`. 27 | 28 | Due to a limitation in _Crashlytics_, dynamic frameworks cannot call into the _Crashlytics_ 29 | interfaces and instead the responsibility must be delegated to the main executable itself. 30 | 31 | Keep in mind that if `CrashlyticsCollectCustomLogs` is set to `NO` in the application's 32 | `Info.plist`, all of `Crashlytics` logging will no-op (including this stream). 33 | 34 | @note Crashlytics doesn't work in the simulator so it doesn't make sense to use a `TLSCrashlyticsOutputStream` while in the simulator. 35 | */ 36 | @interface TLSCrashlyticsOutputStream : NSObject 37 | 38 | /** 39 | Subclass MUST override this method to output the message to crashlytics. 40 | 41 | `TLS_OUTPUTLOGMESSAGETOCRASHLYTICS_DEFAULT_IMPL` can be used by 42 | subclasses to implement the default behavior of just calling `CLSLog`. 43 | */ 44 | - (void)outputLogMessageToCrashlytics:(nonnull NSString *)message; 45 | 46 | /** 47 | Crashlytics has a cap for a log message before it effectively disables logging. 48 | 49 | This output stream will cap the log message at 16,384 characters 50 | (which can be between 16KB and 64KB, depending on encoding). 51 | 52 | By default `discardLargeLogMessages` is NO and therefore the message will be truncated. 53 | Override and return `YES` to discard large log messages instead of truncating them. 54 | 55 | Default == NO. 56 | */ 57 | - (BOOL)discardLargeLogMessages; 58 | 59 | @end 60 | 61 | //! convenience macro to easily implement the required method for Crashlytics support 62 | #define TLS_OUTPUTLOGMESSAGETOCRASHLYTICS_DEFAULT_IMPL \ 63 | - (void)outputLogMessageToCrashlytics:(NSString *)message \ 64 | { \ 65 | CLSLog(@"%@", message); \ 66 | } 67 | -------------------------------------------------------------------------------- /Classes/TLSCrashlyticsOutputStream.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLSCrashlyticsOutputStream.m 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/11/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #import "TLSCrashlyticsOutputStream.h" 21 | #import "TLSLog.h" 22 | 23 | static const NSUInteger kMaxLogMessageLength = 16 * 1024; 24 | 25 | @implementation TLSCrashlyticsOutputStream 26 | 27 | - (void)tls_outputLogInfo:(TLSLogMessageInfo *)logInfo 28 | { 29 | // [CHANNEL][LEVEL]CALLSITE_INFO : MESSAGE 30 | NSString *message = [logInfo composeFormattedMessageWithOptions:TLSComposeLogMessageInfoLogChannel | 31 | TLSComposeLogMessageInfoLogLevel | 32 | TLSComposeLogMessageInfoLogCallsiteInfoForWarnings]; 33 | 34 | // Message too large? 35 | if (message.length > kMaxLogMessageLength) { 36 | if (self.discardLargeLogMessages) { 37 | // discard 38 | return; 39 | } 40 | 41 | // Truncate our message 42 | message = [message substringToIndex:kMaxLogMessageLength]; 43 | } 44 | 45 | // Delegate to the subclass 46 | [self outputLogMessageToCrashlytics:message]; 47 | } 48 | 49 | - (void)outputLogMessageToCrashlytics:(NSString *)message 50 | { 51 | NSLog(@"Crashlytics integration with TwitterLoggingService is incomplete! You must override %@ in your %@ subclass %@.", NSStringFromSelector(_cmd), NSStringFromClass([TLSCrashlyticsOutputStream class]), NSStringFromClass([self class])); 52 | NSLog(@"%@", message); 53 | } 54 | 55 | - (BOOL)discardLargeLogMessages 56 | { 57 | return NO; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Classes/TLSDeclarations.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLSDeclarations.h 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/11/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #import 21 | 22 | #pragma mark - Constants 23 | 24 | /** Domain for errors stemming from TwitterLoggingService APIs */ 25 | FOUNDATION_EXTERN NSErrorDomain __nonnull const TLSErrorDomain; 26 | 27 | /** Pull out an name for the current thread or `nil` if no name was identified */ 28 | FOUNDATION_EXTERN NSString * __nullable TLSCurrentThreadName(void); 29 | 30 | /** 31 | These are syslog compatible log levels for use with *TwitterLoggingService*. 32 | `TLSLog.h` only exposes easy macros for Error, Warning, Information and Debug. 33 | 34 | # Number of levels 35 | 36 | static const NSUInteger TLSLogLevelCount = TLSLogLevelDebug + 1; 37 | 38 | ## Levels to strings 39 | 40 | FOUNDATION_EXTERN NSString *TLSLogLevelToString(TLSLogLevel level); 41 | 42 | */ 43 | typedef NS_ENUM(NSInteger, TLSLogLevel) 44 | { 45 | /** Present for syslog compatability */ 46 | TLSLogLevelEmergency = 0, 47 | /** Present for syslog compatability */ 48 | TLSLogLevelAlert, 49 | /** Present for syslog compatability */ 50 | TLSLogLevelCritical, 51 | /** Use `TLSLogError` (See TLSLog) */ 52 | TLSLogLevelError, 53 | /** Use `TLSLogWarning` (See TLSLog) */ 54 | TLSLogLevelWarning, 55 | /** Present for syslog compatability */ 56 | TLSLogLevelNotice, 57 | /** Use `TLSLogInformation` (See TLSLog) */ 58 | TLSLogLevelInformation, 59 | /** Use `TLSLogDebug` (See TLSLog) */ 60 | TLSLogLevelDebug 61 | }; 62 | 63 | //! Number of log levels 64 | static const NSInteger TLSLogLevelCount = TLSLogLevelDebug + 1; 65 | 66 | /** 67 | A set of flags that can be used to identify specific log levels in one mask. 68 | Used by `TLSOutputStream` conforming objects for filtering log levels. 69 | */ 70 | typedef NS_OPTIONS(NSInteger, TLSLogLevelMask) 71 | { 72 | /** Emergency */ 73 | TLSLogLevelMaskEmergency = (1 << TLSLogLevelEmergency), 74 | /** Alert */ 75 | TLSLogLevelMaskAlert = (1 << TLSLogLevelAlert), 76 | /** Critical */ 77 | TLSLogLevelMaskCritical = (1 << TLSLogLevelCritical), 78 | /** Error */ 79 | TLSLogLevelMaskError = (1 << TLSLogLevelError), 80 | /** Warning */ 81 | TLSLogLevelMaskWarning = (1 << TLSLogLevelWarning), 82 | /** Notice */ 83 | TLSLogLevelMaskNotice = (1 << TLSLogLevelNotice), 84 | /** Information */ 85 | TLSLogLevelMaskInformation = (1 << TLSLogLevelInformation), 86 | /** Debug */ 87 | TLSLogLevelMaskDebug = (1 << TLSLogLevelDebug), 88 | 89 | /** All Levels */ 90 | TLSLogLevelMaskAll = 0xFF, 91 | /** No Levels */ 92 | TLSLogLevelMaskNone = 0, 93 | 94 | /** Emergency and above */ 95 | TLSLogLevelMaskEmergencyAndAbove = TLSLogLevelMaskEmergency, 96 | /** Alert and above */ 97 | TLSLogLevelMaskAlertAndAbove = TLSLogLevelMaskEmergencyAndAbove | TLSLogLevelMaskAlert, 98 | /** Critical and above */ 99 | TLSLogLevelMaskCriticalAndAbove = TLSLogLevelMaskAlertAndAbove | TLSLogLevelMaskCritical, 100 | /** Error and above */ 101 | TLSLogLevelMaskErrorAndAbove = TLSLogLevelMaskCriticalAndAbove | TLSLogLevelMaskError, 102 | /** Warning and above */ 103 | TLSLogLevelMaskWarningAndAbove = TLSLogLevelMaskErrorAndAbove | TLSLogLevelMaskWarning, 104 | /** Notice and above */ 105 | TLSLogLevelMaskNoticeAndAbove = TLSLogLevelMaskWarningAndAbove | TLSLogLevelMaskNotice, 106 | /** Information and above */ 107 | TLSLogLevelMaskInformationAndAbove = TLSLogLevelMaskNoticeAndAbove | TLSLogLevelMaskInformation, 108 | /** Debug and above (effectively everything except out of bounds values) */ 109 | TLSLogLevelMaskDebugAndAbove = TLSLogLevelMaskInformationAndAbove | TLSLogLevelMaskDebug 110 | }; 111 | 112 | /** Advanced options for logging a message */ 113 | typedef NS_OPTIONS(NSInteger, TLSLogMessageOptions) { 114 | /** no options (default behavior) */ 115 | TLSLogMessageOptionsNone = 0, 116 | /** ignore the `[TLSLoggingService maximumSafeMessageLength]` capping of the message */ 117 | TLSLogMessageOptionsIgnoringMaximumSafeMessageLength = 1 << 0, 118 | }; 119 | 120 | /** 121 | Options for how to compose a `TLSLogMessageInfo` into a message string 122 | Selects which components of the message will be in the composed string. 123 | All components will format as `@"[TIMESTAMP][THREAD][CHANNEL][LEVEL](__FILE__:__LINE__ __PRETTY_FUNCTION___) : MESSAGE"` 124 | */ 125 | typedef NS_OPTIONS(NSInteger, TLSComposeLogMessageInfoOptions) { 126 | /** 127 | No options 128 | `@" : MESSAGE"` 129 | */ 130 | TLSComposeLogMessageInfoNoOptions = 0, 131 | 132 | //! TIMESTAMP 133 | 134 | //! Log the `TIMESTAMP` as the time since logging started as *HHH:mm:ss.MMM* (hours, minutes, seconds, milliseconds) 135 | TLSComposeLogMessageInfoLogTimestampAsTimeSinceLoggingStarted = 1 << 0, 136 | //! Log the `TIMESTAMP` as the local time as *HHH:mm:ss.MMM* (hours, minutes, seconds, milliseconds) 137 | TLSComposeLogMessageInfoLogTimestampAsLocalTime = 1 << 1, 138 | //! Log the `TIMESTAMP` as the UTC time as *HHH:mm:ss.MMM* (hours, minutes, seconds, milliseconds) 139 | TLSComposeLogMessageInfoLogTimestampAsUTCTime = 1 << 2, 140 | 141 | //! THREAD [THREADNAME] | [THREADID] | [THREADNAME(THREADID)] 142 | //! Log the `THREAD` identifier 143 | TLSComposeLogMessageInfoLogThreadId = 1 << 4, 144 | //! Log the `THREAD` name. @note Take care since thread names can be long and might not be ideal for all logs. 145 | TLSComposeLogMessageInfoLogThreadName = 1 << 5, 146 | 147 | //! CHANNEL 148 | //! Log the `CHANNEL` 149 | TLSComposeLogMessageInfoLogChannel = 1 << 8, 150 | 151 | //! LEVEL 152 | //! Log the `LEVEL` 153 | TLSComposeLogMessageInfoLogLevel = 1 << 12, 154 | 155 | //! Callsite Info: (__FILE__:__LINE__ __PRETTY_FUNCTION___) 156 | //! Log the callsite info always: `(__FILE__:__LINE__ __PRETTY_FUNCTION___)` 157 | TLSComposeLogMessageInfoLogCallsiteInfoAlways = 1 << 16, 158 | //! Log the callsite info when message's *level* is `TLSLogLevelWarning` or higher: `(__FILE__:__LINE__ __PRETTY_FUNCTION___)` 159 | TLSComposeLogMessageInfoLogCallsiteInfoForWarnings = 1 << 17, 160 | 161 | //! Caching 162 | //! Do not cache the composed log message 163 | TLSComposeLogMessageInfoDoNotCache = 1 << 31, 164 | 165 | /** 166 | Default options 167 | `@"[TIMESTAMP][THREADID][CHANNEL][LEVEL](__FILE__:__LINE__ __PRETTY_FUNCTION___) : MESSAGE"` 168 | Where `TIMESTAMP` is the time since logging started. 169 | Where `(__FILE__:__LINE__ __PRETTY_FUNCTION__)` is only present for Warning and above. 170 | */ 171 | TLSComposeLogMessageInfoDefaultOptions = TLSComposeLogMessageInfoLogTimestampAsTimeSinceLoggingStarted | 172 | TLSComposeLogMessageInfoLogThreadId | 173 | TLSComposeLogMessageInfoLogChannel | 174 | TLSComposeLogMessageInfoLogLevel | 175 | TLSComposeLogMessageInfoLogCallsiteInfoForWarnings, 176 | }; 177 | 178 | #pragma mark - Declarations 179 | 180 | /** 181 | Encapsulation of log message information. 182 | 183 | All properties of `TLSLogMessageInfo` are readonly and populated at initialization time with the exception of `composeFormattedMessage`. 184 | */ 185 | @interface TLSLogMessageInfo : NSObject 186 | 187 | /** The `TLSLogLevel` */ 188 | @property (nonatomic, readonly) TLSLogLevel level; 189 | /** The `@__FILE__` of the log message */ 190 | @property (nonatomic, nonnull, copy, readonly) NSString *file; 191 | /** The `@__FUNCTION__` of the log message */ 192 | @property (nonatomic, nonnull, copy, readonly) NSString *function; 193 | /** The `__LINE__` of the log message */ 194 | @property (nonatomic, readonly) NSInteger line; 195 | /** The `NSString*` channel */ 196 | @property (nonatomic, nonnull, copy, readonly) NSString *channel; 197 | /** The context object */ 198 | @property (nonatomic, nullable, readonly) id contextObject; 199 | /** The log message's timestamp */ 200 | @property (nonatomic, nonnull, readonly) NSDate *timestamp; 201 | /** how long the `TLSLoggingService` instance had been alive when this log message was made */ 202 | @property (nonatomic, readonly) NSTimeInterval logLifespan; 203 | /** The thread identifier (mach_port_t) that the message was logged from */ 204 | @property (nonatomic, readonly) unsigned int threadId; 205 | /** The thread name of the thread that was logged from */ 206 | @property (nonatomic, nullable, copy, readonly) NSString *threadName; 207 | /** The log message */ 208 | @property (nonatomic, nonnull, copy, readonly) NSString *message; 209 | 210 | /** 211 | Composes a log message in predefined format which is cached for the lifetime of this object. 212 | @return A log message string using `TLSComposeLogMessageInfoDefaultOptions` 213 | */ 214 | - (nonnull NSString *)composeFormattedMessage; 215 | 216 | /** 217 | Composes a log message in predefined format which is cached for the lifetime of this object. 218 | @return A log message string using the given `TLSComposeLogMessageInfoOptions` _options_ 219 | */ 220 | - (nonnull NSString *)composeFormattedMessageWithOptions:(TLSComposeLogMessageInfoOptions)options; 221 | 222 | /** 223 | Composes a string that combines the _file_, _function_ and _line_ information. 224 | @return a string in the format `@"(__FILE__:__LINE__ __FUNCTION)"` 225 | */ 226 | - (nonnull NSString *)composeFileFunctionLineString; 227 | 228 | /** 229 | Designated initializer 230 | */ 231 | - (nonnull instancetype)initWithLevel:(TLSLogLevel)level 232 | file:(nonnull NSString *)file 233 | function:(nonnull NSString *)function 234 | line:(NSInteger)line 235 | channel:(nonnull NSString *)channel 236 | timestamp:(nonnull NSDate *)timestamp 237 | logLifespan:(NSTimeInterval)logLifespan 238 | threadId:(unsigned int)threadId 239 | threadName:(nullable NSString *)threadName 240 | contextObject:(nullable id)contextObject 241 | message:(nonnull NSString *)message NS_DESIGNATED_INITIALIZER; 242 | 243 | /** 244 | `NS_UNAVAILABLE` 245 | */ 246 | - (nonnull instancetype)init NS_UNAVAILABLE; 247 | /** 248 | `NS_UNAVAILABLE` 249 | */ 250 | + (nonnull instancetype)new NS_UNAVAILABLE; 251 | 252 | @end 253 | -------------------------------------------------------------------------------- /Classes/TLSDeclarations.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLSDeclarations.m 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/11/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #include 21 | 22 | #import 23 | #import 24 | #import 25 | 26 | NSErrorDomain const TLSErrorDomain = @"TLSErrorDomain"; 27 | 28 | @implementation TLSLogMessageInfo 29 | { 30 | NSDictionary *_formattedMessages; 31 | NSString *_fileFunctionLineString; 32 | } 33 | 34 | - (instancetype)initWithLevel:(TLSLogLevel)level 35 | file:(NSString *)file 36 | function:(NSString *)function 37 | line:(NSInteger)line 38 | channel:(NSString *)channel 39 | timestamp:(NSDate *)timestamp 40 | logLifespan:(NSTimeInterval)logLifespan 41 | threadId:(unsigned int)threadId 42 | threadName:(NSString *)threadName 43 | contextObject:(id)contextObject 44 | message:(NSString *)message 45 | { 46 | if (self = [super init]) { 47 | _level = level; 48 | _file = [file copy]; 49 | _function = [function copy]; 50 | _line = line; 51 | _channel = [channel copy]; 52 | _contextObject = contextObject; 53 | _timestamp = timestamp; 54 | _logLifespan = logLifespan; 55 | _threadId = threadId; 56 | _threadName = [threadName copy]; 57 | _message = [message copy]; 58 | } 59 | return self; 60 | } 61 | 62 | - (instancetype)init 63 | { 64 | [self doesNotRecognizeSelector:_cmd]; 65 | abort(); 66 | } 67 | 68 | - (NSString *)composeFormattedMessage 69 | { 70 | return [self composeFormattedMessageWithOptions:TLSComposeLogMessageInfoDefaultOptions]; 71 | } 72 | 73 | - (NSString *)composeFormattedMessageWithOptions:(TLSComposeLogMessageInfoOptions)options 74 | { 75 | NSNumber *optionsKey = @(options); 76 | NSString *composedMessage = _formattedMessages[optionsKey]; 77 | if (!composedMessage) { 78 | 79 | // wrap work in autorelease pool so that on exit memory impact 80 | // is not different than a property access 81 | 82 | @autoreleasepool { 83 | 84 | const TLSLogLevel level = self.level; 85 | NSMutableString *mComposedMessage = [[NSMutableString alloc] init]; 86 | 87 | // TIMESTAMP 88 | { 89 | NSString *logTimestamp = nil; 90 | if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogTimestampAsTimeSinceLoggingStarted)) { 91 | NSTimeInterval logLifespan = self.logLifespan; 92 | const BOOL negative = logLifespan < 0.0; 93 | if (negative) { 94 | logLifespan *= -1.0; 95 | } 96 | 97 | unsigned long seconds = (unsigned long)logLifespan; 98 | unsigned long minutes = seconds / 60; 99 | 100 | const unsigned long msecs = (logLifespan - (NSTimeInterval)seconds) * 1000; 101 | const unsigned long hours = minutes / 60; 102 | 103 | seconds -= minutes * 60; 104 | minutes -= hours * 60; 105 | 106 | logTimestamp = [NSString stringWithFormat:((negative) ? @"-%02lu:%02lu:%02lu.%03lu" : @"%03lu:%02lu:%02lu.%03lu"), 107 | hours, 108 | minutes, 109 | seconds, 110 | msecs]; 111 | } else if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogTimestampAsLocalTime)) { 112 | static NSDateFormatter *sFormatter = nil; 113 | static dispatch_once_t onceToken; 114 | dispatch_once(&onceToken, ^{ 115 | sFormatter = [[NSDateFormatter alloc] init]; 116 | sFormatter.dateFormat = @"HH':'mm':'ss'.'SSS"; 117 | sFormatter.timeZone = [NSTimeZone localTimeZone]; 118 | }); 119 | logTimestamp = [sFormatter stringFromDate:self.timestamp]; 120 | } else if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogTimestampAsUTCTime)) { 121 | static NSDateFormatter *sFormatter = nil; 122 | static dispatch_once_t onceToken; 123 | dispatch_once(&onceToken, ^{ 124 | sFormatter = [[NSDateFormatter alloc] init]; 125 | sFormatter.dateFormat = @"HH':'mm':'ss'.'SSS"; 126 | sFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; // UTC == GMT 127 | }); 128 | logTimestamp = [sFormatter stringFromDate:self.timestamp]; 129 | } 130 | 131 | if (logTimestamp) { 132 | [mComposedMessage appendFormat:@"[%@]", logTimestamp]; 133 | } 134 | } 135 | 136 | // THREAD 137 | if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogThreadId | TLSComposeLogMessageInfoLogThreadName)) { 138 | [mComposedMessage appendString:@"["]; 139 | NSString *threadName = self.threadName; 140 | const BOOL hasName = TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogThreadName) 141 | && (threadName != nil); 142 | if (hasName) { 143 | [mComposedMessage appendString:threadName]; 144 | } 145 | if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogThreadId)) { 146 | [mComposedMessage appendFormat:(hasName) ? @"(0x%x)" : @"0x%x", self.threadId]; 147 | } 148 | [mComposedMessage appendString:@"]"]; 149 | } 150 | 151 | // CHANNEL 152 | if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogChannel)) { 153 | [mComposedMessage appendFormat:@"[%@]", self.channel]; 154 | } 155 | 156 | // LEVEL 157 | if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogLevel)) { 158 | [mComposedMessage appendFormat:@"[%@]", TLSLogLevelToString(level)]; 159 | } 160 | 161 | // Call Site Info 162 | if (TLS_BITMASK_INTERSECTS_FLAGS(options, TLSComposeLogMessageInfoLogCallsiteInfoAlways | TLSComposeLogMessageInfoLogCallsiteInfoForWarnings) && level <= TLSLogLevelWarning) { 163 | [mComposedMessage appendString:[self composeFileFunctionLineString]]; 164 | } 165 | 166 | [mComposedMessage appendFormat:@" : %@", self.message]; 167 | 168 | composedMessage = [mComposedMessage copy]; 169 | if (TLS_BITMASK_EXCLUDES_FLAGS(options, TLSComposeLogMessageInfoDoNotCache)) { 170 | if (!_formattedMessages) { 171 | _formattedMessages = @{ optionsKey : composedMessage }; 172 | } else { 173 | NSMutableDictionary *mMessages = [_formattedMessages mutableCopy]; 174 | mMessages[optionsKey] = composedMessage; 175 | _formattedMessages = [mMessages copy]; 176 | } 177 | } 178 | } // autoreleasepool 179 | } 180 | return composedMessage; 181 | } 182 | 183 | - (NSString *)composeFileFunctionLineString 184 | { 185 | if (!_fileFunctionLineString) { 186 | _fileFunctionLineString = [NSString stringWithFormat:@"(%@:%li %@)", self.file.lastPathComponent, (long)self.line, self.function]; 187 | } 188 | return _fileFunctionLineString; 189 | } 190 | 191 | @end 192 | 193 | NSString *TLSLogLevelToString(TLSLogLevel level) 194 | { 195 | static NSString * const sLevelStrings[] = { 196 | @"OMG", 197 | @"ALR", 198 | @"CRI", 199 | @"ERR", 200 | @"WRN", 201 | @"not", 202 | @"inf", 203 | @"dbg" 204 | }; 205 | 206 | TLS_COMPILER_ASSERT(((sizeof(sLevelStrings) / sizeof(NSString *)) == TLSLogLevelCount), sLevelStrings_NOT_EQUAL_TO_TLSLogLevelCount); 207 | 208 | if (level >= TLSLogLevelCount) { 209 | #if DEBUG 210 | NSCAssert(false, @"Unknown logging level!"); 211 | #endif 212 | return [NSString stringWithFormat:@"???[%tu]", level]; 213 | } 214 | 215 | return sLevelStrings[level]; 216 | } 217 | 218 | NSString *TLSLogChannelApplicationDefault() 219 | { 220 | static NSString *sAppChannel = nil; 221 | static dispatch_once_t sOnceToken; 222 | dispatch_once(&sOnceToken, ^{ 223 | CFBundleRef bundle = CFBundleGetMainBundle(); 224 | if (bundle) { 225 | sAppChannel = (__bridge NSString *)(CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleNameKey)); 226 | if (!sAppChannel) { 227 | sAppChannel = (__bridge NSString *)(CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleExecutableKey)); 228 | } 229 | } 230 | if (!sAppChannel) { 231 | sAppChannel = TLSGetProcessBinaryName(); 232 | } 233 | if (!sAppChannel) { 234 | sAppChannel = @"Default"; 235 | } 236 | }); 237 | return sAppChannel; 238 | } 239 | 240 | NSString *TLSGetProcessBinaryName() 241 | { 242 | NSString *name = nil; 243 | 244 | // This will retrieve the executing argument of the process, 245 | // which could be an absolute or relative path. 246 | // It (unfortunately) could also be a link. 247 | 248 | // Set up our sysctl variables 249 | int mib[3]; 250 | size_t argMax = 0; 251 | size_t sizeArgMax = sizeof(argMax); 252 | char *args = NULL; 253 | 254 | mib[0] = CTL_KERN; 255 | mib[1] = KERN_ARGMAX; 256 | mib[2] = 0; 257 | 258 | // determine the max size we'd need to allocate 259 | if (sysctl(mib, 2, &argMax, &sizeArgMax, NULL, 0) != -1) { 260 | if (sizeArgMax > 0 && argMax > 0) { 261 | args = (char*)malloc(argMax); 262 | } 263 | } 264 | 265 | if (args) { 266 | mib[1] = KERN_PROCARGS2; 267 | mib[2] = getpid(); 268 | 269 | // get the kernal process arguments 270 | if (sysctl(mib, 3, args, &argMax, NULL, 0) != -1) { 271 | // get to the second argument by finding the first NULL character 272 | char *strP; 273 | char *termP = &args[argMax]; 274 | for (strP = args; strP < termP; strP++) { 275 | if ('\0' == *strP) { 276 | break; 277 | } 278 | } 279 | 280 | // strip out the leading NULL characters so we get to the process launch path 281 | while (strP < termP && *strP == '\0') { 282 | strP++; 283 | } 284 | 285 | if (strP < termP) { 286 | // Ensure we don't go out of bounds in case there is malicious code 287 | // that doesn't NULL terminate it's arguments. 288 | int len = 0; 289 | for (char* strP2 = strP; *strP2 != '\0' && strP2 < termP; strP2++) { 290 | len++; 291 | } 292 | 293 | char cPath[PATH_MAX+1] = { 0 }; 294 | if (PATH_MAX < len) { 295 | // strip leading characters if necessary to fit in our buffer 296 | strP += len - PATH_MAX; 297 | } 298 | memcpy(cPath, strP, len); 299 | name = [@(cPath) lastPathComponent]; 300 | } 301 | } 302 | 303 | free(args); 304 | } 305 | 306 | return name; 307 | } 308 | -------------------------------------------------------------------------------- /Classes/TLSFileOutputStream+Protected.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLSFileOutputStream+Protected.h 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/11/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #import 21 | 22 | /** 23 | ## Protected category 24 | 25 | The Protected category contains methods of interested for any class subclassing `TLSFileOutputStream` and can be broken into 3 different sets of methods: 26 | 27 | # write methods 28 | 29 | Methods for writing data to the open log file. Do not override these (except for `writeNewline`). 30 | 31 | - (void)writeBytes:(const char*)bytes length:(size_t)length; 32 | - (void)writeByte:(const char)byte; 33 | - (void)writeData:(NSData *)data; 34 | - (void)writeString:(NSString *)string; 35 | - (void)writeNewline; // writes '\n' character by default. Can be overridden. 36 | 37 | # data output method 38 | 39 | Method that actual writes the log message data. `tls_outputLogInfo:` just converts the log message into what should be written and then calls this. 40 | To customize log message output, override `tls_outputLogInfo:` and call `outputLogData:` with the custom data output. 41 | Don't override `outputLogData:`. 42 | 43 | - (void)outputLogData:(NSData *)data; 44 | */ 45 | 46 | @interface TLSFileOutputStream (Protected) 47 | 48 | /** 49 | Convenience method to call createLogFileDirectoryAtPath:error: with the defaultLogFileDirectoryPath. 50 | It can be overridden to point to a different default log file directory for the given stream subclass. 51 | */ 52 | + (BOOL)createDefaultLogFileDirectoryOrError:(out NSError * __nullable __autoreleasing * __nullable)errorOut; 53 | 54 | /** 55 | Create a log file directory at the designated path and set the `logFileDirectoryPath` property. 56 | @return NO if there is an error and _errorOut_ will be set 57 | */ 58 | + (BOOL)createLogFileDirectoryAtPath:(nonnull NSString*)logFileDirectoryPath 59 | error:(out NSError * __nullable __autoreleasing * __nullable)errorOut; 60 | 61 | /** 62 | This is the method that actually writes the formatted log message data. 63 | The default implementation of `tls_outputLogInfo:` just formats the log message into what should be written and then calls this. 64 | To customize log message output, override `tls_outputLogInfo:` and call this method with the custom data output. 65 | 66 | **DO NOT override this method ** 67 | */ 68 | - (void)outputLogData:(nonnull NSData *)data; 69 | 70 | /** 71 | This overrideable method performs the inner operation of opening a log at the given filepath. 72 | The directory containing the file designated by the new file to be created must already exist (generally achieved by separately calling createLogFileDirectoryAtPath:error:). 73 | This will set the `logFile`, `logFilePath` and `logFileDirectoryPath` properties and reset the `bytesWritten`. 74 | @param logFilePath must be non-nil 75 | @return YES if the file was opened, and NO if not. 76 | */ 77 | - (BOOL)openLogFilePath:(nonnull NSString*)logFilePath 78 | error:(out NSError * __nullable __autoreleasing * __nullable)errorOut; 79 | 80 | #pragma mark Write Methods 81 | 82 | /** do not override */ 83 | - (void)writeBytes:(const char* __nonnull)bytes length:(size_t)length; 84 | /** do not override */ 85 | - (void)writeByte:(const char)byte; 86 | /** do not override */ 87 | - (void)writeData:(nonnull NSData *)data; 88 | /** do not override */ 89 | - (void)writeString:(nonnull NSString *)string; 90 | /** override if you want newlines to be different than default of `'\n'` */ 91 | - (void)writeNewline; 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /Classes/TLSFileOutputStream.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLSFileOutputStream.h 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/11/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #import 21 | 22 | NS_ASSUME_NONNULL_BEGIN 23 | 24 | /** 25 | This is the base class for other file output stream classes to extend. 26 | 27 | ### Buffered I/O with File Output Streams 28 | 29 | Something important to consider is I/O buffering. 30 | The file output stream will output to a file descriptor but for system performance an I/O write 31 | doesn't happen synchronously, it gets buffered until one of the following is encountered: 32 | 33 | 1. enough output is buffered 34 | 2. the file descriptor is closed 35 | 3. the file descriptor is explicitly flushed 36 | 37 | For debugging, you can use the exposed property on the file output stream to explicitly flush on 38 | every write, but it is recommended to only do so when trying to debug something specific and never 39 | be enabled in production builds. See `flushAfterEveryWriteEnabled` 40 | 41 | To offer increased control over output streams buffering, `TLSLoggingService` exposes a `flush` 42 | method. It is recommended that you `flush` whenever you encounter an explicit need to have the 43 | buffered I/O be output to disk, such as: 44 | 45 | 1. When the app backgrounds 46 | 2. When the app is terminated 47 | 3. When a crash is detected (call it JIT flushing) 48 | 4. When the logs need to be read (like when you zip them up for a bug report) 49 | 50 | */ 51 | @interface TLSFileOutputStream : NSObject 52 | { 53 | @protected 54 | FILE *_logFile; 55 | NSUInteger _bytesWritten; 56 | NSString *_logFilePath; 57 | BOOL _flushAfterEveryWriteEnabled; 58 | } 59 | 60 | /** 61 | The `FILE *` being written to 62 | established during initialization 63 | */ 64 | @property (nonatomic, readonly) FILE *logFile; 65 | /** 66 | Number of bytes written to the `logFile` 67 | established during initialization 68 | */ 69 | @property (nonatomic, readonly) NSUInteger bytesWritten; 70 | /** 71 | The path to the `logFile` 72 | established during initialization 73 | */ 74 | @property (nonatomic, copy, readonly) NSString *logFilePath; 75 | /** 76 | The director containing the `logFile` 77 | established during initialization 78 | */ 79 | @property (nonatomic, copy, readonly) NSString *logFileDirectoryPath; 80 | 81 | /** 82 | Enable flushing after every write. Expensive with I/O, should only be used for debugging. 83 | Default is `NO`. 84 | */ 85 | @property (nonatomic, getter=isFlushAfterEveryWriteEnabled) BOOL flushAfterEveryWriteEnabled; 86 | 87 | /** 88 | The encoding of the logged data. 89 | Default is `NSUTF8StringEncoding`. 90 | */ 91 | @property (nonatomic, readonly) NSStringEncoding tls_loggedDataEncoding; 92 | 93 | /** 94 | The format to log the message with. 95 | Default is `TLSComposeLogMessageInfoDefaultOptions` 96 | */ 97 | @property (nonatomic) TLSComposeLogMessageInfoOptions composeLogMessageOptions; 98 | 99 | /** 100 | Initialize the `TLSFileOutputStream` with the provided settings 101 | @param logFilePath the directory where the log files will live. cannot be nil 102 | @param logFileName the short file name to append as a path component to create the full logFilePath 103 | @param errorOut an output reference to get any errors that occur while creating the output stream. If there is an error, the return value will be `non-nil`. 104 | */ 105 | - (nullable instancetype)initWithLogFileDirectoryPath:(NSString*)logFilePath 106 | logFileName:(NSString*)logFileName 107 | error:(out NSError * __nullable __autoreleasing * __nullable)errorOut NS_DESIGNATED_INITIALIZER; 108 | 109 | /** 110 | Convenience initializer - calls the designated initializer with the `defaultLogFileDirectoryPath` 111 | 112 | @param logFileName - short name (without path). cannot be nil 113 | @param errorOut - address of an NSError* to contain any initialization errors 114 | */ 115 | - (nullable instancetype)initWithLogFileName:(NSString*)logFileName 116 | error:(out NSError * __nullable __autoreleasing * __nullable)errorOut; 117 | 118 | /** 119 | NS_UNAVAILABLE: callers are not allowed to instantiate this class without a logFile name or path 120 | */ 121 | - (nonnull instancetype)init NS_UNAVAILABLE; 122 | /** 123 | NS_UNAVAILABLE: callers are not allowed to instantiate this class without a logFile name or path 124 | */ 125 | + (nonnull instancetype)new NS_UNAVAILABLE; 126 | 127 | /** 128 | @return the User's Caches directory under a directory named "logs" 129 | */ 130 | + (NSString *)defaultLogFileDirectoryPath; 131 | 132 | /** 133 | Reset the log and clear it 134 | */ 135 | - (BOOL)resetAndReturnError:(out NSError * __nullable __autoreleasing * __nullable)error; 136 | 137 | #pragma mark TLSOutputStream 138 | 139 | /** 140 | flush the file I/O buffer 141 | */ 142 | - (void)tls_flush; 143 | 144 | /** 145 | write the _logInfo_ provided to the open log file 146 | */ 147 | - (void)tls_outputLogInfo:(TLSLogMessageInfo *)logInfo; 148 | 149 | @end 150 | 151 | NS_ASSUME_NONNULL_END 152 | -------------------------------------------------------------------------------- /Classes/TLSFileOutputStream.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLSFileOutputStream.m 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/11/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #import "TLS_Project.h" 21 | #import "TLSFileOutputStream+Protected.h" 22 | 23 | static NSString * const TLSFileOutputEventKeyNewLogFilePath = @"newLogFilePath"; 24 | 25 | @implementation TLSFileOutputStream 26 | 27 | #pragma mark - initialization/cleanup 28 | 29 | - (instancetype)initWithLogFileDirectoryPath:(NSString*)logFileDirectoryPath 30 | logFileName:(NSString*)logFileName 31 | error:(out NSError **)errorOut 32 | { 33 | if (0 == [logFileDirectoryPath length]) { 34 | if (errorOut) { 35 | *errorOut = [NSError errorWithDomain:NSPOSIXErrorDomain 36 | code:EINVAL 37 | userInfo:@{ @"message" : @"unable to create directory without a name", 38 | @"exceptionName" : NSInvalidArgumentException }]; 39 | } 40 | return nil; 41 | } 42 | 43 | if (0 == [logFileName length]) { 44 | if (errorOut) { 45 | *errorOut = [NSError errorWithDomain:NSPOSIXErrorDomain 46 | code:EINVAL 47 | userInfo:@{ @"message" : @"unable to create file using empty name", 48 | @"exceptionName" : NSInvalidArgumentException }]; 49 | } 50 | return nil; 51 | } 52 | 53 | if (![[self class] createLogFileDirectoryAtPath:logFileDirectoryPath error:errorOut]) { 54 | return nil; 55 | } 56 | 57 | if (self = [super init]) { 58 | _composeLogMessageOptions = TLSComposeLogMessageInfoDefaultOptions; 59 | if (![self openLogFilePath:[logFileDirectoryPath stringByAppendingPathComponent:logFileName] error:errorOut]) { 60 | return nil; 61 | } 62 | } 63 | 64 | return self; 65 | } 66 | 67 | - (instancetype)initWithLogFileName:(NSString*)logFileName 68 | error:(out NSError **)errorOut 69 | { 70 | return [self initWithLogFileDirectoryPath:[TLSFileOutputStream defaultLogFileDirectoryPath] 71 | logFileName:logFileName 72 | error:errorOut]; 73 | } 74 | 75 | - (instancetype)init 76 | { 77 | [self doesNotRecognizeSelector:_cmd]; 78 | abort(); // will never be reached, but prevents compiler warning 79 | } 80 | 81 | - (void)dealloc 82 | { 83 | if (_logFile) { 84 | fflush(_logFile); 85 | fclose(_logFile); 86 | } 87 | } 88 | 89 | #pragma mark - public class method implementations 90 | 91 | + (NSString *)defaultLogFileDirectoryPath 92 | { 93 | static NSString *_defaultLogFileDirectoryPath; 94 | static dispatch_once_t sOnceToken; 95 | dispatch_once(&sOnceToken, ^{ 96 | @autoreleasepool { 97 | NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; 98 | #if !TARGET_OS_IPHONE || TARGET_OS_MACCATALYST 99 | // platform may be non-sandboxed, or "sandbox" may contain sym-links outside of expected sandbox 100 | // ensure unique path using bundle-id or process name for safety (if possible) 101 | NSString *extraPath = [[NSBundle mainBundle] bundleIdentifier] ?: TLSGetProcessBinaryName() ?: @"TLS"; 102 | path = [path stringByResolvingSymlinksInPath]; 103 | if (![path containsString:[NSString stringWithFormat:@"/%@/", extraPath]]) { 104 | path = [path stringByAppendingPathComponent:extraPath]; 105 | } 106 | #endif 107 | _defaultLogFileDirectoryPath = [path stringByAppendingPathComponent:@"logs"]; 108 | } 109 | }); 110 | return _defaultLogFileDirectoryPath; 111 | } 112 | 113 | - (NSStringEncoding)tls_loggedDataEncoding 114 | { 115 | return NSUTF8StringEncoding; 116 | } 117 | 118 | - (BOOL)resetAndReturnError:(out NSError * __nullable * __nullable)error 119 | { 120 | if (_logFile) { 121 | fclose(_logFile); 122 | _logFile = NULL; 123 | [[NSFileManager defaultManager] removeItemAtPath:_logFilePath error:NULL]; 124 | } 125 | 126 | return [self openLogFilePath:_logFilePath error:error]; 127 | } 128 | 129 | #pragma mark - TLSOutputStream protocol implementation 130 | 131 | - (void)tls_flush 132 | { 133 | if (_logFile) { 134 | fflush(_logFile); 135 | } 136 | } 137 | 138 | - (void)tls_outputLogInfo:(TLSLogMessageInfo *)logInfo 139 | { 140 | NSString *message = [logInfo composeFormattedMessageWithOptions:self.composeLogMessageOptions]; 141 | NSData *messageData = [message dataUsingEncoding:self.tls_loggedDataEncoding]; 142 | [self outputLogData:messageData]; 143 | } 144 | 145 | @end 146 | 147 | @implementation TLSFileOutputStream(Protected) 148 | 149 | #pragma mark - "protected" method implementations 150 | 151 | - (void)writeBytes:(const char*)bytes length:(size_t)length 152 | { 153 | if (_logFile && bytes != NULL && length > 0) { 154 | _bytesWritten += fwrite(bytes, 1, length, _logFile); 155 | if (_flushAfterEveryWriteEnabled) { 156 | fflush(_logFile); 157 | } 158 | } 159 | } 160 | 161 | - (void)writeByte:(const char)byte 162 | { 163 | [self writeBytes:&byte length:1]; 164 | } 165 | 166 | - (void)writeNewline 167 | { 168 | [self writeBytes:"\n" length:1]; 169 | } 170 | 171 | - (void)writeData:(NSData *)data 172 | { 173 | [self writeBytes:(const char*)data.bytes length:data.length]; 174 | } 175 | 176 | - (void)writeString:(NSString *)string 177 | { 178 | [self writeData:[string dataUsingEncoding:self.tls_loggedDataEncoding]]; 179 | } 180 | 181 | + (BOOL)createDefaultLogFileDirectoryOrError:(out NSError **)errorOut 182 | { 183 | return [self createLogFileDirectoryAtPath:[TLSFileOutputStream defaultLogFileDirectoryPath] 184 | error:errorOut]; 185 | } 186 | 187 | + (BOOL)createLogFileDirectoryAtPath:(NSString*)logFileDirectoryPath 188 | error:(out NSError **)errorOut 189 | { 190 | if (0 == [logFileDirectoryPath length]) { 191 | if (errorOut) { 192 | *errorOut = [NSError errorWithDomain:NSPOSIXErrorDomain 193 | code:EINVAL 194 | userInfo:@{ @"message" : @"unable to create directory without a name", 195 | @"exceptionName" : NSInvalidArgumentException }]; 196 | } 197 | return NO; 198 | } 199 | 200 | NSFileManager* fm = [NSFileManager defaultManager]; 201 | NSError* error; 202 | [fm createDirectoryAtPath:logFileDirectoryPath 203 | withIntermediateDirectories:YES 204 | attributes:nil 205 | error:&error]; 206 | 207 | if (!error) { 208 | BOOL isDir = NO; 209 | if (![fm fileExistsAtPath:logFileDirectoryPath isDirectory:&isDir]) { 210 | if (errorOut) { 211 | error = [NSError errorWithDomain:NSPOSIXErrorDomain 212 | code:ENOENT 213 | userInfo:@{ @"message" : [NSString stringWithFormat:@"'%@' not created", logFileDirectoryPath], 214 | @"exceptionName" : NSObjectInaccessibleException }]; 215 | } 216 | } else if (!isDir) { 217 | if (errorOut) { 218 | error = [NSError errorWithDomain:NSPOSIXErrorDomain 219 | code:EEXIST 220 | userInfo:@{ @"message" : [NSString stringWithFormat:@"'%@' already exists, but is not a directory", logFileDirectoryPath], 221 | @"exceptionName" : NSObjectInaccessibleException }]; 222 | } 223 | } 224 | } 225 | 226 | if (error) { 227 | if (errorOut) { 228 | *errorOut = error; 229 | } 230 | return NO; 231 | } 232 | 233 | return YES; 234 | } 235 | 236 | - (BOOL)openLogFilePath:(NSString*)logFilePath 237 | error:(NSError **)errorOut 238 | { 239 | if (0 == [logFilePath length]) { 240 | if (errorOut) { 241 | *errorOut = [NSError errorWithDomain:NSPOSIXErrorDomain 242 | code:EINVAL 243 | userInfo:@{ @"message" : @"unable to create file using empty name", 244 | @"exceptionName" : NSInvalidArgumentException }]; 245 | } 246 | return NO; 247 | } 248 | 249 | FILE *newLogFile = fopen(logFilePath.UTF8String, "w"); 250 | if (!newLogFile) { 251 | if (errorOut) { 252 | int errCode = errno; 253 | NSDictionary *info = @{ TLSFileOutputEventKeyNewLogFilePath : (logFilePath) ?: [NSNull null], 254 | @"message" : @"Could not create file for logging to!", 255 | @"exceptionName" : NSObjectInaccessibleException }; 256 | *errorOut = [NSError errorWithDomain:NSPOSIXErrorDomain 257 | code:errCode 258 | userInfo:info]; 259 | } 260 | return NO; 261 | } 262 | 263 | if (_logFile) { 264 | [self tls_flush]; 265 | fclose(_logFile); 266 | } 267 | 268 | _logFilePath = [logFilePath copy]; 269 | _logFileDirectoryPath = [_logFilePath stringByDeletingLastPathComponent]; 270 | _logFile = newLogFile; 271 | _bytesWritten = 0; 272 | 273 | return YES; 274 | } 275 | 276 | - (void)outputLogData:(NSData *)data 277 | { 278 | [self writeData:data]; 279 | [self writeNewline]; 280 | } 281 | 282 | @end 283 | -------------------------------------------------------------------------------- /Classes/TLSLog.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLSLog.h 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/11/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #ifndef __TLSLOG_H__ 21 | #define __TLSLOG_H__ 22 | 23 | #import 24 | #import 25 | 26 | @class TLSLoggingService; 27 | 28 | NS_ASSUME_NONNULL_BEGIN 29 | 30 | #pragma mark Helper Macros 31 | 32 | /** 33 | Helper macro for the file name macro. 34 | 35 | `__FILE__` is the historical C macro that is replaced with the full file path of the current file being compiled (e.g. `/Users/username/workspace/project/source/subfolder/anotherfolder/implementation/file.c`) 36 | `__FILE_NAME__` is the new C macro in clang that is replaced with the file name of the current file being compiled (e.g. `file.c`) 37 | 38 | By default, if `__FILE_NAME__` is availble with the current compiler, it will be used. 39 | This behavior can be overridden by providing a value for `TLS_FILE_NAME` to the compiler, like `-DTLS_FILE_NAME=__FILE__` or `-DTLS_FILE_NAME=\"redacted\"` 40 | */ 41 | #if !defined(TLS_FILE_NAME) 42 | #ifdef __FILE_NAME__ 43 | #define TLS_FILE_NAME __FILE_NAME__ 44 | #else 45 | #define TLS_FILE_NAME __FILE__ 46 | #endif 47 | #endif 48 | 49 | #pragma mark Essential Macros 50 | 51 | //! Root Macro. Provide the _level_, _channel_ and format string. 52 | #define TLSLog(level, channel, ...) \ 53 | if (TLSCanLog(nil, level, channel, nil)) { \ 54 | TLSLogEx(nil, level, channel, @(__FILE__), @(__PRETTY_FUNCTION__), __LINE__, nil, TLSLogMessageOptionsNone, __VA_ARGS__); \ 55 | } 56 | 57 | //! Log to Error level 58 | #define TLSLogError(channel, ...) TLSLog(TLSLogLevelError, channel, __VA_ARGS__) 59 | //! Log to Warning level 60 | #define TLSLogWarning(channel, ...) TLSLog(TLSLogLevelWarning, channel, __VA_ARGS__) 61 | //! Log to Information level 62 | #define TLSLogInformation(channel, ...) TLSLog(TLSLogLevelInformation, channel, __VA_ARGS__) 63 | //! Log to Debug level 64 | #define TLSLogDebug(channel, ...) TLSLog(TLSLogLevelDebug, channel, __VA_ARGS__) 65 | 66 | #pragma mark Convenience Functions 67 | 68 | //! Convert the log level to a short parsable string 69 | FOUNDATION_EXTERN NSString *TLSLogLevelToString(TLSLogLevel level); 70 | 71 | //! A default application log channel if no custom channel is desired 72 | FOUNDATION_EXTERN NSString *TLSLogChannelApplicationDefault(void) __attribute__((const)); 73 | 74 | //! Macro to a default application log channel 75 | #define TLSLogChannelDefault TLSLogChannelApplicationDefault() 76 | 77 | #pragma mark TLSLog Helper Functions 78 | 79 | //! Log a message using formatted message 80 | FOUNDATION_EXTERN void TLSLogEx(TLSLoggingService * __nullable service, 81 | TLSLogLevel level, 82 | NSString *channel, 83 | NSString *file, 84 | NSString *function, 85 | NSInteger line, 86 | id __nullable contextObject, 87 | TLSLogMessageOptions options, 88 | NSString *format, ...) NS_FORMAT_FUNCTION(9,10); 89 | 90 | //! Log a message using a fully constructed string 91 | FOUNDATION_EXTERN void TLSLogString(TLSLoggingService * __nullable service, 92 | TLSLogLevel level, 93 | NSString *channel, 94 | NSString *file, 95 | NSString *function, 96 | NSInteger line, 97 | id __nullable contextObject, 98 | TLSLogMessageOptions options, 99 | NSString *message); 100 | 101 | //! Log a message using a variable arguments list 102 | FOUNDATION_EXTERN void TLSvaLog(TLSLoggingService * __nullable service, 103 | TLSLogLevel level, 104 | NSString *channel, 105 | NSString *file, 106 | NSString *function, 107 | NSInteger line, 108 | id __nullable contextObject, 109 | TLSLogMessageOptions options, 110 | NSString *format, 111 | va_list arguments); 112 | 113 | //! Determine if the given _level_, _channel_ and _contextObject_ can be logged 114 | FOUNDATION_EXTERN BOOL TLSCanLog(TLSLoggingService * __nullable service, 115 | TLSLogLevel level, 116 | NSString *channel, 117 | id __nullable contextObject); 118 | 119 | NS_ASSUME_NONNULL_END 120 | 121 | #endif // __TLSLOG_H__ 122 | -------------------------------------------------------------------------------- /Classes/TLSLog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TLSLog.swift 3 | // TwitterLoggingService 4 | // 5 | // Created on 2/3/16. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | import Foundation 21 | 22 | /** 23 | Static class for logging with *Twitter Logging Service* 24 | */ 25 | public class TLSLog { 26 | 27 | /** 28 | Usage: 29 | 30 | TLSLog.log(TLSLogLevel.Error, "ChannelToLog", myContextObject, "This is my log message with info: \(info)") 31 | 32 | See also: `TLSLogError`, `TLSLogWarning`, `TLSLogInformation` and `TLSLogDebug` 33 | */ 34 | public final class func log(service: TLSLoggingService? = nil, 35 | _ level: TLSLogLevel, 36 | _ channel: String, 37 | _ context: Any?, 38 | _ message: @autoclosure () -> String, 39 | options: TLSLogMessageOptions = TLSLogMessageOptions(), 40 | file: StaticString = #file, 41 | function: StaticString = #function, 42 | line: Int = #line) 43 | { 44 | if (!TLSCanLog(service, level, channel, context)) { 45 | return 46 | } 47 | 48 | TLSLogString(service, 49 | level, 50 | channel, 51 | String(describing: file), 52 | String(describing: function), 53 | Int(line), 54 | context, 55 | options, 56 | message()) 57 | } 58 | 59 | /** 60 | Usage: 61 | 62 | TLSLog.error("ChannelToLog", "This is my log message with info: \(info)") 63 | */ 64 | public final class func error(_ channel: String, 65 | _ message: @autoclosure () -> String, 66 | options: TLSLogMessageOptions = TLSLogMessageOptions(), 67 | file: StaticString = #file, 68 | function: StaticString = #function, 69 | line: Int = #line) 70 | { 71 | log(.error, 72 | channel, 73 | nil /*context*/, 74 | message(), 75 | options: options, 76 | file: file, 77 | function: function, 78 | line: line) 79 | } 80 | 81 | /** 82 | Usage: 83 | 84 | TLSLog.warning("ChannelToLog", "This is my log message with info: \(info)") 85 | */ 86 | public final class func warning(_ channel: String, 87 | _ message: @autoclosure () -> String, 88 | options: TLSLogMessageOptions = TLSLogMessageOptions(), 89 | file: StaticString = #file, 90 | function: StaticString = #function, 91 | line: Int = #line) 92 | { 93 | log(.warning, 94 | channel, 95 | nil /*context*/, 96 | message(), 97 | options: options, 98 | file: file, 99 | function: function, 100 | line: line) 101 | } 102 | 103 | /** 104 | Usage: 105 | 106 | TLSLog.information("ChannelToLog", "This is my log message with info: \(info)") 107 | */ 108 | public final class func information(_ channel: String, 109 | _ message: @autoclosure () -> String, 110 | options: TLSLogMessageOptions = TLSLogMessageOptions(), 111 | file: StaticString = #file, 112 | function: StaticString = #function, 113 | line: Int = #line) 114 | { 115 | log(.information, 116 | channel, 117 | nil /*context*/, 118 | message(), 119 | options: options, 120 | file: file, 121 | function: function, 122 | line: line) 123 | } 124 | 125 | /** 126 | Usage: 127 | 128 | TLSLog.debug("ChannelToLog", "This is my log message with info: \(info)") 129 | 130 | *Note*: Only logs on `DEBUG` builds 131 | */ 132 | public final class func debug(_ channel: String, 133 | _ message: @autoclosure () -> String, 134 | options: TLSLogMessageOptions = TLSLogMessageOptions(), 135 | file: StaticString = #file, 136 | function: StaticString = #function, 137 | line: Int = #line) 138 | { 139 | log(.debug, 140 | channel, 141 | nil /*context*/, 142 | message(), 143 | options: options, 144 | file: file, 145 | function: function, 146 | line: line) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Classes/TLSLoggingService+Advanced.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLSLoggingService+Advanced.h 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/11/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #import 21 | 22 | NS_ASSUME_NONNULL_BEGIN 23 | 24 | @protocol TLSLoggingServiceDelegate; 25 | 26 | @interface TLSLoggingService (Advanced) 27 | 28 | /** 29 | The delegate for the `TLSLoggingService` 30 | */ 31 | @property (atomic, nullable, weak) id delegate; 32 | 33 | /** 34 | the maximum length of a log message that is considered "safe". 35 | Anything larger will check the `TLSLoggingServiceDelegate` for how to behave, default being to truncate the message. 36 | `0` means no maximum. 37 | 38 | Default == `0` 39 | */ 40 | @property (nonatomic, readwrite) NSUInteger maximumSafeMessageLength; 41 | 42 | /** 43 | The time that the `TLSLoggingService` was initialized for convenience. 44 | */ 45 | @property (nonatomic, readonly) NSDate *startupTimestamp; 46 | /** 47 | the set of `id` objects 48 | */ 49 | @property (atomic, nonnull, readonly) NSSet> *outputStreams; 50 | 51 | /** 52 | Call this when any of the results of a `TLSOutputStream`'s `TLSFiltering` methods change. 53 | If `TLSCANLOGMODE` is not `1` this is a no-op. 54 | */ 55 | - (void)updateOutputStream:(id)stream; 56 | /** 57 | Remove the provided _stream_ 58 | */ 59 | - (void)removeOutputStream:(id)stream; 60 | 61 | /** 62 | synchronously flushes all internal queues and calls flush on all `TLSOutputStream`s that implement `flush` 63 | */ 64 | - (void)flush; 65 | 66 | /** 67 | synchronously execute the given block on the `TLSLoggingService` instance's transaction queue. 68 | Don't muck with `TLSLoggingService` or other queues/threads from within the _block_. 69 | */ 70 | - (void)dispatchSynchronousTransaction:(dispatch_block_t NS_NOESCAPE)block; 71 | /** 72 | asynchronously execute the given block on the `TLSLoggingService` intances's transaction queue. 73 | Don't muck with `TLSLoggingService` or other queues/threads from within the _block_. 74 | */ 75 | - (void)dispatchAsynchronousTransaction:(dispatch_block_t)block; 76 | 77 | /** 78 | @return a set of output streams that support getting the past logged message data (i.e. conform to `TLSDataRetrieval`) 79 | */ 80 | - (NSSet> *)outputStreamsThatSupportLoggedDataRetrieval; 81 | 82 | /** 83 | @return past log message data from a given stream 84 | */ 85 | - (nullable NSData *)retrieveLoggedDataFromOutputStream:(id)stream 86 | maxBytes:(NSUInteger)maxBytes; 87 | 88 | @end 89 | 90 | /** Delegate protocol for `TLSLoggingService` */ 91 | @protocol TLSLoggingServiceDelegate 92 | 93 | @optional 94 | 95 | /** 96 | Method to indicate the behavior for the _service_ to use when a log message exceeds the maximum safe length. 97 | @param service The `TLSLoggingService` 98 | @param maxSafeLength the maximum length that was exceeded 99 | @param level The `TLSLogLevel` of the message 100 | @param channel The channel of the message 101 | @param file The `@(__FILE__)` of the message (or `@(__FILE_NAME__)` on modern clang compilers -- use `@(TLS_FILE_NAME)` 102 | @param function The `@(__FUNCTION__)` of the message 103 | @param line The __LINE__ of the message 104 | @param contextObject the context object of the message (or `nil`) 105 | @param message The message itself 106 | @return a length of `0` to discard the message, a length below `[message length]` to log after truncating to the returned length and any other length (gte `message.length`) to log as-is 107 | 108 | Default == `0`, discard message 109 | */ 110 | - (NSUInteger)tls_loggingService:(TLSLoggingService *)service 111 | lengthToLogForMessageExceedingMaxSafeLength:(NSUInteger)maxSafeLength 112 | level:(TLSLogLevel)level 113 | channel:(NSString *)channel 114 | file:(NSString *)file 115 | function:(NSString *)function 116 | line:(NSInteger)line 117 | contextObject:(nullable id)contextObject 118 | message:(NSString *)message; 119 | 120 | @end 121 | 122 | NS_ASSUME_NONNULL_END 123 | -------------------------------------------------------------------------------- /Classes/TLSLoggingService.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLSLoggingService.h 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/11/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #import 21 | 22 | /** 23 | Singleton object for a project to log messages in an efficient, thread safe and asynchronous way. 24 | 25 | TLSLoggingService functions as follows: 26 | 27 | 1) ingests log messages (using `TLSLog` macros in `TLSLog.h`) 28 | 29 | 2) enqueues the log messages to its owned background queue 30 | 31 | 3) distributes the log messages to the available `TLSOutputStream` objects 32 | 33 | ## Less is more! 34 | 35 | Projects will seldom need to directly use `TLSLoggingService`. 36 | Nearly all functionality for *TwitterLoggingService* can be found in `TLSLog.h` (See `TLSLog`). 37 | 38 | */ 39 | @interface TLSLoggingService : NSObject 40 | 41 | /** 42 | Access to the shared `TLSLoggingService` singleton instance. 43 | */ 44 | + (nonnull instancetype)sharedInstance; 45 | 46 | /** 47 | Initializer is available for any case where a distinct `TLSLoggingService` would be desired. 48 | All most all use cases will want to use the `sharedInstance` though. 49 | */ 50 | - (nonnull instancetype)init; 51 | 52 | /** 53 | Add an output stream to the `TLSLoggingService`. 54 | Streams are added in a thread safe manner and in charge of their own log message filtering by how they implement the `TLSFiltering` protocol. 55 | Subclass an existing `TLSOutputStream` to change its filtering behavior. 56 | @note It is recommended you only have 1 output stream that logs to the console and that it is either not added to the `TLSLoggingService` in `RELEASE` builds or filters out all log messages. 57 | */ 58 | - (void)addOutputStream:(nonnull id)stream; 59 | 60 | /** 61 | Start logging the message (asynchronously). 62 | The best option is almost always to use the `TLSLog` macros in `TLSLog.h`. 63 | See `TLSLogError`, `TLSLogWarning`, `TLSLogInformation`, `TLSLogDebug` 64 | @param level the logging level to log at. 65 | @param channel the logging channel to log at. If `nil`, won't log. 66 | @param file the `@__FILE__` (or `__FILE_NAME__` with modern clang compiler) -- use `@TLS_FILE_NAME` 67 | @param function the `@__FUNCTION__` 68 | @param line the `__LINE__` 69 | @param contextObject any additional context to be used when logging (advanced, should often just be `nil`). 70 | @param options `TLSLogMessageOptions` to log with (default is `0`) 71 | @param message the `NSString` formatted message. 72 | */ 73 | - (void)logWithLevel:(TLSLogLevel)level 74 | channel:(nonnull NSString *)channel 75 | file:(nonnull NSString *)file 76 | function:(nonnull NSString *)function 77 | line:(NSInteger)line 78 | contextObject:(nullable id)contextObject 79 | options:(TLSLogMessageOptions)options 80 | message:(nonnull NSString *)message, ... NS_FORMAT_FUNCTION(8,9); 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /Classes/TLSProtocols.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLSProtocols.h 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/11/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #import 21 | 22 | /** 23 | The reasons that a log message was filtered 24 | */ 25 | typedef NS_OPTIONS(NSInteger, TLSFilterStatus) 26 | { 27 | /** OK means no filtering */ 28 | TLSFilterStatusOK = 0, 29 | /** The log level prevented logging the message */ 30 | TLSFilterStatusCannotLogLevel = (1 << 0), 31 | /** The log channel prevented logging the message */ 32 | TLSFilterStatusCannotLogChannel = (1 << 1), 33 | /** The log context prevented logging the message */ 34 | TLSFilterStatusCannotLogContextObject = (1 << 2), 35 | 36 | /** 37 | Provide this as the reason if the reason for filtering is not due to the log level, 38 | log channel or log context object nor due to an exclusive combination of those 3. 39 | */ 40 | TLSFilterStatusCannotLogExternal = (1 << 7) 41 | }; 42 | 43 | /** 44 | Defines filtering methods used for logging. Used by `TLSOutputStream` implementations. 45 | If the behavior of any of the implemented methods in a `TLSOutputStream` change, that stream must be provided to `TLSLoggingService`'s `updateOutputStream:` method. 46 | @warning All methods of `TLSFiltering` must never call a `TLSLoggingService` method nor a `TLSLog` function. Doing so could result in unintended deadlocks. 47 | */ 48 | @protocol TLSFiltering 49 | 50 | @optional 51 | 52 | /** 53 | Indicate whether the known filterable attributes (level, channel and contextObject) should result in the log message being filtered. 54 | 55 | Consider it a last chance for custom filtering to provide valuable filtering. 56 | If not implemented, defaults to `TLSFilterStatusOK`. 57 | 58 | @note None of the predefined `TLSOutputStream` concrete classes define `tls_shouldFilterLevel:channel:contextObject:`. Subclass or create new `TLSOutputStream` classes to utilize this advanced filtering method. 59 | 60 | @warning The implementation of this method must never call a `TLSLoggingService` method nor a `TLSLog` function. Doing so could result in unintended deadlocks. 61 | */ 62 | - (TLSFilterStatus)tls_shouldFilterLevel:(TLSLogLevel)level 63 | channel:(nonnull NSString *)channel 64 | contextObject:(nullable id)contextObject; 65 | 66 | @end 67 | 68 | /** 69 | The necessary protocol for implementing a logging output stream that can be added to `TLSLoggingService` 70 | */ 71 | @protocol TLSOutputStream 72 | 73 | @required 74 | 75 | /** 76 | Called by `TLSLoggingService` on a serial dispatch queue to log a message. 77 | 78 | If this output stream can be used outside of the `TLSLoggingService`, thread safety is up to the implemented 79 | (no `TLSOutputStream` objects in `TLSLogging` are thread safe outside of the `TLSLoggingService`). 80 | The implementation details on how to output the log info is up to the output stream. 81 | It is acceptable to log synchronously, though be aware that synchronous logging can back up the queue of log messages. 82 | @note It is recommended that any slow logging by a `TLSOutputStream` that occurs be done asynchronously (such as over a network connection or across processes). 83 | */ 84 | - (void)tls_outputLogInfo:(nonnull TLSLogMessageInfo *)logInfo; 85 | 86 | @optional 87 | 88 | /** 89 | Flush anything buffered in the output stream out to it's destination. 90 | 91 | It is possible for logs to be stuck in an I/O buffer so when it is important for logs to be completely output, this method enables that. 92 | @note Example: `TLSFileOutputStream` implements the `tls_flush` method to flush the I/O buffer of the file to disk. 93 | */ 94 | - (void)tls_flush; 95 | 96 | @end 97 | 98 | /** 99 | Data retrieval protocol for `TLSOutputStream` objects. 100 | 101 | Implement this protocol on `TLSOutputStream` objects that can have their past logged data retrieved. 102 | */ 103 | @protocol TLSDataRetrieval 104 | 105 | @required 106 | 107 | /** Get the past log data given a maximum number of bytes. */ 108 | - (nullable NSData *)tls_retrieveLoggedData:(NSUInteger)maxBytes; 109 | 110 | /** The format that the log data is retrievable as (`NSUTF8StringEncoding` is very common). */ 111 | @property (nonatomic, readonly) NSStringEncoding tls_loggedDataEncoding; 112 | 113 | @end 114 | 115 | /** 116 | Base event type for use in the protocol `TLSFileOutputStreamEvent` 117 | */ 118 | typedef NSInteger TLSFileOutputEvent; 119 | 120 | /** 121 | File stream event protocol for use by `TLSFileOutputStream` subclasses 122 | 123 | Implement this protocol in the subclass of `TLSFileOutputStream` to (1) notify the subclass of the event; and (2) have the base implementation log contextual log info based on the event 124 | 125 | E.g. for a rolling log to output JSON instead of log lines when events occur. 126 | The JSON log can implement/override the items in the following protocol in order to change the event log output to be into a format that complies with the JSON, 127 | or just disable the event logging altogether. 128 | */ 129 | @protocol TLSFileOutputStreamEvent 130 | 131 | /** 132 | Signal that the event has started, with information about the event 133 | */ 134 | - (void)tls_fileOutputEventBegan:(TLSFileOutputEvent)event 135 | info:(nullable NSDictionary *)info; 136 | 137 | /** 138 | Signal that the event has completed successfully, with information about the event 139 | */ 140 | - (void)tls_fileOutputEventFinished:(TLSFileOutputEvent)event 141 | info:(nullable NSDictionary *)info; 142 | 143 | /** 144 | Signal that the event has failed to complete successfully, with information about the event failure 145 | */ 146 | - (void)tls_fileOutputEventFailed:(TLSFileOutputEvent)event 147 | info:(nullable NSDictionary *)info error:(nonnull NSError *)error; 148 | 149 | @end 150 | -------------------------------------------------------------------------------- /Classes/TLSRollingFileOutputStream.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLSRollingFileOutputStream.h 3 | // TwitterLoggingService 4 | // 5 | // Created by Kirk Beitz on 06/07/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #import 21 | #import 22 | 23 | /** 24 | Enumeration of events the `TLSRollingFileOutputStream` will go through 25 | 26 | ## Constants for use with TLSRollingFileOutputEvent events 27 | 28 | // for RolloverLogs: new log file. for Initialize: new log file (only on finished or some failed). 29 | FOUNDATION_EXTERN NSString * const TLSRollingFileOutputEventKeyNewLogFilePath; 30 | // for RolloverLogs: previous log file. for PurgeLog: purge file. 31 | FOUNDATION_EXTERN NSString * const TLSRollingFileOutputEventKeyOldLogFilePath; 32 | */ 33 | typedef NS_ENUM(TLSFileOutputEvent, TLSRollingFileOutputEvent) { 34 | /** when the `TLSFileOutputStream` is initialized */ 35 | TLSRollingFileOutputEventInitialize, 36 | /** when `tls_outputLogInfo:` is called on the `TLSFileOutputStream` */ 37 | TLSRollingFileOutputEventOutputLogData, 38 | /** when the `TLSRollingFileOutputStream`'s single log file size limit has been reached and it is rolling over the log */ 39 | TLSRollingFileOutputEventRolloverLogs, 40 | /** when the `TLSRollingFileOutputStream` has reached its log limit and needs to prune its old logs */ 41 | TLSRollingFileOutputEventPruneLogs, 42 | /** occurs during `TLSRollingFileOutputEventPruneLogs` */ 43 | TLSRollingFileOutputEventPurgeLog 44 | }; 45 | 46 | /** 47 | A concrete extension of `TLSOutputStream` for logging logs to file(s) on disk, using a rolling log approach. 48 | 49 | That is to say it gets things to disk and out of memory as fast as possible and once the log file limit is reached, it rolls over to the next log file - pruning old files along the way. 50 | 51 | ## Constants 52 | 53 | FOUNDATION_EXTERN const NSUInteger TLSRollingFileOutputStreamDefaultMaxBytesPerLogFile; // 256KB (aka 1024 * 256) 54 | FOUNDATION_EXTERN const NSUInteger TLSRollingFileOutputStreamDefaultMaxLogFiles; // 10 files 55 | FOUNDATION_EXTERN NSString * const TLSRollingFileOutputStreamDefaultLogFilePrefix; // @"log." as a prefix 56 | 57 | */ 58 | @interface TLSRollingFileOutputStream : TLSFileOutputStream 59 | 60 | /** 61 | Max bytes per log file. 62 | Default is `256KB`. 63 | Minimum value is `1KB`, maximum value is `1GB`. 64 | This is a soft upper bound: when a log file exceeds the max, it will rollover. 65 | */ 66 | @property (nonatomic, readonly) NSUInteger maxBytesPerLogFile; 67 | /** 68 | Max number of log files. 69 | Default is `10`. 70 | Min is `1`, max is the lesser of `(4GB / maxBytesPerLogFile)` and `1024`. 71 | */ 72 | @property (nonatomic, readonly) NSUInteger maxLogFiles; 73 | /** 74 | The prefix for each log file. 75 | Default is `@"log."` 76 | */ 77 | @property (nonatomic, nonnull, copy, readonly) NSString *logFilePrefix; 78 | 79 | /** 80 | Initialize the `TLSRollingFileOutputStream` with the provided settings 81 | @param logFileDirectoryPath the directory where the log files will live. By default uses `defaultLogFileDirectoryPath`. 82 | @param logFilePrefix the string to prefix all created log files with. Default is `TLSFileOutputStreamDefaultLogFilePrefix`. 83 | @param maxLogFiles the maximum number of log files to maintain before old files are deleted. Defaults is `TLSFileOutputStreamDefaultMaxLogFiles`. Min is 1. Max is the lesser of (4GB / *maxBytesPerLogFile*) and 1024. 84 | @param maxBytesPerLogFile the max bytes per log file before the log is rolled over. Default `TLSFileOutputStreamDefaultMaxBytesPerLogFile`. Min is 1KB. Max is 1GB. 85 | @param errorOut an output reference to get any errors that occur while creating the output stream. If there is an error, the return value will be `nil`. 86 | @note *maxBytesPerLogFile* is a soft maximum. Once that cap is exceeded, the log rolls over to the next log file. That doesn't mean it won't exceed the max number of bytes per log file though. 87 | */ 88 | - (nullable instancetype)initWithLogFileDirectoryPath:(nullable NSString *)logFileDirectoryPath 89 | logFilePrefix:(nullable NSString *)logFilePrefix 90 | maxLogFiles:(NSUInteger)maxLogFiles 91 | maxBytesPerLogFile:(NSUInteger)maxBytesPerLogFile 92 | error:(out NSError * __nullable __autoreleasing * __nullable)errorOut NS_DESIGNATED_INITIALIZER; 93 | 94 | /** See initWithLogFileDirectoryPath:logFilePrefix:maxLogFiles:maxBytesPerLogFile:error: */ 95 | - (nullable instancetype)initWithLogFileDirectoryPath:(nullable NSString *)logFileDirectoryPath 96 | logFilePrefix:(nullable NSString *)logFilePrefix 97 | maxLogFiles:(NSUInteger)maxLogFiles 98 | maxBytesPerLogFile:(NSUInteger)maxBytesPerLogFile NS_SWIFT_UNAVAILABLE("use `throws` version for Swift"); 99 | 100 | /** See initWithLogFileDirectoryPath:logFilePrefix:maxLogFiles:maxBytesPerLogFile:error: */ 101 | - (nullable instancetype)initWithLogFileDirectoryPath:(nullable NSString *)logFileDirectoryPath 102 | error:(out NSError * __nullable __autoreleasing * __nullable)errorOut; 103 | 104 | /** See initWithLogFileDirectoryPath:logFilePrefix:maxLogFiles:maxBytesPerLogFile:error: */ 105 | - (nullable instancetype)initWithLogFileDirectoryPath:(nullable NSString *)logFileDirectoryPath NS_SWIFT_UNAVAILABLE("use `throws` version for Swift"); 106 | 107 | /** See initWithLogFileDirectoryPath:logFilePrefix:maxLogFiles:maxBytesPerLogFile:error: */ 108 | - (nullable instancetype)initAndReturnError:(__autoreleasing NSError * __nullable * __nullable)errorOut; 109 | - (nullable instancetype)initWithOutError:(__autoreleasing NSError * __nullable * __nullable)errorOut __attribute__((deprecated("use initAndReturnError: instead"))); 110 | 111 | /** NS_UNAVAILABLE */ 112 | - (nullable instancetype)initWithLogFileDirectoryPath:(nullable NSString*)logFilePath 113 | logFileName:(nullable NSString*)logFileName 114 | error:(out NSError * __nullable __autoreleasing * __nullable)errorOut NS_UNAVAILABLE; 115 | /** NS_UNAVAILABLE */ 116 | - (nullable instancetype)initWithLogFileName:(nullable NSString*)logFileName 117 | error:(out NSError * __nullable __autoreleasing * __nullable)errorOut NS_UNAVAILABLE; 118 | 119 | /** Unavailable because super init is NS_UNAVAILABLE */ 120 | - (nonnull instancetype)init NS_UNAVAILABLE; 121 | /** Unavailable because super init is NS_UNAVAILABLE */ 122 | + (nonnull instancetype)new NS_UNAVAILABLE; 123 | 124 | #pragma mark - protocol TLSDataRetrieval 125 | /** 126 | Get the past logged data 127 | @param maxBytes The maximum number of bytes to get from the log file(s). `2` to `4` times *maxBytesPerLogFile* is a suggestion. Min is *maxBytesPerLogFile*. 128 | @return NSData object with up to *maxBytes* of log data. 129 | @note *maxBytes* is a hard limit. Will retrieve the past log files so long as it doesn't surpass *maxBytes*. That is to say, the log data is loaded `1` entire log file at a time - no partial files will be loaded. 130 | */ 131 | - (nullable NSData *)tls_retrieveLoggedData:(NSUInteger)maxBytes; 132 | 133 | @end 134 | 135 | FOUNDATION_EXTERN NSString * __nonnull const TLSRollingFileOutputStreamDefaultLogFilePrefix; // @"log." as a prefix 136 | 137 | FOUNDATION_EXTERN const NSUInteger TLSRollingFileOutputStreamDefaultMaxBytesPerLogFile; // 256KB (aka 1024 * 256) 138 | FOUNDATION_EXTERN const NSUInteger TLSRollingFileOutputStreamDefaultMaxLogFiles; // 10 files 139 | -------------------------------------------------------------------------------- /Classes/TLS_Project.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLS_Project.h 3 | // TwitterLoggingService 4 | // 5 | // Created on 3/24/16. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | /* This header is private to Twitter Logging Service */ 21 | 22 | #import 23 | 24 | /* 25 | Static Asserts (asserts that trigger at compile time) 26 | 27 | Call `TLS_COMPILER_ASSERT` with the condition and the message. 28 | The message MUST be variable name compliant (no whitespace, alpha, numeric and '_') 29 | 30 | Example: 31 | 32 | TLS_COMPILER_ASSERT((sizeof(sArray) / sizeof(sArray[0])) == kExpectedArrayItemCount, 33 | array_count_didnt_match_expected_count); 34 | */ 35 | #define __TLS_COMPILER_ASSERT(line, msg) \ 36 | TLS_COMPILER_ASSERT_##line##_##msg 37 | #define _TLS_COMPILER_ASSERT(line, msg) \ 38 | __TLS_COMPILER_ASSERT(line, msg) 39 | #define TLS_COMPILER_ASSERT(cond, msg) \ 40 | typedef char _TLS_COMPILER_ASSERT(__LINE__, msg) [ (cond) ? 1 : -1 ] 41 | 42 | //! Best effort attempt to get the binary name of the current process 43 | FOUNDATION_EXTERN NSString *TLSGetProcessBinaryName(void); 44 | 45 | /** Does the `mask` have at least 1 of the bits in `flags` set */ 46 | #define TLS_BITMASK_INTERSECTS_FLAGS(mask, flags) (((mask) & (flags)) != 0) 47 | /** Does the `mask` have all of the bits in `flags` set */ 48 | #define TLS_BITMASK_HAS_SUBSET_FLAGS(mask, flags) (((mask) & (flags)) == (flags)) 49 | /** Does the `mask` have none of the bits in `flags` set */ 50 | #define TLS_BITMASK_EXCLUDES_FLAGS(mask, flags) (((mask) & (flags)) == 0) 51 | 52 | #pragma mark - Objective-C attribute support 53 | 54 | #if defined(__has_attribute) && (defined(__IPHONE_14_0) || defined(__MAC_10_16) || defined(__MAC_11_0) || defined(__TVOS_14_0) || defined(__WATCHOS_7_0)) 55 | # define TLS_SUPPORTS_OBJC_DIRECT __has_attribute(objc_direct) 56 | #else 57 | # define TLS_SUPPORTS_OBJC_DIRECT 0 58 | #endif 59 | 60 | #if defined(__has_attribute) 61 | # define TLS_SUPPORTS_OBJC_FINAL __has_attribute(objc_subclassing_restricted) 62 | #else 63 | # define TLS_SUPPORTS_OBJC_FINAL 0 64 | #endif 65 | 66 | #pragma mark - Objective-C Direct Support 67 | 68 | #if TLS_SUPPORTS_OBJC_DIRECT 69 | # define tls_nonatomic_direct nonatomic,direct 70 | # define tls_atomic_direct atomic,direct 71 | # define TLS_OBJC_DIRECT __attribute__((objc_direct)) 72 | # define TLS_OBJC_DIRECT_MEMBERS __attribute__((objc_direct_members)) 73 | #else 74 | # define tls_nonatomic_direct nonatomic 75 | # define tls_atomic_direct atomic 76 | # define TLS_OBJC_DIRECT 77 | # define TLS_OBJC_DIRECT_MEMBERS 78 | #endif // #if TLS_SUPPORTS_OBJC_DIRECT 79 | 80 | #pragma mark - Objective-C Final Support 81 | 82 | #if TLS_SUPPORTS_OBJC_FINAL 83 | # define TLS_OBJC_FINAL __attribute__((objc_subclassing_restricted)) 84 | #else 85 | # define TLS_OBJC_FINAL 86 | #endif // #if TLS_SUPPORTS_OBJC_FINAL 87 | 88 | -------------------------------------------------------------------------------- /Classes/TwitterLoggingService.h: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterLoggingService.h 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/11/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | 20 | #import 21 | 22 | #pragma mark Primary Header 23 | 24 | #import 25 | 26 | #pragma mark Support Headers 27 | 28 | #import 29 | #import 30 | #import 31 | #import 32 | #import 33 | #import 34 | #import 35 | #import 36 | #import 37 | -------------------------------------------------------------------------------- /ExampleLogger/ExampleAppConsoleViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleFirstViewController.h 3 | // ExampleLogger 4 | // 5 | // Created on 12/24/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface ExampleAppConsoleViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ExampleLogger/ExampleAppConsoleViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleFirstViewController.m 3 | // ExampleLogger 4 | // 5 | // Created on 12/24/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | 9 | #import "ExampleAppConsoleViewController.h" 10 | #import "ExampleTextView.h" 11 | #import "TLSLoggingService+ExampleAdditions.h" 12 | 13 | @interface ExampleAppConsoleViewController () 14 | 15 | @end 16 | 17 | @implementation ExampleAppConsoleViewController 18 | 19 | - (instancetype)init 20 | { 21 | if (self = [super initWithNibName:nil bundle:nil]) { 22 | self.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Console" image:[UIImage imageNamed:@"second"] tag:2]; 23 | self.navigationItem.title = @"In-App Console"; 24 | } 25 | return self; 26 | } 27 | 28 | - (void)loadView 29 | { 30 | [super loadView]; 31 | self.view.backgroundColor = [UIColor yellowColor]; 32 | } 33 | 34 | - (void)viewDidLoad 35 | { 36 | [super viewDidLoad]; 37 | 38 | ExampleTextView *textView = [TLSLoggingService sharedInstance].globalLogTextView; 39 | textView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 40 | textView.frame = self.view.bounds; 41 | [self.view addSubview:textView]; 42 | 43 | if ([UIWindow instancesRespondToSelector:@selector(tintColor)]) { 44 | textView.contentInset = UIEdgeInsetsMake(20, 0, 44, 0); 45 | } 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /ExampleLogger/ExampleAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleAppDelegate.h 3 | // ExampleLogger 4 | // 5 | // Created on 12/24/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface ExampleAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | @property (strong, nonatomic) UITabBarController *tabBarController; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ExampleLogger/ExampleAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleAppDelegate.m 3 | // ExampleLogger 4 | // 5 | // Created on 12/24/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "ExampleAppConsoleViewController.h" 13 | #import "ExampleAppDelegate.h" 14 | #import "ExampleConfigureViewController.h" 15 | #import "ExampleMakeLogsViewController.h" 16 | #import "TLSLoggingService+ExampleAdditions.h" 17 | 18 | @implementation ExampleAppDelegate 19 | 20 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 21 | { 22 | [TLSLoggingService prepareExample]; 23 | 24 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 25 | 26 | self.tabBarController = [[UITabBarController alloc] init]; 27 | self.tabBarController.viewControllers = @[ [[ExampleMakeLogsViewController alloc] init], 28 | [[ExampleAppConsoleViewController alloc] init], 29 | [[ExampleConfigureViewController alloc] init] ]; 30 | 31 | self.window.rootViewController = self.tabBarController; 32 | self.window.backgroundColor = [UIColor orangeColor]; 33 | if ([self.window respondsToSelector:@selector(setTintColor:)]) { 34 | self.window.tintColor = [UIColor blueColor]; 35 | } 36 | [self.window makeKeyAndVisible]; 37 | 38 | TLSLogInformation(TLSLogChannelDefault, @"%@", NSStringFromSelector(_cmd)); 39 | return YES; 40 | } 41 | 42 | - (void)applicationWillResignActive:(UIApplication *)application 43 | { 44 | TLSLogInformation(TLSLogChannelDefault, @"%@", NSStringFromSelector(_cmd)); 45 | } 46 | 47 | - (void)applicationDidEnterBackground:(UIApplication *)application 48 | { 49 | TLSLogInformation(TLSLogChannelDefault, @"%@", NSStringFromSelector(_cmd)); 50 | } 51 | 52 | - (void)applicationWillEnterForeground:(UIApplication *)application 53 | { 54 | TLSLogInformation(TLSLogChannelDefault, @"%@", NSStringFromSelector(_cmd)); 55 | } 56 | 57 | - (void)applicationDidBecomeActive:(UIApplication *)application 58 | { 59 | TLSLogInformation(TLSLogChannelDefault, @"%@", NSStringFromSelector(_cmd)); 60 | } 61 | 62 | - (void)applicationWillTerminate:(UIApplication *)application 63 | { 64 | TLSLogWarning(TLSLogChannelDefault, @"%@", NSStringFromSelector(_cmd)); 65 | [[TLSLoggingService sharedInstance] flush]; 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /ExampleLogger/ExampleConfigureViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleThirdViewController.h 3 | // ExampleLogger 4 | // 5 | // Created on 12/24/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface ExampleConfigureViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ExampleLogger/ExampleConfigureViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleThirdViewController.m 3 | // ExampleLogger 4 | // 5 | // Created on 12/24/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | 9 | #import "ExampleConfigureViewController.h" 10 | #import "ExampleTextView.h" 11 | #import "TLS_Project.h" 12 | #import "TLSLoggingService+ExampleAdditions.h" 13 | 14 | @interface ExampleConfigureViewController () 15 | @property (nonatomic) UITableView *tableView; 16 | @property (nonatomic) NSArray *channels; 17 | @property (nonatomic) NSArray *levels; 18 | @property (nonatomic) NSArray *masks; 19 | @end 20 | 21 | @implementation ExampleConfigureViewController 22 | 23 | - (instancetype)init 24 | { 25 | if (self = [super initWithNibName:nil bundle:nil]) { 26 | self.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Config" image:[UIImage imageNamed:@"third"] tag:3]; 27 | self.navigationItem.title = @"Configure"; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)loadView 33 | { 34 | [super loadView]; 35 | 36 | self.channels = @[TLSLogChannelDefault, ExampleLogChannelOne, ExampleLogChannelTwo, ExampleLogChannelThree]; 37 | self.levels = @[@"Error", @"Warning", @"Information", @"Debug"]; 38 | self.masks = @[@(TLSLogLevelMaskErrorAndAbove), @(TLSLogLevelMaskWarning), @(TLSLogLevelMaskInformation | TLSLogLevelMaskNotice), @(TLSLogLevelMaskDebug)]; 39 | 40 | self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped]; 41 | self.tableView.autoresizesSubviews = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 42 | self.tableView.delegate = self; 43 | self.tableView.dataSource = self; 44 | self.tableView.backgroundColor = [UIColor whiteColor]; 45 | [self.view addSubview:self.tableView]; 46 | 47 | if ([UIWindow instancesRespondToSelector:@selector(tintColor)]) { 48 | self.tableView.contentInset = UIEdgeInsetsMake(20, 0, 44, 0); 49 | } 50 | } 51 | 52 | - (void)viewDidLoad 53 | { 54 | [super viewDidLoad]; 55 | } 56 | 57 | #pragma mark - UITableViewDelegate/DataSource 58 | 59 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 60 | { 61 | return 2; 62 | } 63 | 64 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 65 | { 66 | return (section == 0) ? self.levels.count : self.channels.count; 67 | } 68 | 69 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 70 | { 71 | return (section == 0) ? @"Log Levels" : @"Log Channels"; 72 | } 73 | 74 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 75 | { 76 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; 77 | if (!cell) { 78 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"]; 79 | } 80 | 81 | NSString *title = nil; 82 | BOOL checked = NO; 83 | if (0 == indexPath.section) { 84 | title = [self.levels objectAtIndex:indexPath.row]; 85 | TLSLogLevelMask mask = [[self.masks objectAtIndex:indexPath.row] integerValue]; 86 | checked = TLS_BITMASK_INTERSECTS_FLAGS([TLSLoggingService sharedInstance].globalLogTextView.permittedLoggingLevels, mask); 87 | } else { 88 | title = [self.channels objectAtIndex:indexPath.row]; 89 | checked = [[TLSLoggingService sharedInstance] isChannelOn:title]; 90 | } 91 | cell.accessoryType = (checked) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone; 92 | cell.textLabel.text = title; 93 | 94 | return cell; 95 | } 96 | 97 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 98 | { 99 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 100 | 101 | UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; 102 | BOOL wasChecked = (UITableViewCellAccessoryCheckmark == cell.accessoryType); 103 | if (0 == indexPath.section) { 104 | TLSLogLevelMask permitted = [TLSLoggingService sharedInstance].globalLogTextView.permittedLoggingLevels; 105 | TLSLogLevelMask mask = [[self.masks objectAtIndex:indexPath.row] integerValue]; 106 | if (wasChecked) { 107 | permitted &= ~mask; 108 | } else { 109 | permitted |= mask; 110 | } 111 | [TLSLoggingService sharedInstance].globalLogTextView.permittedLoggingLevels = permitted; 112 | } else { 113 | NSString *level = [self.channels objectAtIndex:indexPath.row]; 114 | [[TLSLoggingService sharedInstance] setChannel:level on:!wasChecked]; 115 | } 116 | cell.accessoryType = (wasChecked) ? UITableViewCellAccessoryNone : UITableViewCellAccessoryCheckmark; 117 | } 118 | 119 | @end 120 | -------------------------------------------------------------------------------- /ExampleLogger/ExampleLogger-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIRequiresFullScreen 32 | 33 | UIStatusBarTintParameters 34 | 35 | UINavigationBar 36 | 37 | Style 38 | UIBarStyleDefault 39 | Translucent 40 | 41 | 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | UIInterfaceOrientationPortraitUpsideDown 49 | 50 | UISupportedInterfaceOrientations~ipad 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | UIInterfaceOrientationPortraitUpsideDown 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /ExampleLogger/ExampleMakeLogsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleSecondViewController.h 3 | // ExampleLogger 4 | // 5 | // Created on 12/24/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface ExampleMakeLogsViewController : UIViewController 12 | @end 13 | -------------------------------------------------------------------------------- /ExampleLogger/ExampleMakeLogsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleSecondViewController.m 3 | // ExampleLogger 4 | // 5 | // Created on 12/24/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | 9 | #import 10 | 11 | #import "ExampleMakeLogsViewController.h" 12 | #import "TLSLoggingService+ExampleAdditions.h" 13 | 14 | @interface ExampleMakeLogsViewController () 15 | @property (nonatomic) UISegmentedControl *levelControl; 16 | @property (nonatomic) UISegmentedControl *channelControl; 17 | @property (nonatomic) UITextField *logMessageField; 18 | @property (nonatomic) UIButton *logButton; 19 | 20 | @property (nonatomic, copy) NSArray *channels; 21 | @end 22 | 23 | @implementation ExampleMakeLogsViewController 24 | 25 | - (instancetype)init 26 | { 27 | if (self = [super initWithNibName:nil bundle:nil]) { 28 | self.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"+Log" image:[UIImage imageNamed:@"first"] tag:1]; 29 | self.navigationItem.title = @"Add Log"; 30 | } 31 | return self; 32 | } 33 | 34 | - (void)loadView 35 | { 36 | [super loadView]; 37 | self.view.backgroundColor = [UIColor whiteColor]; 38 | CGRect frame; 39 | 40 | self.levelControl = [[UISegmentedControl alloc] initWithItems:@[ @"Debug", @"Info", @"Warning", @"Error"]]; 41 | frame = self.levelControl.frame; 42 | frame.size.width = self.view.bounds.size.width - 20; 43 | frame.origin.x = 10; 44 | frame.origin.y = 10; 45 | if ([UIWindow instancesRespondToSelector:@selector(tintColor)]) { 46 | frame.origin.y += 20; 47 | } 48 | self.levelControl.frame = frame; 49 | [self.levelControl setSelectedSegmentIndex:1]; // Info 50 | [self.view addSubview:self.levelControl]; 51 | 52 | self.channels = @[TLSLogChannelDefault, ExampleLogChannelOne, ExampleLogChannelTwo, ExampleLogChannelThree]; 53 | self.channelControl = [[UISegmentedControl alloc] initWithItems:self.channels]; 54 | NSUInteger appIndex = [self.channels indexOfObject:TLSLogChannelDefault]; 55 | if (appIndex != NSNotFound) { 56 | [self.channelControl setTitle:@"APP" forSegmentAtIndex:0]; 57 | } 58 | [self.channelControl setSelectedSegmentIndex:0]; 59 | frame = self.channelControl.frame; 60 | frame.size.width = self.view.bounds.size.width - 20; 61 | frame.origin.x = 10; 62 | frame.origin.y = 10 + self.levelControl.frame.size.height + self.levelControl.frame.origin.y; 63 | self.channelControl.frame = frame; 64 | [self.view addSubview:self.channelControl]; 65 | 66 | frame.origin.y += frame.size.height + 10; 67 | self.logMessageField = [[UITextField alloc] initWithFrame:frame]; 68 | self.logMessageField.placeholder = @"Your log message here"; 69 | self.logMessageField.borderStyle = UITextBorderStyleRoundedRect; 70 | self.logMessageField.delegate = self; 71 | [self.view addSubview:self.logMessageField]; 72 | 73 | frame.origin.y += frame.size.height + 10; 74 | self.logButton = [UIButton buttonWithType:UIButtonTypeCustom]; 75 | self.logButton.frame = frame; 76 | if ([[UIApplication sharedApplication].keyWindow respondsToSelector:@selector(tintColor)]) { 77 | self.logButton.backgroundColor = [[UIApplication sharedApplication].keyWindow tintColor]; 78 | } else { 79 | self.logButton.backgroundColor = [UIColor blueColor]; 80 | } 81 | self.logButton.layer.cornerRadius = 5; 82 | [self.logButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 83 | [self.logButton setTitleColor:[UIColor grayColor] forState:UIControlStateSelected]; 84 | [self.logButton setTitleColor:[UIColor grayColor] forState:UIControlStateHighlighted]; 85 | [self.logButton setTitleColor:[UIColor grayColor] forState:UIControlStateDisabled]; 86 | [self.logButton setTitle:@"Log Message" forState:UIControlStateNormal]; 87 | [self.logButton addTarget:self action:@selector(didHitButton:) forControlEvents:UIControlEventTouchUpInside]; 88 | [self.view addSubview:self.logButton]; 89 | } 90 | 91 | - (void)viewDidLoad 92 | { 93 | [super viewDidLoad]; 94 | } 95 | 96 | #pragma mark - UITextFieldDelegate 97 | 98 | - (void)textFieldDidBeginEditing:(UITextField *)textField 99 | { 100 | NSLog(@""); 101 | } 102 | 103 | - (BOOL)textFieldShouldReturn:(UITextField *)textField 104 | { 105 | [textField resignFirstResponder]; 106 | return YES; 107 | } 108 | 109 | #pragma mark - Methods 110 | 111 | - (void)didHitButton:(id)sender 112 | { 113 | [self.logMessageField resignFirstResponder]; 114 | TLSLog(self.level, self.channel, @"%@", self.message); 115 | } 116 | 117 | - (TLSLogLevel)level 118 | { 119 | switch (self.levelControl.selectedSegmentIndex) { 120 | case 3: 121 | return TLSLogLevelError; 122 | case 2: 123 | return TLSLogLevelWarning; 124 | case 1: 125 | return TLSLogLevelInformation; 126 | case 0: 127 | default: 128 | return TLSLogLevelDebug; 129 | } 130 | } 131 | 132 | - (NSString *)channel 133 | { 134 | return [self.channels objectAtIndex:self.channelControl.selectedSegmentIndex]; 135 | } 136 | 137 | - (NSString *)message 138 | { 139 | NSString *message = self.logMessageField.text; 140 | if (message.length == 0) { 141 | message = self.logMessageField.placeholder; 142 | if (!message) { 143 | message = @""; 144 | } 145 | } 146 | return message; 147 | } 148 | 149 | @end 150 | -------------------------------------------------------------------------------- /ExampleLogger/ExampleTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleTextView.h 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/24/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | 9 | #import 10 | 11 | @import UIKit; 12 | 13 | // NOTE: for this demo, ExampleTextView will grow it's text content unbounded. This is not a good thing in practice. 14 | @interface ExampleTextView : UITextView 15 | @property (nonatomic) TLSLogLevelMask permittedLoggingLevels; 16 | - (IBAction)clear:(id)sender; 17 | @end 18 | -------------------------------------------------------------------------------- /ExampleLogger/ExampleTextView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleTextView.m 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/24/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | 9 | #import "ExampleTextView.h" 10 | #import "TLSLoggingService+ExampleAdditions.h" 11 | 12 | @implementation ExampleTextView 13 | { 14 | NSString *_buffer; 15 | } 16 | 17 | - (id)initWithFrame:(CGRect)frame 18 | { 19 | self = [super initWithFrame:frame]; 20 | if (self) { 21 | _buffer = @""; 22 | _permittedLoggingLevels = TLSLogLevelMaskAll; 23 | super.editable = NO; 24 | } 25 | return self; 26 | } 27 | 28 | - (void)setEditable:(BOOL)editable 29 | { 30 | // noop 31 | } 32 | 33 | - (IBAction)clear:(id)sender 34 | { 35 | self.text = @""; 36 | [[TLSLoggingService sharedInstance] dispatchAsynchronousTransaction:^{ 37 | self->_buffer = @""; 38 | }]; 39 | } 40 | 41 | #pragma mark - TLSFiltering 42 | 43 | - (void)setPermittedLoggingLevels:(TLSLogLevelMask)permittedLoggingLevels 44 | { 45 | [[TLSLoggingService sharedInstance] updateOutputStream:self]; 46 | } 47 | 48 | - (TLSFilterStatus)tls_shouldFilterLevel:(TLSLogLevel)level channel:(NSString *)channel contextObject:(id)contextObject 49 | { 50 | if (0 == (self.permittedLoggingLevels & (1 << level))) { 51 | return TLSFilterStatusCannotLogLevel; 52 | } 53 | 54 | if (![[TLSLoggingService sharedInstance] isChannelOnViaTransactionQueue:channel]) { 55 | return TLSFilterStatusCannotLogChannel; 56 | } 57 | 58 | return TLSFilterStatusOK; 59 | } 60 | 61 | #pragma mark - TLSOutputStream 62 | 63 | - (void)tls_outputLogInfo:(TLSLogMessageInfo *)logInfo 64 | { 65 | @autoreleasepool { 66 | _buffer = [_buffer stringByAppendingFormat:@"%@\n", logInfo.composeFormattedMessage]; 67 | } 68 | NSString *buffer = _buffer; 69 | dispatch_async(dispatch_get_main_queue(), ^() { 70 | self.text = buffer; 71 | }); 72 | } 73 | 74 | #pragma mark - TLSDataRetrieval 75 | 76 | - (NSData *)tls_retrieveLoggedData:(NSUInteger)maxBytes 77 | { 78 | NSData *data = [_buffer dataUsingEncoding:self.tls_loggedDataEncoding]; 79 | if (data.length > maxBytes) { 80 | data = [data subdataWithRange:NSMakeRange(data.length - maxBytes, maxBytes)]; 81 | } 82 | return data; 83 | } 84 | 85 | - (NSStringEncoding)tls_loggedDataEncoding 86 | { 87 | return NSUTF8StringEncoding; 88 | } 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "size" : "29x29", 20 | "idiom" : "iphone", 21 | "filename" : "icon-5.png", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "size" : "29x29", 26 | "idiom" : "iphone", 27 | "filename" : "icon-4.png", 28 | "scale" : "3x" 29 | }, 30 | { 31 | "size" : "40x40", 32 | "idiom" : "iphone", 33 | "filename" : "icon-3.png", 34 | "scale" : "2x" 35 | }, 36 | { 37 | "size" : "40x40", 38 | "idiom" : "iphone", 39 | "filename" : "icon-2.png", 40 | "scale" : "3x" 41 | }, 42 | { 43 | "size" : "57x57", 44 | "idiom" : "iphone", 45 | "filename" : "icon-57.png", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "size" : "57x57", 50 | "idiom" : "iphone", 51 | "filename" : "icon-57@2x.png", 52 | "scale" : "2x" 53 | }, 54 | { 55 | "size" : "60x60", 56 | "idiom" : "iphone", 57 | "filename" : "icon-2.png", 58 | "scale" : "2x" 59 | }, 60 | { 61 | "size" : "60x60", 62 | "idiom" : "iphone", 63 | "filename" : "icon.png", 64 | "scale" : "3x" 65 | }, 66 | { 67 | "idiom" : "ipad", 68 | "size" : "20x20", 69 | "scale" : "1x" 70 | }, 71 | { 72 | "idiom" : "ipad", 73 | "size" : "20x20", 74 | "scale" : "2x" 75 | }, 76 | { 77 | "idiom" : "ipad", 78 | "size" : "29x29", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "29x29", 83 | "idiom" : "ipad", 84 | "filename" : "icon-5.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "idiom" : "ipad", 89 | "size" : "40x40", 90 | "scale" : "1x" 91 | }, 92 | { 93 | "size" : "40x40", 94 | "idiom" : "ipad", 95 | "filename" : "icon-3.png", 96 | "scale" : "2x" 97 | }, 98 | { 99 | "idiom" : "ipad", 100 | "size" : "50x50", 101 | "scale" : "1x" 102 | }, 103 | { 104 | "idiom" : "ipad", 105 | "size" : "50x50", 106 | "scale" : "2x" 107 | }, 108 | { 109 | "size" : "72x72", 110 | "idiom" : "ipad", 111 | "filename" : "icon-72.png", 112 | "scale" : "1x" 113 | }, 114 | { 115 | "size" : "72x72", 116 | "idiom" : "ipad", 117 | "filename" : "icon-72@2x.png", 118 | "scale" : "2x" 119 | }, 120 | { 121 | "size" : "76x76", 122 | "idiom" : "ipad", 123 | "filename" : "icon-6.png", 124 | "scale" : "1x" 125 | }, 126 | { 127 | "size" : "76x76", 128 | "idiom" : "ipad", 129 | "filename" : "icon-7.png", 130 | "scale" : "2x" 131 | }, 132 | { 133 | "size" : "83.5x83.5", 134 | "idiom" : "ipad", 135 | "filename" : "icon-1.png", 136 | "scale" : "2x" 137 | }, 138 | { 139 | "idiom" : "ios-marketing", 140 | "size" : "1024x1024", 141 | "scale" : "1x" 142 | } 143 | ], 144 | "info" : { 145 | "version" : 1, 146 | "author" : "xcode" 147 | } 148 | } -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-1.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-2.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-3.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-4.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-5.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-57.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-57@2x.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-6.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-7.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-72.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/AppIcon.appiconset/icon-72@2x.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/AppIcon.appiconset/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/AppIcon.appiconset/icon.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "12.0", 8 | "subtype" : "2688h", 9 | "scale" : "3x" 10 | }, 11 | { 12 | "orientation" : "landscape", 13 | "idiom" : "iphone", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "12.0", 16 | "subtype" : "2688h", 17 | "scale" : "3x" 18 | }, 19 | { 20 | "orientation" : "portrait", 21 | "idiom" : "iphone", 22 | "extent" : "full-screen", 23 | "minimum-system-version" : "12.0", 24 | "subtype" : "1792h", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "orientation" : "landscape", 29 | "idiom" : "iphone", 30 | "extent" : "full-screen", 31 | "minimum-system-version" : "12.0", 32 | "subtype" : "1792h", 33 | "scale" : "2x" 34 | }, 35 | { 36 | "orientation" : "portrait", 37 | "idiom" : "iphone", 38 | "extent" : "full-screen", 39 | "minimum-system-version" : "11.0", 40 | "subtype" : "2436h", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "orientation" : "landscape", 45 | "idiom" : "iphone", 46 | "extent" : "full-screen", 47 | "minimum-system-version" : "11.0", 48 | "subtype" : "2436h", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "orientation" : "portrait", 53 | "idiom" : "iphone", 54 | "extent" : "full-screen", 55 | "minimum-system-version" : "8.0", 56 | "subtype" : "736h", 57 | "scale" : "3x" 58 | }, 59 | { 60 | "orientation" : "landscape", 61 | "idiom" : "iphone", 62 | "extent" : "full-screen", 63 | "minimum-system-version" : "8.0", 64 | "subtype" : "736h", 65 | "scale" : "3x" 66 | }, 67 | { 68 | "orientation" : "portrait", 69 | "idiom" : "iphone", 70 | "extent" : "full-screen", 71 | "minimum-system-version" : "8.0", 72 | "subtype" : "667h", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "orientation" : "portrait", 77 | "idiom" : "iphone", 78 | "extent" : "full-screen", 79 | "minimum-system-version" : "7.0", 80 | "scale" : "2x" 81 | }, 82 | { 83 | "orientation" : "portrait", 84 | "idiom" : "iphone", 85 | "extent" : "full-screen", 86 | "minimum-system-version" : "7.0", 87 | "subtype" : "retina4", 88 | "scale" : "2x" 89 | }, 90 | { 91 | "orientation" : "portrait", 92 | "idiom" : "ipad", 93 | "extent" : "full-screen", 94 | "minimum-system-version" : "7.0", 95 | "scale" : "1x" 96 | }, 97 | { 98 | "orientation" : "landscape", 99 | "idiom" : "ipad", 100 | "extent" : "full-screen", 101 | "minimum-system-version" : "7.0", 102 | "scale" : "1x" 103 | }, 104 | { 105 | "orientation" : "portrait", 106 | "idiom" : "ipad", 107 | "extent" : "full-screen", 108 | "minimum-system-version" : "7.0", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "orientation" : "landscape", 113 | "idiom" : "ipad", 114 | "extent" : "full-screen", 115 | "minimum-system-version" : "7.0", 116 | "scale" : "2x" 117 | }, 118 | { 119 | "orientation" : "portrait", 120 | "idiom" : "iphone", 121 | "extent" : "full-screen", 122 | "scale" : "1x" 123 | }, 124 | { 125 | "orientation" : "portrait", 126 | "idiom" : "iphone", 127 | "extent" : "full-screen", 128 | "scale" : "2x" 129 | }, 130 | { 131 | "orientation" : "portrait", 132 | "idiom" : "iphone", 133 | "extent" : "full-screen", 134 | "subtype" : "retina4", 135 | "scale" : "2x" 136 | }, 137 | { 138 | "orientation" : "portrait", 139 | "idiom" : "ipad", 140 | "extent" : "to-status-bar", 141 | "scale" : "1x" 142 | }, 143 | { 144 | "orientation" : "portrait", 145 | "idiom" : "ipad", 146 | "extent" : "full-screen", 147 | "scale" : "1x" 148 | }, 149 | { 150 | "orientation" : "landscape", 151 | "idiom" : "ipad", 152 | "extent" : "to-status-bar", 153 | "scale" : "1x" 154 | }, 155 | { 156 | "orientation" : "landscape", 157 | "idiom" : "ipad", 158 | "extent" : "full-screen", 159 | "scale" : "1x" 160 | }, 161 | { 162 | "orientation" : "portrait", 163 | "idiom" : "ipad", 164 | "extent" : "to-status-bar", 165 | "scale" : "2x" 166 | }, 167 | { 168 | "orientation" : "portrait", 169 | "idiom" : "ipad", 170 | "extent" : "full-screen", 171 | "scale" : "2x" 172 | }, 173 | { 174 | "orientation" : "landscape", 175 | "idiom" : "ipad", 176 | "extent" : "to-status-bar", 177 | "scale" : "2x" 178 | }, 179 | { 180 | "orientation" : "landscape", 181 | "idiom" : "ipad", 182 | "extent" : "full-screen", 183 | "scale" : "2x" 184 | } 185 | ], 186 | "info" : { 187 | "version" : 1, 188 | "author" : "xcode" 189 | } 190 | } -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/first.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "first.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "first@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/first.imageset/first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/first.imageset/first.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/first.imageset/first@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/first.imageset/first@2x.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/second.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "second.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "second@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/second.imageset/second.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/second.imageset/second.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/second.imageset/second@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/second.imageset/second@2x.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/third.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "third.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "third@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/third.imageset/third.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/third.imageset/third.png -------------------------------------------------------------------------------- /ExampleLogger/Images.xcassets/third.imageset/third@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/ios-twitter-logging-service/23522c14d768f3acb4dbf9cdcd08ac8c733eeee3/ExampleLogger/Images.xcassets/third.imageset/third@2x.png -------------------------------------------------------------------------------- /ExampleLogger/TLSLoggingService+ExampleAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLSLoggingService+ExampleAdditions.h 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/24/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | 9 | #import 10 | 11 | FOUNDATION_EXTERN NSString * const ExampleLogChannelOne; 12 | FOUNDATION_EXTERN NSString * const ExampleLogChannelTwo; 13 | FOUNDATION_EXTERN NSString * const ExampleLogChannelThree; 14 | 15 | @class ExampleTextView; 16 | 17 | @interface TLSLoggingService (ExampleAdditions) 18 | 19 | + (void)prepareExample; 20 | 21 | - (ExampleTextView *)globalLogTextView; 22 | - (BOOL)isChannelOn:(NSString *)channel; 23 | - (BOOL)isChannelOnViaTransactionQueue:(NSString *)channel; 24 | - (void)setChannel:(NSString *)channel on:(BOOL)on; 25 | - (void)setChannels:(NSArray *)channels on:(BOOL)on; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /ExampleLogger/TLSLoggingService+ExampleAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // TLSLoggingService+ExampleAdditions.m 3 | // TwitterLoggingService 4 | // 5 | // Created on 12/24/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | 9 | #import "ExampleTextView.h" 10 | #import "TLSLoggingService+ExampleAdditions.h" 11 | 12 | NSString * const ExampleLogChannelOne = @"One"; 13 | NSString * const ExampleLogChannelTwo = @"Two"; 14 | NSString * const ExampleLogChannelThree = @"Three"; 15 | 16 | static ExampleTextView *gTextView = nil; 17 | static NSMutableSet *sOnChannels = nil; 18 | 19 | @interface ExampleNSLogOutputStream : TLSNSLogOutputStream 20 | @end 21 | 22 | @interface ExampleCrashlyticsOutputStream : TLSCrashlyticsOutputStream 23 | @end 24 | 25 | @implementation TLSLoggingService (ExampleAdditions) 26 | 27 | + (void)prepareExample 28 | { 29 | sOnChannels = [[NSMutableSet alloc] init]; 30 | 31 | TLSLoggingService *manager = [TLSLoggingService sharedInstance]; 32 | [manager addOutputStream:[[TLSRollingFileOutputStream alloc] initAndReturnError:NULL]]; 33 | [manager addOutputStream:[[ExampleNSLogOutputStream alloc] init]]; 34 | [manager addOutputStream:[[ExampleCrashlyticsOutputStream alloc] init]]; // no-op since we don't have Crashlytics in the demo 35 | 36 | gTextView = [[ExampleTextView alloc] initWithFrame:CGRectZero]; 37 | [manager addOutputStream:gTextView]; 38 | 39 | [manager setChannels:@[TLSLogChannelDefault, ExampleLogChannelOne, ExampleLogChannelTwo, ExampleLogChannelThree] on:YES]; 40 | } 41 | 42 | - (BOOL)isChannelOnViaTransactionQueue:(NSString *)channel 43 | { 44 | return [sOnChannels containsObject:channel]; 45 | } 46 | 47 | - (BOOL)isChannelOn:(NSString *)channel 48 | { 49 | __block BOOL on; 50 | [self dispatchSynchronousTransaction:^{ 51 | on = [self isChannelOnViaTransactionQueue:channel]; 52 | }]; 53 | return on; 54 | } 55 | 56 | - (void)setChannel:(NSString *)channel on:(BOOL)on 57 | { 58 | [self setChannels:@[channel] on:on]; 59 | } 60 | 61 | - (void)setChannels:(NSArray *)channels on:(BOOL)on 62 | { 63 | [self dispatchAsynchronousTransaction:^{ 64 | for (NSString *channel in channels) { 65 | if (on) { 66 | [sOnChannels addObject:channel]; 67 | } else { 68 | [sOnChannels removeObject:channel]; 69 | } 70 | } 71 | }]; 72 | } 73 | 74 | - (ExampleTextView *)globalLogTextView 75 | { 76 | return gTextView; 77 | } 78 | 79 | @end 80 | 81 | @implementation ExampleNSLogOutputStream 82 | 83 | - (TLSFilterStatus)tls_shouldFilterLevel:(TLSLogLevel)level channel:(NSString *)channel contextObject:(id)contextObject 84 | { 85 | return [gTextView tls_shouldFilterLevel:level channel:channel contextObject:contextObject]; 86 | } 87 | 88 | @end 89 | 90 | @implementation ExampleCrashlyticsOutputStream 91 | 92 | - (TLSFilterStatus)tls_shouldFilterLevel:(TLSLogLevel)level channel:(NSString *)channel contextObject:(id)contextObject 93 | { 94 | if (TLSLogLevelWarning < level) { 95 | return TLSFilterStatusCannotLogLevel; 96 | } 97 | 98 | return TLSFilterStatusOK; 99 | } 100 | 101 | - (void)outputLogMessageToCrashlytics:(nonnull NSString *)message 102 | { 103 | // no-op for this demo 104 | } 105 | 106 | /* 107 | Normally would uncomment below to for the crashlytics subclass 108 | */ 109 | 110 | // TLS_OUTPUTLOGMESSAGETOCRASHLYTICS_DEFAULT_IMPL; 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /ExampleLogger/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /ExampleLogger/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ExampleLogger 4 | // 5 | // Created on 12/24/13. 6 | // Copyright (c) 2016 Twitter, Inc. 7 | // 8 | 9 | #import "ExampleAppDelegate.h" 10 | 11 | int main(int argc, char * argv[]) 12 | { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([ExampleAppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Resources/BuildConfigurations/ExampleLogger.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleLogger.xcconfig 3 | // TwitterLoggingService 4 | // 5 | // Copyright (c) 2017 Twitter, Inc. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | // 21 | // use this configuration for differences from the default target settings that 22 | // require additional documentation 23 | // 24 | 25 | // 26 | // Build Options 27 | 28 | // Xcode 8 and beyond 29 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 30 | 31 | 32 | // 33 | // Deployment 34 | 35 | // Continue allowing the ExampleLogger.app (linking the lib.a) to be built for iOS 10, 36 | // even when future versions of Xcode default to a higher minimum deployment target. 37 | IPHONEOS_DEPLOYMENT_TARGET = 10.0 38 | TVOS_DEPLOYMENT_TARGET = 10.0 39 | WATCHOS_DEPLOYMENT_TARGET = 3.0 40 | MACOSX_DEPLOYMENT_TARGET = 10.12 41 | 42 | // 43 | // Packaging 44 | 45 | INFOPLIST_FILE = $(TARGET_NAME)/$(PRODUCT_NAME)-Info.plist 46 | PRODUCT_BUNDLE_IDENTIFIER = com.twitter.${PRODUCT_NAME:rfc1034identifier} 47 | PRODUCT_NAME = $(TARGET_NAME) 48 | WRAPPER_EXTENSION = app 49 | 50 | 51 | // 52 | // Search Paths 53 | 54 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon 55 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage 56 | 57 | // 58 | // Signing 59 | 60 | // Required to build the sample app for testing 61 | CODE_SIGN_IDENTITY = iPhone Developer 62 | -------------------------------------------------------------------------------- /Resources/BuildConfigurations/TwitterLoggingService.Debug.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterLoggingService.xcconfig 3 | // TwitterLoggingService 4 | // 5 | // Copyright © 2020 Twitter. All rights reserved. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | // 21 | // use this configuration for differences from the default project settings 22 | // (often recommended by Xcode version upgrades). for project settings that 23 | // must differ between Debug configuration & Release configuration, make the 24 | // specific setting change in the project.pbxproj . 25 | // (cf https://pewpewthespells.com/blog/xcconfig_guide.html#CondVarConfig for 26 | // why use of SETTING[config=Debug] does not do what we want.) 27 | 28 | // Configuration settings file format documentation can be found at: 29 | // https://help.apple.com/xcode/#/dev745c5c974 30 | 31 | 32 | #include "TwitterLoggingService.xcconfig" 33 | 34 | 35 | // 36 | // Arhitectures 37 | 38 | ONLY_ACTIVE_ARCH = YES 39 | 40 | // 41 | // Build Options 42 | 43 | // no need for dSYM when building Debug Configuration 44 | DEBUG_INFORMATION_FORMAT = dwarf 45 | 46 | // 47 | // Swift Compiler Flags - Custom Flags 48 | 49 | // when building for DEBUG, the DEBUG flag should be set 50 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) DEBUG 51 | -------------------------------------------------------------------------------- /Resources/BuildConfigurations/TwitterLoggingService.framework.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterLoggingService.framework.xcconfig 3 | // TwitterLoggingService 4 | // 5 | // Copyright (c) 2017 Twitter, Inc. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | // 21 | // use this configuration for differences from the default target settings 22 | // for target settings that must differ between Debug configuration & Release 23 | // configuration, make the specific setting change in the project.pbxproj . 24 | // (cf https://pewpewthespells.com/blog/xcconfig_guide.html#CondVarConfig for 25 | // why use of SETTING[config=Debug] does not do what we want.) 26 | 27 | 28 | // 29 | // Build Options 30 | 31 | APPLICATION_EXTENSION_API_ONLY = YES 32 | 33 | // FB7419630 - unless dwarf-with-dsym explicitly set, no dSYM will be generated for mac catalyst build 34 | DEBUG_INFORMATION_FORMAT[sdk=macosx*] = dwarf-with-dsym 35 | 36 | 37 | // 38 | // Deployment 39 | 40 | INSTALL_PATH = $(LOCAL_LIBRARY_DIR)/Frameworks 41 | 42 | // Continue allowing the TwitterLoggingService.framework to be built for iOS 10 43 | // even when future versions of Xcode suggest a higher default Minimum Deployment Target 44 | IPHONEOS_DEPLOYMENT_TARGET = 10.0 45 | TVOS_DEPLOYMENT_TARGET = 10.0 46 | WATCHOS_DEPLOYMENT_TARGET = 3.0 47 | MACOSX_DEPLOYMENT_TARGET = 10.12 48 | 49 | SKIP_INSTALL = YES 50 | 51 | 52 | // 53 | // Linking 54 | 55 | DYLIB_INSTALL_NAME_BASE = @rpath 56 | LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks 57 | 58 | 59 | // 60 | // Packaging 61 | 62 | DEFINES_MODULE = YES 63 | INFOPLIST_FILE = ${PRODUCT_NAME}/Info.plist 64 | PRODUCT_BUNDLE_IDENTIFIER = com.twitter.$(PRODUCT_NAME:rfc1034identifier) 65 | PRODUCT_NAME = ${PROJECT_NAME} 66 | 67 | 68 | // 69 | // Signing 70 | 71 | CODE_SIGN_STYLE = Manual 72 | 73 | // 74 | // Static Analyzer - Analysis Policy 75 | 76 | RUN_CLANG_STATIC_ANALYZER = $(RUN_CLANG_STATIC_ANALYZER_FOR_$(PRODUCT_NAME)) 77 | 78 | 79 | // 80 | // Versioning 81 | 82 | VERSIONING_SYSTEM = apple-generic 83 | -------------------------------------------------------------------------------- /Resources/BuildConfigurations/TwitterLoggingService.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterLoggingService.xcconfig 3 | // TwitterLoggingService 4 | // 5 | // Copyright © 2020 Twitter. All rights reserved. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | // 21 | // use this configuration for differences from the default project settings 22 | // (often recommended by Xcode version upgrades). for project settings that 23 | // must differ between Debug configuration & Release configuration, make the 24 | // specific setting change in the project.pbxproj . 25 | // (cf https://pewpewthespells.com/blog/xcconfig_guide.html#CondVarConfig for 26 | // why use of SETTING[config=Debug] does not do what we want.) 27 | 28 | // Configuration settings file format documentation can be found at: 29 | // https://help.apple.com/xcode/#/dev745c5c974 30 | 31 | 32 | // 33 | // Arhitectures 34 | 35 | SDKROOT = iphoneos 36 | 37 | // 38 | // Build Options (iOS) 39 | 40 | ENABLE_BITCODE = NO 41 | ENABLE_TESTABILITY = YES 42 | 43 | // 44 | // Deployment (iOS) 45 | 46 | TARGETED_DEVICE_FAMILY = 1,2 47 | 48 | 49 | // 50 | // Linking 51 | 52 | CURRENT_PROJECT_VERSION = 2.9 53 | DYLIB_COMPATIBILITY_VERSION = 2 54 | DYLIB_CURRENT_VERSION = $(CURRENT_PROJECT_VERSION) 55 | OTHER_LDFLAGS = -ObjC 56 | 57 | // 58 | // Search Paths 59 | 60 | // marked "(Deprecated)" in the UI, but still defaults to YES as iOS Default 61 | ALWAYS_SEARCH_USER_PATHS = NO 62 | 63 | 64 | // 65 | // Apple Clang - Code Generation 66 | 67 | GCC_NO_COMMON_BLOCKS = YES 68 | 69 | 70 | // 71 | // Apple Clang - Language - C++ 72 | 73 | GCC_C_LANGUAGE_STANDARD = gnu99 74 | 75 | 76 | // 77 | // Apple Clang - Language - C++ 78 | 79 | CLANG_CXX_LANGUAGE_STANDARD = gnu++0x 80 | 81 | 82 | // 83 | // Apple Clang - Language - Modules 84 | 85 | CLANG_ENABLE_MODULES = YES 86 | 87 | 88 | // 89 | // Apple Clang - Language - Objective-C 90 | 91 | CLANG_ENABLE_OBJC_ARC = YES 92 | 93 | 94 | // 95 | // Apple Clang - Preprocessing 96 | 97 | ENABLE_STRICT_OBJC_MSGSEND = YES 98 | 99 | 100 | // 101 | // Apple Clang - Warnings - All Languages 102 | 103 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES 104 | CLANG_WARN_BOOL_CONVERSION = YES 105 | CLANG_WARN_COMMA = YES 106 | CLANG_WARN_CONSTANT_CONVERSION = YES 107 | CLANG_WARN_EMPTY_BODY = YES 108 | CLANG_WARN_ENUM_CONVERSION = YES 109 | CLANG_WARN_INFINITE_RECURSION = YES 110 | CLANG_WARN_INT_CONVERSION = YES 111 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES 112 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES 113 | CLANG_WARN_STRICT_PROTOTYPES = YES 114 | CLANG_WARN_UNREACHABLE_CODE = YES 115 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES 116 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR 117 | GCC_WARN_SHADOW = YES 118 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE 119 | GCC_WARN_UNUSED_FUNCTION = YES 120 | GCC_WARN_UNUSED_VARIABLE = YES 121 | 122 | 123 | // 124 | // Apple Clang - Warnings - C++ 125 | 126 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES 127 | CLANG_WARN_SUSPICIOUS_MOVE = YES 128 | 129 | 130 | // 131 | // Apple Clang - Warnings - Objective-C 132 | 133 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 134 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES 135 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR 136 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES 137 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR 138 | GCC_WARN_UNDECLARED_SELECTOR = YES 139 | 140 | // 141 | // Apple Clang - Warnings - Objective-C and ARC 142 | 143 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 144 | 145 | 146 | // 147 | // Swift Compiler - Language 148 | 149 | // (code has #if swift(>=5.0) to handle both cases). 150 | // 151 | // default to 5.0 for Xcode versions 10.2 and beyond. 152 | SWIFT_VERSION = 5.0 153 | // but legacy support for using Swift 4.2 when building using Xcode 10.1 154 | // this specific version must come after the default, or Xcode 10.1 will report an error diagnostic 155 | SWIFT_VERSION[sdk=*12.1] = 4.2 // (*12.1) == (iphoneos12.1 || iphonesimulator12.1 || tvos*12.1 et al) 156 | -------------------------------------------------------------------------------- /Resources/BuildConfigurations/TwitterLoggingServiceLibrary.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterLoggingServiceLibrary.xcconfig 3 | // TwitterLoggingService 4 | // 5 | // Copyright (c) 2017 Twitter, Inc. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | // 21 | // use this configuration for differences from the default target settings, 22 | // for target settings that must differ between Debug configuration & Release 23 | // configuration, make the specific setting change in the project.pbxproj . 24 | // (cf https://pewpewthespells.com/blog/xcconfig_guide.html#CondVarConfig for 25 | // why use of SETTING[config=Debug] does not do what we want.) 26 | 27 | 28 | // 29 | // Build Options 30 | 31 | APPLICATION_EXTENSION_API_ONLY = YES 32 | 33 | 34 | // Deployment 35 | 36 | DSTROOT = "/tmp/${PRODUCT_NAME}.dst" 37 | 38 | // Continue allowing the libTwitterLoggingService.a to be built for iOS 10 39 | // even when future versions of Xcode suggest a higher default Minimum Deployment Target 40 | IPHONEOS_DEPLOYMENT_TARGET = 10.0 41 | TVOS_DEPLOYMENT_TARGET = 10.0 42 | WATCHOS_DEPLOYMENT_TARGET = 3.0 43 | MACOSX_DEPLOYMENT_TARGET = 10.12 44 | 45 | SKIP_INSTALL = YES 46 | 47 | 48 | // 49 | // Linking 50 | 51 | LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks 52 | 53 | // 54 | // Packaging 55 | 56 | PRODUCT_NAME = ${PROJECT_NAME} 57 | 58 | 59 | // 60 | // Apple Clang - Language - Modules 61 | 62 | CLANG_ENABLE_MODULES = YES 63 | 64 | 65 | // 66 | // Static Analyzer - Analysis Policy 67 | 68 | RUN_CLANG_STATIC_ANALYZER = $(RUN_CLANG_STATIC_ANALYZER_FOR_$(PRODUCT_NAME)) 69 | -------------------------------------------------------------------------------- /Resources/BuildConfigurations/TwitterLoggingServiceTests.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterLoggingServiceTests.xcconfig 3 | // TwitterLoggingService 4 | // 5 | // Copyright (c) 2017 Twitter, Inc. 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | // 21 | // use this configuration for differences from the default target settings. 22 | // for target settings that must differ between Debug configuration & Release 23 | // configuration, make the specific setting change in the project.pbxproj . 24 | // (cf https://pewpewthespells.com/blog/xcconfig_guide.html#CondVarConfig for 25 | // why use of SETTING[config=Debug] does not do what we want.) 26 | 27 | // 28 | // Architectures 29 | 30 | ONLY_ACTIVE_ARCH = YES 31 | 32 | 33 | // 34 | // Build Options 35 | 36 | // the following 2 settings are used for the same purpose depending upon whether 37 | // being build using {Xcode 7} or {Xcode 8 and beyond} 38 | 39 | // Xcode 8 and beyond 40 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 41 | 42 | DEBUG_INFORMATION_FORMAT = dwarf 43 | 44 | 45 | // 46 | // Code Signing 47 | 48 | CODE_SIGN_IDENTITY[sdk=tvos*] = 49 | 50 | 51 | // 52 | // Deployment 53 | 54 | // Continue allowing the TwitterLoggingServiceTests (linking the .framework) to be built for iOS 10 55 | // even when future versions of Xcode suggest a higher default Minimum Deployment Target 56 | IPHONEOS_DEPLOYMENT_TARGET = 10.0 57 | TVOS_DEPLOYMENT_TARGET = 10.0 58 | WATCHOS_DEPLOYMENT_TARGET = 3.0 59 | MACOSX_DEPLOYMENT_TARGET = 10.12 60 | 61 | 62 | // 63 | // Linking 64 | 65 | LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks 66 | 67 | 68 | // 69 | // Packaging 70 | 71 | INFOPLIST_FILE = ${PRODUCT_NAME}/${PRODUCT_NAME}-Info.plist 72 | PRODUCT_BUNDLE_IDENTIFIER = com.twitter.${PRODUCT_NAME:rfc1034identifier} 73 | PRODUCT_NAME = ${TARGET_NAME} 74 | 75 | 76 | // Signing 77 | 78 | // This is the result of setting "Sign to run locally" in "Signing & Capabilities" for mac (catalyst) 79 | CODE_SIGN_IDENTITY[sdk=macosx*] = - 80 | 81 | 82 | // 83 | // Swift Compiler - General 84 | 85 | SWIFT_OBJC_BRIDGING_HEADER = ${PRODUCT_NAME}/${PRODUCT_NAME}-Bridging-Header.h 86 | 87 | -------------------------------------------------------------------------------- /Resources/module.modulemap: -------------------------------------------------------------------------------- 1 | module TwitterLoggingService { 2 | 3 | export * 4 | 5 | module TLSLog { 6 | header "TLSLog.h" 7 | export * 8 | } 9 | 10 | module TLSConsoleOutputStreams { 11 | header "TLSConsoleOutputStreams.h" 12 | export * 13 | } 14 | 15 | module TLSCrashlyticsOutputStream { 16 | header "TLSCrashlyticsOutputStream.h" 17 | export * 18 | } 19 | 20 | module TLSDeclarations { 21 | header "TLSDeclarations.h" 22 | export * 23 | } 24 | 25 | module TLSFileOutputStream { 26 | header "TLSFileOutputStream.h" 27 | export * 28 | } 29 | 30 | module TLSFileOutputStream_Protected { 31 | header "TLSFileOutputStream+Protected.h" 32 | export * 33 | } 34 | 35 | module TLSLoggingService { 36 | header "TLSLoggingService.h" 37 | export * 38 | } 39 | 40 | module TLSLoggingService_Advanced { 41 | header "TLSLoggingService+Advanced.h" 42 | export * 43 | } 44 | 45 | module TLSProtocols { 46 | header "TLSProtocols.h" 47 | export * 48 | } 49 | 50 | module TLSRollingFileOutputStream { 51 | header "TLSRollingFileOutputStream.h" 52 | export * 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /TwitterLoggingService.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'TwitterLoggingService' 3 | s.version = '2.9.0' 4 | s.summary = 'Twitter Logging Service is a robust and performant logging framework for iOS and macOS' 5 | s.description = 'Twitter created a framework for logging in order to fulfill the numerous needs of Twitter for iOS including being fast, safe, modular and versatile.' 6 | s.homepage = 'https://github.com/twitter/ios-twitter-logging-service' 7 | s.license = { :type => 'Apache License, Version 2.0', :file => 'LICENSE' } 8 | s.author = { 'Twitter' => 'opensource@twitter.com' } 9 | s.source = { :git => 'https://github.com/twitter/ios-twitter-logging-service.git', :tag => s.version.to_s } 10 | s.ios.deployment_target = '10.0' 11 | s.swift_versions = [ 5.0 ] 12 | 13 | s.subspec 'Default' do |sp| 14 | sp.source_files = 'Classes/**/*' 15 | sp.public_header_files = 'Classes/**/*.h' 16 | end 17 | 18 | s.subspec 'ObjC' do |sp| 19 | sp.source_files = 'Classes/**/*.{h,m,c,cpp,mm}' 20 | sp.public_header_files = 'Classes/**/*.h' 21 | end 22 | 23 | s.default_subspec = 'Default' 24 | end 25 | -------------------------------------------------------------------------------- /TwitterLoggingService.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TwitterLoggingService.xcodeproj/xcshareddata/xcschemes/ExampleLogger.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /TwitterLoggingService.xcodeproj/xcshareddata/xcschemes/TwitterLoggingService macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 77 | 83 | 84 | 85 | 86 | 92 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /TwitterLoggingService.xcodeproj/xcshareddata/xcschemes/TwitterLoggingService.framework tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /TwitterLoggingService.xcodeproj/xcshareddata/xcschemes/TwitterLoggingService.framework.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 77 | 83 | 84 | 85 | 86 | 92 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /TwitterLoggingService.xcodeproj/xcshareddata/xcschemes/TwitterLoggingService.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /TwitterLoggingService/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(CURRENT_PROJECT_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /TwitterLoggingServiceExt/TLSExt.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLSExt.h 3 | // TwitterLoggingService 4 | // 5 | // Created on 6/28/19. 6 | // Copyright © 2020 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @protocol TLSExtOSLogActivityMonitor; 14 | @class TLSLogMessageInfo; 15 | 16 | //! Domain for `TLSExt` errors 17 | FOUNDATION_EXTERN NSErrorDomain const TLSExtErrorDomain; 18 | 19 | //! block for providing a log message info to log 20 | typedef void(^TLSExtOSLogActivityLogMessageBlock)(TLSLogMessageInfo *info); 21 | 22 | //! Set the start time of `os_log` entries (can be rough estimate). For computing the relative time elapsed of any given entry. Leaving unset will use the first encountered `os_log` entry's timestamp (until this function is called). 23 | FOUNDATION_EXTERN void TLSExtSetOSLogActivityStartTimestamp(NSDate *timestamp); 24 | 25 | /** 26 | Register a `TLSExtOSLogActivityMonitor` for monitoring `os_log` messages. 27 | The registration process will also load the private __LoggingSupport.framework__. 28 | @param monitor the `TLSExtOSLogActivityMonitor` to observe events with 29 | @param outError an `NSError` if registration failed 30 | @return returns `YES` if the _monitor_ could register successfully. `NO` if registration failed. 31 | @warning Since this function requires using a private Apple framework, this should ONLY ever be used by non-production apps. Running this code (and potentially even just compiling it!) will result in the app being rejected by Apple. For development purposes ONLY! See `TLSLogExt`. 32 | */ 33 | FOUNDATION_EXTERN BOOL TLSExtRegisterOSLogActivityMonitor(id monitor, NSError * __nullable * __nullable outError); 34 | 35 | //! Convenience function to create a `TLSExtOSLogActivityMonitor` instance. See `TLSExtOSLogActivityMonitor`. 36 | FOUNDATION_EXTERN id TLSExtOLSLogActivityMonitorCreate(NSString * __nullable defaultChannel, 37 | TLSExtOSLogActivityLogMessageBlock __nullable logMessageBlock, 38 | TLSExtOSLogActivityLogMessageBlock __nullable activityStreamEventBlock) NS_RETURNS_RETAINED; 39 | 40 | /** 41 | A protocol for monitoring `os_log` entries. 42 | 43 | Currenly supports _log message_ and _activity stream event_ entries. 44 | 45 | @note log message entries that are _debug_ level will not be passed to the monitor (just too verbose). 46 | @note log message entries that come from `NSLog` will not be passed to the monitor. 47 | 48 | @warning all source messages are from `os_log` so it is critical that no output messages go to 49 | `os_log` or you will hit an infinite loop! Take care you do not log these messages to 50 | `TLSOSLogOutputStream`. `TLSNSLogOutputStream` will be fine since `NSLog` messages are filtered 51 | out by the __TLSExt__ system that captures `os_log` messages. 52 | */ 53 | @protocol TLSExtOSLogActivityMonitor 54 | 55 | @optional 56 | 57 | /** 58 | default channel for the `TLSLogMessageInfo` if a channel cannot be surmised. 59 | If not implemented or `nil`, will fall back to `@"OSLog"`. 60 | */ 61 | - (nullable NSString *)tlsext_defaultChannel; 62 | 63 | /** callback when a _log message_ entry is encountered */ 64 | - (void)tlsext_logMessage:(TLSLogMessageInfo *)info; 65 | 66 | /** callback when an _activity stream event_ occurs */ 67 | - (void)tlsext_activityStreamEvent:(TLSLogMessageInfo *)info; 68 | 69 | // TODO: add support for additional entries like trace messages 70 | 71 | @end 72 | 73 | #if APPLEDOC 74 | /** 75 | # __Twitter Logging Service Extensions__ (aka __TLSExt__) 76 | 77 | __TLSExt__ provides extra functionality outside of __TLS__ itself and can be compiled and linked by 78 | any app consuming __TLS__. 79 | 80 | The features of __TLSExt__ are tied to Private frameworks from Apple and are therefore deemed 81 | _unsafe_ for shipping with production code. 82 | 83 | @warning Only compile / link __TLSExt__ into non-production builds! Compiling/linking __TLSExt__ into production builds __will__ result in app store rejection! 84 | 85 | @note __TLSExt__ depends on __TLS__, but it would be fairly simple to change the `TLSLogMessageInfo` uses into something that doesn't depend on __TLS__ if you want to capture `os_log` activity without depending on __TLS__. 86 | 87 | ## TLSExt Errors 88 | 89 | FOUNDATION_EXTERN NSString * const TLSExtErrorDomain; 90 | 91 | ## os_log activity monitoring 92 | 93 | __TLSExt__ provides APIs to observe `os_log` activity. This is a great feature when capturing logs 94 | to debug issues that might only be seen in the logs from Apple frameworks, or if `os_log` ends up 95 | being a logging mechanism for an app. 96 | 97 | At Twitter, we elect to capture the `os_log` activity and write it to disk in a rolling log. When 98 | an employee encounters an issue and they file a bug report from our dogfood app, these logs (among 99 | others) are zipped up and sent to our bug reporting system. We do not link __TLSExt__ at all in 100 | production -- it is strictly a tool for non-production builds. 101 | 102 | ### Set the start time for activity monitoring 103 | 104 | Call `TLSExtSetOSLogActivityStartTimestamp` with and `NSDate` timestamp to set the starting time 105 | when `os_log` activity started (can be rough estimate). Leaving unset will use the first encountered `os_log` entry's timestamp (until this function is called). 106 | 107 | FOUNDATION_EXTERN void TLSExtSetOSLogActivityStartTimestamp(NSDate *timestamp); 108 | 109 | ### Create an os_log activity monitor instance 110 | 111 | Use `TLSExtOLSLogActivityMonitorCreate` convenience function to create a `TLSExtOSLogActivityMonitor` 112 | instance. See `TLSExtOSLogActivityMonitor`. 113 | 114 | FOUNDATION_EXTERN id \ TLSExtOLSLogActivityMonitorCreate( 115 | NSString * __nullable defaultChannel, 116 | TLSExtOSLogActivityLogMessageBlock __nullable logMessageBlock, 117 | TLSExtOSLogActivityLogMessageBlock __nullable activityStreamEventBlock 118 | ) NS_RETURNS_RETAINED; 119 | 120 | Or instantiate a custom implementation of `TLSExtOSLogActivityMonitor` protocol 121 | 122 | ### Registering an os_log activity monitor 123 | 124 | Register a `TLSExtOSLogActivityMonitor` for monitoring `os_log` messages with `TLSExtRegisterOSLogActivityMonitor` 125 | 126 | FOUNDATION_EXTERN BOOL TLSExtRegisterOSLogActivityMonitor(id \ monitor, 127 | NSError * __nullable * __nullable outError); 128 | 129 | Returns `YES` if the _monitor_ could register successfully. `NO` if registration failed. 130 | 131 | ### CFNETWORK_DIAGNOSTICS monitoring 132 | 133 | With __TLSExt__ support of `os_log` monitoring, it is now possible to easily capture `CFNetwork` 134 | logs to help debug while developing. What used to require a special profile installed on a specific 135 | device followed by a sysdiagnose (takes 10 minutes to gather!), now can just be captured in whatever 136 | logs you prefer. 137 | 138 | To enable `CFNetwork` logs, you must set the `"CFNETWORK_DIAGNOSTICS"` environment variable to a 139 | desired logging level (`"1"`, `"2"` or `"3"`), and it must be done _before_ any `NSURLSession` 140 | instances are instantiated. 141 | 142 | setenv("CFNETWORK_DIAGNOSTICS", "1", 1); 143 | 144 | 3 ways to do it: 145 | 146 | 1. Set the environment variable before running the given app (edit target app's scheme: under Run > Arguments > Environment Variables). 147 | 2. Set the environment variable early during execution, such as during the `main` function starting. 148 | 3. Set the environment variable with a C constructor (so it runs before `main`). 149 | 150 | */ 151 | NS_ROOT_CLASS @interface TLSExt 152 | @end 153 | #endif 154 | 155 | NS_ASSUME_NONNULL_END 156 | -------------------------------------------------------------------------------- /TwitterLoggingServiceExt/TLSExtDefinitions.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | University of Illinois/NCSA 4 | Open Source License 5 | 6 | Copyright (c) 2010 Apple Inc. 7 | All rights reserved. 8 | 9 | Developed by: 10 | 11 | LLDB Team 12 | 13 | http://lldb.llvm.org/ - https://github.com/limneos/oslog 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of 16 | this software and associated documentation files (the "Software"), to deal with 17 | the Software without restriction, including without limitation the rights to 18 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 19 | of the Software, and to permit persons to whom the Software is furnished to do 20 | so, subject to the following conditions: 21 | 22 | * Redistributions of source code must retain the above copyright notice, 23 | this list of conditions and the following disclaimers. 24 | 25 | * Redistributions in binary form must reproduce the above copyright notice, 26 | this list of conditions and the following disclaimers in the 27 | documentation and/or other materials provided with the distribution. 28 | 29 | * Neither the names of the LLDB Team, copyright holders, nor the names of 30 | its contributors may be used to endorse or promote products derived from 31 | this Software without specific prior written permission. 32 | 33 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 34 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 35 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 36 | CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 37 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 38 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE 39 | SOFTWARE. 40 | 41 | *******************************************************************************/ 42 | 43 | #ifndef TLSExtDefinitions_h 44 | #define TLSExtDefinitions_h 45 | 46 | #include 47 | 48 | #define TLS_OS_ACTIVITY_MAX_CALLSTACK 32 49 | 50 | // Enums 51 | 52 | typedef NS_OPTIONS(uint32_t, tls_os_activity_stream_flags_t) { 53 | TLS_OS_ACTIVITY_STREAM_PROCESS_ONLY = 0x00000001, 54 | TLS_OS_ACTIVITY_STREAM_SKIP_DECODE = 0x00000002, 55 | TLS_OS_ACTIVITY_STREAM_PAYLOAD = 0x00000004, 56 | TLS_OS_ACTIVITY_STREAM_HISTORICAL = 0x00000008, 57 | TLS_OS_ACTIVITY_STREAM_CALLSTACK = 0x00000010, 58 | TLS_OS_ACTIVITY_STREAM_DEBUG = 0x00000020, 59 | TLS_OS_ACTIVITY_STREAM_BUFFERED = 0x00000040, 60 | TLS_OS_ACTIVITY_STREAM_NO_SENSITIVE = 0x00000080, 61 | TLS_OS_ACTIVITY_STREAM_INFO = 0x00000100, 62 | TLS_OS_ACTIVITY_STREAM_PROMISCUOUS = 0x00000200, 63 | TLS_OS_ACTIVITY_STREAM_PRECISE_TIMESTAMPS = 0x00000200 64 | }; 65 | 66 | typedef NS_ENUM(uint32_t, tls_os_activity_stream_type_t) { 67 | TLS_OS_ACTIVITY_STREAM_TYPE_ACTIVITY_CREATE = 0x0201, 68 | TLS_OS_ACTIVITY_STREAM_TYPE_ACTIVITY_TRANSITION = 0x0202, 69 | TLS_OS_ACTIVITY_STREAM_TYPE_ACTIVITY_USERACTION = 0x0203, 70 | 71 | TLS_OS_ACTIVITY_STREAM_TYPE_TRACE_MESSAGE = 0x0300, 72 | 73 | TLS_OS_ACTIVITY_STREAM_TYPE_LOG_MESSAGE = 0x0400, 74 | TLS_OS_ACTIVITY_STREAM_TYPE_LEGACY_LOG_MESSAGE = 0x0480, 75 | 76 | TLS_OS_ACTIVITY_STREAM_TYPE_SIGNPOST_BEGIN = 0x0601, 77 | TLS_OS_ACTIVITY_STREAM_TYPE_SIGNPOST_END = 0x0602, 78 | TLS_OS_ACTIVITY_STREAM_TYPE_SIGNPOST_EVENT = 0x0603, 79 | 80 | TLS_OS_ACTIVITY_STREAM_TYPE_STATEDUMP_EVENT = 0x0A00, 81 | }; 82 | 83 | typedef NS_ENUM(uint32_t, tls_os_activity_stream_event_t) { 84 | TLS_OS_ACTIVITY_STREAM_EVENT_STARTED = 1, 85 | TLS_OS_ACTIVITY_STREAM_EVENT_STOPPED = 2, 86 | TLS_OS_ACTIVITY_STREAM_EVENT_FAILED = 3, 87 | TLS_OS_ACTIVITY_STREAM_EVENT_CHUNK_STARTED = 4, 88 | TLS_OS_ACTIVITY_STREAM_EVENT_CHUNK_FINISHED = 5, 89 | }; 90 | 91 | // Types 92 | 93 | typedef uint64_t tls_os_activity_id_t; 94 | typedef struct tls_os_activity_stream_s *tls_os_activity_stream_t; 95 | typedef struct tls_os_activity_stream_entry_s *tls_os_activity_stream_entry_t; 96 | 97 | #define TLS_OS_ACTIVITY_STREAM_COMMON() \ 98 | uint64_t trace_id; \ 99 | uint64_t timestamp; \ 100 | uint64_t thread; \ 101 | const uint8_t *image_uuid; \ 102 | const char *image_path; \ 103 | struct timeval tv_gmt; \ 104 | struct timezone tz; \ 105 | uint32_t offset 106 | 107 | typedef struct tls_os_activity_stream_common_s { 108 | TLS_OS_ACTIVITY_STREAM_COMMON(); 109 | } * tls_os_activity_stream_common_t; 110 | 111 | struct tls_os_activity_create_s { 112 | TLS_OS_ACTIVITY_STREAM_COMMON(); 113 | const char *name; 114 | tls_os_activity_id_t creator_aid; 115 | uint64_t unique_pid; 116 | }; 117 | 118 | struct tls_os_activity_transition_s { 119 | TLS_OS_ACTIVITY_STREAM_COMMON(); 120 | tls_os_activity_id_t transition_id; 121 | }; 122 | 123 | typedef struct tls_os_log_message_s { 124 | TLS_OS_ACTIVITY_STREAM_COMMON(); 125 | const char *format; 126 | const uint8_t *buffer; 127 | size_t buffer_sz; 128 | const uint8_t *privdata; 129 | size_t privdata_sz; 130 | const char *subsystem; 131 | const char *category; 132 | uint32_t oversize_id; 133 | uint8_t ttl; 134 | bool persisted; 135 | } * tls_os_log_message_t; 136 | 137 | typedef struct tls_os_trace_message_v2_s { 138 | TLS_OS_ACTIVITY_STREAM_COMMON(); 139 | const char *format; 140 | const void *buffer; 141 | size_t bufferLen; 142 | void *payload; 143 | } * tls_os_trace_message_v2_t; 144 | 145 | typedef struct tls_os_activity_useraction_s { 146 | TLS_OS_ACTIVITY_STREAM_COMMON(); 147 | const char *action; 148 | bool persisted; 149 | } * tls_os_activity_useraction_t; 150 | 151 | typedef struct tls_os_signpost_s { 152 | TLS_OS_ACTIVITY_STREAM_COMMON(); 153 | const char *format; 154 | const uint8_t *buffer; 155 | size_t buffer_sz; 156 | const uint8_t *privdata; 157 | size_t privdata_sz; 158 | const char *subsystem; 159 | const char *category; 160 | uint64_t duration_nsec; 161 | uint32_t callstack_depth; 162 | uint64_t callstack[TLS_OS_ACTIVITY_MAX_CALLSTACK]; 163 | } * tls_os_signpost_t; 164 | 165 | typedef struct tls_os_activity_statedump_s { 166 | TLS_OS_ACTIVITY_STREAM_COMMON(); 167 | char *message; 168 | size_t message_size; 169 | char image_path_buffer[PATH_MAX]; 170 | } * tls_os_activity_statedump_t; 171 | 172 | struct tls_os_activity_stream_entry_s { 173 | tls_os_activity_stream_type_t type; 174 | 175 | // information about the process streaming the data 176 | pid_t pid; 177 | uint64_t proc_id; 178 | const uint8_t *proc_imageuuid; 179 | const char *proc_imagepath; 180 | 181 | // the activity associated with this streamed event 182 | tls_os_activity_id_t activity_id; 183 | tls_os_activity_id_t parent_id; 184 | 185 | union { 186 | struct tls_os_activity_stream_common_s common; 187 | struct tls_os_activity_create_s activity_create; 188 | struct tls_os_activity_transition_s activity_transition; 189 | struct tls_os_log_message_s log_message; 190 | struct tls_os_trace_message_v2_s trace_message; 191 | struct tls_os_activity_useraction_s useraction; 192 | struct tls_os_signpost_s signpost; 193 | struct tls_os_activity_statedump_s statedump; 194 | }; 195 | 196 | }; 197 | 198 | // Blocks 199 | 200 | typedef bool (^tls_os_activity_stream_block_t)(tls_os_activity_stream_entry_t entry, 201 | int error); 202 | 203 | typedef void (^tls_os_activity_stream_event_block_t)(tls_os_activity_stream_t stream, 204 | tls_os_activity_stream_event_t event); 205 | 206 | // Functions 207 | 208 | typedef tls_os_activity_stream_t (*tls_os_activity_stream_for_pid_t)(pid_t pid, 209 | tls_os_activity_stream_flags_t flags, 210 | tls_os_activity_stream_block_t stream_block); 211 | 212 | typedef void (*tls_os_activity_stream_resume_t)(tls_os_activity_stream_t stream); 213 | 214 | typedef void (*tls_os_activity_stream_cancel_t)(tls_os_activity_stream_t stream); 215 | 216 | typedef char *(*tls_os_log_copy_formatted_message_t)(tls_os_log_message_t log_message); 217 | 218 | typedef void (*tls_os_activity_stream_set_event_handler_t)(tls_os_activity_stream_t stream, 219 | tls_os_activity_stream_event_block_t block); 220 | 221 | #endif /* TLSExtDefinitions_h */ 222 | -------------------------------------------------------------------------------- /TwitterLoggingServiceTests/TwitterLoggingServiceTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | -------------------------------------------------------------------------------- /TwitterLoggingServiceTests/TwitterLoggingServiceTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | function ci_lib() { 6 | NAME=$1 7 | xcodebuild -project TwitterLoggingService.xcodeproj \ 8 | -scheme "TwitterLoggingService" \ 9 | -destination "platform=iOS Simulator,name=${NAME}" \ 10 | -sdk iphonesimulator \ 11 | build test 12 | } 13 | 14 | function ci_demo() { 15 | NAME=$1 16 | xcodebuild -project TwitterLoggingService.xcodeproj \ 17 | -scheme "ExampleLogger" \ 18 | -destination "platform=iOS Simulator,name=${NAME}" \ 19 | -sdk iphonesimulator \ 20 | build 21 | } 22 | 23 | ci_lib "iPhone 8" && ci_demo "iPhone 8" 24 | --------------------------------------------------------------------------------