CleanroomLogger provides an extensible Swift-based logging API that is simple, lightweight and performant

Overview

HBC Digital logo      Gilt Tech logo

CleanroomLogger

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

The API provided by CleanroomLogger is designed to be readily understood by anyone familiar with packages such as CocoaLumberjack and log4j.

CleanroomLogger is part of the Cleanroom Project from Gilt Tech.

Swift compatibility

This is the master branch. It uses Swift 4.1 and requires Xcode 9.3 to compile.

Current status

Branch Build status
master Build status: master branch

Contents

Key Benefits of CleanroomLogger

▶︎ Built for speed

You don’t have to choose between smooth scrolling and collecting meaningful log information. CleanroomLogger does very little work on the calling thread, so it can get back to business ASAP.

 A modern logging engine with first-class legacy support

CleanroomLogger takes advantage of Apple’s new Unified Logging System (aka “OSLog” or “os_log”) when running on iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0 or higher.

On systems where OSLog isn’t available, CleanroomLogger gracefully falls back to other standard output mechanisms, automatically.

 100% documented

Good documentation is critical to the usefulness of any open-source framework. In addition to the extensive high-level documentation you’ll find below, the CleanroomLogger API itself is 100% documented.

 Organize and filter messages by severity

Messages are assigned one of five severity levels: the most severe is error, followed by warning, info, debug and verbose, the least severe. Knowing a message’s severity lets you perform additional filtering; for example, to minimize the overhead of logging in App Store binaries, you could choose to log only warnings and errors in release builds.

 Color-coded log messages

Quickly spot problems at runtime in the Xcode console, where log messages are color coded by severity:

◽️ Verbose messages are tagged with a small gray square — easy to ignore
◾️ Debug messages have a black square; easier to spot, but still de-emphasized
🔷 Info messages add a splash of color in the form of a blue diamond
🔶 Warnings are highlighted with a fire-orange diamond
❌ Error messages stand out with a big red X — hard to miss!

 UNIX-friendly

Support for standard UNIX output streams is built-in. Use StandardOutputLogRecorder and StandardErrorLogRecorder to direct output to stdout and stderr, respectively.

Or, use the StandardStreamsLogRecorder to send verbose, debug and info messages to stdout while warnings and errors go to stderr.

 Automatic handling of OS_ACTIVITY_MODE

When Xcode 8 was introduced, the console pane got a lot more chatty. This was due to the replacement of the ASL facility with OSLog. To silence the extra chatter, developers discovered that setting the OS_ACTIVITY_MODE environment variable to “disable would revert to the old logging behavior. It turns out that this silences OSLog altogether, so no output is sent to the console pane. CleanroomLogger notices when the setting is present, and echoes messages to stdout or stderr in addition to logging them through the os_log() function.

See where your code is logging

If you’re just using print() or NSLog() everywhere, it can sometimes be difficult to figure out what code is responsible for which log messages. By default, CleanroomLogger outputs the source file and line responsible for issuing each log message, so you can go straight to the source:

🔶 AppleTart.framework didn’t load due to running on iOS 8 (AppleTartShim.swift:19)
◾️ Uploaded tapstream batch (TapstreamTracker.swift:166)
◽️ Presenting AccountNavigationController from SaleListingController (BackstopDeepLinkNavigator.swift:174)
🔷 Successfully navigated to .account for URL: gilt://account (DeepLinkConsoleOutput.swift:104)
❌ Unrecognized URL: CountrySelector (GiltOnTheGoDeepLinkRouter.swift:100)

 Rotating log files

CleanroomLogger provides simple file-based logging support as well as a self-pruning rotating log directory implementation.

 Super-simple execution tracing

Developers often use logging to perform tracing. Rather than writing lots of different log messages to figure out what your program is doing at runtime, just sprinkle your source with Log.debug?.trace() and Log.verbose?.trace() calls, and you’ll see exactly what lines your code hits, when, and on what thread, as well as the signature of the executing function:

2017-01-05 13:46:16.681 -05:00 | 0001AEC4 ◾️ —> StoreDataTransaction.swift:42 - executeTransaction()
2017-01-05 13:46:16.683 -05:00 | 00071095 ◾️ —> LegacyStoresDeepLinking.swift:210 - viewControllerForRouter(_:destination:)
2017-01-05 13:46:16.683 -05:00 | 0001AEC4 ◽️ —> StoreDataTransaction.swift:97 - executeTransaction(completion:)
2017-01-05 13:46:16.684 -05:00 | 00071095 ◾️ —> ContainerViewController.swift:132 - setContentViewController(_:animated:completion:)
2017-01-05 13:46:16.684 -05:00 | 00071095 ◾️ —> DefaultBackstopDeepLinkNavigator.swift:53 - navigate(to:via:using:viewController:displayOptions:completion:)
2017-01-05 13:46:16.687 -05:00 | 00071095 ◽️ —> ViewControllerBase.swift:79 - viewWillAppear

 Useful built-in formatters

CleanroomLogger ships with two general-purpose log formatters: the ReadableLogFormatter is handy for human consumption, while the ParsableLogFormatter is useful for machine processing. Both can be customized via the initializer.

A formatter constructed using ReadableLogFormatter() yields log output that looks like:

