|
1 | 1 | # CrashKiOS - Crash reporting for Kotlin/iOS |
2 | 2 |
|
3 | 3 | Thin library that provides symbolicated crash reports for Kotlin code on |
4 | | -iOS. Currently implemented for Crashlytics and Bugsnag. |
| 4 | +iOS. If you would like to log exceptions you can use CrashKiOS directly, but |
| 5 | +you'll probably want to check out [Kermit](https://github.com/touchlab/Kermit/). Kermit is a logging library that |
| 6 | +also includes implementations for Crashlytics and Bugsnag. |
| 7 | + |
| 8 | +> ## **We're Hiring!** |
| 9 | +> |
| 10 | +> Touchlab is looking for a Mobile Developer, with Android/Kotlin experience, who is eager to dive into Kotlin Multiplatform Mobile (KMM) development. Come join the remote-first team putting KMM in production. [More info here](https://go.touchlab.co/careers-gh). |
5 | 11 |
|
6 | 12 | ## The Problem |
7 | 13 |
|
@@ -30,172 +36,12 @@ That's what this library is for. |
30 | 36 |
|
31 | 37 | ## The Solution |
32 | 38 |
|
33 | | - With some stack trace visibility improvements included in Kotlin 1.3.60, we can report |
34 | | - "handled errors" to Crashlytics and Bugsnag, and have them record symbolicated crash reports. |
35 | | - These can be sent explicitly, but more likely, reported as part of the uncaught exception handler. |
36 | | - |
37 | | - ## Usage |
38 | | - |
39 | | - The crash library itself is minimal, but because of current transitive code rules and how interop |
40 | | - works with Objc and Swift, we'll be copying some code manually. |
41 | | - |
42 | | - ### Gradle |
43 | | - |
44 | | - ```groovy |
45 | | -kotlin { |
46 | | - sourceSets { |
47 | | - iosMain { |
48 | | - dependencies { |
49 | | - api "co.touchlab:crashkios:0.2.1" |
50 | | - } |
51 | | - } |
52 | | - } |
53 | | -} |
54 | | -``` |
55 | | - |
56 | | - ### Kotlin |
| 39 | +Some crash reporting libraries support a way to symbolicate custom stack traces. The solution is relatively simple. |
| 40 | +We get the pointers to functions called on a Kotlin Throwable, and send those to the crash reporting tools. With |
| 41 | +DSYM data, the crash reporting tools can trace that back to source code file and line numbers. |
57 | 42 |
|
58 | | - In your app, you'll need to include some extra passthrough code. In your iOS Kotlin source, add the |
59 | | - following function |
60 | | - |
61 | | - ```kotlin |
62 | | -import co.touchlab.crashkios.CrashHandler |
63 | | -import co.touchlab.crashkios.setupCrashHandler |
64 | | - |
65 | | -fun crashInit(handler: CrashHandler){ |
66 | | - setupCrashHandler(handler) |
67 | | -} |
68 | | -``` |
69 | | - |
70 | | -In our sample, we put this in a file called `CrashIntegration.kt`. If it's in a different file, you'll |
71 | | -need to know that for the Swift config. [See CrashIntegration.kt](sample/src/iosMain/kotlin/sample/CrashIntegration.kt) |
72 | | - |
73 | | -This will be called from your iOS code to set the default crash handler. There are other options, |
74 | | -but this is generally what you want to do. The name `crashInit` is unimportant, but creating this |
75 | | -method forces the Kotlin compiler to include the necessary crash handling code. In the future, with |
76 | | -proper transitive dependency settings, this may not be necessary. |
77 | | - |
78 | | -### Swift |
79 | | - |
80 | | -In Swift, you need to add a `CrashHandler` instance specific to the crash reporting service you'll |
81 | | -be using. We currently support Crashlytics and Bugsnag. Setup currently involves copy/pasting some |
82 | | -Swift/Objc code rather than using interop bindings in Kotlin. This is more manual, but simpler. We |
83 | | -may add interop code in the future. |
84 | | - |
85 | | -#### Crashlytics |
86 | | - |
87 | | -Create the `CrashHandler` class |
88 | | - |
89 | | -```swift |
90 | | -class CrashlyticsCrashHandler: CrashkiosCrashHandler { |
91 | | - override func crashParts( |
92 | | - addresses: [KotlinLong], |
93 | | - exceptionType: String, |
94 | | - message: String) { |
95 | | - let exceptionModel = ExceptionModel(name: exceptionType, reason: message) |
96 | | - exceptionModel.stackTrace = addresses.map { |
97 | | - StackFrame(address: UInt(truncating: $0)) |
98 | | - } |
99 | | - Crashlytics.crashlytics().record(exceptionModel: exceptionModel) |
100 | | - } |
101 | | -} |
102 | | -``` |
103 | | - |
104 | | -Note, `CrashkiosCrashHandler` and possibly `KotlinLong` may have different generated names from |
105 | | -the Kotlin compiler output. |
106 | | - |
107 | | -Inside `AppDelegate.swift`, or wherever you do app setup, add the following: |
108 | | - |
109 | | -```swift |
110 | | - CrashIntegrationKt.crashInit(handler: CrashlyticsCrashHandler()) |
111 | | -``` |
112 | | - |
113 | | -To see all of this code in a sample app, see [sample/iosAppCrashlytics](sample/iosAppCrashlytics) |
114 | | - |
115 | | -#### Bugsnag |
116 | | - |
117 | | -Bugsnag's setup is a little more complicated. We create an extension of `NSException` which is provided |
118 | | -to the Bugsnag crash reporting library. |
119 | | - |
120 | | -In Swift, add the following: |
121 | | - |
122 | | -```swift |
123 | | -class CrashNSException: NSException { |
124 | | - init(callStack:[NSNumber], exceptionType: String, message: String) { |
125 | | - super.init(name: NSExceptionName(rawValue: exceptionType), reason: message, userInfo: nil) |
126 | | - self._callStackReturnAddresses = callStack |
127 | | - } |
128 | | - |
129 | | - required init?(coder: NSCoder) { |
130 | | - fatalError("init(coder:) has not been implemented") |
131 | | - } |
132 | | - |
133 | | - private lazy var _callStackReturnAddresses: [NSNumber] = [] |
134 | | - override var callStackReturnAddresses: [NSNumber] { |
135 | | - get { return _callStackReturnAddresses } |
136 | | - set { _callStackReturnAddresses = newValue } |
137 | | - } |
138 | | -} |
139 | | - |
140 | | -class BugsnagCrashHandler: CrashkiosCrashHandler { |
141 | | - override func crashParts(addresses: [KotlinLong], exceptionType: String, message: String) { |
142 | | - Bugsnag.notify(CrashNSException(callStack: addresses, exceptionType: exceptionType, message: message)) |
143 | | - } |
144 | | -} |
145 | | -``` |
146 | | - |
147 | | -Note, `CrashkiosCrashHandler` and possibly `KotlinLong` may have different generated names from |
148 | | -the Kotlin compiler output. |
149 | | - |
150 | | -Inside `AppDelegate.swift`, or wherever you do app setup, add the following: |
151 | | - |
152 | | -```swift |
153 | | - CrashIntegrationKt.crashInit(handler: BugsnagCrashHandler()) |
154 | | -``` |
155 | | - |
156 | | -To see all of this code in a sample app, see [sample/iosAppBugsnag](sample/iosAppBugsnag) |
157 | | - |
158 | | -## Status |
159 | | - |
160 | | -Release builds will show method names from the stack, but not line numbers. Debug builds include line numbers. There may be a setting to |
161 | | -change that, but we haven't found it yet. |
162 | | - |
163 | | -We currently add the following config to each build binary. We still need to experiment and may be able to remove some |
164 | | -of the config. |
165 | | - |
166 | | -```groovy |
167 | | -freeCompilerArgs += "-Xg0" |
168 | | -if(it instanceof org.jetbrains.kotlin.gradle.plugin.mpp.Framework) { |
169 | | - isStatic = true |
170 | | -} |
171 | | -``` |
172 | | - |
173 | | -### Upload dSYMs to Crashlytics |
174 | | - |
175 | | -If you find that you're missing events, or you're getting a warning message in the Crashlytics dashboard about missing dSYMs, then following instructions can help you ensure that Crashlytics always has your most up-to-date dSYMs. |
176 | | - |
177 | | -In Xcode, select the iosApp project on the lefthand side, then switch to the Build Phases tab. Beneath that, on the left, is a "+" button (circled in the image below). Select that, and then in the dropdown menu that appears, select "New run script phase". Name the new script something along the lines of "Upload Crashlytics dSYMs", or something similarly descriptive. |
178 | | - |
179 | | - |
180 | | - |
181 | | -Paste in the following script: |
182 | | - |
183 | | -```Pods/FirebaseCrashlytics/upload-symbols -gsp iosApp/GoogleServiceInfo.plist -p ios ../build/bin/ios/debugFramework/sample.framework.dSYM/ |
184 | | -Pods/FirebaseCrashlytics/upload-symbols -gsp iosApp/GoogleService-Info.plist -p ios ../build/bin/ios/releaseFramework/sample.framework.dSYM/ |
185 | | -``` |
186 | | - |
187 | | -Note a few things about this script: |
188 | | - |
189 | | -- It will upload both your debug and release dSYMs every time you run your project in order to ensure that Firebase has any and all relevant dSYMs. |
190 | | -- Depending on the configuration of your project, the relative paths in the script may need to be different. Ensure that you're pointing to the following: |
191 | | - - GoogleService-Info.plist |
192 | | - - Debug [shared_library_name].framework.dSYM |
193 | | - - Release [shared_library_name].framework.dSYM |
194 | | - |
195 | | -If the folder structure of your shared Kotlin library is different than what it is here, or you're having difficulty finding your dSYMs for any reason, you can use the following script to print a list of them: |
196 | | - |
197 | | -```find ../build -name "*.dSYM"``` |
| 43 | +## Usage |
198 | 44 |
|
199 | | -Note again that the `../build` relative path is configured to this repository's structure. Be sure that the script points to your Kotlin library's `/build` folder, which may be somewhere else, based on the configuration of your project. |
| 45 | +You can use CrashKiOS directly, but it would be easier to integrate [Kermit](https://github.com/touchlab/Kermit/) and |
| 46 | +use those implementations for Crashlytices and Bugsnag. |
200 | 47 |
|
201 | | -The primary resource for these tips was [this page of Firebase's Crashlytics guide](https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports). |
0 commit comments