Elegant and extensive logging facility for OS X & iOS (includes database, Telnet and HTTP servers)

Related tags

Logging XLFacility
Overview

Overview

Build Status Version Platform License

XLFacility, which stands for Extensive Logging Facility, is an elegant and powerful logging facility for OS X & iOS. It was written from scratch with the following goals in mind:

  • Drop-in replacement of NSLog() along with trivial to use macros to log messages anywhere in your app without impacting performance
  • Support a wide variety of logging destinations aka "loggers"
  • Customizable logging formats
  • Modern, clean and compact codebase fully taking advantage of the latest Obj-C runtime and Grand Central Dispatch
  • Easy to understand architecture with the ability to write custom loggers in a few lines of code
  • No dependencies on third-party source code
  • Available under a friendly New BSD License

Built-in loggers:

  • Standard output and standard error
  • Apple System Logger
  • Local file
  • Local SQLite database
  • Telnet server which can be accessed from a terminal on a different computer to monitor log messages as they arrive
  • HTTP server which can be accessed from a web browser on a different computer to browse the past log messages and see live updates
  • Raw TCP connection which can send log messages to a remote server as they happen
  • User interface logging window overlay for OS X & iOS apps

Requirements:

  • OS X 10.8 or later (x86_64)
  • iOS 8.0 or later (armv7, armv7s or arm64)
  • ARC memory management only

Getting Started

Download or check out the latest release of XLFacility then add the "XLFacility", "GCDTelnetServer/GCDTelnetServer" and "GCDTelnetServer/GCDNetworking/GCDNetworking" subfolders to your Xcode project.

Alternatively, you can install XLFacility using CocoaPods by simply adding this line to your Xcode project's Podfile:

pod "XLFacility", "~> 1.0"

This provides all loggers including the server ones but if you want just the "core" of XLFacility and the basic loggers, use instead:

pod "XLFacility/Core", "~> 1.0"

Drop-in NSLog Replacement

In the precompiled header file for your Xcode project, insert the following:

#ifdef __OBJC__
#import "XLFacilityMacros.h"
#define NSLog(...) XLOG_INFO(__VA_ARGS__)
#endif

From this point on, any calls to NSLog() in your app source code to log a message will be replaced by ones to XLFacility. Note that this will not affect calls to NSLog() done by Apple frameworks or third-party libraries in your app (see "Capturing Stderr and Stdout" further in this document for a potential solution).

Test-Driving Your (Modified) App

So far nothing has really changed on the surface except that when running your app from Xcode, messages logged with NSLog() now appear in the console like this:

00:00:00.248 [INFO     ]> Hello World!

While previously they would look like that:

2014-10-12 02:41:29.842 TestApp[37006:2455985] Hello World!

That's the first big difference between XLFacility and NSLog(): you can customize the output to fit your taste. Try adding #import "XLStandardLogger.h" to the top of the main.m file of your app and then inserting this line inside main() before UIApplication or NSApplication gets called:

[[XLStandardLogger sharedErrorLogger] setFormat:XLLoggerFormatString_NSLog];

Run your app again and notice how messages in the console now look exactly like when using NSLog().

Let's use a custom compact format instead:

[[XLStandardLogger sharedErrorLogger] setFormat:@"[%l | %q] %m"];

Run your app again and messages in the Xcode console should now look like this:

[INFO | com.apple.main-thread] Hello World!

See XLLogger.h for the full list of format specifiers supported by XLFacility.

Logging Messages With XLFacility

Like pretty much all logging systems, XLFacility defines various logging levels, which are by order of importance: DEBUG, VERBOSE, INFO, WARNING, ERROR, EXCEPTION and ABORT. The idea is that when logging a message, you also provide the corresponding importance level: for instance VERBOSE to trace and help debug what is happening in the code, versus WARNING and above to report actual issues. The logging system can then be configured to "drop" messages that are below a certain level, allowing the user to control the "signal-to-noise" ratio.

By default, when building your app in "Release" configuration, XLFacility ignores messages at the DEBUG and VERBOSE levels. When building in "Debug" configuration (requires the DEBUG preprocessor constant evaluating to non-zero), it keeps everything.