2017-01-06 02:06:53.679 -05:00 | Debug   | 001BEF88 | DeepLinkRouterImpl.swift:132 - displayOptions(for:via:displaying:)
2017-01-06 02:06:53.682 -05:00 | Verbose | 001BEF88 | UIWindowViewControllerExtension.swift:133 - rootTabBarController: nil
2017-01-06 02:06:53.683 -05:00 | Info    | 001BEF88 | DeepLinkConsoleOutput.swift:104 - Successfully navigated to storeSale for URL: gilt://sale/women/winter-skin-rescue
2017-01-06 02:07:01.761 -05:00 | Error   | 001BEF88 | Checkout.swift:302 - The user transaction failed
2017-01-06 02:07:02.397 -05:00 | Warning | 001BEF88 | MemoryCache.swift:233 - Caching is temporarily disabled due to a recent memory warning

When the same log messages are handled by a formatter constructed using ParsableLogFormatter(), the timestamp is output in UNIX format, tab is used as the field delimiter, and the severity is indicated numerically:

1483686413.67946	2	001BEF88	DeepLinkRouterImpl.swift:132 - displayOptions(for:via:displaying:)
1483686413.68170	1	001BEF88	UIWindowViewControllerExtension.swift:133 - rootTabBarController: nil
1483686413.68342	3	001BEF88	DeepLinkConsoleOutput.swift:104 - Successfully navigated to storeSale for URL: gilt://sale/women/winter-skin-rescue
1483686421.76101	5	001BEF88	Checkout.swift:302 - The user transaction failed
1483686422.39651	4	001BEF88	MemoryCache.swift:233 - Caching is temporarily disabled due to a recent memory warning

 Easy mix-and-match formatting

If the built-in formatters don’t fit the bill, you can use the FieldBasedLogFormatter to assemble just about any kind of log format possible.

Let’s say you wanted a log formatter with the timestamp in ISO 8601 date format, a tab character, the source file and line number of the call site, followed by the severity as an uppercase string right-justified in a 8-character field, then a colon and a space, and finally the log entry’s payload. You could do this by constructing a FieldBasedLogFormatter as follows:

FieldBasedLogFormatter(fields: [
	.timestamp(.custom("yyyy-MM-dd'T'HH:mm:ss.SSSZ")),
	.delimiter(.tab),
	.callSite,
	.severity(.custom(textRepresentation: .uppercase, truncateAtWidth: nil, padToWidth: 8, rightAlign: true)),
	.literal(": "),
	.payload])

The resulting output would look like:

2017-01-08T12:55:17.905-0500	DeepLinkRouterImpl.swift:207        DEBUG: destinationForURL
2017-01-08T12:55:20.716-0500	DefaultDeepLinkRouter.swift:95       INFO: Attempting navigation to storeSale
2017-01-08T12:55:21.995-0500	LegacyUserEnvironment.swift:109		ERROR: Can’t fetch user profile without user guid
2017-01-08T12:55:25.960-0500	DeepLinkConsoleOutput.swift:104	  WARNING: Can’t find storeProduct for URL
2017-01-08T12:55:33.457-0500	ProductViewController.swift:92    VERBOSE: deinit

 Fully extensible

CleanroomLogger exposes three primary extension points for implementing your own custom logic:

  • LogRecorders are used to record formatted log messages. Typically, this involves writing the message to a stream or data store of some kind. You can provide your own LogRecorder implementations to utilize facilities not natively supported by CleanroomLogger: to store messages in a database table or send them to a remote HTTP endpoint, for example.
  • LogFormatters are used to generate text representations of each LogEntry to be recorded.
  • LogFilters get a chance to inspect—and potentially reject—a LogEntry before it is passed to a LogRecorder.

License

CleanroomLogger is distributed under the MIT license.

CleanroomLogger is provided for your use—free-of-charge—on an as-is basis. We make no guarantees, promises or apologies. Caveat developer.

Adding CleanroomLogger to your project

Carthage compatible

The simplest way to integrate CleanroomLogger is with the Carthage dependency manager.

First, add this line to your Cartfile:

github "emaloney/CleanroomLogger" ~> 6.0.0

Then, use the carthage command to update your dependencies.

Finally, you’ll need to integrate CleanroomLogger into your project in order to use the API it provides.

Once successfully integrated, just add the following statement to any Swift file where you want to use CleanroomLogger:

import CleanroomLogger

See the Integration document for additional details on integrating CleanroomLogger into your project.

Using CleanroomLogger

The main public API for CleanroomLogger is provided by Log.

Log maintains five static read-only LogChannel properties that correspond to one of five severity levels indicating the importance of messages sent through that channel. When sending a message, you would select a severity appropriate for that message, and use the corresponding channel:

  • Log.error — The highest severity; something has gone wrong and a fatal error may be imminent
  • Log.warning — Something appears amiss and might bear looking into before a larger problem arises
  • Log.info — Something notable happened, but it isn’t anything to worry about
  • Log.debug — Used for debugging and diagnostic information (not intended for use in production code)
  • Log.verbose - The lowest severity; used for detailed or frequently occurring debugging and diagnostic information (not intended for use in production code)

Each of these LogChannels provide three functions to record log messages:

  • trace() — This function records a log message with program execution trace information including the source code filename, line number and name of the calling function.
  • message(String) — This function records the log message passed to it.
  • value(Any?) — This function attempts to record a log message containing a string representation of the optional Any value passed to it.

Enabling logging

By default, logging is disabled, meaning that none of the Log’s channels have been populated. As a result, they have nil values and any attempts to perform logging will silently fail.

In order to use CleanroomLogger, you must explicitly enable logging, which is done by calling one of the Log.enable() functions.

Ideally, logging is enabled at the first possible point in the application’s launch cycle. Otherwise, critical log messages may be missed during launch because the logger wasn’t yet initialized.

The best place to put the call to Log.enable() is at the first line of your app delegate’s init().

