Skip to content

read logcat logs in nonblocking mode with end timeout #466

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/org/acra/ACRAConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ private ACRAConstants(){}

public static final boolean DEFAULT_LOGCAT_FILTER_BY_PID = false;

public static final boolean DEFAULT_NON_BLOCKING_READ_FOR_LOGCAT = false;

public static final boolean DEFAULT_SEND_REPORTS_IN_DEV_MODE = true;

public static final String DEFAULT_APPLICATION_LOGFILE = DEFAULT_STRING_VALUE;
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/acra/annotation/ReportsCrashes.java
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,14 @@
*/
boolean logcatFilterByPid() default ACRAConstants.DEFAULT_LOGCAT_FILTER_BY_PID;

/**
* Set this to true if you want to read logcat lines in a non blocking way for your
* thread. It has a default timeout of 3 seconds.
*
* @return true if you want that reading of logcat lines to not block current thread.
*/
boolean nonBlockingReadForLogcat() default ACRAConstants.DEFAULT_NON_BLOCKING_READ_FOR_LOGCAT;

/**
* Set this to false if you want to disable sending reports in development
* mode. Only signed application packages will send reports. Default value
Expand Down
38 changes: 25 additions & 13 deletions src/main/java/org/acra/collector/LogCatCollector.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.acra.util.IOUtils;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -97,33 +98,44 @@ public String collectLogCat(@NonNull ACRAConfiguration config, @Nullable String
commandLine.addAll(logcatArgumentsList);

try {
final Process process = Runtime.getRuntime().exec(commandLine.toArray(new String[commandLine.size()]));
final Process process = new ProcessBuilder().command(commandLine).redirectErrorStream(true).start();

if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Retrieving logcat output...");

// Dump stderr to null
new Thread(new Runnable() {
@Override
public void run() {
try {
IOUtils.streamToString(process.getErrorStream());
} catch (IOException ignored) {
}
}
}).start();

final String finalMyPidStr = myPidStr;
logcatBuf.add(IOUtils.streamToString(process.getInputStream(), new Predicate<String>() {
logcatBuf.add(streamToString(config, process.getInputStream(), new Predicate<String>() {
@Override
public boolean apply(String s) {
return finalMyPidStr == null || s.contains(finalMyPidStr);
}
}));
}, tailCount));
process.destroy();

} catch (IOException e) {
ACRA.log.e(LOG_TAG, "LogCatCollector.collectLogCat could not retrieve data.", e);
}

return logcatBuf.toString();
}

/**
* Reads an InputStream into a string in an non blocking way for current thread
* It has a default timeout of 3 seconds.
*
* @param config AcraConfig to use when collecting logcat.
* @param input the stream
* @param filter should return false for lines which should be excluded
* @param limit the maximum number of lines to read (the last x lines are kept)
* @return the read string
* @throws IOException
*/
@NonNull
private String streamToString(@NonNull ACRAConfiguration config, @NonNull InputStream input, Predicate<String> filter, int limit) throws IOException {
if (config.nonBlockingReadForLogcat()) {
return IOUtils.streamToStringNonBlockingRead(input, filter, limit);
} else {
return IOUtils.streamToString(input, filter, limit);
}
}
}
6 changes: 6 additions & 0 deletions src/main/java/org/acra/config/ACRAConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public final class ACRAConfiguration implements Serializable {
private final String sharedPreferencesName;
private final int socketTimeout;
private final boolean logcatFilterByPid;
private final boolean nonBlockingReadForLogcat;
private final boolean sendReportsInDevMode;

private final ImmutableSet<String> excludeMatchingSharedPreferencesKeys;
Expand Down Expand Up @@ -149,6 +150,7 @@ public final class ACRAConfiguration implements Serializable {
sharedPreferencesName = builder.sharedPreferencesName();
socketTimeout = builder.socketTimeout();
logcatFilterByPid = builder.logcatFilterByPid();
nonBlockingReadForLogcat = builder.nonBlockingReadForLogcat();
sendReportsInDevMode = builder.sendReportsInDevMode();
excludeMatchingSharedPreferencesKeys = new ImmutableSet<String>(builder.excludeMatchingSharedPreferencesKeys());
excludeMatchingSettingsKeys = new ImmutableSet<String>(builder.excludeMatchingSettingsKeys());
Expand Down Expand Up @@ -338,6 +340,10 @@ public boolean logcatFilterByPid() {
return logcatFilterByPid;
}

public boolean nonBlockingReadForLogcat() {
return nonBlockingReadForLogcat;
}

public boolean sendReportsInDevMode() {
return sendReportsInDevMode;
}
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/org/acra/config/ConfigurationBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public final class ConfigurationBuilder {
private String sharedPreferencesName;
private Integer socketTimeout;
private Boolean logcatFilterByPid;
private Boolean nonBlockingReadForLogcat;
private Boolean sendReportsInDevMode;

private String[] excludeMatchingSharedPreferencesKeys;
Expand Down Expand Up @@ -166,6 +167,7 @@ public ConfigurationBuilder(@NonNull Application app) {
sharedPreferencesName = annotationConfig.sharedPreferencesName();
socketTimeout = annotationConfig.socketTimeout();
logcatFilterByPid = annotationConfig.logcatFilterByPid();
nonBlockingReadForLogcat = annotationConfig.nonBlockingReadForLogcat();
sendReportsInDevMode = annotationConfig.sendReportsInDevMode();
excludeMatchingSharedPreferencesKeys = annotationConfig.excludeMatchingSharedPreferencesKeys();
excludeMatchingSettingsKeys = annotationConfig.excludeMatchingSettingsKeys();
Expand Down Expand Up @@ -646,6 +648,18 @@ public ConfigurationBuilder setLogcatFilterByPid(boolean filterByPid) {
return this;
}

/**
* @param nonBlockingRead true if you want that collecting of logcat lines
* should not block current thread. Read operation
* has a timeout of 3 seconds.
* @return this instance
*/
@NonNull
public ConfigurationBuilder setNonBlockingReadForLogcat(boolean nonBlockingRead) {
nonBlockingReadForLogcat = nonBlockingRead;
return this;
}

/**
* @param sendReportsInDevMode false if you want to disable sending reports in development
* mode. Reports will be sent only on signed applications.
Expand Down Expand Up @@ -1078,6 +1092,13 @@ boolean logcatFilterByPid() {
return DEFAULT_LOGCAT_FILTER_BY_PID;
}

boolean nonBlockingReadForLogcat() {
if (nonBlockingReadForLogcat != null) {
return nonBlockingReadForLogcat;
}
return DEFAULT_NON_BLOCKING_READ_FOR_LOGCAT;
}

boolean sendReportsInDevMode() {
if (sendReportsInDevMode != null) {
return sendReportsInDevMode;
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/org/acra/util/IOUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public boolean apply(String s) {
}
};
private static final int NO_LIMIT = -1;
private static final int READ_TIMEOUT = 3000;

private IOUtils() {
}
Expand Down Expand Up @@ -128,4 +129,39 @@ public static String streamToString(@NonNull InputStream input, Predicate<String
safeClose(reader);
}
}

/**
* Reads an InputStream into a string in an non blocking way for current thread
* It has a default timeout of 3 seconds.
*
* @param input the stream
* @param filter should return false for lines which should be excluded
* @param limit the maximum number of lines to read (the last x lines are kept)
* @return the read string
* @throws IOException
*/
@NonNull
public static String streamToStringNonBlockingRead(@NonNull InputStream input, Predicate<String> filter, int limit) throws IOException {
final BufferedReader reader = new BufferedReader(new InputStreamReader(input), ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES);
final NonBlockingBufferedReader nonBlockingReader = new NonBlockingBufferedReader(reader);
try {
String line;
final List<String> buffer = limit == NO_LIMIT ? new LinkedList<String>() : new BoundedLinkedList<String>(limit);
long end = System.currentTimeMillis() + READ_TIMEOUT;
while ((System.currentTimeMillis() < end)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this mean that it will always block for 3 seconds?

Copy link
Contributor Author

@c-romeo c-romeo Jun 4, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I've added a read timeout limit. Not sure if this should be exposed to ACRAConfiguration.
In my tests if timeout was reached in few seconds I received on remote server logs with empty logcat field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if a developer don't want any delay on main / exception thread, acra's work should be done in a separate thread. For the moment I use a helper method in which I use a Runnable and start a new thread; again, not sure if a queue should be use.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is, is that it's not a timeout.
This read will block for exactly 3 seconds regardless of whether there is any log to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. this is the fix
if ((line = nonBlockingReader.readLine()) != null) { if (filter.apply(line)) { buffer.add(line); } } else { break; }

try {
if ((line = nonBlockingReader.readLine()) != null) {
if (filter.apply(line)) {
buffer.add(line);
}
} else {
break;
}
} catch (InterruptedException e) {}
}
return TextUtils.join("\n", buffer);
} finally {
nonBlockingReader.close();
}
}
}
52 changes: 52 additions & 0 deletions src/main/java/org/acra/util/NonBlockingBufferedReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.acra.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
* @author C-Romeo
* @since 4.9.0
*/
public class NonBlockingBufferedReader {
private final BlockingQueue<String> lines = new LinkedBlockingQueue<String>();
private boolean closed = false;
private Thread backgroundReaderThread = null;

public NonBlockingBufferedReader(final BufferedReader bufferedReader) {
backgroundReaderThread = new Thread(new Runnable() {
@Override
public void run() {
try {
while (!Thread.interrupted()) {
String line = bufferedReader.readLine();
if (line == null) {
break;
}
lines.add(line);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
closed = true;
IOUtils.safeClose(bufferedReader);
}
}
});
backgroundReaderThread.setDaemon(true);
backgroundReaderThread.start();
}

public String readLine() throws InterruptedException {
return closed && lines.isEmpty() ? null : lines.poll(500L, TimeUnit.MILLISECONDS);
}

public void close() {
if (backgroundReaderThread != null) {
backgroundReaderThread.interrupt();
backgroundReaderThread = null;
}
}
}