IMPORTANT: So far you've seen how to "override" NSLog() calls in your source code to redirect messages to XLFacility at the INFO level but this is not the best approach. Instead don't use NSLog() at all but call directly XLFacility functions to log messages.

You can log messages in XLFacility by calling the logging methods on the shared XLFacility instance or by using the macros from XLFacilityMacros.h. The latter is highly recommended as macros produce the exact same logging results but are quite easier to the eye, faster to type, and most importantly they avoid evaluating their arguments unless necessary.

The following macros are available to log messages at various levels:

  • XLOG_DEBUG(...): Becomes a no-op if building "Release" (i.e. if the DEBUG preprocessor constant evaluates to zero)
  • XLOG_VERBOSE(...)
  • XLOG_INFO(...)
  • XLOG_WARNING(...)
  • XLOG_ERROR(...)
  • XLOG_EXCEPTION(__EXCEPTION__): Takes an NSException and not a format string (the message is generated automatically from the exception)
  • XLOG_ABORT(...): Calls abort() to immediately terminate the app after logging the message

When calling the macros, except for XLOG_EXCEPTION(), use the standard format specifiers from Obj-C like in NSLog(), +[NSString stringWithFormat:...], etc... For instance:

XLOG_WARNING(@"Unable to load URL \"%@\": %@", myURL, myError);

Other useful macros available to use in your source code:

  • XLOG_CHECK(__CONDITION__): Checks a condition and if false calls XLOG_ABORT() with an automatically generated message
  • XLOG_UNREACHABLE(): Calls XLOG_ABORT() with an automatically generated message if the app reaches this point
  • XLOG_DEBUG_CHECK(__CONDITION__): Same as XLOG_CHECK() but becomes a no-op if building in "Release" configuration (i.e. if the DEBUG preprocessor constant evaluates to zero)
  • XLOG_DEBUG_UNREACHABLE(): Same as XLOG_UNREACHABLE() but becomes a no-op if building in "Release" configuration (i.e. if the DEBUG preprocessor constant evaluates to zero)

Here are some example use cases:

- (void)processString:(NSString*)string {
  XLOG_CHECK(string);  // Passing a nil string is a serious programming error and we can't continue
  // Do something
}

- (void)checkString:(NSString*)string {
  if ([string hasPrefix:@"foo"]) {
    // Do something
  } else if ([string hasPrefix:@"bar"]) {
    // Do something
  } else {
    XLOG_DEBUG_UNREACHABLE();  // This should never happen
  }
}

Messages logged with XLFacility can be associated with an optional tag which is an arbitrary string. This is a powerful feature that lets you for instance capture as part of the log message the source file name and line number. See XLFacilityMacros.h for more information.

Fun With Remote Logging

Going back to the main.m file of your app, add #import "XLTelnetServerLogger.h" to the top, and insert this line before UIApplication or NSApplication gets called:

[XLSharedFacility addLogger:[[XLTelnetServerLogger alloc] init]];

What we are doing here is adding a secondary "logger" to XLFacility so that log messages are sent to two destinations simultaneously.

Run your app locally on your computer (use the iOS Simulator for an iOS app) then enter his command in Terminal app:

telnet localhost 2323

You should see this output on screen:

$ telnet localhost 2323
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
You are connected to TestApp[37006] (in color!)

Any call to NSLog() in your app's source code is now being sent live to your Terminal window. And when you connect to your app, as a convenience to make sure you haven't missed anything, XLTelnetServerLogger will immediately replay all messages logged since the app was launched (this behavior can be changed).

What's really interesting and useful is connecting to your app while it's running on another Mac or on a real iPhone / iPad. As long as your home / office / WiFi network doesn't block communication on port 2323 (the default port used by XLTelnetServerLogger), you should be able to remotely connect by simply entering telnet YOUR_DEVICE_IP_ADDRESS 2323 in Terminal on your computer.

Of course, like you've already done above with XLStandardLogger, you can customize the format used by XLTelnetServerLogger, for instance like this:

XLLogger* logger = [[XLTelnetServerLogger alloc] init];
logger.format = @"[%l | %q] %m";
[XLSharedFacility addLogger:logger];