If you’d rather not do that for some reason, the next best place to put it is in the application(_:willFinishLaunchingWithOptions:) function of your app delegate. You’ll notice that we’re specifically recommending the will function, not the typical did, because the former is called earlier in the application’s launch cycle.

Note: During the running lifetime of an application process, only the first call to Log.enable() function will have any effect. All subsequent calls are ignored silently. You can also prevent CleanroomLogger from being enabled altogether by calling Log.neverEnable().

Logging examples

To record items in the log, simply select the appropriate channel and call the appropriate function.

Here are a few examples:

Logging an arbitrary text message

Let’s say your application just finished launching. This is a significant event, but it isn’t an error. You also might want to see this information in production app logs. Therefore, you decide the appropriate LogSeverity is .info and you select the corresponding LogChannel, which is Log.info. Then, to log a message, just call the channel’s message() function:

Log.info?.message("The application has finished launching.")

Logging a trace message

If you’re working on some code and you’re curious about the order of execution, you can sprinkle some trace() calls around.

This function outputs the filename, line number and name of the calling function.

For example, if you put the following code on line 364 of a file called ModularTable.swift in a function with the signature tableView(_:cellForRowAt:):

Log.debug?.trace()

Assuming logging is enabled for the .debug severity, the following message would be logged when that line gets executed:

ModularTable.swift:364 — tableView(_:cellForRowAt:)

Logging an arbitrary value

The value() function can be used for outputting information about a specific value. The function takes an argument of type Any? and is intended to accept any valid runtime value.

For example, you might want to output the IndexPath value passed to your UITableViewDataSource’s tableView(_:cellForRowAt:) function:

Log.verbose?.value(indexPath)

This would result in output looking like:

= IndexPath: [0, 2]

The function also handles optionals:

var str: String?
Log.verbose?.value(str)

The output for this would be:

= nil

CleanroomLogger In Depth

This section delves into the particulars of configuring and customizing CleanroomLogger to suit your needs.

Configuring CleanroomLogger

CleanroomLogger is configured when one of the Log.enable() function variants is called. Configuration can occur at most once within the lifetime of the running process. And once set, the configuration can’t be changed; it’s immutable.

The LogConfiguration protocol represents the mechanism by which CleanroomLogger can be configured. LogConfigurations allow encapsulating related settings and behavior within a single entity, and CleanroomLogger can be configured with multiple LogConfiguration instances to allow combining behaviors.

Each LogConfiguration specifies:

  • The minimumSeverity, a LogSeverity value that determines which log entries get recorded. Any LogEntry with a severity less than the configuration’s mimimumSeverity will not be passed along to any LogRecorders specified by that configuration.
  • An array of LogFilters. Each LogFilter is given a chance to cause a given log entry to be ignored.
  • A synchronousMode property, which determines whether synchronous logging should be used when processing log entries for the given configuration. This feature is intended to be used during debugging and is not recommended for production code.
  • Zero or more contained LogConfigurations. For organizational purposes, each LogConfiguration can in turn contain additional LogConfigurations. The hierarchy is not meaningful, however, and is flattened at configuration time.
  • An array of LogRecorders that will be used to write log entries to the underlying logging facility. If a configuration has no LogRecorders, it is assumed to be a container of other LogConfigurations only, and is ignored when the configuration hierarchy is flattened.

When CleanroomLogger receives a request to log something, zero or more LogConfigurations are selected to handle the request:

  1. The severity of the incoming LogEntry is compared against the minimumSeverity of each LogConfiguration. Any LogConfiguration whose minimumSeverity is equal to or less than the severity of the LogEntry is selected for further consideration.
  2. The LogEntry is then passed sequentially to the shouldRecord(entry:) function of each of the LogConfiguration’s filters. If any LogFilter returns false, the associated configuration will not be selected to record that log entry.
XcodeLogConfiguration

Ideally suited for live viewing during development, the XcodeLogConfiguration examines the runtime environment to optimize CleanroomLogger for use within Xcode.

XcodeLogConfiguration takes into account:

  • Whether the new Unified Logging System (also known as “OSLog”) is available; it is only present as of iOS 10.0, macOS 10.12, tvOS 10.0, and watchOS 3.0. By default, logging falls back to stdout and stderr if Unified Logging is unavailable.

  • The value of the OS_ACTIVITY_MODE environment variable; when it is set to “disable”, attempts to log via OSLog are silently ignored. In such cases, log output is echoed to stdout and stderr to ensure that messages are visible in Xcode.

  • The severity of the message. For UNIX-friendly behavior, .verbose, .debug and .info messages are directed to the stdout stream of the running process, while .warning and .error messages are sent to stderr.

When using the Unified Logging System, messages in the Xcode console appear prefixed with an informational header that looks like:

2017-01-04 22:56:47.448224 Gilt[5031:89847] [CleanroomLogger]	
2017-01-04 22:56:47.448718 Gilt[5031:89847] [CleanroomLogger]	
2017-01-04 22:56:47.449487 Gilt[5031:89847] [CleanroomLogger]	
2017-01-04 22:56:47.450127 Gilt[5031:89847] [CleanroomLogger]	
2017-01-04 22:56:47.450722 Gilt[5031:89847] [CleanroomLogger]	

This header is not added by CleanroomLogger; it is added as a result of using OSLog within Xcode. It shows the timestamp of the log entry, followed by the process name, the process ID, the calling thread ID, and the logging system name.

