blob: f6c88d5e39f5a208b36a3308b22529e1068d65a8 [file] [log] [blame]
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.android.logcat;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log;
import com.android.tools.idea.logcat.StackTraceExpander;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.android.util.AndroidOutputReceiver;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by IntelliJ IDEA.
* User: Eugene.Kudelevsky
* Date: Sep 12, 2009
* Time: 9:01:05 PM
*/
public class AndroidLogcatReceiver extends AndroidOutputReceiver {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.logcat.AndroidLogcatReceiver");
private static Pattern LOG_PATTERN =
Pattern.compile("^\\[\\s(\\d\\d-\\d\\d\\s\\d\\d:\\d\\d:\\d\\d\\.\\d+)\\s+(\\d*):\\s*(\\S+)\\s([VDIWEAF])/(.*)\\]$", Pattern.DOTALL);
/** Prefix to use for all lines without a header. */
public static final String CONTINUATION_LINE_PREFIX = StringUtil.repeatSymbol(' ', 4);
/** Prefix to use for stack trace lines. */
public static final String STACK_TRACE_LINE_PREFIX = CONTINUATION_LINE_PREFIX + StringUtil.repeatSymbol(' ', 8);
/**
* Prefix to use for stack trace lines that were expanded in by {@link StackTraceExpander}.
* The only reason this is different from {@link #STACK_TRACE_LINE_PREFIX} is that we want {@link ExceptionFolding}
* class to be able to determine and fold just the expanded lines.
*/
public static final String EXPANDED_STACK_TRACE_LINE_PREFIX = STACK_TRACE_LINE_PREFIX.replace(' ', '\u00A0');
/** Prefix to use for the stack trace "Caused by:" lines. */
public static final String STACK_TRACE_CAUSE_LINE_PREFIX = CONTINUATION_LINE_PREFIX + Character.toString(' ');
private LogMessageHeader myLastMessageHeader;
private volatile boolean myCanceled = false;
private Log.LogLevel myPrevLogLevel;
private final Writer myWriter;
private final IDevice myDevice;
private final StackTraceExpander myStackTraceExpander = new StackTraceExpander(CONTINUATION_LINE_PREFIX,
STACK_TRACE_LINE_PREFIX,
EXPANDED_STACK_TRACE_LINE_PREFIX,
STACK_TRACE_CAUSE_LINE_PREFIX);
public AndroidLogcatReceiver(IDevice device, Writer writer) {
myDevice = device;
myWriter = new PrintWriter(writer);
}
@Override
public void processNewLine(String line) {
Matcher matcher = LOG_PATTERN.matcher(line);
if (myLastMessageHeader == null && matcher.matches()) {
myLastMessageHeader = new LogMessageHeader();
myLastMessageHeader.myTime = matcher.group(1);
myLastMessageHeader.myPid = Integer.valueOf(matcher.group(2));
String tid = matcher.group(3).trim();
long tidValue;
try {
// Thread id's may be in hex on some platforms.
// Decode and store them in radix 10.
tidValue = Long.decode(tid.trim());
} catch (NumberFormatException e) {
tidValue = -1;
}
myLastMessageHeader.myTid = Long.toString(tidValue);
myLastMessageHeader.myAppPackage =
myDevice == null ? "" : myDevice.getClientName(myLastMessageHeader.myPid);
myLastMessageHeader.myLogLevel = getByLetterString(matcher.group(4));
myLastMessageHeader.myTag = matcher.group(5).trim();
}
else {
if (line.length() == 0) return;
String text;
if (myLastMessageHeader == null) {
text = myStackTraceExpander.expand(line);
} else {
text = getFullMessage(line, myLastMessageHeader);
}
try {
myWriter.write(text + '\n');
}
catch (IOException ignored) {
LOG.info(ignored);
}
myLastMessageHeader = null;
}
}
@Nullable
private static Log.LogLevel getByLetterString(@Nullable String s) {
if (s == null) {
return null;
}
final Log.LogLevel logLevel = Log.LogLevel.getByLetterString(s);
/* LogLevel doesn't support messages with severity "F". Log.wtf() is supposed
* to generate "A", but generates "F" */
if (logLevel == null && s.equals("F")) {
return Log.LogLevel.ASSERT;
}
return logLevel;
}
@Override
public boolean isCancelled() {
return myCanceled;
}
static class LogMessageHeader {
String myTime;
Log.LogLevel myLogLevel;
int myPid;
String myTid;
String myAppPackage;
String myTag;
}
private static String getFullMessage(String message, LogMessageHeader header) {
return AndroidLogcatFormatter.formatMessage(message, header);
}
public void cancel() {
myCanceled = true;
}
}