You can even add multiples instances of XLTelnetServerLogger to XLFacility, each listening on a unique port and configured differently.

IMPORTANT: It's not recommended that you ship your app on the App Store with XLTelnetServerLogger active by default as this could be a security and / or privacy issue for your users. Since you can add and remove loggers at any point during the lifecyle of your app, you can instead expose a user interface setting that will dynamically add or remove XLTelnetServerLogger from XLFacility.

Log Monitoring From Your Web Browser

Do the same modification as you've done above to add suport for XLTelnetServerLogger but use XLHTTPServerLogger instead. When your app is running go to http://127.0.0.1:8080/ or http://YOUR_DEVICE_IP_ADDRESS:8080/ in your web browser. You should be able to see all the XLFacility log messages from your app since it started. The web page will even automatically refresh when new log messages are available.

IMPORTANT: For the same reasons than for XLTelnetServerLogger, it's also not recommended that you ship your app on the App Store with XLHTTPServerLogger active by default.

Onscreen Logging Overlay

On OS X & iOS apps you can easily have an overlay logging window that appears whenever log messages are sent to XLFacility. Simply take advantage of XLUIKitOverlayLogger or XLAppKitOverlayLogger like this:

iOS version

#import "XLUIKitOverlayLogger.h"

@implementation MyAppDelegate

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  [XLSharedFacility addLogger:[XLUIKitOverlayLogger sharedLogger]];
  
  // Rest of your app initialization code goes here
}

@end

OS X version

#import "XLAppKitOverlayLogger.h"

@implementation MyAppDelegate

- (void)applicationDidFinishLaunching:(NSNotification*)notification {
  [XLSharedFacility addLogger:[XLAppKitOverlayLogger sharedLogger]];
  
  // Rest of your app initialization code goes here
}

@end

Archiving Log Messages

There are a couple ways to save persistently across app relaunches the log messages sent to XLFacility:

The simplest solution is to use XLFileLogger to save log messages to a plain text file like this:

XLFileLogger* fileLogger = [[XLFileLogger alloc] initWithFilePath:@"my-file.log" append:YES];
fileLogger.minLogLevel = kXLLogLevel_Error;
fileLogger.format = @"%d\t%m";
[XLSharedFacility addLogger:fileLogger];

The more powerful solution is to use XLDatabaseLogger which uses a SQLite database under the hood:

XLDatabaseLogger* databaseLogger = [[XLDatabaseLogger alloc] initWithDatabasePath:@"my-database.db" appVersion:0];
[XLSharedFacility addLogger:databaseLogger];

Note that XLDatabaseLogger serializes the log messages to the database as-is and does not format them i.e. its format property has no effect.

You can easily "replay" later the saved log messages, for instance to display them in a log window in your application interface or to send them to a server:

[databaseLogger enumerateRecordsAfterAbsoluteTime:0.0
                                         backward:NO
                                       maxRecords:0
                                       usingBlock:^(int appVersion, XLLogRecord* record, BOOL* stop) {
  // Do something with each log record
  printf("%s\n", [record.message UTF8String]);
}];

Filtering XLFacility Log Messages

Use the minLogLevel property on the XLFacility shared instance to have XLFacility ignore all log messages below a certain level.

You can also control the minimum and maximum log level on each logger using their minLogLevel and maxLogLevel properties. You can even set a fully custom log record filter on a logger like this:

myLogger.logRecordFilter = ^BOOL(XLLogger* logger, XLLogRecord* record) {
  return [record.tag hasPrefix:@"com.my-app."];
};

Logging Exceptions

Call [XLSharedFacility setLogsUncaughtExceptions:YES] early enough in your app (typically from main() before UIApplication or NSApplication gets called) to have XLFacility install an uncaught exception handler to automatically call XLOG_EXCEPTION() passing the exception before the app terminates.

If you want instead to log all exceptions, as they are created and wether or not they are caught, use [XLSharedFacility setLogsInitializedExceptions:YES] instead. Note that this will also log exceptions that are not thrown either.

In both cases, XLFacility will capture the current callstack as part of the log message.

Capturing Stderr and Stdout

