1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.ddmuilib.logcat;
18
19import com.android.ddmlib.Log.LogLevel;
20
21import java.util.ArrayList;
22import java.util.List;
23import java.util.regex.Matcher;
24import java.util.regex.Pattern;
25
26/**
27 * Class to parse raw output of {@code adb logcat -v long} to {@link LogCatMessage} objects.
28 */
29public final class LogCatMessageParser {
30    private LogLevel mCurLogLevel = LogLevel.WARN;
31    private String mCurPid = "?";
32    private String mCurTid = "?";
33    private String mCurTag = "?";
34    private String mCurTime = "?:??";
35
36    /**
37     * This pattern is meant to parse the first line of a log message with the option
38     * 'logcat -v long'. The first line represents the date, tag, severity, etc.. while the
39     * following lines are the message (can be several lines).<br>
40     * This first line looks something like:<br>
41     * {@code "[ 00-00 00:00:00.000 <pid>:0x<???> <severity>/<tag>]"}
42     * <br>
43     * Note: severity is one of V, D, I, W, E, A? or F. However, there doesn't seem to be
44     *       a way to actually generate an A (assert) message. Log.wtf is supposed to generate
45     *       a message with severity A, however it generates the undocumented F level. In
46     *       such a case, the parser will change the level from F to A.<br>
47     * Note: the fraction of second value can have any number of digit.<br>
48     * Note: the tag should be trimmed as it may have spaces at the end.
49     */
50    private static Pattern sLogHeaderPattern = Pattern.compile(
51            "^\\[\\s(\\d\\d-\\d\\d\\s\\d\\d:\\d\\d:\\d\\d\\.\\d+)"
52          + "\\s+(\\d*):\\s*(\\S+)\\s([VDIWEAF])/(.*)\\]$");
53
54    /**
55     * Parse a list of strings into {@link LogCatMessage} objects. This method
56     * maintains state from previous calls regarding the last seen header of
57     * logcat messages.
58     * @param lines list of raw strings obtained from logcat -v long
59     * @param pidToNameMapper mapper to obtain the app name given a pid
60     * @return list of LogMessage objects parsed from the input
61     */
62    public List<LogCatMessage> processLogLines(String[] lines,
63            LogCatPidToNameMapper pidToNameMapper) {
64        List<LogCatMessage> messages = new ArrayList<LogCatMessage>(lines.length);
65
66        for (String line : lines) {
67            if (line.length() == 0) {
68                continue;
69            }
70
71            Matcher matcher = sLogHeaderPattern.matcher(line);
72            if (matcher.matches()) {
73                mCurTime = matcher.group(1);
74                mCurPid = matcher.group(2);
75                mCurTid = matcher.group(3);
76                mCurLogLevel = LogLevel.getByLetterString(matcher.group(4));
77                mCurTag = matcher.group(5).trim();
78
79                /* LogLevel doesn't support messages with severity "F". Log.wtf() is supposed
80                 * to generate "A", but generates "F". */
81                if (mCurLogLevel == null && matcher.group(4).equals("F")) {
82                    mCurLogLevel = LogLevel.ASSERT;
83                }
84            } else {
85                LogCatMessage m = new LogCatMessage(mCurLogLevel, mCurPid, mCurTid,
86                        pidToNameMapper.getName(mCurPid),
87                        mCurTag, mCurTime, line);
88                messages.add(m);
89            }
90        }
91
92        return messages;
93    }
94}
95