To ensure consistent output across platforms, the XcodeLogConfiguration will mimic this header even when logging to stdout and stderr. You can disable this behavior by passing false as the mimicOSLogOutput argument. When disabled, a more concise header is used, showing just the timestamp and the calling thread ID:

2017-01-04 23:46:17.225 -05:00 | 00071095
2017-01-04 23:46:17.227 -05:00 | 00071095
2017-01-04 23:46:17.227 -05:00 | 000716CA
2017-01-04 23:46:17.228 -05:00 | 000716CA
2017-01-04 23:46:17.258 -05:00 | 00071095

To make it easier to quickly identify important log messages at runtime, the XcodeLogConfiguration makes use of the XcodeLogFormatter, which embeds a color-coded representation of each message’s severity:

◽️ Verbose messages are tagged with a small gray square — easy to ignore
◾️ Debug messages have a black square; easier to spot, but still de-emphasized
🔷 Info messages add a splash of color in the form of a blue diamond
🔶 Warnings are highlighted with a fire-orange diamond
❌ Error messages stand out with a big red X — hard to miss!

The simplest way to enable CleanroomLogger using the XcodeLogConfiguration is by calling:

Log.enable()

Thanks to the magic of default parameter values, this is equivalent to the following Log.enable() call:

Log.enable(minimumSeverity: .info,
                 debugMode: false,
          verboseDebugMode: false,
            stdStreamsMode: .useAsFallback,
          mimicOSLogOutput: true,
              showCallSite: true,
                   filters: [])

This configures CleanroomLogger using an XcodeLogConfiguration with default settings.

Note: If either debugMode or verboseDebugMode is true, the XcodeLogConfiguration will be used in synchronousMode, which is not recommended for production code.

The call above is also equivalent to:

Log.enable(configuration: XcodeLogConfiguration())
RotatingLogFileConfiguration

The RotatingLogFileConfiguration can be used to maintain a directory of log files that are rotated daily.

Warning: The RotatingLogFileRecorder created by the RotatingLogFileConfiguration assumes full control over the log directory. Any file not recognized as an active log file will be deleted during the automatic pruning process, which may occur at any time. This means if you’re not careful about the directoryPath you pass, you may lose valuable data!

At a minimum, the RotatingLogFileConfiguration requires you to specify the minimumSeverity for logging, the number of days to keep log files, and a directory in which to store those files:

// logDir is a String holding the filesystem path to the log directory
let rotatingConf = RotatingLogFileConfiguration(minimumSeverity: .info,
                                                     daysToKeep: 7,
                                                  directoryPath: logDir)

Log.enable(configuration: rotatingConf)

The code above would record any log entry with a severity of .info or higher in a file that would be kept for at least 7 days before being pruned. This particular configuration uses the ReadableLogFormatter to format log entries.

The RotatingLogFileConfiguration can also be used to specify synchronousMode, a set of LogFilters to apply, and one or more custom LogFormatters.

Multiple Configurations

CleanroomLogger also supports multiple configurations, allowing different logging behaviors to be in use simultaneously.

Whenever a message is logged, every LogConfiguration is consulted separately and given a chance to process the message. By supplying a minimumSeverity and unique set of LogFilters, each configuration can specify its own logic for screening out unwanted messages. Surviving messages are then passed to the configuration’s LogFormatters, each in turn, until one returns a non-nil string. That string—the formatted log message—is ultimately passed to one or more LogRecorders for writing to some underlying logging facility.

Note that each configuration is a self-contained, stand-alone entity. None of the settings, behaviors or actions of a given LogConfiguration will affect any other.

For an example of how this works, imagine adding a debug mode XcodeLogConfiguration to the rotatingConf declared above. You could do this by writing:

Log.enable(configuration: [XcodeLogConfiguration(debugMode: true), rotatingConf])

In this example, both the XcodeLogConfiguration and the RotatingLogFileConfiguration will be consulted as each logging call occurs. Because the XcodeLogConfiguration is declared with debugMode: true, it will operate in synchronousMode while rotatingConf will operate asynchronously.

Further, the XcodeLogConfiguration will result in messages being logged via the Unified Logging System (if available) and/or the running process’s stdout and stderr streams. The RotatingLogFileConfiguration, on the other hand, results in messages being written to a file.

Finally, each configuration results in a different message format being used.

Implementing Your Own Configuration

Although you can provide your own implementation of the LogConfiguration protocol, it may be simpler to create a BasicLogConfiguration instance and pass the relevant parameters to the initializer.

You can also subclass BasicLogConfiguration if you’d like to encapsulate your configuration further.

A Complicated Example

Let’s say you want configure CleanroomLogger to:

  1. Print .verbose, .debug and .info messages to stdout while directing .warning and .error messages to stderr
  2. Mirror all messages to OSLog, if it is available on the runtime platform
  3. Create a rotating log file directory at the path /tmp/CleanroomLogger to store .info, .warning and .error messages for up to 15 days

Further, you want the log entries for each to be formatted differently:

  1. An XcodeLogFormatter for stdout and stderr
  2. A ReadableLogFormatter for OSLog
  3. A ParsableLogFormatter for the log files

To configure CleanroomLogger to do all this, you could write:

var configs = [LogConfiguration]()

// create a recorder for logging to stdout & stderr
// and add a configuration that references it
let stderr = StandardStreamsLogRecorder(formatters: [XcodeLogFormatter()])
configs.append(BasicLogConfiguration(recorders: [stderr]))

// create a recorder for logging via OSLog (if possible)
// and add a configuration that references it
if let osLog = OSLogRecorder(formatters: [ReadableLogFormatter()]) {
	// the OSLogRecorder initializer will fail if running on 
	// a platform that doesn’t support the os_log() function
	configs.append(BasicLogConfiguration(recorders: [osLog]))
}