If you use XLFacility functions exclusively in your app to log messages, then everything you log from your source code will go to XLFacility. If you use third-party source code, you might be able to patch or override its calls to NSLog(), printf() or equivalent as demonstrated at the beginning of this document. However this will not work for Apple or third-party libraries or frameworks.

XLFacility has a powerful feature that allows to capture the standard output and standard error from your app. Just call [XLSharedFacility setCapturesStandardError:YES] (respectively [XLSharedFacility setCapturesStandardOutput:YES]) and from this point on anything written to the standard output (respectively standard error) will be split on newlines boundaries and automatically become separate log messages in XLFacility with the INFO (respectively ERROR) level.

Writing Custom Loggers

You can write a custom logger in a few lines of code by using XLCallbackLogger like this:

[XLSharedFacility addLogger:[XLCallbackLogger loggerWithCallback:^(XLCallbackLogger* logger, XLLogRecord* record) {
  // Do something with the log record
  printf("%s\n", [record.message UTF8String]);
}]];

To implement more complex loggers, you will need to subclass XLLogger and implement at least the -logRecord: method:

@interface MyLogger : XLLogger
@end

@implementation MyLogger

- (void)logRecord:(XLLogRecord*)record {
  // Do something with the log record
  NSString* formattedMessage = [self formatRecord:record];
  printf("%s", [formattedMessage UTF8String]);
}

@end

If you need to perform specific setup and cleanup operations when an instance of your logger is added or removed from XLFacility, also implement the -open and -close methods.

IMPORTANT: Due to the way XLFacility works, logger instances do not need to be reentrant, but they need to be able to run on arbitrary threads.

