iOS 10
NSLog
Debugging
Xcode
iOS Development

iOS 10 doesn't print NSLogs

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Starting with iOS 10, many developers discovered that NSLog output was no longer reliably appearing in the Xcode console. This was not a bug but a deliberate consequence of Apple introducing the Unified Logging System (os_log). Understanding what changed and how to migrate your logging will save you hours of confused debugging.

Why NSLog Stopped Showing Up

Apple replaced the legacy ASL (Apple System Logger) backend with the Unified Logging System in iOS 10 and macOS 10.12. NSLog still works under the hood, but its output is now routed through this new system. Several factors combine to hide its messages:

  1. Xcode console filtering changed. Xcode 8 and later versions changed how they display log output. The console sometimes filters messages by subsystem, making NSLog output invisible unless you adjust the filter settings.
  2. Log level suppression. The Unified Logging System assigns log levels (default, info, debug, error, fault). Messages at the debug level are suppressed by default and only appear when explicitly enabled for a specific subsystem.
  3. Simulator vs. device differences. NSLog output may appear in the Simulator console but not on a physical device, because the device's logging daemon applies stricter filtering.

If you open the Console.app on macOS and filter for your app's process name, you will usually find the NSLog messages there. They are being generated; they are just not reaching the Xcode console panel in the way you expect.

The Unified Logging System

Apple's replacement is the os_log family of functions, introduced in iOS 10. This system provides structured logging with subsystems, categories, and log levels, giving you much finer control than NSLog ever offered.

The key log levels are:

LevelPurposePersisted?
defaultGeneral informationYes
infoHelpful but non-essential detailNo (until collected)
debugDeveloper-only detailNo
errorError conditionsYes
faultCritical failures (captures backtraces)Yes

Using os_log (Objective-C)

In Objective-C, import os/log.h and create a log object scoped to your subsystem and category.

objectivec
1#import <os/log.h>
2
3os_log_t myLog = os_log_create("com.myapp.networking", "requests");
4
5os_log(myLog, "Request started for URL: %{public}@", url);
6os_log_error(myLog, "Request failed with status: %d", statusCode);
7os_log_debug(myLog, "Response headers: %{private}@", headers);

Note the %{public}@ and %{private}@ format specifiers. By default, dynamic string values are redacted in logs for privacy. Mark values as public when you need them visible in Console.app on release builds.

Using Logger (Swift)

Starting with iOS 14, Apple introduced the Logger struct, which provides a more Swift-friendly API with string interpolation.

swift
1import OSLog
2
3let logger = Logger(subsystem: "com.myapp.networking", category: "requests")
4
5logger.info("Request started for \\(url, privacy: .public)")
6logger.error("Request failed with status \\(statusCode)")
7logger.debug("Full response body received")

For projects that still need to support iOS 10 through iOS 13, use os_log directly:

swift
1import os.log
2
3let log = OSLog(subsystem: "com.myapp.networking", category: "requests")
4os_log("Request started for %{public}@", log: log, type: .info, url.absoluteString)

Viewing Logs

There are three main ways to read os_log output:

  1. Xcode console. Make sure the console filter is not hiding your messages. Click the filter bar at the bottom and ensure your subsystem is included.
  2. Console.app. Open Console.app on your Mac, select your connected device or simulator, and filter by subsystem (for example, com.myapp.networking). Enable "Include Info Messages" and "Include Debug Messages" from the Action menu.
  3. Command line. Use the log command in Terminal:
bash
log stream --predicate 'subsystem == "com.myapp.networking"' --level debug

Common Pitfalls

  • Assuming NSLog is broken. NSLog still writes to the Unified Logging System. The messages are generated but may be filtered out in Xcode. Check Console.app before concluding that logging is not working.
  • Forgetting to mark values as public. Dynamic values in os_log are private by default. If your logs show \<private> instead of actual values, add %{public}@ (Objective-C) or privacy: .public (Swift) to the format specifier.
  • Using debug level in production testing. Messages logged at os_log_debug or logger.debug are not persisted and only appear when streaming in real time. Use default or info level for messages you need to retrieve after the fact.
  • Mixing up os_log and print/NSLog. Using print() or NSLog() alongside os_log creates inconsistent behavior. Standardize on one logging approach across your codebase to avoid confusion about where messages end up.
  • Not specifying a subsystem and category. Logging to OS_LOG_DEFAULT without a custom subsystem makes it nearly impossible to filter your messages out of the thousands of system logs.

Summary

  • iOS 10 introduced the Unified Logging System, which changed how NSLog output is routed and displayed in Xcode.
  • NSLog still works but its messages may be filtered out of the Xcode console. Use Console.app or the log CLI to verify they exist.
  • Replace NSLog with os_log (Objective-C) or Logger (Swift, iOS 14 and later) for structured logging with subsystems, categories, and log levels.
  • Mark dynamic values as public explicitly, because os_log redacts them by default for privacy.
  • Always define a custom subsystem and category so you can filter your logs efficiently in Console.app and the Xcode console.

Course illustration
Course illustration

All Rights Reserved.