// create a configuration for a 15-day rotating log directory
let fileCfg = RotatingLogFileConfiguration(minimumSeverity: .info,
												daysToKeep: 15,
											 directoryPath: "/tmp/CleanroomLogger",
												formatters: [ParsableLogFormatter()])

// crash if the log directory doesn’t exist yet & can’t be created
try! fileCfg.createLogDirectory()

configs.append(fileCfg)

// enable logging using the LogRecorders created above
Log.enable(configuration: configs)

Customized Log Formatting

The LogFormatter protocol is consulted when attempting to convert a LogEntry into a string.

CleanroomLogger ships with several high-level LogFormatter implementations for specific purposes:

  • XcodeLogFormatter — Optimized for live viewing of a log stream in Xcode. Used by the XcodeLogConfiguration by default.
  • ParsableLogFormatter — Ideal for logs intended to be ingested for parsing by other processes.
  • ReadableLogFormatter — Ideal for logs intended to be read by humans.

The latter two LogFormatters are both subclasses of StandardLogFormatter, which provides a basic mechanism for customizing the behavior of formatting.

You can also assemble an entirely custom formatter quite easily using the FieldBasedLogFormatter, which lets you mix and match Fields to roll your own formatter.

API documentation

For detailed information on using CleanroomLogger, API documentation is available.

Design Philosophy

The application developer should be in full control of logging process-wide. As with any code that executes, there’s an expense to logging, and the application developer should get to decide how to handle the tradeoff between the utility of collecting logs and the expense of collecting them at a given level of detail.

CleanroomLogger’s configuration can only be set once during an application’s lifecycle; after that, the configuration becomes immutable. Any third-party frameworks using CleanroomLogger will be limited to what is explicitly allowed by the application developer. Therefore, embedded code using CleanroomLogger is inherently well behaved.

We believe so strongly in this philosophy that we even built a feature for developers that never want CleanroomLogger used within their applications. That’s right, we created a way for developers to avoid using our project altogether. So if you need to include a third-party library that uses CleanroomLogger but you don’t want to incur any logging overhead, just call Log.neverEnable() instead of Log.enable(). CleanroomLogger will be disabled entirely.

Respect for the calling thread. Functions like print() and NSLog() can do a lot of work on the calling thread, and when used from the main thread, that can lead to lower frame rates and choppy scrolling.

When CleanroomLogger is asked to log something, it is immediately handed off to an asynchronous background queue for further dispatching, letting the calling thread get back to work as quickly as possible. Each LogRecorder also maintains its own asynchronous background queue used to format log messages and write them to the underlying storage facility. This design ensures that if one recorder gets bogged down, it won’t block the processing of log messages by any other recorder.

Avoid needless code execution. The logging API provided by CleanroomLogger takes advantage of Swift short-circuiting to avoid executing code when it is known that no messages of a given severity will ever be logged.

For example, in production code with .info as the minimum LogSeverity, messages with a severity of .verbose or .debug will always be ignored. In such a case, Log.debug and Log.verbose would be nil, allowing efficient short-circuiting of any code attempting to use these inactive log channels. Code like Log.verbose?.trace() and Log.debug?.message("Loading URL: \(url)") would effectively become no-ops at runtime. Debug logging adds zero overhead to your production builds, so don’t be shy about taking advantage of it.

Architectural Overview

CleanroomLogger is designed to avoid performing formatting or logging work on the calling thread, making use of Grand Central Dispatch (GCD) queues for efficient processing.

In terms of threads of execution, each request to log anything can go through three main phases of processing:

  1. On the calling thread:

  2. Caller attempts to issue a log request by calling a logging function (eg., message(), trace() or value()) of the appropriate LogChannel maintained by Log. - If there is no LogChannel for the given severity of the log message (because CleanroomLogger hasn’t yet been enabled() or it is not configured to log at that severity), Swift short-circuiting prevents further execution. This makes it possible to leave debug logging calls in place when shipping production code without affecting performance.

  3. If a LogChannel does exist, it creates an immutable LogEntry struct to represent the thing being logged.

  4. The LogEntry is then passed to the LogReceptacle associated with the LogChannel.

  5. Based on the severity of the LogEntry, the LogReceptacle selects one or more LogConfigurations to use for recording the message. Among other things, these configurations determine whether further processing proceeds synchronously or asynchronously when passed to the underlying LogReceptacle’s GCD queue. (Synchronous processing is useful during debugging, but is not recommended for general production code.)

  6. On the LogReceptacle queue:

  7. The LogEntry is passed through zero or more LogFilters that are given a chance to prevent further processing of the LogEntry. If any filter indicates that LogEntry should not be recorded, processing stops.

  8. The LogConfiguration is used to determine which LogRecorders (if any) will be used to record the LogEntry.

  9. For each LogRecorder instance specified by the configuration, the LogEntry is then dispatched to the GCD queue provided by the LogRecorder.

  10. On each LogRecorder queue:

  11. The LogEntry is passed sequentially to each LogFormatter provided by the LogRecorder, giving the formatters a chance to create the formatted message for the LogEntry. - If no LogFormatter returns a string representation of LogEntry, further processing stops and nothing is recorded. - If any LogFormatter returns a non-nil value to represent the formatted message of the LogEntry, that string is then passed to the LogRecorder for final logging.

About

The Cleanroom Project began as an experiment to re-imagine Gilt’s iOS codebase in a legacy-free, Swift-based incarnation.

