Loading tools/protologtool/README.md +27 −14 Original line number Diff line number Diff line Loading @@ -8,8 +8,13 @@ ProtoLogTool incorporates three different modes of operation: ### Code transformation Command: `process <protolog class path> <protolog implementation class path> <protolog groups class path> <config.jar> [<input.java>] <output.srcjar>` Command: `protologtool transform-protolog-calls --protolog-class <protolog class name> --protolog-impl-class <protolog implementation class name> --loggroups-class <protolog groups class name> --loggroups-jar <config jar path> --output-srcjar <output.srcjar> [<input.java>]` In this mode ProtoLogTool transforms every ProtoLog logging call in form of: ```java Loading @@ -17,16 +22,20 @@ ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2); ``` into: ```java if (GROUP_NAME.isLogToAny()) { ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 123456, "Format string %d %s or null", value1, value2); if (ProtoLogImpl.isEnabled(GROUP_NAME)) { int protoLogParam0 = value1; String protoLogParam1 = String.valueOf(value2); ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 123456, 0b0100, "Format string %d %s or null", protoLogParam0, protoLogParam1); } ``` where `ProtoLog`, `ProtoLogImpl` and `ProtoLogGroup` are the classes provided as arguments (can be imported, static imported or full path, wildcard imports are not allowed) and, `x` is the logging method. The transformation is done on the source level. A hash is generated from the format string and log level and inserted after the `ProtoLogGroup` argument. The format string is replaced string, log level and log group name and inserted after the `ProtoLogGroup` argument. After the hash we insert a bitmask specifying the types of logged parameters. The format string is replaced by `null` if `ProtoLogGroup.GROUP_NAME.isLogToLogcat()` returns false. If `ProtoLogGroup.GROUP_NAME.isEnabled()` returns false the log statement is removed entirely from the resultant code. returns false the log statement is removed entirely from the resultant code. The real generated code is inlined and a number of new line characters is added as to preserve line numbering in file. Input is provided as a list of java source file names. Transformed source is saved to a single source jar file. The ProtoLogGroup class with all dependencies should be provided as a compiled Loading @@ -34,8 +43,12 @@ jar file (config.jar). ### Viewer config generation Command: `viewerconf <protolog class path> <protolog implementation class path <protolog groups class path> <config.jar> [<input.java>] <output.json>` Command: `generate-viewer-config --protolog-class <protolog class name> --loggroups-class <protolog groups class name> --loggroups-jar <config jar path> --viewer-conf <viewer.json> [<input.java>]` This command is similar in it's syntax to the previous one, only instead of creating a processed source jar it writes a viewer configuration file with following schema: Loading @@ -46,8 +59,9 @@ it writes a viewer configuration file with following schema: "123456": { "message": "Format string %d %s", "level": "ERROR", "group": "GROUP_NAME" }, "group": "GROUP_NAME", "at": "com\/android\/server\/example\/Class.java" } }, "groups": { "GROUP_NAME": { Loading @@ -60,13 +74,13 @@ it writes a viewer configuration file with following schema: ### Binary log viewing Command: `read <viewer.json> <wm_log.pb>` Command: `read-log --viewer-conf <viewer.json> <wm_log.pb>` Reads the binary ProtoLog log file and outputs a human-readable LogCat-like text log. ## What is ProtoLog? ProtoLog is a logging system created for the WindowManager project. It allows both binary and text logging ProtoLog is a generic logging system created for the WindowManager project. It allows both binary and text logging and is tunable in runtime. It consists of 3 different submodules: * logging system built-in the Android app, * log viewer for reading binary logs, Loading Loading @@ -94,8 +108,7 @@ To add a new ProtoLogGroup simple create a new enum ProtoLogGroup member with de To add a new logging statement just add a new call to ProtoLog.x where x is a log level. After doing any changes to logging groups or statements you should run `make update-protolog` to update viewer configuration saved in the code repository. After doing any changes to logging groups or statements you should build the project and follow instructions printed by the tool. ## How to change settings on device in runtime? Use the `adb shell su root cmd window logging` command. To get help just type Loading tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt +15 −15 Original line number Diff line number Diff line Loading @@ -32,9 +32,14 @@ object CodeUtils { .map { c -> c.toInt() }.reduce { h, c -> h * 31 + c } } fun isWildcardStaticImported(code: CompilationUnit, className: String): Boolean { return code.findAll(ImportDeclaration::class.java) .any { im -> im.isStatic && im.isAsterisk && im.name.toString() == className } fun checkWildcardStaticImported(code: CompilationUnit, className: String, fileName: String) { code.findAll(ImportDeclaration::class.java) .forEach { im -> if (im.isStatic && im.isAsterisk && im.name.toString() == className) { throw IllegalImportException("Wildcard static imports of $className " + "methods are not supported.", ParsingContext(fileName, im)) } } } fun isClassImportedOrSamePackage(code: CompilationUnit, className: String): Boolean { Loading @@ -58,24 +63,19 @@ object CodeUtils { .map { im -> im.name.toString().substringAfterLast('.') }.toSet() } fun concatMultilineString(expr: Expression): String { fun concatMultilineString(expr: Expression, context: ParsingContext): String { return when (expr) { is StringLiteralExpr -> expr.asString() is BinaryExpr -> when { expr.operator == BinaryExpr.Operator.PLUS -> concatMultilineString(expr.left) + concatMultilineString(expr.right) concatMultilineString(expr.left, context) + concatMultilineString(expr.right, context) else -> throw InvalidProtoLogCallException( "messageString must be a string literal " + "or concatenation of string literals.", expr) "expected a string literal " + "or concatenation of string literals, got: $expr", context) } else -> throw InvalidProtoLogCallException("messageString must be a string literal " + "or concatenation of string literals.", expr) } } fun getPositionString(fileName: String): String { return when { else -> fileName else -> throw InvalidProtoLogCallException("expected a string literal " + "or concatenation of string literals, got: $expr", context) } } } tools/protologtool/src/com/android/protolog/tool/LogLevel.kt +3 −2 Original line number Diff line number Diff line Loading @@ -22,7 +22,7 @@ enum class LogLevel { DEBUG, VERBOSE, INFO, WARN, ERROR, WTF; companion object { fun getLevelForMethodName(name: String, node: Node): LogLevel { fun getLevelForMethodName(name: String, node: Node, context: ParsingContext): LogLevel { return when (name) { "d" -> DEBUG "v" -> VERBOSE Loading @@ -30,7 +30,8 @@ enum class LogLevel { "w" -> WARN "e" -> ERROR "wtf" -> WTF else -> throw InvalidProtoLogCallException("Unknown log level $name", node) else -> throw InvalidProtoLogCallException("Unknown log level $name in $node", context) } } } Loading tools/protologtool/src/com/android/protolog/tool/ParsingContext.kt 0 → 100644 +26 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * 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 com.android.protolog.tool import com.github.javaparser.ast.Node data class ParsingContext(val filePath: String, val lineNumber: Int) { constructor(filePath: String, node: Node) : this(filePath, if (node.range.isPresent) node.range.get().begin.line else -1) constructor() : this("", -1) } tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt +20 −15 Original line number Diff line number Diff line Loading @@ -38,23 +38,27 @@ open class ProtoLogCallProcessor( private fun getLogGroupName( expr: Expression, isClassImported: Boolean, staticImports: Set<String> staticImports: Set<String>, fileName: String ): String { val context = ParsingContext(fileName, expr) return when (expr) { is NameExpr -> when { expr.nameAsString in staticImports -> expr.nameAsString else -> throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup", expr) throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr", context) } is FieldAccessExpr -> when { expr.scope.toString() == protoLogGroupClassName || isClassImported && expr.scope.toString() == protoLogGroupSimpleClassName -> expr.nameAsString else -> throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup", expr) throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr", context) } else -> throw InvalidProtoLogCallException("Invalid group argument " + "- must be ProtoLogGroup enum member reference", expr) "- must be ProtoLogGroup enum member reference: $expr", context) } } Loading @@ -69,12 +73,10 @@ open class ProtoLogCallProcessor( !call.scope.isPresent && staticLogImports.contains(call.name.toString()) } open fun process(code: CompilationUnit, callVisitor: ProtoLogCallVisitor?): CompilationUnit { if (CodeUtils.isWildcardStaticImported(code, protoLogClassName) || CodeUtils.isWildcardStaticImported(code, protoLogGroupClassName)) { throw IllegalImportException("Wildcard static imports of $protoLogClassName " + "and $protoLogGroupClassName methods are not supported.") } open fun process(code: CompilationUnit, callVisitor: ProtoLogCallVisitor?, fileName: String): CompilationUnit { CodeUtils.checkWildcardStaticImported(code, protoLogClassName, fileName) CodeUtils.checkWildcardStaticImported(code, protoLogGroupClassName, fileName) val isLogClassImported = CodeUtils.isClassImportedOrSamePackage(code, protoLogClassName) val staticLogImports = CodeUtils.staticallyImportedMethods(code, protoLogClassName) Loading @@ -86,22 +88,25 @@ open class ProtoLogCallProcessor( .filter { call -> isProtoCall(call, isLogClassImported, staticLogImports) }.forEach { call -> val context = ParsingContext(fileName, call) if (call.arguments.size < 2) { throw InvalidProtoLogCallException("Method signature does not match " + "any ProtoLog method.", call) "any ProtoLog method: $call", context) } val messageString = CodeUtils.concatMultilineString(call.getArgument(1)) val messageString = CodeUtils.concatMultilineString(call.getArgument(1), context) val groupNameArg = call.getArgument(0) val groupName = getLogGroupName(groupNameArg, isGroupClassImported, staticGroupImports) getLogGroupName(groupNameArg, isGroupClassImported, staticGroupImports, fileName) if (groupName !in groupMap) { throw InvalidProtoLogCallException("Unknown group argument " + "- not a ProtoLogGroup enum member", call) "- not a ProtoLogGroup enum member: $call", context) } callVisitor?.processCall(call, messageString, LogLevel.getLevelForMethodName( call.name.toString(), call), groupMap.getValue(groupName)) call.name.toString(), call, context), groupMap.getValue(groupName)) } return code } Loading Loading
tools/protologtool/README.md +27 −14 Original line number Diff line number Diff line Loading @@ -8,8 +8,13 @@ ProtoLogTool incorporates three different modes of operation: ### Code transformation Command: `process <protolog class path> <protolog implementation class path> <protolog groups class path> <config.jar> [<input.java>] <output.srcjar>` Command: `protologtool transform-protolog-calls --protolog-class <protolog class name> --protolog-impl-class <protolog implementation class name> --loggroups-class <protolog groups class name> --loggroups-jar <config jar path> --output-srcjar <output.srcjar> [<input.java>]` In this mode ProtoLogTool transforms every ProtoLog logging call in form of: ```java Loading @@ -17,16 +22,20 @@ ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2); ``` into: ```java if (GROUP_NAME.isLogToAny()) { ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 123456, "Format string %d %s or null", value1, value2); if (ProtoLogImpl.isEnabled(GROUP_NAME)) { int protoLogParam0 = value1; String protoLogParam1 = String.valueOf(value2); ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 123456, 0b0100, "Format string %d %s or null", protoLogParam0, protoLogParam1); } ``` where `ProtoLog`, `ProtoLogImpl` and `ProtoLogGroup` are the classes provided as arguments (can be imported, static imported or full path, wildcard imports are not allowed) and, `x` is the logging method. The transformation is done on the source level. A hash is generated from the format string and log level and inserted after the `ProtoLogGroup` argument. The format string is replaced string, log level and log group name and inserted after the `ProtoLogGroup` argument. After the hash we insert a bitmask specifying the types of logged parameters. The format string is replaced by `null` if `ProtoLogGroup.GROUP_NAME.isLogToLogcat()` returns false. If `ProtoLogGroup.GROUP_NAME.isEnabled()` returns false the log statement is removed entirely from the resultant code. returns false the log statement is removed entirely from the resultant code. The real generated code is inlined and a number of new line characters is added as to preserve line numbering in file. Input is provided as a list of java source file names. Transformed source is saved to a single source jar file. The ProtoLogGroup class with all dependencies should be provided as a compiled Loading @@ -34,8 +43,12 @@ jar file (config.jar). ### Viewer config generation Command: `viewerconf <protolog class path> <protolog implementation class path <protolog groups class path> <config.jar> [<input.java>] <output.json>` Command: `generate-viewer-config --protolog-class <protolog class name> --loggroups-class <protolog groups class name> --loggroups-jar <config jar path> --viewer-conf <viewer.json> [<input.java>]` This command is similar in it's syntax to the previous one, only instead of creating a processed source jar it writes a viewer configuration file with following schema: Loading @@ -46,8 +59,9 @@ it writes a viewer configuration file with following schema: "123456": { "message": "Format string %d %s", "level": "ERROR", "group": "GROUP_NAME" }, "group": "GROUP_NAME", "at": "com\/android\/server\/example\/Class.java" } }, "groups": { "GROUP_NAME": { Loading @@ -60,13 +74,13 @@ it writes a viewer configuration file with following schema: ### Binary log viewing Command: `read <viewer.json> <wm_log.pb>` Command: `read-log --viewer-conf <viewer.json> <wm_log.pb>` Reads the binary ProtoLog log file and outputs a human-readable LogCat-like text log. ## What is ProtoLog? ProtoLog is a logging system created for the WindowManager project. It allows both binary and text logging ProtoLog is a generic logging system created for the WindowManager project. It allows both binary and text logging and is tunable in runtime. It consists of 3 different submodules: * logging system built-in the Android app, * log viewer for reading binary logs, Loading Loading @@ -94,8 +108,7 @@ To add a new ProtoLogGroup simple create a new enum ProtoLogGroup member with de To add a new logging statement just add a new call to ProtoLog.x where x is a log level. After doing any changes to logging groups or statements you should run `make update-protolog` to update viewer configuration saved in the code repository. After doing any changes to logging groups or statements you should build the project and follow instructions printed by the tool. ## How to change settings on device in runtime? Use the `adb shell su root cmd window logging` command. To get help just type Loading
tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt +15 −15 Original line number Diff line number Diff line Loading @@ -32,9 +32,14 @@ object CodeUtils { .map { c -> c.toInt() }.reduce { h, c -> h * 31 + c } } fun isWildcardStaticImported(code: CompilationUnit, className: String): Boolean { return code.findAll(ImportDeclaration::class.java) .any { im -> im.isStatic && im.isAsterisk && im.name.toString() == className } fun checkWildcardStaticImported(code: CompilationUnit, className: String, fileName: String) { code.findAll(ImportDeclaration::class.java) .forEach { im -> if (im.isStatic && im.isAsterisk && im.name.toString() == className) { throw IllegalImportException("Wildcard static imports of $className " + "methods are not supported.", ParsingContext(fileName, im)) } } } fun isClassImportedOrSamePackage(code: CompilationUnit, className: String): Boolean { Loading @@ -58,24 +63,19 @@ object CodeUtils { .map { im -> im.name.toString().substringAfterLast('.') }.toSet() } fun concatMultilineString(expr: Expression): String { fun concatMultilineString(expr: Expression, context: ParsingContext): String { return when (expr) { is StringLiteralExpr -> expr.asString() is BinaryExpr -> when { expr.operator == BinaryExpr.Operator.PLUS -> concatMultilineString(expr.left) + concatMultilineString(expr.right) concatMultilineString(expr.left, context) + concatMultilineString(expr.right, context) else -> throw InvalidProtoLogCallException( "messageString must be a string literal " + "or concatenation of string literals.", expr) "expected a string literal " + "or concatenation of string literals, got: $expr", context) } else -> throw InvalidProtoLogCallException("messageString must be a string literal " + "or concatenation of string literals.", expr) } } fun getPositionString(fileName: String): String { return when { else -> fileName else -> throw InvalidProtoLogCallException("expected a string literal " + "or concatenation of string literals, got: $expr", context) } } }
tools/protologtool/src/com/android/protolog/tool/LogLevel.kt +3 −2 Original line number Diff line number Diff line Loading @@ -22,7 +22,7 @@ enum class LogLevel { DEBUG, VERBOSE, INFO, WARN, ERROR, WTF; companion object { fun getLevelForMethodName(name: String, node: Node): LogLevel { fun getLevelForMethodName(name: String, node: Node, context: ParsingContext): LogLevel { return when (name) { "d" -> DEBUG "v" -> VERBOSE Loading @@ -30,7 +30,8 @@ enum class LogLevel { "w" -> WARN "e" -> ERROR "wtf" -> WTF else -> throw InvalidProtoLogCallException("Unknown log level $name", node) else -> throw InvalidProtoLogCallException("Unknown log level $name in $node", context) } } } Loading
tools/protologtool/src/com/android/protolog/tool/ParsingContext.kt 0 → 100644 +26 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * 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 com.android.protolog.tool import com.github.javaparser.ast.Node data class ParsingContext(val filePath: String, val lineNumber: Int) { constructor(filePath: String, node: Node) : this(filePath, if (node.range.isPresent) node.range.get().begin.line else -1) constructor() : this("", -1) }
tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt +20 −15 Original line number Diff line number Diff line Loading @@ -38,23 +38,27 @@ open class ProtoLogCallProcessor( private fun getLogGroupName( expr: Expression, isClassImported: Boolean, staticImports: Set<String> staticImports: Set<String>, fileName: String ): String { val context = ParsingContext(fileName, expr) return when (expr) { is NameExpr -> when { expr.nameAsString in staticImports -> expr.nameAsString else -> throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup", expr) throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr", context) } is FieldAccessExpr -> when { expr.scope.toString() == protoLogGroupClassName || isClassImported && expr.scope.toString() == protoLogGroupSimpleClassName -> expr.nameAsString else -> throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup", expr) throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr", context) } else -> throw InvalidProtoLogCallException("Invalid group argument " + "- must be ProtoLogGroup enum member reference", expr) "- must be ProtoLogGroup enum member reference: $expr", context) } } Loading @@ -69,12 +73,10 @@ open class ProtoLogCallProcessor( !call.scope.isPresent && staticLogImports.contains(call.name.toString()) } open fun process(code: CompilationUnit, callVisitor: ProtoLogCallVisitor?): CompilationUnit { if (CodeUtils.isWildcardStaticImported(code, protoLogClassName) || CodeUtils.isWildcardStaticImported(code, protoLogGroupClassName)) { throw IllegalImportException("Wildcard static imports of $protoLogClassName " + "and $protoLogGroupClassName methods are not supported.") } open fun process(code: CompilationUnit, callVisitor: ProtoLogCallVisitor?, fileName: String): CompilationUnit { CodeUtils.checkWildcardStaticImported(code, protoLogClassName, fileName) CodeUtils.checkWildcardStaticImported(code, protoLogGroupClassName, fileName) val isLogClassImported = CodeUtils.isClassImportedOrSamePackage(code, protoLogClassName) val staticLogImports = CodeUtils.staticallyImportedMethods(code, protoLogClassName) Loading @@ -86,22 +88,25 @@ open class ProtoLogCallProcessor( .filter { call -> isProtoCall(call, isLogClassImported, staticLogImports) }.forEach { call -> val context = ParsingContext(fileName, call) if (call.arguments.size < 2) { throw InvalidProtoLogCallException("Method signature does not match " + "any ProtoLog method.", call) "any ProtoLog method: $call", context) } val messageString = CodeUtils.concatMultilineString(call.getArgument(1)) val messageString = CodeUtils.concatMultilineString(call.getArgument(1), context) val groupNameArg = call.getArgument(0) val groupName = getLogGroupName(groupNameArg, isGroupClassImported, staticGroupImports) getLogGroupName(groupNameArg, isGroupClassImported, staticGroupImports, fileName) if (groupName !in groupMap) { throw InvalidProtoLogCallException("Unknown group argument " + "- not a ProtoLogGroup enum member", call) "- not a ProtoLogGroup enum member: $call", context) } callVisitor?.processCall(call, messageString, LogLevel.getLevelForMethodName( call.name.toString(), call), groupMap.getValue(groupName)) call.name.toString(), call, context), groupMap.getValue(groupName)) } return code } Loading