Hệ thống ghi nhật ký Android hướng đến mục tiêu đạt được khả năng hỗ trợ tiếp cận phổ quát và dễ sử dụng, giả sử mọi dữ liệu nhật ký đều có thể được biểu thị dưới dạng một chuỗi ký tự. Giả định này phù hợp với hầu hết các trường hợp sử dụng, đặc biệt là khi khả năng đọc nhật ký là yếu tố quan trọng mà không cần đến các công cụ chuyên dụng. Tuy nhiên, trong những môi trường đòi hỏi hiệu suất ghi nhật ký cao và kích thước nhật ký bị hạn chế, thì việc ghi nhật ký dựa trên văn bản không phải là lựa chọn tối ưu. Một trường hợp như vậy là WindowManager, yêu cầu một hệ thống ghi nhật ký mạnh mẽ có thể xử lý nhật ký chuyển đổi cửa sổ theo thời gian thực với tác động tối thiểu đến hệ thống.
ProtoLog là giải pháp thay thế để đáp ứng nhu cầu ghi nhật ký của WindowManager và các dịch vụ tương tự. ProtoLog mang lại những lợi ích sau so với logcat:
- Sử dụng ít tài nguyên hơn để ghi nhật ký.
- Theo quan điểm của nhà phát triển, nó hoạt động giống như khung ghi nhật ký Android mặc định.
- Cho phép bạn bật hoặc tắt các câu lệnh nhật ký trong thời gian chạy.
- Cũng có thể ghi vào logcat.
Để tối ưu hoá mức sử dụng bộ nhớ, ProtoLog sử dụng cơ chế lưu trữ chuỗi. Cơ chế này tính toán và lưu một hàm băm đã biên dịch của thông báo. Để cải thiện hiệu suất, ProtoLog thực hiện việc lưu trữ chuỗi trong quá trình biên dịch cho các dịch vụ hệ thống. Nó chỉ ghi lại giá trị nhận dạng và đối số của thông báo trong thời gian chạy. Khi bạn tạo dấu vết ProtoLog hoặc lấy báo cáo lỗi, ProtoLog sẽ tự động kết hợp từ điển thông báo được tạo tại thời điểm biên dịch. Điều này cho phép giải mã thông báo từ mọi bản dựng.
ProtoLog lưu trữ các thông báo ở định dạng nhị phân (proto) trong dấu vết Perfetto.
Quá trình giải mã thông báo diễn ra trong trace_processor của Perfetto. Quy trình này giải mã các thông báo proto nhị phân, dịch giá trị nhận dạng thông báo thành chuỗi bằng cách sử dụng từ điển thông báo được nhúng và định dạng chuỗi bằng các đối số động.
ProtoLog hỗ trợ các cấp độ nhật ký giống như android.utils.Log, đó là d, v, i, w, e và wtf.
ProtoLog phía máy khách
Ban đầu, ProtoLog chỉ dành cho phía máy chủ của WindowManager, hoạt động trong một quy trình và thành phần duy nhất. Sau đó, nó được mở rộng để bao gồm mã shell WindowManager trong quy trình Giao diện người dùng hệ thống. Tuy nhiên, việc sử dụng ProtoLog đòi hỏi mã thiết lập phức tạp. Ngoài ra, hoạt động ghi nhật ký Proto bị hạn chế đối với máy chủ hệ thống và các quy trình Giao diện người dùng hệ thống. Điều này gây khó khăn cho việc kết hợp vào các quy trình khác và yêu cầu thiết lập vùng đệm bộ nhớ riêng cho từng quy trình. ProtoLog hiện có sẵn cho mã phía máy khách, giúp bạn không cần thêm mã nguyên mẫu.
Không giống như mã dịch vụ hệ thống, mã phía máy khách thường bỏ qua việc lưu trữ chuỗi tại thời gian biên dịch. Thay vào đó, quá trình nội suy chuỗi sẽ diễn ra linh hoạt trong một luồng nền. Do đó, mặc dù ProtoLog phía máy khách mang lại những lợi ích về việc sử dụng bộ nhớ tương đương với ProtoLog trên các dịch vụ hệ thống, nhưng nó sẽ gây ra mức hao tổn hiệu suất cao hơn một chút và không có lợi thế về việc giảm bộ nhớ của bộ nhớ được ghim mà đối tượng tương ứng phía máy chủ mang lại.
Nhóm ProtoLog
Thông báo ProtoLog được sắp xếp thành các nhóm gọi là ProtoLogGroups, tương tự như cách thông báo Logcat được sắp xếp theo TAG. Các ProtoLogGroups này đóng vai trò là các cụm thông báo mà bạn có thể bật hoặc tắt trong thời gian chạy. Chúng cũng kiểm soát việc các thông báo có bị xoá trong quá trình biên dịch hay không và vị trí ghi nhật ký (proto, logcat hoặc cả hai). Mỗi ProtoLogGroup đều có các thuộc tính sau:
enabled: Khi được đặt thànhfalse, các thông báo trong nhóm này sẽ bị loại trừ trong quá trình biên dịch và không có sẵn trong thời gian chạy.logToProto: Xác định xem nhóm này có ghi nhật ký bằng định dạng nhị phân hay không.logToLogcat: Xác định xem nhóm này có ghi nhật ký vào logcat hay không.tag: Tên của nguồn thông báo được ghi nhật ký.
Mỗi quy trình sử dụng ProtoLog phải có một thực thể ProtoLogGroup được định cấu hình.
Loại đối số được hỗ trợ
ProtoLog định dạng các chuỗi nội bộ bằng cách sử dụng android.text.TextUtils#formatSimple(String, Object...), vì vậy cú pháp của ProtoLog cũng giống như vậy.
ProtoLog hỗ trợ các loại đối số sau:
%b– boolean%d,%x– kiểu số nguyên (ngắn, số nguyên hoặc dài)%f– loại dấu phẩy động (float hoặc double)%s– chuỗi%%– ký tự phần trăm theo nghĩa đen
Hệ số sửa đổi về chiều rộng và độ chính xác, chẳng hạn như %04d và %10b, được hỗ trợ.
Tuy nhiên, argument_index và flags không được hỗ trợ.
Sử dụng ProtoLog trong một dịch vụ mới
Để sử dụng ProtoLog trong một dịch vụ mới, hãy làm theo các bước sau:
- Tạo một định nghĩa
ProtoLogGroupcho dịch vụ này. Khởi tạo định nghĩa trước khi sử dụng lần đầu. Ví dụ: khởi chạy nó trong quá trình tạo:
ProtoLog.init(ProtoLogGroup.values());Sử dụng
ProtoLogtheo cách tương tự như khi bạn sử dụngandroid.util.Log:ProtoLog.v(WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId);
Bật tính năng tối ưu hoá thời gian biên dịch
Để bật ProtoLog tại thời điểm biên dịch trong một quy trình, bạn phải thay đổi các quy tắc xây dựng của quy trình đó và gọi tệp nhị phân protologtool.
ProtoLogTool là một tệp nhị phân chuyển đổi mã thực hiện việc lưu trữ chuỗi và cập nhật lệnh gọi ProtoLog. Tệp nhị phân này chuyển đổi mọi lệnh gọi ghi nhật ký ProtoLog như minh hoạ trong ví dụ sau:
ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);
thành:
if (ProtoLogImpl.isEnabled(GROUP_NAME)) {
int protoLogParam0 = value1;
String protoLogParam1 = String.valueOf(value2);
ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 1234560b0100, protoLogParam0, protoLogParam1);
}
Trong ví dụ này, ProtoLog, ProtoLogImpl và ProtoLogGroup là các lớp được cung cấp dưới dạng đối số (có thể được nhập, nhập tĩnh hoặc đường dẫn đầy đủ, không được phép nhập ký tự đại diện) và x là phương thức ghi nhật ký.
Quá trình chuyển đổi được thực hiện ở cấp nguồn. Một hàm băm được tạo từ chuỗi định dạng, cấp nhật ký và tên nhóm nhật ký, rồi được chèn sau đối số ProtoLogGroup. Mã được tạo thực tế được nội tuyến và một số ký tự dòng mới được thêm vào để giữ nguyên việc đánh số dòng trong tệp.
Ví dụ:
genrule {
name: "wm_shell_protolog_src",
srcs: [
":protolog-impl", // protolog lib
":wm_shell_protolog-groups", // protolog groups declaration
":wm_shell-sources", // source code
],
tools: ["protologtool"],
cmd: "$(location protologtool) transform-protolog-calls " +
"--protolog-class com.android.internal.protolog.ProtoLog " +
"--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
"--loggroups-jar $(location :wm_shell_protolog-groups) " +
"--viewer-config-file-path /system_ext/etc/wmshell.protolog.pb " +
"--legacy-viewer-config-file-path /system_ext/etc/wmshell.protolog.json.gz " +
"--legacy-output-file-path /data/misc/wmtrace/shell_log.winscope " +
"--output-srcjar $(out) " +
"$(locations :wm_shell-sources)",
out: ["wm_shell_protolog.srcjar"],
}
Tuỳ chọn dòng lệnh
Một trong những ưu điểm chính của ProtoLog là bạn có thể bật hoặc tắt tính năng này trong thời gian chạy. Ví dụ: bạn có thể ghi nhật ký chi tiết hơn trong một bản dựng, bị vô hiệu hoá theo mặc định và bật nhật ký đó trong quá trình phát triển cục bộ để gỡ lỗi một vấn đề cụ thể. Ví dụ: mẫu này được dùng trong WindowManager với các nhóm WM_DEBUG_WINDOW_TRANSITIONS và WM_DEBUG_WINDOW_TRANSITIONS_MIN cho phép các loại nhật ký chuyển đổi khác nhau, trong đó nhóm trước được bật theo mặc định.
Bạn có thể định cấu hình ProtoLog bằng Perfetto khi bắt đầu một dấu vết. Bạn cũng có thể định cấu hình ProtoLog cục bộ bằng dòng lệnh adb.
Lệnh adb shell cmd protolog_configuration hỗ trợ các đối số sau:
help
Print this help text.
groups (list | status)
list - lists all ProtoLog groups registered with ProtoLog service"
status <group> - print the status of a ProtoLog group"
logcat (enable | disable) <group>"
enable or disable ProtoLog to logcat
Mẹo sử dụng hiệu quả
ProtoLog sử dụng tính năng lưu trữ chuỗi cho cả thông báo và mọi đối số chuỗi được truyền. Điều này có nghĩa là để khai thác nhiều lợi ích hơn từ ProtoLog, các thông báo phải tách các giá trị lặp lại thành các biến.
Ví dụ: hãy xem xét câu lệnh sau:
Protolog.v(MY_GROUP, "%s", "The argument value is " + argument);
Khi được tối ưu hoá tại thời điểm biên dịch, nó sẽ chuyển thành:
ProtologImpl.v(MY_GROUP, 0x123, "The argument value is " + argument);
Nếu ProtoLog được dùng trong mã có đối số A,B,C:
Protolog.v(MY_GROUP, "%s", "The argument value is A");
Protolog.v(MY_GROUP, "%s", "The argument value is B");
Protolog.v(MY_GROUP, "%s", "The argument value is C");
Protolog.v(MY_GROUP, "%s", "The argument value is A");
Điều này dẫn đến các thông báo sau trong bộ nhớ:
Dict:
0x123: "%s"
0x111: "The argument value is A"
0x222: "The argument value is B"
0x333: "The argument value is C"
Message1 (Hash: 0x123, Arg1: 0x111)
Message2 (Hash: 0x123, Arg2: 0x222)
Message3 (Hash: 0x123, Arg3: 0x333)
Message4 (Hash: 0x123, Arg1: 0x111)
Nếu thay vào đó, câu lệnh ProtoLog được viết như sau:
Protolog.v(MY_GROUP, "The argument value is %s", argument);
Bộ nhớ đệm sẽ kết thúc như sau:
Dict:
0x123: "The argument value is %s" (24 b)
0x111: "A" (1 b)
0x222: "B" (1 b)
0x333: "C" (1 b)
Message1 (Hash: 0x123, Arg1: 0x111)
Message2 (Hash: 0x123, Arg2: 0x222)
Message3 (Hash: 0x123, Arg3: 0x333)
Message4 (Hash: 0x123, Arg1: 0x111)
Trình tự này giúp giảm 35% mức sử dụng bộ nhớ.
Trình xem Winscope
Thẻ trình xem ProtoLog của Winscope cho thấy các dấu vết ProtoLog được sắp xếp theo định dạng bảng. Bạn có thể lọc dấu vết theo cấp nhật ký, thẻ, tệp nguồn (nơi có câu lệnh ProtoLog) và nội dung thông báo. Bạn có thể lọc tất cả các cột. Khi bạn nhấp vào dấu thời gian ở cột đầu tiên, dòng thời gian sẽ chuyển đến dấu thời gian của tin nhắn. Ngoài ra, khi bạn nhấp vào Chuyển đến thời gian hiện tại, bảng ProtoLog sẽ quay lại dấu thời gian được chọn trong dòng thời gian:
Hình 1. Trình xem ProtoLog