admin管理员组文章数量:1290978
I'm working on a SwiftUI application where we use Sentry to catch any errors and send them to a server where we can analyze them later on. We are currently using Sentry but we could swith to any other tool if it offers better error handling, howerver, I believe the limitations we are facing are from the languge itself, and not the tool.
What I would like to know is how to handle errors in Swift in a way that I can always achieve this two objectives:
- get a meaningful stack trace of the error
- be able to show a custom error message to the user when the error occurs (this means that the app cannot crash ans close itself).
I've created a test application that showcases the different ways in which an error can be handled:
struct TestSentryApp: App {
init() {
//Initialize Sentry
SentrySDK.start { options in
options.dsn = "SECRET_DSN_GOES_HERE"
options.debug = false
options.tracesSampleRate = 1.0
options.profilesSampleRate = 1.0
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
//Define a custom error
enum CustomError : Error {
case validationError(message: String)
case internalError(message: String, innerError: Error? = nil)
}
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Error Handling Tests")
Button("Test A") {
try! throwingFunction()
}
Button("Test B") {
do {
try throwingFunction()
}
catch {
//We could show a message to the user here informing about the error here
SentrySDK.capture(error: error)
}
}
Button("Test C") {
do {
try wrappedErrorThrowingFunction()
}
catch {
//We could show the message of the internalError here to the user, but still understand what happened because we have the innerError
SentrySDK.capture(error: error)
}
}
}
}
func throwingFunction() throws {
throw CustomError.validationError(message: "Error thrown on purpose.")
}
func wrappedErrorThrowingFunction() throws {
do {
try throwingFunction()
}
catch {
throw CustomError.internalError(message: "Unexpecetd error", innerError: error)
}
}
}
Pressing each of the buttons behaves in the following way:
- Button A: does not perform any special handling of the error and in return we get a clear stack trace of the error but the app crashes, so the user does not get any information about what went wrong.
- Button B: captures any possible error, allowing us to inform the user but the stack trace of the error points to the line where we call
SentrySDK.capture
as the line where the error originated, which is not true. - Button C: shows another approach where we wrap any error with our own
CustomError .internalError
. This allows us to show the user the message from the internalError while still keeping (and logging) the innerError for debugging. This is imporant in situations where the innerError contains paths or other sensitive information we do not want to disclose to the user.
The question then is: -None of the above aproaches provides a full stack trace with the actual line of the error, but for the code in Button A, which crashes the app. Is there any way to always get a full stack trace without having to crash the app? -Can I wrap any error with my custom one while maitaining the original stack trace?
I'm working on a SwiftUI application where we use Sentry to catch any errors and send them to a server where we can analyze them later on. We are currently using Sentry but we could swith to any other tool if it offers better error handling, howerver, I believe the limitations we are facing are from the languge itself, and not the tool.
What I would like to know is how to handle errors in Swift in a way that I can always achieve this two objectives:
- get a meaningful stack trace of the error
- be able to show a custom error message to the user when the error occurs (this means that the app cannot crash ans close itself).
I've created a test application that showcases the different ways in which an error can be handled:
struct TestSentryApp: App {
init() {
//Initialize Sentry
SentrySDK.start { options in
options.dsn = "SECRET_DSN_GOES_HERE"
options.debug = false
options.tracesSampleRate = 1.0
options.profilesSampleRate = 1.0
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
//Define a custom error
enum CustomError : Error {
case validationError(message: String)
case internalError(message: String, innerError: Error? = nil)
}
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Error Handling Tests")
Button("Test A") {
try! throwingFunction()
}
Button("Test B") {
do {
try throwingFunction()
}
catch {
//We could show a message to the user here informing about the error here
SentrySDK.capture(error: error)
}
}
Button("Test C") {
do {
try wrappedErrorThrowingFunction()
}
catch {
//We could show the message of the internalError here to the user, but still understand what happened because we have the innerError
SentrySDK.capture(error: error)
}
}
}
}
func throwingFunction() throws {
throw CustomError.validationError(message: "Error thrown on purpose.")
}
func wrappedErrorThrowingFunction() throws {
do {
try throwingFunction()
}
catch {
throw CustomError.internalError(message: "Unexpecetd error", innerError: error)
}
}
}
Pressing each of the buttons behaves in the following way:
- Button A: does not perform any special handling of the error and in return we get a clear stack trace of the error but the app crashes, so the user does not get any information about what went wrong.
- Button B: captures any possible error, allowing us to inform the user but the stack trace of the error points to the line where we call
SentrySDK.capture
as the line where the error originated, which is not true. - Button C: shows another approach where we wrap any error with our own
CustomError .internalError
. This allows us to show the user the message from the internalError while still keeping (and logging) the innerError for debugging. This is imporant in situations where the innerError contains paths or other sensitive information we do not want to disclose to the user.
The question then is: -None of the above aproaches provides a full stack trace with the actual line of the error, but for the code in Button A, which crashes the app. Is there any way to always get a full stack trace without having to crash the app? -Can I wrap any error with my custom one while maitaining the original stack trace?
Share Improve this question edited Feb 13 at 18:02 HangarRash 15k5 gold badges19 silver badges55 bronze badges asked Feb 13 at 17:23 EnricEnric 3251 silver badge7 bronze badges 3- What stack trace are you referring to? This is Swift and not Java so there is no stack trace being created every time an error is thrown. – Joakim Danielson Commented Feb 13 at 17:37
- You are right that the StackTrace terminology is not from Swift, I believe in Swift is called "callStackSymbols", but the meaning is the same. What I want is the list of call stack symbols that lead me to the actual line that caused the error. Is it possible? Or at least is there a better way of handling errors? – Enric Commented Feb 13 at 17:40
- Stack trace or call stack symbols are something you only get when the app crashes. – Joakim Danielson Commented Feb 13 at 17:41
1 Answer
Reset to default 1If you are only throwing your own errors, then sure,
struct CustomError: Error {
let message: String
let innerError: (any Error)?
let callStack: [String] = Thread.callStackSymbols
init(message: String, innerError: (any Error)? = nil) {
self.message = message
self.innerError = innerError
}
}
func throwingFunction() throws {
throw CustomError(message: "Error thrown on purpose.")
}
func wrappedErrorThrowingFunction() throws {
do {
try throwingFunction()
}
catch {
throw CustomError(message: "Unexpecetd error", innerError: error)
}
}
This will record the call stack when the CustomError
is created. You can print the call stack symbols like this:
Button("Test C") {
do {
try wrappedErrorThrowingFunction()
}
catch let error as CustomError {
print(error.callStack.joined(separator: "\n"))
if let cause = error.innerError as? CustomError {
print("Caused by:")
print(cause.callStack.joined(separator: "\n"))
}
}
catch { }
}
For any arbitrary Error
? That's not possible. The only requirement of conforming to Error
is Sendable
. Error
s are not required to contain any call stack information.
try
/catch
/throw
in Swift are just fancy control flow structures. As far as I can tell, not even the runtime keeps track of the call stack when an error is thrown.
本文标签:
版权声明:本文标题:swiftui - How to properly throw and capture errors in Swift to keep the original stack trace of where the error originated - Sta 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741513641a2382754.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论