Hi
So if I have something that goes like this
import Somelib
do {
try someFunction()
...
catch {
print(error)
}
Asuming I have more than call to one function with an error, Is there a way I can also get the line causing the error?
Note, Not using XCode here.
tera
2
You mean the line that did the actual throw error, or the whole "stack of lines" like so:
foo.swift:123
bar.swift:456
someFunction.swift:789
someLib.swift:111 // the line where `try someFunction()` is
To do that automatically swift would have to modify the "error" being returned (e.g. add a userValue dictionary key with, file/line info). Swift doesn't do this.
For you own app you can throw NSError and include the relevant file/line info as a userInfo key.
func makeError(... file: String = __FILE__, line: Int = __LINE__) -> Error {
let userInfo = ["errorFile": file, "errorLine": line]
let error = NSError(..., userInfo: userInfo)
return error
}
...
if ... {
throw makeError(...) // this file + line would be in the error
}
ahti
(Lukas Stabe 🙃)
3
Is this for debugging during development or at runtime (e.g. to print to some log)?
For the former, you should be able to break on swift_willThrow in lldb.
For the latter, you use a wrapper error type and a helper function to wrap errors thrown from the functions you call:
struct SourceLocationError<T: Error>: Error {
let file: String, line: Int, error: T
}
func annotateError<T>(_ proc: @autoclosure () throws -> T, file: String = #file, line: Int = #line) throws -> T {
do {
return try proc()
} catch {
throw SourceLocationError(file: file, line: line, error: error)
}
}
And then use it like this:
do {
try annotateError(someFunction())
} catch {
print(error)
}
Sadly, from what I tried, pre-/postfix operators cant have extra arguments, even when they have default values. That would have made it a bit less visually obtrusive, but if that is an issue a shorter wrapper function name should go a long way.
1 Like
Hi
@2, A call stack would be nice, Although I don't mind just the file and line number
@3, Yes it's for loggingز
All the ways shown here seems cool just except that I have to write an extra function for everything that errors, So I wonder if there's something easier?
I just want whenever I have an error raised I can log it in a way so I know where it happened, I rarely handle errors within the inner functions and just make it go to the highest function where main is because it works for my use case.
tera
5
Nice!
Extending on your example (with recording "a stack" of thrown locations plus function names):
import Foundation
struct FileLocation: CustomStringConvertible {
let file: String
let line: Int
let function: String
var description: String {
"\(file):\(line) \(function)"
}
}
struct SourceLocationError: Error, CustomStringConvertible {
var fileLocations: [FileLocation]
let error: Error
var description: String {
let locs = fileLocations.reduce("") { e, loc in
e.appending("\(loc)\n")
}
return "\(error)\n\(locs)"
}
}
func annotateError<T>(_ proc: @autoclosure () throws -> T, file: String = #fileID, line: Int = #line, function: String = #function) throws -> T {
do {
return try proc()
} catch {
if let error = error as? SourceLocationError {
var error = error
error.fileLocations.append(FileLocation(file: file, line: line, function: function))
throw error
} else {
throw annotatedError(error, file: file, line: line, function: function)
}
}
}
func annotatedError(_ error: Error, file: String = #fileID, line: Int = #line, function: String = #function) -> Error {
SourceLocationError(fileLocations: [FileLocation(file: file, line: line, function: function)], error: error)
}
enum SomeError: Error {
case someError
case someOtherError
}
func foo(with: String, and: Int) throws {
throw annotatedError(SomeError.someError)
// throw SomeError.someError
}
func bar() throws {
try annotateError(foo(with: "", and: 1))
}
func baz(with: String) throws {
try annotateError(bar())
}
func test() {
do {
try annotateError(baz(with: "Hello"))
}
catch {
let err = error
print(err)
}
}
test()
Gives this output:
someError
JT/main.swift:51 foo(with:and:)
JT/main.swift:56 bar()
JT/main.swift:60 baz(with:)
JT/main.swift:65 test()
without using annotatedError when throwing the error "call stack" misses the entry for "foo" but is still reasonable:
someError
JT/main.swift:56 bar()
JT/main.swift:60 baz(with:)
JT/main.swift:65 test()
The file locations are spot on👌:
1 Like