Since then, we’ve expanded the Cleanroom Project to include multi-platform support. Much of our codebase now supports tvOS in addition to iOS, and our lower-level code is usable on macOS and watchOS as well.

Cleanroom Project code serves as the foundation of Gilt on TV, our tvOS app featured by Apple during the launch of the new Apple TV. And as time goes on, we'll be replacing more and more of our existing Objective-C codebase with Cleanroom implementations.

In the meantime, we’ll be tracking the latest releases of Swift & Xcode, and open-sourcing major portions of our codebase along the way.

Contributing

CleanroomLogger is in active development, and we welcome your contributions.

If you’d like to contribute to this or any other Cleanroom Project repo, please read the contribution guidelines. If you have any questions, please reach out to project owner Paul Lee.

Acknowledgements

API documentation is generated using Realm’s jazzy project, maintained by JP Simard and Samuel E. Giddins.

Comments
  • Fails to build with Swift 3.0.1

    Fails to build with Swift 3.0.1

    When trying to build my app with Xcode 8.1 and Swift 3.0.1, I get the following error:

    Module compiled with Swift 3.0 cannot be imported in Swift 3.0.1: Frameworks/CleanroomLogger.framework/Modules/CleanroomLogger.swiftmodule/x86_64.swiftmodule

    Steps to reproduce:

    • Build a project that depends on the current stable version of CleanroomLogger using Xcode 8.1

    A/C:

    • CleanroomLogger builds properly with Xcode 8.1 and Swift 3.0.1
    opened by sssilver 11
  • Support XcodeColors

    Support XcodeColors

    Can CleanroomLogger be integrated with robbiehanson/XcodeColors?

    Or support XcodeColors by myself, how should I support the XcodeColors? I read the code, and I thought the ASLClient's implementation should be swapped.

    opened by mono0926 11
  • Carthage Build Failure

    Carthage Build Failure

    I attempted to build using Carthage and the build failed.

    here's the build results, I've replaced my file path with [root]:

    The following build commands failed: CompileSwift normal x86_64 [root]/Carthage/Checkouts/CleanroomASL/Code/ASLObject.swift CompileSwift normal x86_64 [root]/Carthage/Checkouts/CleanroomASL/Code/ASLClient.swift CompileSwiftSources normal x86_64 com.apple.xcode.tools.swift.compiler (3 failures) [root]/Carthage/Checkouts/CleanroomASL/Code/ASLObject.swift:285:39: error: use of undeclared type 'OptionSetType' [root]/Carthage/Checkouts/CleanroomASL/Code/ASLClient.swift:27:28: error: use of undeclared type 'OptionSetType' [root]/Carthage/Checkouts/CleanroomASL/Code/ASLClient.swift:27:28: error: use of undeclared type 'OptionSetType' [root]/Carthage/Checkouts/CleanroomASL/Code/ASLClient.swift:178:34: error: missing argument label 'currentQueue:' in call [root]/Carthage/Checkouts/CleanroomASL/Code/ASLClient.swift:219:47: error: cannot find an initializer for type 'Int' that accepts an argument list of type '(String)' [root]/Carthage/Checkouts/CleanroomASL/Code/ASLObject.swift:285:39: error: use of undeclared type 'OptionSetType' A shell task failed with exit code 65:

    opened by levous 9
  • n00b? Duplicated output in Xcode console

    n00b? Duplicated output in Xcode console

    I just barely started using this framework and I love it. Except for one thing — I'm getting duplicated messages in my console and it's making it more difficult to parse through logs. This is how I'm configuring my Log:

    Log.enable(minimumSeverity: .verbose, debugMode: true, verboseDebugMode: false, timestampStyle: nil, severityStyle: nil, showCallSite: true, showCallingThread: false, suppressColors: true, filters: [])
    

    And this is where I want to log a message:

    Log.verbose?.message("No transactions. Polling...")
    

    And this is my output:

    2016-11-16 12:16:18.878639 PA QA[24154:3671031] TransactionPoller.swift:68 - No transactions. Polling...
    TransactionPoller.swift:68 - No transactions. Polling...
    

    Am I missing something pretty obvious here?

    opened by clayellis 8
  • Carthage build fails on Xcode 8.2

    Carthage build fails on Xcode 8.2

    I just upgraded to Sierra and Xcode 8.2 and cannot build using Carthage anymore.

    Cartfile:

    github "emaloney/CleanroomLogger" ~> 3.0.0
    

    Output:

    carthage update --platform ios
    *** Fetching CleanroomLogger
    *** Fetching CleanroomASL
    *** Checking out CleanroomASL at "2.0.0"
    *** Checking out CleanroomLogger at "3.0.0"
    *** xcodebuild output can be found in /var/folders/6_/t3d3774j3k51w0xwhdswhw880000gn/T/carthage-xcodebuild.hEfIGK.log
    *** Building scheme "CleanroomASL" in CleanroomASL.xcodeproj
    *** Building scheme "CleanroomLogger" in CleanroomLogger.xcworkspace
    *** Building scheme "CleanroomASL-iOS" in CleanroomASL.xcodeproj
    ** CLEAN FAILED **
    
    
    The following build commands failed:
    	Check dependencies
    (1 failure)
    ** BUILD FAILED **
    
    
    The following build commands failed:
    	Check dependencies
    (1 failure)
    warning: no umbrella header found for target 'CleanroomASL-iOS', module map will not be generated
    warning: no umbrella header found for target 'CleanroomASL-iOS', module map will not be generated
    A shell task (/usr/bin/xcrun xcodebuild -project "/Users/joe/Desktop/CoolApp/Carthage/Checkouts/CleanroomLogger/Libraries/CleanroomASL/CleanroomASL.xcodeproj" -scheme CleanroomASL-iOS -configuration Release -sdk iphoneos ONLY_ACTIVE_ARCH=NO BITCODE_GENERATION_MODE=bitcode CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= CARTHAGE=YES clean build) failed with exit code 65:
    ** CLEAN FAILED **
    
    
    The following build commands failed:
    	Check dependencies
    (1 failure)
    ** BUILD FAILED **
    
    
    The following build commands failed:
    	Check dependencies
    (1 failure)
    

    The tail-end of the xcode build output:

    /usr/bin/xcrun xcodebuild -project "/Users/joe/Desktop/CoolApp/Carthage/Checkouts/CleanroomLogger/Libraries/CleanroomASL/CleanroomASL.xcodeproj" -scheme CleanroomASL-iOS -configuration Release -sdk iphoneos ONLY_ACTIVE_ARCH=NO BITCODE_GENERATION_MODE=bitcode CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= CARTHAGE=YES clean buildBuild settings from command line:
        BITCODE_GENERATION_MODE = bitcode
        CARTHAGE = YES
        CODE_SIGN_IDENTITY = 
        CODE_SIGNING_REQUIRED = NO
        ONLY_ACTIVE_ARCH = NO
        SDKROOT = iphoneos10.2
    
    === CLEAN TARGET CleanroomASL-iOS OF PROJECT CleanroomASL WITH CONFIGURATION Release ===
    
    Check dependencies
    “Use Legacy Swift Language Version” (SWIFT_VERSION) is required to be configured correctly for targets which use Swift. Use the [Edit > Convert > To Current Swift Syntax…] menu to choose a Swift version or use the Build Settings editor to configure the build setting directly.
    “Use Legacy Swift Language Version” (SWIFT_VERSION) is required to be configured correctly for targets which use Swift. Use the [Edit > Convert > To Current Swift Syntax…] menu to choose a Swift version or use the Build Settings editor to configure the build setting directly.
    warning: no umbrella header found for target 'CleanroomASL-iOS', module map will not be generated
    

    My project has been converted to Swift 3 and I have double checked by running:

    find . -name *pbxproj -exec grep SWIFT_VER {} \;
    SWIFT_VERSION = 3.0;
    SWIFT_VERSION = 3.0;
    SWIFT_VERSION = 3.0;
    <snip>
    

    Help would be appreciated.

    Thanks.

    bug awaiting response 
    opened by Laban1 7
  • Crash with emoji as last character in FileLogRecorder

    Crash with emoji as last character in FileLogRecorder

    New to all of Git/Markdown...be kind. I had a crash when output was to the FileLogRecorder.recordFormattedMessage. The offending line was:

    let c = unichar(uniStr[uniStr.endIndex.predecessor()].value)
    

    The value of the message parameter causing the crash was:

    "ABC😺"
    

    Seems easy to reproduce in a OS X Playground with:

    //: Playground - noun: a place where people can play
    
    import Cocoa
    
    var str = "Hello, playground"
    
    let message = "ABC😺"
    let uniStr = message.unicodeScalars
    if uniStr.count > 0 {
        let c = unichar(uniStr[uniStr.endIndex.predecessor()].value)
        // addNewline = !newlineCharset.characterIsMember(c)
    }
    
    bug 
    opened by billThePill 7
  • Weird issue when using gym

    Weird issue when using gym

    Hi, I started having this issue a couple of days ago, where building through gym (fastlane) outputs this error:

    no such module 'ASL'
    

    It looks like you're conditionally importing ASL depending on a #if !XCODE_BUILD

    Can you elaborate a bit more on what this does and how it's supposed to work when building from command line tools?

    opened by vittoriom 7
  • how to change the log's color in XcodeColors

    how to change the log's color in XcodeColors

    CleanroomLogger really great, but i wan't to know how to change the color of logs in XcodeColor According to the document, ColorTable Should be override.

    Could anybody give me an example to set it?

    the following is my way to override the ColorTable but never worked

            let formatter = XcodeLogFormatter(timestampStyle: .Default, severityStyle: .Xcode, delimiterStyle: nil, showCallSite: true, showCallingThread: false, colorizer: nil)
            let config = XcodeLogConfiguration(minimumSeverity: .Verbose, colorTable: HulkColorTable(), formatter: formatter)
            Log.enable(configuration: config)
    
    opened by IamAlchemist 6
  • Lack of examples

    Lack of examples

    This looks promising but the lack of examples make it irritating to work with. For example, long strings are not logged and it's not known if there is a size limit, etc. Simple Log.info?.message("hello world works") but if you have a pretty long string, it's just totally ignored.

    awaiting response 
    opened by Nathan187 6
  • Does DefaultLogConfiguration use the specified formatters?

    Does DefaultLogConfiguration use the specified formatters?

    I'm quite confused about this, but could it be that the log formatters passed as a parameter to the DefaultLogConfiguration initializer are just ignored?

    Looking at the code, I can't see any property in the log configuration class where they are stored.

    This is at the very minimum confusing, other than a possible bug :)

    opened by vittoriom 5
  • CleanroomBase dependency

    CleanroomBase dependency

    I like the approach of the framework but is the base dependency really needed? A "light-weight" logging library should be self-contained and not depend on another project. If I want to use CleanroomLogger I have to pull in code which is actually not needed/used.

    opened by jberkel 5
  • Support rolling log files

    Support rolling log files

    Support defining the maximum size a log file can become before the log file is rolled (a new log file for the same date is created which continues the logging).

    This helps avoiding gigantic log files which cannot be sent from customers to developers.

    opened by AlexanderMarchant 0
  • Feature Request: Disable Logging or change Configuration applied while session is active

    Feature Request: Disable Logging or change Configuration applied while session is active

    According to docs, there is no way to change the configurations being applied once the Logging has been enabled for the first time, which caused my change of minimumSeverity being ignored. It would be of vital importance to be able to change configuration while app session is active, or being able to load new ones or that the call to Log.enable() is not ignored after the first enabling. A workaround would be to change a value of active configuration dynamically, in my case I would only need to be able to change minimumSeverity dynamically from the code.

    opened by SantiPerti 0
  • Enable arm64-simulator slice creation in XCFramework by Carthage

    Enable arm64-simulator slice creation in XCFramework by Carthage

    Carthage didn't create an ARM64 slice for simulators because the xcconfig file did specify which VALID_ARCHS should be build. Removing that specification did enable Carthage to build a slice for ARM64-simulator. That was a huge problem because on M1/Apple Silicon machines generation of code coverage reports for tests is only possible when running an ARM64 app in the simulator. Without this change one could explore ARM for simulators and only run on x64 but loose code coverage with that trick. VALID_ARCHS is outdated and no longer available/visible in current Xcode versions anyway.

    opened by Gucky 0
  • There is no logs in xcode console

    There is no logs in xcode console

    Log.enable()
    Log.info?.message("hello")
    

    I use swift init --executable create a new project, add dependencies to Package.swift, but no logs in xcode console.

    opened by dehengxu 0