Comments
  • Enhancement request - exposing Macros to Swift

    Enhancement request - exposing Macros to Swift

    Hello again!

    Just want to request the exposure of the macros (or re-implementation) to Swift to reduce the unneeded code clutter.

    I attempted the below type of approach from SO but there is a bit of complexity in the macros i'm not familiar with. If they could be "wrapped" instead of re-implementing then a single code base could be maintained.

    Anyway though I would ask. Either way enjoying the lib so thanks for sharing it.

    http://stackoverflow.com/questions/24133695/how-to-use-objective-c-code-with-define-macros-in-swift

    .h file:

    define AD_SIZE CGSizeMake(320, 50)

    • (CGSize)adSize; .m file:
    • (CGSize)adSize { return AD_SIZE; } And in Swift:

    Since this is a class method you can now use it almost as you would the #define. If you change your #define macro - it will be reflected in the new method you created In Swift:

    let size = YourClass.adSize()What I did is to create a class method that returns the #define.

    Example:

    .h file:

    define AD_SIZE CGSizeMake(320, 50)

    • (CGSize)adSize; .m file:
    • (CGSize)adSize { return AD_SIZE; } And in Swift:

    Since this is a class method you can now use it almost as you would the #define. If you change your #define macro - it will be reflected in the new method you created In Swift:

    let size = YourClass.adSize()

    enhancement 
    opened by nickvelloff 17
  • Fix deadlock

    Fix deadlock

    Conditions for the deadlock:

    • Emit a log with level >= kXLLogLevel_Error from the main thread
    • Have a logger that post a notification
    • Have an observer for that notification registered on the main queue: [NSNotificationCenter.defaultCenter addObserverForName:notificationName object:nil queue:NSOperationQueue.mainQueue usingBlock:…]

    Fix for the deadlock: always using dispatch_async instead of dispatch_sync on the lock queue for [self _logRecord:record]

    opened by 0xced 7
  • XLFacilityMacros.h and Swift

    XLFacilityMacros.h and Swift

    XLFacility is working properly when added to the bridging header.

    After adding XLFacilityMacros.h to the header they do not seem to be recognized. I know that Swift essentially doesn't adopt the Macros concept in the same way.

    Is there a way I can do this?

    Otherwise logging will look like this: XLFacility.sharedFacility().logMessage("oauthTokenViable true, Attempt registerUserToken", withTag: nil, level: XLLogLevel.LogLevel_Debug)

    Header entries:

    import "XLFacilityMacros.h"

    import "XLFacility.h"

    question 
    opened by nickvelloff 4
  • XLTCPClientLogger - Queue X log records for delivery upon connecting

    XLTCPClientLogger - Queue X log records for delivery upon connecting

    After adding an XLTCPClientLogger instance, log records can be added prior to the first connection being opened with a remote host. These log records never get delivered to the remote host.

    I think it would be helpful if XLTCPClientLogger had an option to queue X log records offline (in-memory only) so they could be delivered once a connection was established.

    enhancement 
    opened by gcox 3
  • Fixes issue allowing loggers to remain open after being removed

    Fixes issue allowing loggers to remain open after being removed

    The _closeAllLoggers function iterates over the _loggers set variable in order to call performClose on each logger, but the removeAllLoggers function clears that set prior to calling _closeAllLoggers, preventing it from closing any of the loggers that were removed.

    opened by gcox 2
  • XLHTTPServerConnection did {opne|close} over IPv6

    XLHTTPServerConnection did {opne|close} over IPv6

    Hi,

    When running the HTTP logger, all logs are being hard to see because the logger keep saying in loop : GCDTCPServer did connect to peer at "::1c1e:1f90:0:0" (59433) XLHTTPServerConnection did open over IPv6 from ::a7b3:9eb9:85d9:bf (8080) to ::1c1e:1f90:0:0 (59433) XLHTTPServerConnection did close over IPv6 from ::a7b3:9eb9:85d9:bf (8080) to ::1c1e:1f90:0:0 (59433) GCDTCPServer did disconnect from peer at "::1c1e:1f90:0:0" (59433)

    here is a screenshot of the results screen shot 2015-07-24 at 10 16 15

    You can see that between all those "open/close connection" lines, there are 2 or 3 lines of code pretty hard to see at the first sight

    all I do in my code is calling this line in my appDelegate:DidFinishedLaunching:

    [XLSharedFacility addLogger:[[XLHTTPServerLogger alloc] init]];
    

    and then, I use those macros to log my events:

    XLOG_DEBUG(...), XLOG_INFO(...)
    

    Is there any way to prevent it ?

    question 
    opened by Khronoss 2
  • XLFileLogger - Consider adding auto-rolling/max size/max files to keep

    XLFileLogger - Consider adding auto-rolling/max size/max files to keep

    It would be helpful, for me at least, if the file logger supported auto-rolling new messages into a new file based on a max file size. I'd prefer to just provide it with a directory path, maybe a file name format to use for each file, a max file size, and a max number of files to keep around.

    enhancement 
    opened by gcox 2
  • XLUIKitOverlayLogger appears then disappears

    XLUIKitOverlayLogger appears then disappears

    Hi.

    I'm not clear based on the documentation the correct usage of the XLUIKitOverlayLogger. It works well but I'm not sure how to get it to appear and remove as needed.

    question 
    opened by nickvelloff 2
  • Add support for Carthage

    Add support for Carthage

    Hi.

    It would be great if you could support the Carthage package manager as well as Cocoapods.

    While I am not certain, I think this only requires that you check in the shared Xcode scheme for building the framework. So far I can see that is not in the repository.

    *** Skipped building XLFacility due to the error:
    Dependency "XLFacility" has no shared framework schemes for any of the platforms: Mac
    

    From the Carthage docs:

    Share your Xcode schemes Carthage will only build Xcode schemes that are shared from your .xcodeproj. You can see if all of your intended schemes build successfully by running carthage build --no-skip-current, then checking the Carthage/Build folder.

    If an important scheme is not built when you run that command, open Xcode and make sure that the scheme is marked as Shared, so Carthage can discover it.

    Thanks.

    Matt

    opened by mmower 0
Releases(1.5.11)
Owner
Pierre-Olivier Latour
Pierre-Olivier Latour
Twitter Logging Service is a robust and performant logging framework for iOS clients

