Record precondition message in crash reports

I don't see precondition's message being recorded in crash logs:

// expression is false
precondition(expression, message)

Can I have customise the "termination reason" or something similar? Or add a custom field that'll appear in the crash log?

if !expression {
    // PUT SOMETHING HERE TO HAVE `message` RECORDED, BUT WHAT?
    precondition(expression, message)
}
1 Like

This is an old issue, with many comments.

3 Likes

But I do not see fatalError messages in crash reports either! Was it changed since then and now, and if before we could have used condition+fatalError as a "better" version of precondition now this workaround doesn't work either?

I believe Apple intentionally removes the messages from crash reports in the name of privacy. If you use a more complex crash reporter system it may be able to extract extra info, but it's still pretty limited. Reporters like Crashlytics also have logging you can enable to help narrow down when a crash occurred, even if you can't get the exact message.

1 Like

Right. I am looking for a workaround that doesn't involve third party libraries or infrastructures.

I found I can change the thread name to my liking but there are some limitations (santitizing?) so that's not an ideal workaround.

Quinn (@eskimo), just found your article mentioning MetricKit on iOS 14. The documentation is a bit too succinct, please confirm that that is NOT the right tool to achieve the wanted result (of getting the wanted string into the crash log).

Any idea of a workaround?

Sure: use a custom helper that raises a fatalError, and users can see a wonderful error message that says why things have turned wrong.

func myPrecondition(
    _ condition: @autoclosure() -> Bool,
    _ message: @autoclosure() -> String = "",
    file: StaticString = #file,
    line: UInt = #line)
{
    if !condition() {
        fatalError(message(), file: file, line: line)
    }
}

It doesn't inform the compiler of the intention, and behaves identically in debug, release, and unchecked configurations, but, well: sometimes users are more important than compiler optimizations.

Beware on my sample code I use #file instead of one of its variants: I just pasted code written in early days of Swift and hadn't any opportunity to refresh it.

Hmm, does this work on iOS? Last I tried fatalError messages are not getting recorded in the crash logs on iOS.

Yep, I'd use #fileID instead of #file. I also record #function.

BTW, why @autoclosure() for condition?

Because I had the hope that I could eventually detect unchecked builds one day :sweat_smile:

At this point it's premature optimization, thanks for spotting it :-)

As far as I know, yes it does… Nobody has opened an issue on GRDB since the initial user request and its resolution.

It doesn't work for me... not even on macOS!

import SwiftUI

struct ContentView: View {
    init() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            fatalError("Can you see me?")
        }
    }
    var body: some View {
        Text("Hello, world!").padding(20)
    }
}

@main struct MinAppApp: App {
    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

No where in the crash logs I see a string "Can you see me?"
Tested with release build on macOS / iOS simulator / iOS device.

Using fatalError() instead of precondition() fixes the problem where you don't get error messages even in local development, which is what that user was complaining about. Neither of them log the error in crash reports.

One thing you might try is using OSLog to log the error reason then call fatalError() with no message. IIRC, the static part of an os_log message should appear in crash reports, but interpolated values from the program may be redacted. Since fatalError("...") and precondition("...") dynamically render the string before reporting it, it appears to the OS as if it's an entirely interpolated string, but using OSLog directly should avoid that.

Thanks for the tip. Could Swift solve this by offering overloads of fatalError and others to take a StaticString rather than the current String autoclosure?

This didn't help:

            _ = OSLogMessage(stringLiteral: "Can you see me?")
            fatalError()

Will it help if I write directly to this variable?

extern struct crashreporter_annotations_t gCRAnnotations

struct crashreporter_annotations_t {
  uint64_t version;          // unsigned long
  uint64_t message;          // char *
  uint64_t signature_string; // char *
  uint64_t backtrace;        // char *
  uint64_t message2;         // char *
  uint64_t thread;           // uint64_t
  uint64_t dialog_mode;      // unsigned int
  uint64_t abort_cause;      // unsigned int
};

(not sure yet how to try that.)

Wouldn't you have to actually log the message, not just create one?

1 Like

Thanks, never used that before :slight_smile: Same result, nothing in the crash report:

            let logger = Logger()
            logger.fault("Can you see me?")
            fatalError()

Interestingly, this is the stack trace for the fatalError call:

//Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
//0   libswiftCore.dylib                       0x1d060c594 _assertionFailure(_:_:file:line:flags:) + 672
//1   libswiftCore.dylib                       0x1d060c594 _assertionFailure(_:_:file:line:flags:) + 672
//2   MinApp                                   0x1049f3290 closure #1 in ContentView.init() + 248 (MinAppApp.swift:9)

Coming from C, it's strange to see the name "assert" in the method name - "assert" is for debug only builds and is wiped out of release builds.