Releases(6.0.0)
  • 5.0.0(Jan 9, 2017)

    • Supports Apple's new Unified Logging System aka OSLog aka os_log • Removes the CleanroomASL dependency — CleanroomLogger is now dependency-free! • Adds back color coding; we lost this when XcodeColors was wiped out by Xcode 8. Although it is now in a different form:

    ◽️ Verbose messages are tagged with a small gray square — easy to ignore
    ◾️ Debug messages have a black square; easier to spot, but still de-emphasized
    🔷 Info messages add a splash of color in the form of a blue diamond
    🔶 Warnings are highlighted with a fire-orange diamond
    🛑 Error messages stand out with a red stop sign — hard to miss!
    
    Source code(tar.gz)
    Source code(zip)
  • 4.0.0(Jan 2, 2017)

    Because there's sadly no room for XcodeColors in a post-Xcode 8.0 world, this release removes support for XcodeColors and the various text colorization hooks on which it relied.

    The following entities have been removed from the CleanroomLogger API:

    • Color • ColorTable • ColorizingLogFormatter • DefaultColorTable • TextColorizer • XcodeColorsTextColorizer

    In addition, various functions have been rewritten to remove parameters related to text colorization behavior.

    Source code(tar.gz)
    Source code(zip)
  • 3.0.0(Oct 18, 2016)

    This is the first official Swift 3.0 release of this project.

    We are calling this an interim release because we have not yet completed the task of updating the APIs across our codebase to adopt the Swift 3 style.

    Until that task is complete, our Swift 3 releases will be considered interim, as there is a high likelihood of future breaking API changes.

    Source code(tar.gz)
    Source code(zip)
  • 2.2.2(Oct 18, 2016)

  • 2.2.0(Sep 8, 2016)

  • 2.1.5(Sep 8, 2016)

  • 2.1.0(Mar 22, 2016)

  • 1.5.3(Sep 16, 2015)

    • Xcode 7.1 beta (7B60) or higher is required to build for OS X and tvOS.

    • Xcode 7.0 GM (7A218) or higher (but not Xcode 7.1 beta) is required to build for watchOS.

    • Xcode 7.0 or 7.1 beta can be used to build for iOS.

    Source code(tar.gz)
    Source code(zip)
