I would like to log error reports for my app to the Android Market error console; it looks like I can use Log.wtf for this.
The documentation for Log.wtf says:
What a Terrible Failure: Report a condition that should never happen. The error will always be logged at level ASSERT with the call stack. Depending on system configuration, a report may be added to the DropBoxManager and/or the process may be terminated immediately with an error dialog.
In my case, I can catch these exceptions and recover from them by showing an error message; I don't want my app to crash, but I do want the report to be sent to the error console.
Under what circumstances will Log.wtf terminate my app? Is it possible to get an error report without causing the app to crash?
It depends on your system settings (certain options can be enabled for debugging but are disabled on normal devices). They are settings enabled when android is compiled for the device and possibly the kernel.
I would suggest using Log.e() with a prefix instead of Log.wtf() to avoid any problems e.g. WTF: Something terrible happened
Here is what happens when you call a Log.wtf()
-> Log.java
/**
* What a Terrible Failure: Report an exception that should never happen.
* Similar to {#link #wtf(String, Throwable)}, with a message as well.
* #param tag Used to identify the source of a log message.
* #param msg The message you would like logged.
* #param tr An exception to log. May be null.
*/
public static int wtf(String tag, String msg, Throwable tr) {
TerribleFailure what = new TerribleFailure(msg, tr);
int bytes = println_native(LOG_ID_MAIN, ASSERT, tag, getStackTraceString(tr));
sWtfHandler.onTerribleFailure(tag, what);
return bytes;
}
-> Log.java
private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() {
public void onTerribleFailure(String tag, TerribleFailure what) {
RuntimeInit.wtf(tag, what);
}
};
-> RuntimeInit.java
/**
* Report a serious error in the current process. May or may not cause
* the process to terminate (depends on system settings).
*
* #param tag to record with the error
* #param t exception describing the error site and conditions
*/
public static void wtf(String tag, Throwable t) {
try {
if (ActivityManagerNative.getDefault()
.handleApplicationWtf(mApplicationObject, tag,
new ApplicationErrorReport.CrashInfo(t))) {
// The Activity Manager has already written us off -- now exit.
Process.killProcess(Process.myPid());
System.exit(10);
}
} catch (Throwable t2) {
Slog.e(TAG, "Error reporting WTF", t2);
}
}
-> ActivityManagerNative.java
public boolean handleApplicationWtf(IBinder app, String tag,
ApplicationErrorReport.CrashInfo crashInfo)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(app);
data.writeString(tag);
crashInfo.writeToParcel(data, 0);
mRemote.transact(HANDLE_APPLICATION_WTF_TRANSACTION, data,
reply, 0);
reply.readException();
boolean res = reply.readInt() != 0;
reply.recycle();
data.recycle();
return res;
}
Following nebkat information. Beware using WTF: the API level of the device must be 8 or higher.
Related
I have a project where all errors and warnings were written in logs in classes where they occurred. I started to use crashlytics. And tried to combine all the logic connected to logging and errors to a specific class. And force this class to decide where to report an error (error + warning) and where to log warnings. But when I made it I started to receive all the errors as they have occurred in this class. Is it possible to rename errors? For example, manually add activity name in the report so I will get different errors and not the same error all the time. Not like on the picture.
class AppLogs {
companion object CrashReport {
private fun reportToCrashlytics(
errorMessage: String,
user: String = "user id",
log: String = "log",
additionalInfoTitle: String = "title",
additionalInfoMessage: String = " message"
) {
FirebaseCrashlytics.getInstance().log(log)
FirebaseCrashlytics.getInstance().setUserId(user)
FirebaseCrashlytics.getInstance()
.setCustomKey(additionalInfoTitle, additionalInfoMessage)
FirebaseCrashlytics.getInstance().recordException(throw Exception(errorMessage))
}
private fun reportLog(errorMessage: String) {
Timber.e(errorMessage)
}
fun report(errorMessage: String, errorType: Char) {
if (errorType == 'e') {
reportLog(errorMessage)
reportToCrashlytics(errorMessage)
} else {
reportLog(errorMessage)
}
}
}
}
Answer for the actual issue
... manually add activity name in the report so I will get different errors and not the same error all the time ...
The answer is short - no it is not possible (without enormous effort).
But why?
Crashlytics uses the stack trace of the throwable object which was reported. Using this stack allows us to get reports that track the exception right to the line where it occurred.
It means that to create another issue record in the list of issues you have to modify the stack trace of the throwable object.
TrimmedThrowableData is the class that collects stack trace data to report. As you can see it gets unmodified stack trace of the throwable.
Most likely the only solution
If you want to get different stack trace - create Exception objects where reportToCrashlytics is used and pass these objects as arguments. Or throw and catch exceptions, and report exceptions that were caught.
Previous answer (bug found)
You are throwing the exception making the application crash. You should create Exception object and just pass it in as an argument.
If you open IDE you will see that the line where throw is used has recordException method highlighted and if you hover a cursor over it you will see the following:
It means that you never execute recordException because arguments are evaluated first and then passed into the function. But the function is never reached because throw crashes the application.
Do not use throw keyword:
FirebaseCrashlytics.getInstance().recordException(Exception(errorMessage))
You can change Your Stack trace like this :
public class CustomException extends Exception {
public CustomException(String message, int lineNumber) {
super(message);
StackTraceElement[] stackTrace = getStackTrace();
StackTraceElement[] newStackTrace = new StackTraceElement[stackTrace.length + 1];
System.arraycopy(stackTrace, 0, newStackTrace, 1, stackTrace.length);
newStackTrace[0] = new StackTraceElement("className", "methodName", "fileName", lineNumber);
setStackTrace(newStackTrace);
}
}
I have some Android code in Kotlin that is getting the status of a stored SMS message:
val status = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Sms.STATUS))
Later on, I compare this value to the different constants to see what the status means:
print(when (status) {
Telephony.Sms.STATUS_COMPLETE -> "complete"
Telephony.Sms.STATUS_FAILED -> "failed"
Telephony.Sms.STATUS_NONE -> "none"
Telephony.Sms.STATUS_PENDING -> "pending"
else -> status.toString()
})
Usually, I get strings like "complete", "none" and "pending". However, sometimes "70" is returned, which means that the status doesn't match any of the constants found listed here. What does 70 mean here?
After digging in the source code (specifically class SmsMessage, field status) I found out this document. When you scroll to section 9.2.3.15, page 68, you will see list of possible status codes for SMS. Your code (70) means
1000110 SM Validity Period Expired
In some cases, this error code is returned when the carrier determines that it is impossible to route the SMS and the message has to be dropped as it is being looped between platforms.
Error code : 70
Destination permanently unavailable The destination (i.e., “dst”)
phone number is not active and there is no indication of when it will become available again. Note that this is a broad error code where the carrier has not indicated the reason for the destination unavailability. Check the “dst” phone number to ensure that it is correct. Also, try sending messages to an alternative number to ensure that all other parts of your application are working.
These are the other error code.
/** TP-Status: no status received. */
public static final int STATUS_NONE = -1;
/** TP-Status: complete. */
public static final int STATUS_COMPLETE = 0;
/** TP-Status: pending. */
public static final int STATUS_PENDING = 32;
/** TP-Status: failed. */
public static final int STATUS_FAILED = 64;
I'm trying to get data from a monitor to an Android application and I've took the IHE - PCD-01 transaction as a model.
The scheme is simple, is based on achieve the interconnection between the monitor and the tablet, where the monitor sends constantly information and the application is listening.
But what I don't understand is if I need an ACK or not after every message. Does anyone can help me with this?
TL;DR yes, nothing special here, support the usual HL7 ACK/NACK driven by MSH-15, MSH-16 fields. ACK-ing everything by default is "better safe then sorry"
The document "IHE Patient Care Device (PCD), Technical Framework, Volume 2 (PCD TF-2) Transactions, Revision 1.0 - Final Text, August 12, 2011" available at http://www.ihe.net/technical_framework/upload/ihe_pcd_tf_vol2_ft_2011-08-12.pdf says
..The common static definition of the HL7 acknowledgement (ACK) message is described in Appendix G, "HL7 Implementation Notes"..
which says
G.1 Network Guidelines
The HL7 2.6 standard does not define a network communications protocol. Beginning with HL7 2.2, the definitions of lower layer protocols were moved to the Implementation Guide, but are not HL7 requirements. The IHE Framework makes these recommendations:
Applications shall use the Minimal Lower Layer Protocol defined in Appendix C of the HL7 Implementation Guide.
An application that wants to send a message (initiate a transaction) will initiate a network connection to start the transaction. The receiver application will respond with an acknowledgement or response to query but will not initiate new transactions on this network connection
G.1.1 Acknowledgment Modes
ACKNOWLEDGMENT MESSAGES
Acknowledgment messages may be defined on an application basis. However the simple general acknowledgment message (ACK) may be used where the application does not define a special message (application level acknowledgment) and in other cases as described in Section 2.9, "Message Processing Rules".
The IHE PCD transaction PCD-03 supports „enhanced mode‟ acknowledgements. See discussion under PCD-03 Transactions as well as in B.1 MSH – Message Header Segment and B.2 MSA – Message Acknowledgement Segment
and document "Health Level Seven, Version 2.6 © 2007, Chapter 2: Control" coming from the "HL7 Messaging Standard Version 2.6" package which can be downloaded from http://www.hl7.org/implement/standards/product_brief.cfm?product_id=185 describes the accept and validate behavior in
2.9.2 Message response using the original processing rules
..too long to quote..
2.9.3 Response using enhanced acknowledgement
..too long to quote..
depending on the values of MSH-15 Accept Acknowledgement Type and MSH-16 Application Acknowledgment Type fields in the HL7 message
The above chapters from the HL7 standard contain what you want to read and implement/support.
EDIT:
Simply put, in HL7 protocol in every message sent the sender may request an ACK receipt by flagging appropriate fields in the message header segment. IHE does not remove this rule and does not enforce any other but enables any other convention to be defined on an application basis. Correct expected behavior is defined by the HL7 specification and in order to get it right and create a conforming implementation (without hidden surprises for your 3rd parties) you may need to read it several times (see also Stack Overflow: How can I make my system HL7 certified?)
For example this is how HAPI library handles the ACKing, snippet comes from http://sourceforge.net/p/hl7api/code/764/tree/tags/Root_REL_1_2/hapi-mvn/hapi-base/src/main/java/ca/uhn/hl7v2/protocol/impl/ProcessorImpl.java
/**
* #see ca.uhn.hl7v2.protocol.Processor#cycle(boolean)
*/
public void cycle(boolean expectingAck) throws HL7Exception {
log.debug("In cycle({})", expectingAck);
cleanReservations();
cleanAcceptAcks();
cleanReservedMessages();
Transportable in = null;
try {
if (expectingAck) {
in = tryReceive(myContext.getLocallyDrivenTransportLayer());
} else {
in = tryReceive(myContext.getRemotelyDrivenTransportLayer());
}
} catch (TransportException e) {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {}
throw e;
}
// log
if (in != null) {
log.debug("Received message: {}", in.getMessage());
} else {
log.debug("Received no message");
}
// If we have a message, handle it
if (in != null) {
String acceptAckNeeded = null;
// String appAckNeeded = null;
String ackCode = null;
String ackId = null;
try {
String[] fieldPaths = {"MSH-15", "MSH-16", "MSA-1", "MSA-2"};
String[] fields = PreParser.getFields(in.getMessage(), fieldPaths);
acceptAckNeeded = fields[0];
// appAckNeeded = fields[1];
ackCode = fields[2];
ackId = fields[3];
} catch (HL7Exception e) {
log.warn("Failed to parse accept ack fields in incoming message", e);
}
if (ackId != null && ackCode != null && ackCode.startsWith("C")) {
long expiryTime = System.currentTimeMillis() + 1000 * 60;
myAcceptAcks.put(ackId, new ExpiringTransportable(in, expiryTime));
} else {
AcceptAcknowledger.AcceptACK ack = AcceptAcknowledger.validate(getContext(), in);
if ((acceptAckNeeded != null && acceptAckNeeded.equals(AL))
|| (acceptAckNeeded != null && acceptAckNeeded.equals(ER) && !ack.isAcceptable())
|| (acceptAckNeeded != null && acceptAckNeeded.equals(SU) && ack.isAcceptable())) {
trySend(myContext.getRemotelyDrivenTransportLayer(), ack.getMessage());
}
if (ack.isAcceptable()) {
if (isReserved(ackId)) {
log.debug("Received expected ACK message with ACK ID: {}", ackId);
removeReservation(ackId);
long expiryTime = System.currentTimeMillis() + 1000 * 60 * 5;
myAvailableMessages.put(ackId, new ExpiringTransportable(in, expiryTime));
} else {
log.debug("Sending message to router");
Transportable out = myContext.getRouter().processMessage(in);
sendAppResponse(out);
}
} else {
// TODO: should we do something more here? Might be nice to
// allow a configurable handler for this situation
log.warn("Incoming message was not acceptable");
}
}
} else {
String transport = expectingAck ? " Locally driven " : "Remotely driven";
log.debug("{} TransportLayer.receive() returned null.", transport);
}
sleepIfNeeded();
log.debug("Exiting cycle()");
}
Thanks for your answer :)
of course that it is better to use an ACK to make sure if the receiver is getting the message but what I wanted to know if it was mandatory or not using the PCD-01 transaction.
I've read your documents and what I've understood is that the use of ACK depends on the MSH-15 and MSH-16 fields content, but with the following information:
An application that wants to send a message (initiate a transaction) will initiate a network connection to start the transaction. The receiver application will respond with an acknowledgement or response to query but will not initiate new transactions on this network connection
I understand that the ACK is only at the beginning of the connection not after every message, is it right?
Cannot find the reason why I am getting "Null pointer exception" . I could give an explicit check to see if "getStorageState is null" but that doesnt explain why I am getting this error.
The error is :
java.lang.NullPointerException
at android.os.Environment.getStorageState(Environment.java:719)
at android.os.Environment.getExternalStorageState(Environment.java:694)
at com.ciqual.android.insight.sessionService.RemoveFiles(SessionService.java:664)
com.vyshas.android.sessionService.onEndSession(SessionService.java:460)
at : (the line that error points to is this :)(seen in jellybean 4.3 and kitkat)
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
//
}
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState) wouldnt cause a NPE while Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) does cause NPE and crashes the application.
I still dont know why getExternalStorageState is null eventhough I have permissions set but the solutions atleast wouldn't crash the application.
I have similar repot from Android 4.3 .
Source code is:
/**
* Gets the current state of the primary "external" storage device.
*
* #see #getExternalStorageDirectory()
*/
public static String getExternalStorageState() {
try {
IMountService mountService = IMountService.Stub.asInterface(ServiceManager
.getService("mount"));
final StorageVolume primary = getPrimaryVolume();
return mountService.getVolumeState(primary.getPath());
} catch (RemoteException rex) {
Log.w(TAG, "Failed to read external storage state; assuming REMOVED: " + rex);
return Environment.MEDIA_REMOVED;
}
}
I have nothing to do but try catch it.
I would like to have the whole stacktrace in Google Analytic's report's for my mobile application.
I wrote class that print's the stacktrace and put's it into string, but it doesn't work.
My custom ExceptionParser:
#Override
public String getDescription(String threadName, Throwable throwable) {
return threadName + " " + getStackTrace(throwable);
}
private String getStackTrace(Throwable throwable) {
final Writer result = new StringWriter();
final PrintWriter printWriter = new PrintWriter(result);
throwable.printStackTrace(printWriter);
return result.toString();
}
And I set it like this:
EasyTracker.getTracker().setExceptionParser(new StacktraceExceptionParser());
The method below combines the entire stack trace into a single comma separated line, which may help in case Analytics returns just the first line. But there still may be a length limit so it may be prudent to do filtering to eliminate items you do not need (see comment)
public String getCombinedStackTrace(Throwable aThrowable) {
final StringBuilder result = new StringBuilder();
result.append(aThrowable.toString());
result.append(',');
String oneElement;
for (StackTraceElement element : aThrowable.getStackTrace() ) {
// you can do some filtering here, selecting only the elements you need
oneElement = element.toString();
result.append( oneElement );
result.append( ",");
}
return result.toString();
}
I second Nikolay's comment about using an error reporting library. I found it to be tremendously helpful.
I know this thread is old but I am trying to figure out how to get this working, but just for completeness there is a useful method on Log that does what you want
String stackTraceStr = Log.getStackTraceString(exception);
EDIT: In response to the 100 char limit comment
I could never get EasyTracker.getTracker().setExceptionParser(...) working, infact I do not think it works, so I followed the blog post here http://dandar3.blogspot.co.uk/2013/03/google-analytics-easytracker-detailed.html
The important point in the blog post is to make sure you set your ExceptionParser on the GA exception handler:
// Make sure you set the context on EasyTracker first
EasyTracker.getInstance().setContext(this);
// As in in the blog post, we have to get the ExceptionReporter
// in order to set the ExceptionParser
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
if (uncaughtExceptionHandler instanceof ExceptionReporter) {
ExceptionReporter exceptionReporter = (ExceptionReporter) uncaughtExceptionHandler;
exceptionReporter.setExceptionParser(new AnalyticsExceptionParser());
}
This worked for me and logs more than 100 chars.
Analytics may be limiting the size of messages you can send. I suggest using a real error reporting library like ACRA or BugSense instead. You will get extra features such as device info, configuration details and better error reporting (combining multiple exceptions if they have the same trace, etc.).