OSLog in Swift macro doesn't persist original file/line number

I need to log to OSLog and into a file in parallel (due to OSLogStore not being able to provide old logs (FB13191608)).

In Objective-C I can accomplish this with a macro using __FILE__ and __LINE__ in the macro implementation which still reference the position in the original code.

I tried to create a Swift macro to make this work from Swift. However, log() takes the file and line number of the macro definition file instead of the position in the calling code. So when I click on the metadata link, I'm taken to the macro instead of the calling location.

Is there any solution to that? #file and #line are correctly set in the macro but there’s no way to specify file and line number to the log() function (FB13204310).

Replies

but there’s no way to specify file and line number to the log(…) function

Right.

The system log implementation does not log file and line numbers in the traditional way. Rather, the log ends up including:

  • The Mach-O UUID of the caller — If you look at the C API, you’ll see that under the macros there’s a dso parameter that is effectively a pointer to the mach_header[_64].

  • The offset into that Mach-O — It finds the caller by walking up the stack, and then calculates the offset from the dso.

This allows log clients to recover the file and line info from a .dSYM, in much the same way as symbolication [1]. The primary motivation for this is space saving, in your program, the transient log buffers, and the final on-disk log.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] See Adding Identifiable Symbol Names to a Crash Report.

Thanks for the background info, Quinn. What is your recommendation to solve the problem? Could we replicate the C macro from Swift, specifying the dso parameter? How could we set it to the macro invocation (rather than the macro implementation) location?

Could we replicate the C macro from Swift, specifying the dso parameter?

It’s probably possible to call the C API from Swift [1] but the C API has a very different syntax than the Swift API, relying on printf-style format strings. It’s hard to recommend going down that path.

What is your recommendation to solve the problem?

I’m not entirely sure why you’re having problems with this, but that’s probably because I’m not fully up-to-speed with Swift macros. Let’s assume you have this setup:

let log = Logger(subsystem: "com.example.my-product", category: "default")
var logToFile: ((_ file: String, _ line: Int, _ message: String) -> Void)? = nil

And a log point like this:

log.log("answer: \(42)")

I would’ve thought that creating a macro that expands to this:

log.log("answer: \(42)")
logToFile?(#fileID, #line, "\(42)")

wouldn’t be too hard. And in that case there’s no wrapper around logToFile — it’s being called directly from the original callsite — and thus #fileID and #line will do the right thing.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Getting a value for the dso parameter from the very obscure dsohandle() function.

The problem is not in the logToFile, since as you mention, #file and #line do the right thing. The problem is on the OSLog side. The log.log("answer: \(42)") logs with the file and line number of the macro, not the calling site. So the linked code location is always in the macro, making the link useless.

The log.log("answer: \(42)") logs with the file and line number of the macro, not the calling site.

Hmmm, I did not expect that. I’ve no clue as to why you’re seeing that behaviour.

I have a couple of suggestions here:

  • If you want to continue this in public, I recommend that you bounce on over to Swift Forums in the hope of engaging folks with more concrete experience working with macros. A good place to start would be the Development > Macros area.

  • If you want to seek formal support, you could open a DTS tech support incident.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"