源码:dongdongqiang1/dir

log.py :

import inspect


class Logger:
    """Simple logging utility with adjustable levels at runtime"""
    LEVELS = {'DEBUG': 10, 'INFO': 20, 'WARN': 30, 'ERROR': 40, 'CRIT': 50}


    def __init__(self, level_name='INFO'):
        self.level = self.LEVELS.get(level_name, 20)
        self.crit(f"init log level = {level_name}")


    def set_level(self, level_name):
        self.level = self.LEVELS.get(level_name, 20)
        self.crit(f"set log level = {level_name}")

    def _log(self, level_name, *args, **kwargs):
        """Internal logging method: checks level and outputs if allowed"""
        if self.LEVELS[level_name] < self.level:
            return

        # Get caller's frame (skip current _log and specific level methods)
        frame = inspect.currentframe().f_back.f_back
        funcname = frame.f_code.co_name
        lineno = frame.f_lineno

        if self.LEVELS.get(level_name) >= self.LEVELS.get("WARN"):
            print(f"{funcname} {lineno}: [{level_name}]", *args, **kwargs)
        else:
            print(f"{funcname} {lineno}:", *args, **kwargs)

    def debug(self, *args, **kwargs):
        self._log('DEBUG', *args, **kwargs)

    def info(self, *args, **kwargs):
        self._log('INFO', *args, **kwargs)

    def warn(self, *args, **kwargs):
        self._log('WARN', *args, **kwargs)

    def error(self, *args, **kwargs):
        self._log('ERROR', *args, **kwargs)

    def crit(self, *args, **kwargs):
        self._log('CRIT', *args, **kwargs)
 

config.json:

{
    "combine_file": {
        "log_level": "INFO"
    },
    "parse_log": {
        "log_level": "INFO",
        "app_keywords": [
            "Waking up from",
            "setPowerMode to 2",
            "Finished setting power mode 2",
            "SurfaceControl: setDisplayBrightness"
        ],
        "kernel_keywords": [
            "",
            "lcm_prepare: enter",
            "lcm_prepare: exit",
            "lcm_setbacklight_cmdq: bl level"
        ]
    },
    "auto_analysis": {
        "log_level": "INFO"
    }
}

config_loader.py:

import json
import sys
from log import Logger

logger = Logger("INFO")
CONFIG_PATH = "./config.json"

def load_config():
    """Load configuration file. Exit program if loading fails."""
    logger.info(f"Loading configuration from {CONFIG_PATH}")
    try:
        with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
            config = json.load(f)
        logger.info("Configuration loaded successfully.")
    except FileNotFoundError:
        logger.error(f"Configuration file not found: {CONFIG_PATH}")
        sys.exit(1)
    except json.JSONDecodeError as e:
        logger.error(f"Invalid JSON format in configuration file: {e}")
        sys.exit(1)
    except Exception as e:
        logger.error(f"Unexpected error while loading configuration: {e}")
        sys.exit(1)

    logger.info(f"Final configuration:\n{json.dumps(config, indent=4, ensure_ascii=False)}")
    return config

config = load_config()
 

parse_log.py :

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Log Parser Application