Twitter Logging Service Background Twitter created a framework for logging in order to fulfill the following requirements: fast (no blocking the main

Twitter 290 Nov 15, 2022
A logging backend for swift-log that sends logging messages to Logstash (eg. the ELK stack)

LoggingELK LoggingELK is a logging backend library for Apple's swift-log The LoggingELK library provides a logging backend for Apple's apple/swift-log

null 17 Nov 15, 2022
Log every incoming notification to view them again later, also includes attachments and advanced settings to configure

Vē Natively integrated notification logger Installation Add this repository to your package manager

alexandra 43 Dec 25, 2022
TraceLog is a highly configurable, flexible, portable, and simple to use debug logging system for Swift and Objective-C applications running on Linux, macOS, iOS, watchOS, and tvOS.

Please star this github repository to stay up to date. TraceLog Introduction TraceLog is a highly configurable, flexible, portable, and simple to use

Tony Stone 52 Oct 28, 2022
In-App iOS Debugging Tool With Enhanced Logging, Networking Info, Crash reporting And More.

The debugger tool for iOS developer. Display logs, network request, device informations, crash logs while using the app. Easy accessible with its bubble head button ?? . Easy to integrate in any apps, to handle development or testing apps easier. First version, there is plenty of room for improvement.

Remi ROBERT 1.8k Dec 29, 2022
A fast & simple, yet powerful & flexible logging framework for Mac and iOS

CocoaLumberjack CocoaLumberjack is a fast & simple, yet powerful & flexible logging framework for macOS, iOS, tvOS and watchOS. How to get started Fir

null 12.9k Jan 9, 2023
CleanroomLogger provides an extensible Swift-based logging API that is simple, lightweight and performant

CleanroomLogger CleanroomLogger provides an extensible Swift-based logging API that is simple, lightweight and performant. The API provided by Cleanro

null 1.3k Dec 8, 2022
Logging utility for Swift and Objective C

Swell - Swift Logging A logging utility for Swift and Objective C. ##Features Turn on logging during development, turn them off when building for the

Hubert Rabago 361 Jun 29, 2022
Simple, lightweight and flexible debug logging framework written in Swift

AELog Simple, lightweight and flexible debug logging minion written in Swift If you find yourself in upcoming statements, then you probably want to us

Marko Tadić 28 Jul 6, 2022
Simple logging for simples needs.

Simple logging for simples needs.

native.dev.br 0 May 30, 2022
Convenient & secure logging during development & release in Swift 3, 4 & 5

Colorful, flexible, lightweight logging for Swift 3, Swift 4 & Swift 5. Great for development & release with support for Console, File & cloud platfor

SwiftyBeaver 5.6k Jan 4, 2023
Class for logging excessive blocking on the main thread

Watchdog Class for logging excessive blocking on the main thread. It watches the main thread and checks if it doesn’t get blocked for more than define

Wojtek Lukaszuk 1.8k Dec 6, 2022
Willow is a powerful, yet lightweight logging library written in Swift.

Willow Willow is a powerful, yet lightweight logging library written in Swift. Features Requirements Migration Guides Communication Installation Cocoa

Nike Inc. 1.3k Nov 16, 2022
Swift Logging Utility for Xcode & Google Docs

QorumLogs Swift Logging Utility in Xcode & Google Docs

Goktug Yilmaz 777 Jul 15, 2022
A lightweight logging framework for Swift

HeliumLogger Provides a lightweight logging implementation for Swift which logs to standard output. Features Logs output to stdout by default. You can

Kitura 174 Nov 30, 2022
A lightweight logging framework for Swift

HeliumLogger Provides a lightweight logging implementation for Swift which logs to standard output. Features Logs output to stdout by default. You can

Kitura 174 Nov 30, 2022
A flexible logging library written in Swift

Puppy Puppy is a flexible logging library written in Swift ?? It supports multiple transports(console, file, syslog, and oslog) as loggers. It not onl

Koichi Yokota 92 Dec 29, 2022
An extensible logging framework for Swift

Log is a powerful logging framework that provides built-in themes and formatters, and a nice API to define your owns. Get the most out of Log by insta

Damien 825 Nov 6, 2022
A powerful input-agnostic swift logging framework made to speed up development with maximum readability.

The Swift logging framework. Atlantis is an extremely powerful logging framework that I've created for everyday use, including enterprise development

Andrew Aquino 199 Jan 2, 2023