os_log repeating log entry multiple times - swift

Using Unified Logging, I'm getting multiple copies of each log entry (23 to be exact). I'm wondering what I might be doing wrong.
I'm using Swift 4.1, Xcode 9.4.1, Console Version 1.0 (2.0.52), and building an iOS 11.0 app.
The relevant code looks like:
import os.log
...
// property
let log = OSLog(subsystem: Constants.subsystem, category: Constants.category)
override func viewDidLoad() {
super.viewDidLoad()
os_log("Useless message...", log: log, type: .debug)
}
And, in the Console (only showing the Message column), I get:
Subsystem, Category, Time, and Process values are identical for every entry.
And, the output only shows once in Xcode's console window.
What silly thing have I done now? (Or, is this a 🤪 bug?)
Update: I set a breakpoint at the log statement to test. It's only hit once, but as soon as it executes, the 23 entries show in the Console.
Update 2: added UUID to log for test: same result: UUID was the same in all 23 entries.
os_log("Useless message... %#", log: log, type: .debug, String(describing: UUID()))
Results:
Update 3: Fixed...
Cleaned the project, deleted derived data, erased the content in the simulator - all of which I'd done multiple times before.
This time, though, I also shut down, then restarted Xcode - and now it's working fine! Weirdness!

Related

Swift: OSLog/os_log not showing up in console app

I am building a Swift 5 application with XCode 10.3. For this, I have a framework which contains a implementation for a logsystem (for debugging purposes). The default implementation for this logsystem is based on OSLog/os_log. When using the system in the consuming app, then none of the logs appear in the Console app. However, when placing breakpoints, I can see that the os_log statement (see code example below) is reached and that the correct parameters are passed on to the os_log function. However, when I use os_log or NSLog in the host application, then they do show up.
I have verified that it is not an issue with the LogSystem / DefaultLogImplementation types as all of the breakpoints that need to be hit, are hit, and all unit tests are green. In addition, the os_log statement is reached and executed, but the logs do not appear. I have verified that all messages are shown in the console app, I have verified that I have selected the correct device, I have tried multiple filters (and even dug through all the logs without filters enabled)...
Can anyone help / give a pointer at what the issue may be? I am currently suspecting that there is a bug in the implementation of OSLog/os_log.
Code sample
App-side code, which consumes code similar to the examples provided below
class SomeClass {
private let logSystem = LogSystem()
func doSomethingImportant() {
// Do important stuff and log the result
logSystem.debug("Finished process with result \(result)")
}
}
Framework-side code, which is consumed by the app
public class LogSystem {
private let logImplementation: LogImplementation
init(_ logImplementation: LogImplementation = DefaultLogImplementation()) {
self.logImplementation = logImplementation
}
public func debug(_ message: String) {
logImplementation.log(message, type: .debug) // And some other parameters...
}
public func error(_ message: String) {
// Similar to debug(:)...
}
}
public protocol LogImplementation {
func log(_ message: String, type: LogType, ...other parameters...)
}
public class DefaultLogImplementation: LogImplementation {
func log(_ message: String, type: LogType, ...other parameters...) {
let parsedType = parseLogType(type) // Function that parses LogType to OSLogType
let log = OSLog(subsystem: "my.subsystem.domain", category: "myCategory")
os_log("%{private}#", log: log, type: parsedType, message) // Breakpoint here is reached, but log does not appear in console app (even with debugger attached, which should remove the effect of "%{private}%". Either way, at the very least censored logs should still appear in console app.
}
}
Additional info
Swift version: 5.0
XCode version: 10.3
Target device: iOS 12.2 iPhone X simulator
NSLog appears in console app: Yes
Selected correct device in console app: Yes
Correct filters: Yes
Update 2019-08-16 13:00 (Amsterdam time)
It appears that only .debug level messages are not appearing in the Console app. This bug occurs when using any simulator device in combination with OSLog. I have tried several commands to fix this:
sudo log config --mode level:debug,persist:debug
sudo log config --subsystem my.reverse.domain.name --mode level:debug,persist:debug
Neither of them fixed the issue. In fact, not a single debug-level message of the simulator is showing up in the console app (not even from iOS itself). Yes, the option to show .info and .debug level messages is enabled.
I tried setting the log-level for the simulator specifically through the following command:
xcrun simctl spawn booted log config --mode level:debug
But this result in an error:
log: Simulator unable to set system mode
In the console app, there are two options to include / hide debug and info messages:
I've spoken with an Apple DTS Engineer and it's a know issues not being able to see debug messages using a simulator (in Xcode 11 too): open a feedback