Parses log files to analyze screen wake-up time records.
Supports both application logs and kernel logs with configurable log levels.
"""

import datetime
import os
import re
import sys
import tkinter as tk
from tkinter import filedialog

from log import Logger
from config_loader import config

# ----------------------------------------------------------------------
# Configuration & Constants
# ----------------------------------------------------------------------
APP_KEYWORDS = tuple(config["parse_log"]["app_keywords"])
KERNEL_KEYWORDS = tuple(config["parse_log"]["kernel_keywords"])
logger = Logger(config["parse_log"]["log_level"])

# Time patterns used to extract timestamps from log lines
TIME_PATTERN_1 = r"\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}"   # "MM-DD HH:MM:SS.mmm"
TIME_PATTERN_2 = r"\[\s*(\d+\.\d{6})\]"                      # "[ seconds.microseconds ]"

# Reason pattern for app logs (e.g., "reason=xyz")
REASON_PATTERN = r"reason=([^,]+)"

# Table layout constants
TABLE_FORMAT = "| {:^3} | {:^24} | {:^18} | {:^18} | {:^18} | {:^18} | {:^5} | {:^5} |"
SEPARATOR_LINE = "|-{0:-^3}-+-{0:-^24}-+-{0:-^18}-+-{0:-^18}-+-{0:-^18}-+-{0:-^18}-+-{0:-^5}-+-{0:-^5}-|".format("")


# ----------------------------------------------------------------------
# Redirect stdout/stderr to both console and Tkinter Text widget
# ----------------------------------------------------------------------
class StdoutRedirector:
    """Redirects print output to a Tkinter Text widget while preserving console output."""
    def __init__(self, text_widget, original_stream):
        """
        Initialize with the target Text widget and the original stream (sys.__stdout__ / sys.__stderr__).
        """
        self.text_widget = text_widget
        self.original_stream = original_stream

    def write(self, message):
        """Write message to original stream and append to Text widget."""
        self.original_stream.write(message)
        self.text_widget.insert(tk.END, message)
        self.text_widget.see(tk.END)          # auto-scroll to bottom
        self.text_widget.update_idletasks()   # keep UI responsive

    def flush(self):
        """Required for compatibility with logging and other libraries."""
        self.original_stream.flush()


# ----------------------------------------------------------------------
# Data Classes
# ----------------------------------------------------------------------
class LogRecord:
    """Holds parsed information for one screen wake-up event."""

    def __init__(self):
        """Initialize fields with default empty values."""
        self.reason = ""                     # extracted reason string
        self.logs = ["" for _ in range(4)]   # raw log lines (index 0..3)
        self.times = ["" for _ in range(4)]  # human-readable timestamps (MM-DD HH:MM:SS.mmm)
        self.kernel_times = ["" for _ in range(4)]  # numeric kernel timestamps (seconds)
        self.delta_t12 = 0                   # time difference between keyword[1] and keyword[2]
        self.delta_t13 = 0                   # time difference between keyword[1] and keyword[3]


# ----------------------------------------------------------------------
# Log Parser
# ----------------------------------------------------------------------
class LogParser:
    """Parses log files and builds a list of LogRecord objects."""

    def __init__(self, log_keywords):
        """
        Initialize parser with a set of 4 keywords to search for.

        Args:
            log_keywords (tuple): Tuple of 4 keywords (index 0..3).
        """
        self.log_keywords = log_keywords
        self.records = []                    # list of completed LogRecord
        self.current_record = LogRecord()    # record currently being built

    def parse_file(self, file_path):
        """
        Parse a log file and return the number of records found.

        Args:
            file_path (str): Path to the log file.

        Returns:
            int: Number of parsed records.
        """
        logger.info(f"Start parsing file: {file_path}")

        # Reset state for a new file
        self.records = []
        self.current_record = LogRecord()

        try:
            with open(file_path, 'r', encoding='ISO-8859-1') as file:
                lines = file.readlines()
        except FileNotFoundError:
            logger.error(f"File not found: {file_path}")
            return 0
        except Exception as e:
            logger.error(f"Error reading file: {e}")
            return 0

        # Process each line
        for line in lines:
            self._process_line(line)

        # Save the last record
        self._save_current_record()
        logger.info(f"Parse completed, found {len(self.records)} records")

        # Debug output of all records
        self._debug_print_records()
        return len(self.records)

    def _process_line(self, line):
        """
        Process a single line from the log file.

        Args:
            line (str): A line of log text.
        """
        # ---- Decide if we need to start a new record ----
        if self.log_keywords == APP_KEYWORDS:
            # For app logs, keywords[0] or keywords[1] indicate the beginning of a new event
            if self.log_keywords[0] in line or self.log_keywords[1] in line:
                self._save_current_record()
        else:
            # For kernel logs, only keywords[1] starts a new record
            if self.log_keywords[1] in line:
                self._save_current_record()

        # Loop over all four keywords
        for i in range(4):
            # Kernel logs skip keyword[0] (it is never used in kernel context)
            if self.log_keywords == KERNEL_KEYWORDS and i == 0:
                continue

            if self.log_keywords[i] in line:
                if i == 0:
                    # Extract the reason from the first matching line
                    match = re.search(REASON_PATTERN, line)
                    if match:
                        self.current_record.reason = match.group(1)
                        logger.debug(
                            f"Reason extracted: {match.group(1)} "
                            f"from line: {line[:-1]}"
                        )
                elif i == 2:
                    # For keyword[2], we need a valid keyword[1] first and not already set
                    if self.current_record.logs[1] == "" or self.current_record.logs[2] != "":
                        break
                elif i == 3:
                    # For keyword[3], we need a valid keyword[2] first
                    if self.current_record.logs[2] == "" or self.current_record.logs[3] != "":
                        break

                # Store the raw log line
                self.current_record.logs[i] = line

                time_match = re.search(TIME_PATTERN_1, line)
                if time_match:
                    self.current_record.times[i] = time_match.group()

                if self.log_keywords == KERNEL_KEYWORDS:
                    time_match = re.search(TIME_PATTERN_2, line)
                    if time_match:
                        self.current_record.kernel_times[i] = float(time_match.group(1))

                logger.debug(f"len(records) = {len(self.records)} "
                             f"logs[{i}] = {self.current_record.logs[i][:-1]} "
                             f"times[{i}] = {self.current_record.times[i]} "
                             f"kernel_times[{i}] = {self.current_record.kernel_times[i]}")
                break  # Stop after first matching keyword

    def _calculate_time_difference(self, index1, index2):
        """
        Calculate time difference (in seconds) between two stored timestamps.

        Args:
            index1 (int): Index of the first timestamp (usually 1).
            index2 (int): Index of the second timestamp (2 or 3).

        Returns:
            float: Time difference in seconds, or 0 on error.
        """
        if self.log_keywords == APP_KEYWORDS:
            try:
                time_format = "%Y-%m-%d %H:%M:%S.%f"
                # Write a leap year to avoid errors when the date is 2/29
                t1 = f"2024-{self.current_record.times[index1]}"
                t2 = f"2024-{self.current_record.times[index2]}"
                t1 = datetime.datetime.strptime(t1, time_format)
                t2 = datetime.datetime.strptime(t2, time_format)
                delta = round((t2 - t1).total_seconds(), 4)
            except ValueError as e:
                logger.error(f"Time parse error: {e}")
                delta = 0
            logger.debug(f"{self.current_record.times[index1]} "
                f"{self.current_record.times[index2]} {delta}")
        else:
            delta = round(self.current_record.kernel_times[index2] - self.current_record.kernel_times[index1], 4)
            logger.debug(f"{self.current_record.kernel_times[index1]} {self.current_record.kernel_times[index2]} {delta}")
        return delta

    def _save_current_record(self):
        """Save current record to records list if it is complete."""
        # Check if record is complete (has log1 and log2)
        if self.current_record.logs[1] != "" and self.current_record.logs[2] != "":
            self.current_record.delta_t12 = self._calculate_time_difference(1, 2)

            if self.current_record.logs[3] != "":
                self.current_record.delta_t13 = self._calculate_time_difference(1, 3)

            if self.current_record.delta_t12 >= 0 and self.current_record.delta_t13 >= 0:
                logger.debug(f"current_record = {self.current_record.__dict__}")
                self.records.append(self.current_record)

            self.current_record = LogRecord()

    def _debug_print_records(self):
        """Print all records for debugging purposes."""
        logger.debug(f"len(records) = {len(self.records)}")
        for i, record in enumerate(self.records):
            log_output = []
            for j in range(4):
                log_output.append(
                    f"record.logs[{j}] = "
                    f"{record.logs[j][:-1] if record.logs[j] else ''}"
                )

            logger.debug(
                f"i = {i}\n" +
                "\n".join(log_output) + "\n"
                f"record.times = {record.times}\n"
                f"record.kernel_times = {record.kernel_times}\n"
                f"record.delta_t12 = {record.delta_t12}\n"
                f"record.delta_t13 = {record.delta_t13}\n"
            )

    def show_results(self, file_path=None):
        """
        Display all parse results, optionally writing to a file.

        Args:
            file_path (str, optional): Output file path. Prints to stdout if None.
        """
        out_file = None
        if file_path:
            try:
                out_file = open(file_path, 'w', encoding='utf-8')
            except Exception as e:
                logger.error(f"Failed to open file {file_path}: {e}")
                return
        else:
            out_file = sys.stdout
        # Display by reason groups
        unique_reasons = set(record.reason for record in self.records)
        try:
            for reason in unique_reasons:
                self.show_by_reason(reason, out_file)

            # Display summary table
            self.show_summary(out_file)
        finally:
            if file_path:
                out_file.close()

    def show_by_reason(self, reason, out_file):
        """
        Show records filtered by reason.

        Args:
            reason (str): Reason to filter by.
            out_file: File-like object for output.
        """
        matched_records = [record.delta_t12 for record in self.records if record.reason == reason]

        if not matched_records:
            return

        aver_t12 = round(sum(matched_records) / len(matched_records) * 1000)
        print(f"Table for reason = {reason}, average_t12 = {aver_t12} ms", file=out_file)
        print(f"{'-' * 134}", file=out_file)
        print(TABLE_FORMAT.format("num", "reason", "t0", "t1", "t2", "t3", "t2-t1", "t3-t1"),
            file=out_file)
        print(SEPARATOR_LINE, file=out_file)
        print(TABLE_FORMAT.format("", "",
                f"({self.log_keywords[0][:16]})",
                f"({self.log_keywords[1][:16]})",
                f"({self.log_keywords[2][:16]})",
                f"({self.log_keywords[3][:16]})", "(ms)", "(ms)"
            ), file=out_file)

        for i, record in enumerate(self.records):
            if record.reason == reason:
                print(SEPARATOR_LINE, file=out_file)
                t12_ms = round(record.delta_t12 * 1000)
                t13_ms = (round(record.delta_t13 * 1000) if record.delta_t13 != 0 else "")
                print(TABLE_FORMAT.format(
                        i, record.reason, record.times[0], record.times[1],
                        record.times[2], record.times[3], t12_ms, t13_ms
                    ), file=out_file)

        print(f"{'-' * 134}\n\n", file=out_file)


    def show_summary(self, out_file):
        """
        Print a summary table containing all records.

        Args:
            out_file: File-like object for output.
        """
        if not self.records:
            logger.warn("No records found")
            return

        total_t12 = sum(record.delta_t12 for record in self.records)
        aver_t12 = round(total_t12 / len(self.records) * 1000)

        print(f"Summary table - records: {len(self.records)}, average_t12: {aver_t12} ms", file=out_file)
        print('-' * 134, file=out_file)
        print(TABLE_FORMAT.format("num", "reason", "t0", "t1", "t2", "t3", "t2-t1", "t3-t1"),
            file=out_file)
        print(SEPARATOR_LINE, file=out_file)
        print(TABLE_FORMAT.format("", "",
                f"({self.log_keywords[0][:16]})",
                f"({self.log_keywords[1][:16]})",
                f"({self.log_keywords[2][:16]})",
                f"({self.log_keywords[3][:16]})", "(ms)", "(ms)"
            ), file=out_file)

        for i, record in enumerate(self.records):
            print(SEPARATOR_LINE, file=out_file)
            t12_ms = round(record.delta_t12 * 1000)
            t13_ms = (round(record.delta_t13 * 1000)if record.delta_t13 != 0 else "")
            if self.log_keywords == APP_KEYWORDS:
                print(TABLE_FORMAT.format(i, record.reason, record.times[0], record.times[1],
                        record.times[2], record.times[3], t12_ms, t13_ms
                    ), file=out_file)
            else:
                print(TABLE_FORMAT.format(i, "", "", record.times[1],
                        record.times[2], record.times[3], "", ""
                    ),file=out_file)
                print(TABLE_FORMAT.format("", "", "", record.kernel_times[1],
                        record.kernel_times[2], record.kernel_times[3],t12_ms, t13_ms
                    ),file=out_file)
        print(f"{'-' * 134}\n\n", file=out_file)


# ----------------------------------------------------------------------
# GUI Application
# ----------------------------------------------------------------------
class LogAnalyzerApp:
    """GUI application class."""

    def __init__(self):
        """Initialize the GUI application."""
        self.root = tk.Tk()
        # self.root.geometry("900x700")
        # Make the window open maximized (full screen within desktop environment)
        self.root.state('zoomed')
        self.root.title("Log Parser v1.0")

        # Tkinter variables
        self.keywords_group = tk.IntVar(value=0) # 0 for app, 1 for kernel
        self.log_level = tk.StringVar(value=config["parse_log"]["log_level"])
        self.status_var = tk.StringVar(value="Ready")

        # Build GUI sections
        self._build_control_panel()
        self._build_result_area()
        self._build_status_bar()

        # Redirect output after widgets are created so that self.result_text exists
        self._redirect_output_to_widget(self.result_text)

    def _build_control_panel(self):
        """Create the top control frame with radio buttons, file selector, etc."""
        control_frame = tk.Frame(self.root)
        control_frame.pack(fill=tk.X, padx=10, pady=(10, 0))

        # --- Radio group for keyword selection ---
        radio_frame = tk.Frame(control_frame)
        radio_frame.pack(fill=tk.X, pady=(0, 5))
        tk.Label(radio_frame, text="Select keywords group:").pack(side=tk.LEFT, padx=(0, 10))
        tk.Radiobutton(radio_frame, text="App log keywords", variable=self.keywords_group,
                       value=0, command=self._update_keywords_display).pack(side=tk.LEFT, padx=(0, 20))
        tk.Radiobutton(radio_frame, text="Kernel log keywords", variable=self.keywords_group,
                       value=1, command=self._update_keywords_display).pack(side=tk.LEFT)

        # --- Display the currently selected keywords ---
        self.keywords_label = tk.Label(control_frame, width=80, height=4, bg="white",
                                       anchor="w", justify=tk.LEFT, relief=tk.SUNKEN,
                                       padx=5, pady=5)
        self.keywords_label.pack(fill=tk.X, pady=(0, 10))

        # File selection row (button right, label left with wraplength)
        file_frame = tk.Frame(control_frame)
        file_frame.pack(fill=tk.X)

        # Button packed first to the right to guarantee visibility
        tk.Button(file_frame, text="Select Log File", command=self._select_log).pack(side=tk.RIGHT, padx=(10, 0))

        # File label fills remaining space, wraps long paths
        self.file_label = tk.Label(file_frame,
                                   text="Click the button on the right to select a log file",
                                   anchor="w", justify=tk.LEFT,
                                   relief=tk.GROOVE, padx=5, pady=5, wraplength=600)
        self.file_label.pack(side=tk.LEFT, fill=tk.X, expand=True)

        # Log level and buttons
        setting_frame = tk.Frame(control_frame)
        setting_frame.pack(fill=tk.X, pady=(10, 0))

        tk.Label(setting_frame, text="Log level:").pack(side=tk.LEFT, padx=(0, 5))
        level_menu = tk.OptionMenu(setting_frame, self.log_level, *Logger.LEVELS.keys(),
                                   command=lambda level: logger.set_level(level))
        level_menu.config(width=10)
        level_menu.pack(side=tk.LEFT, padx=(0, 20))

        tk.Button(setting_frame, text="Clear Output", command=self._clear_output).pack(side=tk.RIGHT, padx=(5, 0))
        tk.Button(setting_frame, text="Save Report", command=self._save_report).pack(side=tk.RIGHT)

        self._update_keywords_display()

    def _build_result_area(self):
        """Create the scrollable Text widget for output."""
        result_frame = tk.Frame(self.root)
        result_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        self.result_text = tk.Text(result_frame, wrap=tk.NONE)
        vsb = tk.Scrollbar(result_frame, orient=tk.VERTICAL, command=self.result_text.yview)
        hsb = tk.Scrollbar(result_frame, orient=tk.HORIZONTAL, command=self.result_text.xview)
        self.result_text.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)

        self.result_text.grid(row=0, column=0, sticky="nsew")
        vsb.grid(row=0, column=1, sticky="ns")
        hsb.grid(row=1, column=0, sticky="ew")

        result_frame.grid_rowconfigure(0, weight=1)
        result_frame.grid_columnconfigure(0, weight=1)

    def _build_status_bar(self):
        """Create the bottom status bar."""
        status_bar = tk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN,
                              anchor=tk.W, padx=5, pady=2)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)

    def _redirect_output_to_widget(self, text_widget):
        """Replace sys.stdout and sys.stderr with StdoutRedirector instances."""
        sys.stdout = StdoutRedirector(text_widget, sys.__stdout__)
        sys.stderr = StdoutRedirector(text_widget, sys.__stderr__)

    def _update_keywords_display(self):
        """Update keywords display based on selection."""
        keywords = APP_KEYWORDS if self.keywords_group.get() == 0 else KERNEL_KEYWORDS
        lines = [f"keywords[{i}] = {kw}" for i, kw in enumerate(keywords)]
        self.keywords_label.config(text="\n".join(lines))
        logger.info(f"Keywords group changed to: {keywords}")

    def _select_log(self):
        """Handle 'Select Log File' button: open file dialog, run parser, update status."""
        keywords = APP_KEYWORDS if self.keywords_group.get() == 0 else KERNEL_KEYWORDS
        log_type = "App Log" if self.keywords_group.get() == 0 else "Kernel Log"

        file_path = filedialog.askopenfilename(
            title=f"Select {log_type}",
            filetypes=[("All files", "*.*"), ("Log", "*.log"), ("Text", "*.txt")]
        )
        if not file_path:
            self.file_label.config(text="No file selected")
            logger.info("File selection cancelled")
            self.status_var.set("File selection cancelled")
            return

        # Update label with selected file (will wrap if too long)
        self.file_label.config(text=f"Selected: {file_path}")
        self.status_var.set("Parsing...")

        try:
            parser = LogParser(keywords)
            parser.parse_file(file_path)
            # Display results
            parser.show_results()
            # parser.show_results("analysis_report.txt")
            self.status_var.set(f"Parsing complete, {len(parser.records)} records found.")
        except Exception as e:
            logger.error(f"Unhandled exception during parsing: {e}")
            self.status_var.set("Parsing failed, Check log for details.")

    def _clear_output(self):
        """Clear the GUI log text widget and the console screen"""
        self.result_text.delete("1.0", tk.END)
        os.system("cls") if os.name == "nt" else os.system("clear")

    def _save_report(self):
        """Save the current output to a text file."""
        content = self.result_text.get("1.0", tk.END).strip()
        if not content:
            self.status_var.set("Nothing to save")
            return

        file_path = filedialog.asksaveasfilename(
            defaultextension=".txt",
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
        )
        if file_path:
            try:
                with open(file_path, "w", encoding="utf-8") as f:
                    f.write(content)
                self.status_var.set(f"Report saved to {file_path}")
            except Exception as e:
                self.status_var.set(f"Save failed: {e}")

    def run(self):
        """Start the Tkinter main event loop."""
        self.root.mainloop()

def main():
    """Application entry point."""
    app = LogAnalyzerApp()
    app.run()


if __name__ == "__main__":
    main()
 


 

测试用例,applogcat-log.txt内容:

11-01 12:34:56.123   1386  2091 I PowerManagerService: Waking up from Asleep (uid=1000, reason=WAKE_REASON_POWER_BUTTON, details=android.policy:POWER)...
2025-11-01 12:34:57.123       895   982 I SurfaceFlinger: setPowerMode to 2 from pid=1386, uid=1000
11-01 12:34:58.124     895   895 I SurfaceFlinger: Finished setting power mode 2 on display 4627039422300187648
11-01 12:34:59.125         1386  2303 I SurfaceControl: setDisplayBrightness. {0.1313524, 68.101685, 0.1313524, 68.101685, 0.13545427, 68.101685}
11-01 12:34:59.456         1386  2303 I SurfaceControl: setDisplayBrightness. {0.1313524, 68.101685, 0.1313524, 68.101685, 0.13545427, 68.101685}

11-01 13:34:57.123        895   982 I SurfaceFlinger: setPowerMode to 2 from pid=1386, uid=1000
11-01 13:34:58.123      895   895 I SurfaceFlinger: Finished setting power mode 2 on display 4627039422300187648
11-01 13:34:59.123         1386  2303 I SurfaceControl: setDisplayBrightness. {0.1313524, 68.101685, 0.1313524, 68.101685, 0.13545427, 68.101685}


11-01 14:34:57.123      895   982 I SurfaceFlinger: setPowerMode to 2 from pid=1386, uid=1000
11-01 14:34:58.123      895   895 I SurfaceFlinger: Finished setting power mode 2 on display 4627039422300187648
模拟其它无关log  11-01 14:34:59.123         Parse LCD screen-on records and time, click to select log file


        
        
行  4266: 10-25 10:26:30.125  1386  2091 I PowerManagerService: Waking up from Asleep (uid=1000, reason=WAKE_REASON_TAP, details=android.policy:POWER)...
行  4602: 10-25 10:26:30.176   895   982 I SurfaceFlinger: setPowerMode to 2 from pid=1386, uid=1000
行  6002: 10-25 10:26:30.333   895   895 I SurfaceFlinger: Finished setting power mode 2 on display 4627039422300187648
行  6094: 10-25 10:26:30.339  1386  2303 I SurfaceControl: setDisplayBrightness. {0.1313524, 68.101685, 0.1313524, 68.101685, 0.13545427, 68.101685}
 

测试用例,kmsgcat-log.txt 内容:

2025-11-01 12:34:57.123     <3>[  180.123456][  T791] [2025:11:28 02:43:56](0)[791:composer@3.1-se][LCD_KIT/I]lcm_prepare: enter
11-01 12:34:58.124         <3>[  181.124456][  T791] [2025:11:28 02:43:56](3)[791:composer@3.1-se][LCD_KIT/I]lcm_prepare: exit
11-01 12:34:59.125         <3>[  182.125456][ T1362] [2025:11:28 02:43:56](3)[1362:AALMain][LCD_KIT/I]lcm_setbacklight_cmdq: bl level:863
11-01 12:34:59.456         <3>[  183.126456][ T1362] [2025:11:28 02:43:56](3)[1362:AALMain][LCD_KIT/I]lcm_setbacklight_cmdq: bl level:863

11-01 13:34:57.123 <3>[  181.123456][  T791] [2025:11:28 02:43:56](0)[791:composer@3.1-se][LCD_KIT/I]lcm_prepare: enter
11-01 13:34:58.123 <3>[  182.124456][  T791] [2025:11:28 02:43:56](3)[791:composer@3.1-se][LCD_KIT/I]lcm_prepare: exit
11-01 13:34:59.123 <3>[  183.125456][ T1362] [2025:11:28 02:43:56](3)[1362:AALMain][LCD_KIT/I]lcm_setbacklight_cmdq: bl level:863

11-01 14:34:57.123 <3>[  182.123456][  T791] [2025:11:28 02:43:56](0)[791:composer@3.1-se][LCD_KIT/I]lcm_prepare: enter
11-01 14:34:58.123 <3>[  183.124456][  T791] [2025:11:28 02:43:56](3)[791:composer@3.1-se][LCD_KIT/I]lcm_prepare: exit
模拟其它无关log       [ 2102.724133]   11-01 14:34:59.123  Parse LCD screen-on records and time, click to select log file

行 140: 11-28 02:43:57.268 <3>[  180.082243][  T791] [2025:11:28 02:43:56](0)[791:composer@3.1-se][LCD_KIT/I]lcm_prepare: enter
行 320: 11-28 02:43:57.396 <3>[  180.211307][  T791] [2025:11:28 02:43:56](3)[791:composer@3.1-se][LCD_KIT/I]lcm_prepare: exit
行 456: 11-28 02:43:57.451 <3>[  180.262656][ T1362] [2025:11:28 02:43:56](3)[1362:AALMain][LCD_KIT/I]lcm_setbacklight_cmdq: bl level:863


 

成品效果:

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