Owner
null
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
JustLog brings logging on iOS to the next level. It supports console, file and remote Logstash logging via TCP socket with no effort. Support for logz.io available.

JustLog JustLog takes logging on iOS to the next level. It supports console, file and remote Logstash logging via TCP socket with no effort. Support f

Just Eat 509 Dec 10, 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
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
A simple, straightforward logging API.

Sequoia A simple, straightforward logging API. Install Add the following to your Package.swift file: import PackageDescription let package = Package(

Jordan Baird 0 Dec 18, 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
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
Spy is a flexible, lightweight, multiplatform logging utility written in pure Swift.

Spy is a flexible, lightweight, multiplatform logging utility written in pure Swift. It allows to log with different levels and on different channels. You can define what levels and channels actually are.

AppUnite Sp. z o.o. Spk. 12 Jul 28, 2021
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
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
A simple logging package for Swift

OhMyLog OhMyLog is a simple logging package for Swift. It supports the following features: Six logging levels ( ?? , ?? , ?? , ⚠️ , ?? , ?? ) Display

Junhao Wang 1 Jan 10, 2022
Simple logging for simples needs.

Simple logging for simples needs.

native.dev.br 0 May 30, 2022
Elegant and extensive logging facility for OS X & iOS (includes database, Telnet and HTTP servers)

Overview XLFacility, which stands for Extensive Logging Facility, is an elegant and powerful logging facility for OS X & iOS. It was written from scra

Pierre-Olivier Latour 315 Sep 7, 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
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
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
Swift Logging Utility for Xcode & Google Docs

QorumLogs Swift Logging Utility in Xcode & Google Docs

Goktug Yilmaz 777 Jul 15, 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