Xcode takes long time to print debug results.

When I debug on Xcode it takes around 30 seconds or more to print results of po on Xcode console.
Unfortunately, this is only few information I have on the issue.
However, there is another point to consider. This issue is very specific to a project. This is because when I use po for other projects on same Macbook, it works immediately. Also, this particular project is slow on all other Macbook, and for all team.
I googled it but no relevant answer found. I find it easy to use print(...) rather than using debugging on Xcode console. However, it's more work and requires lots of rebuilds.
I have several explanations:
There is a lot of code (Xcode slows down after a certain amount of code) Also make sure your print statement is towards the top of your page. Xocde goes from top to bottom.
Your Mac is slow. Some Macs after a certain amount of usage slow down. Also if you have a Mac mini or air they are slower than others.
Xcode Beta. If you are using Xcode beta then there might just be a bug.
If none of those answer you provide me with more info and I provide other solutions.
Swift:
Try this solution which helps to reduce log time in debug mode.
Step 1: Create a new file called Utils.swift (File name based on your preference)
Step 2: Add following code in file
import Foundation
import UIKit
struct Utils { }
public func PrintLogs(_ message: Any, file: String = #file, function: String = #function, line: Int = #line) {
#if DEBUG
let className = file.components(separatedBy: "/").last ?? ""
let classNameArr = className.components(separatedBy: ".")
NSLog("\n\n--> Class Name: \(classNameArr[0]) \n--> Function Name: \(function) \n--> Line: \(line)")
print("--> Log Message: \(message)")
#endif
}
Usage: Call PrintLogs("Hello") instead of print("Hello")
Sample Output:
--> Class Name: HomeViewController
--> Function Name: logTest()
--> Line: 81
--> Log Message: Hello

Xcode: +CoreDataProperties.swift issue

I'm designing an app which is going well but I had an issue a while ago whereby I had to create a new model for CoreData because I made alterations to the Entities. I'm up to the fourth version and I had another issue with the app and I cleaned it. Now, this is what I'm getting:
The 'deleted' Attribute is set to NSDate
but after I try to build it again I get the following error:
I thought if I made alterations to the Entity Xcode would pick that up and alter any files accordingly! But that doesn't seem to be the case!
I've tried deleting the +CoreDataProperties.swift files and the 'Shopping List' swift file, recreating the 'Shopping List' swift file, under a different class name, and trying to build it again but I get the same error. This tells me its a CoreData issue, not a Swift issue. Obviously I need the attribute as NSDate but I'm not sure where to go from here!
The only way I can get the app to build is to comment out the 'deleted' attribute in the +CoreDataProperties.swift file and it runs fine.
I have the app running on a test iPhone 6 and the last time I made changes to the Entity I lost all the data I entered manually on the phone because of errors. The only way to get the app back up and running was to delete the app off the phone and reinstall it. I seriously don't want to go down that route again because I have nearly 450 various records on the phone.
If I leave the 'deleted' Attribute commented out when its uploaded to the app store, will it fail to upload, and will it fail to work correctly if the upload is successful?
I'd rather sort the issue before trying!
What you need to do is called light weight migrations. In app delegate you need to tell the app to look for the new version and create presistence store
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/// THIS IS THE CODE YOU NEED TO ADD
let container = NSPersistentContainer(name: "NAMEGOESHERE")
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions = [description]
// End of new code
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
This method worked for me. There are a number of tutorials out there on doing light weight migrations here and there is a stack overflow answer which is one I actually referred to when I had this problem here. By doing migrations your app shall be able to create a new persistance store and in that new store it ll change the attribute of deleted from Bool to NSdate as in the generated managedobject file. Hope this helps

Core Data commands crash ViewController even after delete

