Closed christianselig closed 3 years ago
Hi @christianselig - thanks for the request. At this time, we only support handled errors for Xamarin and Unity apps. We don't have any immediate plans to bring this to iOS native but this is on our radar and we hope to bring this feature to other platforms in the future.
I'll post here if I have any updates. In the meantime, keep an eye out on our roadmap :)
Would you mind making this a little wider and supporting this for all Cocoa apps (iOS, macOS, tvOS and watchOS)? Thanks!
This is a blocker for me and other macOS devs I know for moving from Crashlytics.
This is a hang-up for us as well. I'm loving the new features recently brought into AppCenter, but it's things like this that still make Firebase a little better option.
We would like to see this as well. And it would be great if it could capture a stack trace of where the error was handled at, to provide more insight into what code path resulted in the error.
Hi all - thanks for the feedback. We understand how crucial this feature is for app developers and this is absolutely something we plan to support in the future. Unfortunately, our team is focused on other priorities for the next few months and won't have the bandwidth to work on this. That said, I'll continue to monitor this thread and will take everyone's comments into consideration when we prioritize our future work.
Thanks!
We'd love this as well to be able to migrate away from Crashlytics. Plus CLS_LOG
/CLSLogv
which will only appear if there's a crash (see https://docs.fabric.io/apple/crashlytics/enhanced-reports.html) are useful. Thanks !
I also need the equivalent of CLSLogv in AppCenter.
We're evaluating the Crashlytics substitute with the end of the service. AppCenter is a great option, but this is highly needed feature.
The lack of this feature is one of the reasons PSPDFKit moved away from AppCenter: https://twitter.com/steipete/status/1226896190509285376
Yep, this would be great to have. Without it theres no reason for my team to use AppCenter. Not sure why it's been prioritized for Android, Xamarin, Unity, UWP, WPF and WinForms apps, but suddenly you're too busy to support iOS... which is not exactly a small platform. Any update regarding a timeline on this feature?
This is a must-have feature for me also.
Hey I'm so sorry, I don't have an update at the moment. We are continuosly working to improve our product based on feedback and I will post here if I have a better timeline. Thanks!
It has been a year and still no progress on this. App Center has been very disappointing to work with, I miss HockeyApp.
We were able to get around this limitation in the App Center iOS SDK by calling their Web API instead: https://docs.microsoft.com/en-us/appcenter/diagnostics/upload-crashes#upload-a-crash-report
@justintoth would you be so kind and share your workaround? thanks I mostly got NoValidLogsFoundInLogContainer when playing around with posting some JSON and when I got a success response I was unable to find the posted data in the appcenter UI
Also for us the ability to log Error / NSError objects together with a call stack a precondition to be able to migrate from Crashlythics / Firebase. At the moment we have to work with Firebase for our iOS apps and with AppCenter for our Xamarin apps, which is not exactly convenient.
There are so many circumstances where it would be great for a user to submit an issue that also brings along logs and device data that are part of a crash report. This seems like a trivial addition to the existing system, it's a bit unclear why MS is resisting this. It's not remarkably different than the test crash call.
Sounds like a very useful feature to me too.
Pls., go for it
Absolutely must-have, how is this not on high prio?
This is also something we consider a must have, especially when moving from Crashlytics. For now I'm putting the errors into the Analytics as a custom event, but they really belong with the crashes.
We have recently moved to App Center from Crashlytics and this is a huge problem. It's causing a lot of issues with bug visibility and missing info in its current form. The android bugs are so much easier to find and are much more useful (individually keyed, no truncation etc).
Please prioritise this.
Sorry, I just saw @rist's question... Here is our Titanium code for logging custom App Center crashes, using their SDK for Android and their API for iOS.
// Report error to App Center.
if (utils.android) {
try {
let properties = getProperties(true);
properties = _.filter(properties, function(p) { return !!p.value; });
console.log('Reporting Android error to app center: ' + evt.message + ' with properties: ' + JSON.stringify(properties));
Crashes.trackError(evt.message, properties);
console.log('Reported error to app center successfully!');
}
catch (err) {
// well, that sucks, huh?
console.error('Error not submitted to app center:');
console.error(err);
}
} else {
const user = settings.user();
var data = {
logs: [
{
type: 'managedError',
processId: '123',
id: Ti.Platform.id,
fatal: false,
processName: Ti.App.id,
appLaunchTimestamp: moment.utc().format(),
device: {
appVersion: Ti.App.version,
appBuild: utils.android ? Ti.App.Android.appVersionCode : parseInt(Ti.App.version.replace(/\./g, '')),
sdkName: 'appcenter.custom',
sdkVersion: Ti.version,
osName: Ti.Platform.osname,
osVersion: Ti.Platform.version,
model: Ti.Platform.model,
locale: Ti.Locale.currentLocale
},
exception: {
message: evt.message,
type: 'Error'
},
userId: user && user.emailAddress,
}
]
};
console.log('Reporting iOS error to app center: ' + evt.message + ' with data: ' + JSON.stringify(data));
xhr.call({
verb: 'POST',
url: 'https://in.appcenter.ms/logs?Api-Version=1.0.0',
headers: [
{ key: 'Content-Type', value: 'application/json' },
{ key: 'App-Secret', value: Ti.App.Properties.getString('ti.appcenter.secret.ios') },
{ key: 'Install-ID', value: Ti.App.installId }
],
data: JSON.stringify(data),
disableFlattenData: true,
success: function xhrSuccess(result) {
console.log('Reported error to app center successfully!');
},
error: function xhrError(result) {
console.error('Error not submitted to app center: ' + result.status + ' status code with response: ' + result.responseText);
},
});
}
Thanks @justintoth! I just tried it and the errors are showing up in App Center by using that payload structure. However, they are also logged as crashes and are indistinguishable from the real fatal errors. 😔
@winnie I hope this issue will make it into one of the next iteration plans. It is open for quite some time now and has a considerable amount of votes (it actually is in the top 10 of open feature requests) and it is already supported for other platforms than iOS.
It looks like this is unlikely to happen for another year: https://github.com/microsoft/appcenter/wiki/Roadmap
[…] the App Center team will prioritize improving reliability and performance for the service through mid-2021. This means new feature work will be significantly reduced.
I'm really starting to regret having switched all my apps from Crashlytics to AppCenter...
Hi @MatkovIvan @winnie,
I made a little addition on the MSCrashes class to be able to send error reports. It makes use of the existing trackModelException:withProperties:withAttachments:
internal method of the same class.
I can tell it works well: on the appcenter.ms website, in the Issues section of an iOS or macOS app, you just have to append &errorType=all
to the URL to display the errors in the list along with the crashes. The website should be updated to display the "All" and "Errors" tabs as for Android apps.
If you want to have a look, the commit is on my fork here: https://github.com/gui17aume/appcenter-sdk-apple/commit/e9818b235efa51574af3e4e6a186496f5e9ec37d (I don't know if you're open to pull requests for this kind of additions...)
Is this going to be implemented in the base SDK? Is there any kind of roadmap for App Center? Will the entire App Center be deprecated soon, as there is almost zero communication from developers, nobody responds to tickets, and absolutely mandatory albeit small features are ignored. If it will be deprecated please step forward and tell us so we can look into alternatives.
Thanks.
I built a plain HTTP version in Swift myself because I don't think this isn't going to be resolved soon. Here's my code to send crashes to AppCenter:
func sendError(_ message: String, properties: Dictionary<String, String>? = nil) {
let url = URL(string: "https://in.appcenter.ms/logs?Api-Version=1.0.0")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("xxxxxxxx-yyyy-yyyy-yyyy-zzzzzzzzzzzz", forHTTPHeaderField: "app-secret")
request.addValue(UIDevice.current.identifierForVendor!.uuidString, forHTTPHeaderField: "install-id")
let errorId = UUID().uuidString
let attachmentId = UUID().uuidString
let processId = ProcessInfo.processInfo.processIdentifier
let bundleIdentifier = Bundle.main.bundleIdentifier
let appVersion = Bundle.main.infoDictionary!["CFBundleShortVersionString"]
let appBuild = Bundle.main.infoDictionary!["CFBundleVersion"]
let sdkName = "appcenter.custom"
let osName = "iOS"
let osVersion = UIDevice.current.systemVersion
let model = modelName()
let locale = Locale.current.identifier
var errors: [Dictionary<String, Any>] = [[
"type": "managedError",
"processId": processId,
"id": errorId,
"fatal": false,
"processName": bundleIdentifier,
"appLaunchTimestamp": iso8601withFractionalSeconds(),
"device": [
"appVersion": appVersion,
"appBuild": appBuild,
"sdkName": sdkName,
"sdkVersion": "1.0.0",
"osName": osName,
"osVersion": osVersion,
"model": model,
"locale": locale
],
"exception": [
"message": message,
"type": "Error"
]
]]
if properties != nil {
let propertiesJson = try? JSONSerialization.data(withJSONObject: properties, options: [])
let propertiesBase64: String = propertiesJson!.base64EncodedString()
errors.append([
"type": "errorAttachment",
"contentType": "application/json",
"timestamp": iso8601withFractionalSeconds(),
"data": propertiesBase64,
"errorId": errorId,
"id": attachmentId,
"device": [
"appVersion": appVersion,
"appBuild": appBuild,
"sdkName": sdkName,
"sdkVersion": "1.0.0",
"osName": osName,
"osVersion": osVersion,
"model": model,
"locale": locale
]
])
}
let jsonMap = ["logs": errors]
if let jsonData = try? JSONSerialization.data(withJSONObject: jsonMap, options: []) {
URLSession.shared.uploadTask(with: request, from: jsonData) { data, response, error in
print(data)
print(response)
print(error)
}.resume()
}
}
private func iso8601withFractionalSeconds() -> String {
let iso8601DateFormatter = ISO8601DateFormatter()
iso8601DateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return iso8601DateFormatter.string(from: Date())
}
private func modelName() -> String {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
return identifier
}
Feel free to use, improve and share it back!
@osca Could you share where you found the documentation for this endpoint? I don't see it in https://openapi.appcenter.ms/
but I'd like to do something similar!
@IPWright83, it is linked in one of the comments before of @justintoth in https://github.com/microsoft/appcenter/issues/192#issuecomment-593028522
Link to the doc: https://docs.microsoft.com/en-us/appcenter/diagnostics/upload-crashes#upload-a-crash-report
Has anyone managed to get this working with a react-native
application? I'm trying to use fake crashes to record errors using the custom crash log functionality, but despite getting a success back from the API I can't view anything through the web dashboard :(
Any news on this? This is really a blocker...
For others having this issue where handled errors are being logged into App Center, but there is no overview dashboard or "Errors" tab in the web portal (appcenter.ms): Go to the Diagnostics > Issues page and paste the following behind the URL:
&errorType=handlederror
For anyone still having issues with the lack of custom error logging, I've extended AppCenter
to handle this in a type safe, user friendly way. Thank you @osca for the idea!
extension AppCenter {
/// Sends a custom crash report using the App Center API
/// API documentation available @ https://docs.microsoft.com/en-us/appcenter/diagnostics/upload-crashes#upload-a-crash-report
/// - Parameters:
/// - message: The error message
/// - userID: An optional user identifier associated with the crash
/// - isFatal: Whether the crash is fatal or not. Defaults to false
/// - properties: An optional dictionary with additional properties to be sent as an attachment
/// - callback: An optional callback with the result of the operation.
static func sendError(
message: String,
forUserID userID: String? = nil,
isFatal: Bool = false,
additionalProperties properties: [String: Any]? = nil,
callback: ((Bool) -> Void)? = nil
) {
let exception = AppCenter.Exception(domain: .customError, message: message, isFatal: isFatal)
AppCenter.send(exception, forUserID: userID, additionalProperties: properties, callback: callback)
}
/// Sends a custom crash report using the App Center API
/// API documentation available @ https://docs.microsoft.com/en-us/appcenter/diagnostics/upload-crashes#upload-a-crash-report
/// - Parameters:
/// - exception: The exception payload containing information about the error
/// - userID: An optional user identifier associated with the crash
/// - properties: An optional dictionary with additional properties to be sent as an attachment
/// - callback: An optional callback with the result of the operation. The callback always runs on the main queue.
static func send(
_ exception: AppCenter.Exception,
forUserID userID: String? = nil,
additionalProperties properties: [String: Any]? = nil,
callback: ((Bool) -> Void)? = nil
) {
let error = CustomError(errorType: .managed, exception: exception, userID: userID)
var errors = [error]
if let properties = properties,
let json = try? JSONSerialization.data(withJSONObject: properties, options: []) {
let error = CustomError(errorType: .attachment(data: json, parentErrorID: error.identifier))
errors.append(error)
}
let payload = ErrorsPayload(logs: errors)
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let payloadData = try? encoder.encode(payload)
let task = URLSession.shared.uploadTask(
with: .appCenterCustomErrorRequest,
from: payloadData
) { _, response, error in
guard
error == nil,
let response = response as? HTTPURLResponse,
(200..<300).contains(response.statusCode)
else {
DispatchQueue.main.async { callback?(false) }
return
}
DispatchQueue.main.async { callback?(true) }
}
task.resume()
}
}
fileprivate extension AppCenter {
struct CustomError: Encodable {
let identifier = UUID()
let timestamp: Date
let appLaunchTimestamp: Date
let processId: Int
let processName: String
let device: Device
let userID: String?
let exception: Exception?
var isFatal: Bool { self.exception?.isFatal ?? false }
var data: String? { self.errorType.data?.base64EncodedString() }
var contentType: String? { self.errorType.contentType }
var type: String { self.errorType.type }
var errorID: UUID? { self.errorType.parentErrorID }
private let errorType: ErrorType
init(
errorType: ErrorType,
exception: Exception? = nil,
date: Date = Date(),
appLaunchTimestamp: Date = Date(),
processId: Int = Int(ProcessInfo.processInfo.processIdentifier),
processName: String = ProcessInfo.processInfo.processName,
device: Device = Device(),
userID: String? = nil
) {
self.errorType = errorType
self.exception = exception
self.timestamp = date
self.appLaunchTimestamp = appLaunchTimestamp
self.processId = processId
self.processName = processName
self.device = device
self.userID = userID
}
enum CodingKeys: String, CodingKey {
case appLaunchTimestamp
case contentType
case data
case device
case errorID = "errorId"
case exception
case identifier = "id"
case isFatal = "fatal"
case processId
case processName
case timestamp
case type
case userID = "userId"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.identifier, forKey: .identifier)
try container.encode(self.timestamp, forKey: .timestamp)
try container.encode(self.appLaunchTimestamp, forKey: .appLaunchTimestamp)
try container.encode(self.processId, forKey: .processId)
try container.encode(self.processName, forKey: .processName)
try container.encode(self.exception, forKey: .exception)
try container.encode(self.device, forKey: .device)
try container.encode(self.userID, forKey: .userID)
try container.encode(self.isFatal, forKey: .isFatal)
try container.encode(self.data, forKey: .data)
try container.encode(self.contentType, forKey: .contentType)
try container.encode(self.type, forKey: .type)
try container.encode(self.errorID, forKey: .errorID)
}
}
}
extension AppCenter {
struct Exception: Encodable {
let type: String?
let message: String?
let isFatal: Bool
let strackTrace: String?
let frames: [Frame]?
let innerExceptions: [Exception]?
let threads: [Thread]?
init(
domain: String? = nil,
message: String? = nil,
isFatal: Bool = false,
strackTrace: String? = nil,
frames: [Frame]? = nil,
innerExceptions: [Exception]? = nil,
threads: [Thread]? = nil
) {
self.type = domain
self.message = message
self.isFatal = isFatal
self.strackTrace = strackTrace
self.frames = frames
self.innerExceptions = innerExceptions
self.threads = threads
}
struct Thread: Encodable {
private let identifier: Int
private let frames: [Frame]
private let name: String
}
struct Frame: Encodable {
private let className: String?
private let fileName: String?
private let lineNumber: Int?
private let methodName: String?
}
}
}
extension AppCenter.CustomError {
struct Device: Encodable {
let appVersion: String
let appBuild: String
let sdkName: String
let sdkVersion: String
let osName: String
let osVersion: String
let model: String?
let appNameSpace: String?
let locale: String
let timeZoneOffset: TimeInterval?
init(
appVersion: String = .appVersion,
appBuild: String = .appBuild,
sdkName: String = .appCenterSDKName,
sdkVersion: String = .appCenterSDKVersion,
osName: String = UIDevice.current.systemName,
osVersion: String = UIDevice.current.systemVersion,
model: String? = deviceModelName(),
appNameSpace: String? = Bundle.main.bundleIdentifier,
locale: String = Locale.current.identifier,
timeZoneOffset: TimeInterval? = nil
) {
self.appVersion = appVersion
self.appBuild = appBuild
self.sdkName = sdkName
self.sdkVersion = sdkVersion
self.osName = osName
self.osVersion = osVersion
self.model = model
self.appNameSpace = appNameSpace
self.locale = locale
self.timeZoneOffset = timeZoneOffset
}
}
struct ErrorType: Encodable {
/// Default factories
static let apple: ErrorType = { fatalError("Not implemented") }()
static let handled: ErrorType = { fatalError("Not implemented") }()
static let managed: ErrorType = { .init(type: .managedErrorType) }()
static func attachment(data: Data, parentErrorID: UUID) -> ErrorType {
.init(
type: .attachmentErrorType,
data: data,
contentType: .attachmentErrorContentType,
parentErrorID: parentErrorID
)
}
let data: Data?
let type: String
let contentType: String?
let parentErrorID: UUID?
private init(
type: String,
data: Data? = nil,
contentType: String? = nil,
parentErrorID: UUID? = nil
) {
self.type = type
self.data = data
self.contentType = contentType
self.parentErrorID = parentErrorID
}
}
}
private extension URLRequest {
/// A custom URLRequest configured with the url, method and headers required by the crash report upload API endpoint
static let appCenterCustomErrorRequest: URLRequest = {
let installID = UIDevice.current.identifierForVendor ?? UUID()
var request = URLRequest(url: .appCenterAPIURL)
request.httpMethod = .postHTTPMethod
request.setValue(.contentTypeApplicationJSON, forHTTPHeaderField: .contentTypeHeaderKey)
request.setValue(.appCenterAppID, forHTTPHeaderField: .appCenterSecretHeaderKey)
request.setValue(installID.uuidString, forHTTPHeaderField: .appCenterInstallIDHeaderKey)
return request
}()
}
private extension URL {
// swiftlint:disable:next force_unwrapping
static let appCenterAPIURL = URL(string: "https://in.appcenter.ms/logs?Api-Version=1.0.0")!
}
private extension String {
static let appCenterAppID = "YOUR_APPCENTER_APP_ID"
static let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "No build number found"
static let appCenterInstallIDHeaderKey = "Install-ID"
static let appCenterSDKName = "appcenter.custom"
static let appCenterSDKVersion = "1.0.0"
static let appCenterSecretHeaderKey = "App-Secret"
static let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "No version number found"
static let attachmentErrorContentType = "application/octet-stream"
static let attachmentErrorType = "errorAttachment"
static let bundleIdentifier = Bundle.main.bundleIdentifier
static let contentTypeApplicationJSON = "application/json"
static let contentTypeHeaderKey = "Content-Type"
static let customError = "CustomError"
static let managedErrorType = "managedError"
static let postHTTPMethod = "POST"
}
/// Returns the current device model name i.e. `iPhone12,1`
private func deviceModelName() -> String {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
return identifier
}
/// Convenience encodable root payload used to send the crash report(s)
private struct ErrorsPayload: Encodable {
let logs: [AppCenter.CustomError]
}
This issue has been automatically marked as stale because it has not had any activity for 60 days. It will be closed if no further activity occurs within 15 days of this comment.
AFAIK this is still an issue and since there is no support for it
This issue has been automatically marked as stale because it has not had any activity for 60 days. It will be closed if no further activity occurs within 15 days of this comment.
Still an issue.
Please fix this, this is a major blocker
This issue is open for more than 2 years, it's one of the reasons we're moving to Sentry.
Just saw this: https://github.com/microsoft/appcenter-sdk-apple/releases/tag/4.3.0
[Feature] Add support for tracking handled errors with Crashes.trackError and Crashes.trackException APIs.
Has someone tried it?
Hi foks,
Yes, this feature is implemented and was included in the last release (4.3.0). So, I'm closing this issue.
There is a known bug though, with displaying a symbolicated error for a native iOS app. Until it is fixed, you can check the raw log in Error -> Reports -> Raw
.
Describe the solution you'd like Currently there doesn't appear to be support for logging errors with Diagnostics for errors that are harmful but don't actually cause a crash. Here's an example of how it's handled in Crashlytics, for instance.
Describe alternatives you've considered The Analytics solution could work here, but having it properly tied in with the errors/Diagnostics side of things would be more optimal.
Additional context N/A