| /* |
| * 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; |
| } |
| } |