I was trying to add autosaving to my Core Data-based app and there was a line of code I added to textDidChange in a CollectionViewItem:
theNote?.updateChangeCount(NSDocumentChangeType.ChangeDone)
That gave me a lot of errors, so I commented out the line. I then went in to delete the CocoaAppCD.storedata persistent store file to make it cleaner (I'm still in early dev stages so all that goes in my persistent store is random test materials).
Now, I'm finding, Core Data commands crash my ViewController. Specifically this function:
func createNewNotebook(folderURL: NSURL)
{
let currentNotebook = Notebook(entity: sv.noteType, insertIntoManagedObjectContext:sv.context)
currentNotebook.folderURLstring = folderURL.absoluteString
let noteSet = currentNotebook.mutableSetValueForKey("contains")
print(String(noteSet))
intakeFilesFromFolder(noteSet, currentFolderURL: folderURL)
}
(Notebook and NoteEntity are the two entity types in my Core Data model.) When either the currentNotebook.folder... or let noteSet... commands run, I get these errors in my console:
Brouillon.NoteEntity folderURLstring]: unrecognized selector sent to instance 0x6080000a3a20
2016-07-24 19:27:02.622 Brouillon[8006:361665] Failed to set (contentViewController) user defined inspected property on (NSWindow): -[Brouillon.NoteEntity folderURLstring]: unrecognized selector sent to instance 0x6080000a3a20
and the WindowController is empty with no view filling it (even though these same statements had worked before I added the now-commented-out line). But if I bypass this function so the Core Data statements don't run, the views load. I would have thought that if I had anything dirty left in the database, deleting the CocoaAppCD.storedata file should have fixed it (and I keep deleting that file after every run). But it seems like something in Core Data is still mucked up for me - any ideas?
I figured this out. The problem was I had declared the wrong entity type: my sv.noteType should have been sv.notebookType, since it was a Notebook rather than a NoteEntity. (The error would have been introduced when I was creating the class instantiated as sv, as a class to hold my otherwise-duplicated code.)

Swift: print() vs println() vs NSLog()

