Hệ thống ghi nhật ký của Android hướng đến khả năng tiếp cận và dễ sử dụng trên toàn cầu, giả định rằng tất cả dữ liệu nhật ký 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 các công cụ chuyên biệt. Tuy nhiên, trong các môi trường yêu cầu hiệu suất ghi nhật ký cao và kích thước nhật ký bị hạn chế, tính năng ghi nhật ký dựa trên văn bản có thể không tối ưu. Một trong những trường hợp như vậy là WindowManager, cần có 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ế để giải quyết nhu cầu ghi nhật ký của WindowManager và các dịch vụ tương tự. Các lợi ích chính của ProtoLog so với logcat là:
- Lượng tài nguyên dùng để ghi nhật ký sẽ nhỏ hơn.
- Từ quan điểm của nhà phát triển, cách này giống như sử dụng khung ghi nhật ký Android mặc định.
- Hỗ trợ bật hoặc tắt câu lệnh nhật ký trong thời gian chạy.
- Vẫn có thể ghi nhật ký vào logcat nếu cần.
Để tối ưu hoá mức sử dụng bộ nhớ, ProtoLog sử dụng cơ chế nội trú chuỗi liên quan đến việc tính toán và lưu 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 (đối với các dịch vụ hệ thống), chỉ ghi lại giá trị nhận dạng thông báo và đối số trong thời gian chạy. Ngoài ra, khi tạo dấu vết ProtoLog hoặc nhận 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, cho phép giải mã thông báo từ bất kỳ bản dựng nào.
Khi sử dụng ProtoLog, thông báo sẽ được lưu trữ ở định dạng tệp nhị phân (proto) trong dấu vết Perfetto. Quá trình giải mã thông báo diễn ra bên trong trace_processor
của Perfetto. Quy trình này bao gồm việc giải mã 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 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
, cụ thể là: d
, v
, i
, w
, e
, 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 đó, mã này được mở rộng để bao gồm mã shell WindowManager trong quy trình Giao diện người dùng hệ thống, nhưng việc sử dụng ProtoLog yêu cầu một mã thiết lập nguyên mẫu phức tạp. Hơn nữa, tính năng ghi nhật ký Proto chỉ được giới hạn ở máy chủ hệ thống và các quy trình Giao diện người dùng hệ thống, khiến việc tích hợp vào các quy trình khác trở nên khó khăn và cần thiết phải thiết lập vùng đệm bộ nhớ riêng cho từng quy trình. Tuy nhiên, ProtoLog hiện đã được cung cấp 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 điểm biên dịch. Thay vào đó, việc lưu trữ chuỗi nội bộ sẽ diễn ra một cách linh động trong luồng nền. Do đó, mặc dù ProtoLog ở phía máy khách mang lại các lợi thế về mức sử dụng bộ nhớ tương đương với ProtoLog trên các dịch vụ hệ thống, nhưng lại gây ra mức hao tổn hiệu suất cao hơn một chút và thiếu lợi thế giảm bộ nhớ của bộ nhớ được ghim của đối tác phía máy chủ.
Nhóm ProtoLog
Các thông điệp ProtoLog được sắp xếp thành các nhóm có tên là ProtoLogGroups
, tương tự như cách TAG
sắp xếp các thông điệp Logcat. Các ProtoLogGroups
này đóng vai trò là các cụm thông báo có thể được bật hoặc tắt cùng nhau trong thời gian chạy.
Ngoài ra, các thuộc tính này còn kiểm soát việc có xoá thông báo trong quá trình biên dịch hay không và nơi ghi lại thông báo (proto, logcat hoặc cả hai). Mỗi ProtoLogGroup
bao gồm 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ý ở đị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 nguồn của thông báo đã ghi.
Mỗi quy trình sử dụng ProtoLog phải được định cấu hình một thực thể ProtoLogGroup
.
Loại đối số được hỗ trợ
Trong nội bộ, ProtoLog định dạng các chuỗi sử dụng android.text.TextUtils#formatSimple(String, Object...)
, vì vậy cú pháp của chuỗi này cũng giống như vậy.
ProtoLog hỗ trợ các loại đối số sau:
%b
– boolean%d
,%x
– loại số nguyên (short, integer hoặc long)%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
Các đối tượng sửa đổi chiều rộng và độ chính xác như %04d
và %10b
được hỗ trợ, nhưng argument_index
và flags
thì không.
Sử dụng ProtoLog trong một dịch vụ mới
Cách sử dụng ProtoLog trong một quy trình mới:
Tạo định nghĩa
ProtoLogGroup
cho dịch vụ này.Khởi chạy định nghĩa trước lần sử dụng đầu tiên (ví dụ: khi tạo quy trình):
Protolog.init(ProtologGroup.values());
Sử dụng
Protolog
theo cách tương tự nhưandroid.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á tại thời điểm 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 bản dựng và gọi tệp nhị phân protologtool
.
ProtoLogTool
là 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 biến đổi mọi lệnh gọi ghi nhật ký ProtoLog
như trong ví dụ sau:
ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);
vào:
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ể nhập, nhập tĩnh hoặc đường dẫn đầy đủ, không cho 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. 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ý, đồng thờ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 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, tắt theo mặc định và bật 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 sử dụng trong WindowManager với các nhóm WM_DEBUG_WINDOW_TRANSITIONS
và WM_DEBUG_WINDOW_TRANSITIONS_MIN
cho phép nhiều loại ghi nhật ký chuyển đổi, 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 theo dõi.
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 nội 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à để nhận được nhiều lợi ích hơn từ ProtoLog, các thông điệp phải tách riêng 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, mã này sẽ chuyển thành:
ProtologImpl.v(MY_GROUP, 0x123, "The argument value is " + argument);
Nếu ProtoLog được sử 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)
Thay vào đó, nếu câu lệnh ProtoLog được viết như sau:
Protolog.v(MY_GROUP, "The argument value is %s", argument);
Bộ nhớ đệm trong bộ nhớ 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 hiển thị 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 nhấp vào dấu thời gian trong 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 nhấp vào Chuyển đến thời gian hiện tại, bảng ProtoLog sẽ cuộn trở lại dấu thời gian đã chọn trong dòng thời gian:
Hình 1. Trình xem ProtoLog