What's the difference between print, NSLog and println and when should I use each?
For example, in Python if I wanted to print a dictionary, I'd just print myDict, but now I have 2 other options. How and when should I use each?
A few differences:
print vs println:
The print function prints messages in the Xcode console when debugging apps.
The println is a variation of this that was removed in Swift 2 and is not used any more. If you see old code that is using println, you can now safely replace it with print.
Back in Swift 1.x, print did not add newline characters at the end of the printed string, whereas println did. But nowadays, print always adds the newline character at the end of the string, and if you don't want it to do that, supply a terminator parameter of "".
NSLog:
NSLog adds a timestamp and identifier to the output, whereas print will not;
NSLog statements appear in both the device’s console and debugger’s console whereas print only appears in the debugger console.
NSLog in iOS 10-13/macOS 10.12-10.x uses printf-style format strings, e.g.
NSLog("%0.4f", CGFloat.pi)
that will produce:
2017-06-09 11:57:55.642328-0700 MyApp[28937:1751492] 3.1416
NSLog from iOS 14/macOS 11 can use string interpolation. (Then, again, in iOS 14 and macOS 11, we would generally favor Logger over NSLog. See next point.)
Nowadays, while NSLog still works, we would generally use “unified logging” (see below) rather than NSLog.
Effective iOS 14/macOS 11, we have Logger interface to the “unified logging” system. For an introduction to Logger, see WWDC 2020 Explore logging in Swift.
To use Logger, you must import os:
import os
Like NSLog, unified logging will output messages to both the Xcode debugging console and the device console, too
Create a Logger and log a message to it:
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "network")
logger.log("url = \(url)")
When you observe the app via the external Console app, you can filter on the basis of the subsystem and category. It is very useful to differentiate your debugging messages from (a) those generated by other subsystems on behalf of your app, or (b) messages from other categories or types.
You can specify different types of logging messages, either .info, .debug, .error, .fault, .critical, .notice, .trace, etc.:
logger.error("web service did not respond \(error.localizedDescription)")
So, if using the external Console app, you can choose to only see messages of certain categories (e.g. only show debugging messages if you choose “Include Debug Messages” on the Console “Action” menu). These settings also dictate many subtle issues details about whether things are logged to disk or not. See WWDC video for more details.
By default, non-numeric data is redacted in the logs. In the example where you logged the URL, if the app were invoked from the device itself and you were watching from your macOS Console app, you would see the following in the macOS Console:
url = <private>
If you are confident that this message will not include user confidential data and you wanted to see the strings in your macOS console, you would have to do:
logger.log("url = \(url, privacy: .public)")
Prior to iOS 14/macOS 11, iOS 10/macOS 10.12 introduced os_log for “unified logging”. For an introduction to unified logging in general, see WWDC 2016 video Unified Logging and Activity Tracing.
Import os.log:
import os.log
You should define the subsystem and category:
let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "network")
When using os_log, you would use a printf-style pattern rather than string interpolation:
os_log("url = %#", log: log, url.absoluteString)
You can specify different types of logging messages, either .info, .debug, .error, .fault (or .default):
os_log("web service did not respond", type: .error)
You cannot use string interpolation when using os_log. For example with print and Logger you do:
logger.log("url = \(url)")
But with os_log, you would have to do:
os_log("url = %#", url.absoluteString)
The os_log enforces the same data privacy, but you specify the public visibility in the printf formatter (e.g. %{public}# rather than %#). E.g., if you wanted to see it from an external device, you'd have to do:
os_log("url = %{public}#", url.absoluteString)
You can also use the “Points of Interest” log if you want to watch ranges of activities from Instruments:
let pointsOfInterest = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: .pointsOfInterest)
And start a range with:
os_signpost(.begin, log: pointsOfInterest, name: "Network request")
And end it with:
os_signpost(.end, log: pointsOfInterest, name: "Network request")
For more information, see https://stackoverflow.com/a/39416673/1271826.
Bottom line, print is sufficient for simple logging with Xcode, but unified logging (whether Logger or os_log) achieves the same thing but offers far greater capabilities.
The power of unified logging comes into stark relief when debugging iOS apps that have to be tested outside of Xcode. For example, when testing background iOS app processes like background fetch, being connected to the Xcode debugger changes the app lifecycle. So, you frequently will want to test on a physical device, running the app from the device itself, not starting the app from Xcode’s debugger. Unified logging lets you still watch your iOS device log statements from the macOS Console app.
If you're using Swift 2, now you can only use print() to write something to the output.
Apple has combined both println() and print() functions into
one.
Updated to iOS 9
By default, the function terminates the line it prints by adding a line break.
print("Hello Swift")
Terminator
To print a value without a line break after it, pass an empty string as the terminator
print("Hello Swift", terminator: "")
Separator
You now can use separator to concatenate multiple items
print("Hello", "Swift", 2, separator:" ")
Both
Or you could combine using in this way
print("Hello", "Swift", 2, separator:" ", terminator:".")
Moreover, Swift 2 has debugPrint() (and CustomDebugStringConvertible protocol)!
Don't forget about debugPrint() which works like print() but most suitable for debugging.
Examples:
Strings
print("Hello World!") becomes Hello World
debugPrint("Hello World!") becomes "Hello World" (Quotes!)
Ranges
print(1..<6) becomes 1..<6
debugPrint(1..<6) becomes Range(1..<6)
Any class can customize their debug string representation via CustomDebugStringConvertible protocol.
To add to Rob's answer, since iOS 10.0, Apple has introduced an entirely new "Unified Logging" system that supersedes existing logging systems (including ASL and Syslog, NSLog), and also surpasses existing logging approaches in performance, thanks to its new techniques including log data compression and deferred data collection.
From Apple:
The unified logging system provides a single, efficient, performant API for capturing messaging across all levels of the system. This unified system centralizes the storage of log data in memory and in a data store on disk.
Apple highly recommends using os_log going forward to log all kinds of messages, including info, debug, error messages because of its much improved performance compared to previous logging systems, and its centralized data collection allowing convenient log and activity inspection for developers. In fact, the new system is likely so low-footprint that it won't cause the "observer effect" where your bug disappears if you insert a logging command, interfering the timing of the bug to happen.
You can learn more about this in details here.
To sum it up: use print() for your personal debugging for convenience (but the message won't be logged when deployed on user devices). Then, use Unified Logging (os_log) as much as possible for everything else.
iOS logger
NSLog - add meta info (like timestamp and identifier) and allows you to output 1023 symbols. Also print message into Console. The slowest method. Is not safe because other applications has an access to log file
#import Foundation
NSLog("SomeString")
print - prints all string to Xcode. Has better performance than previous
#import Foundation
print("SomeString")
println (only available Swift v1) and add \n at the end of string
os_log (from iOS v10) - prints 32768 symbols also prints to console. Has better performance than previous
#import os.log
os_log("SomeIntro: %#", log: .default, type: .info, "someString")
Logger (from iOS v14) - prints 32768 symbols also prints to console. Has better performance than previous
#import os
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "someCategory")
logger.log("\(s)")
There's another method called dump() which can also be used for logging:
func dump<T>(T, name: String?, indent: Int, maxDepth: Int, maxItems: Int)
Dumps an object’s contents using its mirror to standard output.
From Swift Standard Library Functions