# MLog **Repository Path**: ym521/mlog ## Basic Information - **Project Name**: MLog - **Description**: 全局替换系统Log 实现动态开启日志和转发 - **Primary Language**: Unknown - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-06 - **Last Updated**: 2026-09-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # MLog [![](https://www.jitpack.io/v/com.gitee.ym521/mlog.svg)](https://www.jitpack.io/#com.gitee.ym521/mlog) #### 介绍 一个日志重定向工具集,由 **MLogPlugin(插桩插件,主)** 与 **MLog 库(运行时支持库,辅助 MLogPlugin 使用)** 两部分组成: - **MLogPlugin**:基于 ASM 的 Android Gradle 插件,编译期把项目中的 `android.util.Log` 调用**全局重定向**到 `MLog`,业务代码零改动; - **MLog 库(辅助)**:为插桩后的字节码提供运行时 hook 类 `MLog`(含 UDP 转发器等)。插桩只做“改调用目标”,真正干活的日志逻辑都在辅助库 `MLog` 里;业务侧也可手动调用。 两者配合实现: - **日志动态开关**:无需改代码、无需重新打包,通过系统属性即可在线上/线下随时开启或关闭日志输出(release 包默认关闭,debug 构建默认开启); - **UDP 远程转发**:可选把设备端日志实时转发到 PC(局域网 UDP 接收端,如 LogViewer),`logcat` 与远程通道互不影响; - **零业务侵入**:业务代码照常写 `Log.d(...)`,无需 import 任何新类。 ##### 功能特性 | 特性 | 说明 | | --- | --- | | 编译期插桩 | MLogPlugin 通过 AGP Instrumentation API(ASM9)实现,替换 `Log` 的 `v/d/i/w/e/wtf` 静态调用(含带 `Throwable` 重载) | | 全局/包级替换 | 支持 `projectPackages` 包前缀过滤;默认全局替换(自动排除 `com.android.mlog` 自身,防递归) | | 运行时辅助 | MLog 库辅助 MLogPlugin 使用:插件只在编译期改写字节码,运行期调用统一落在 mlog 库的 `MLog` 上(缺库会 NoClassDefFoundError) | | 动态开关 | 运行时按包名匹配系统属性 `debug.log.enable`,release 构建可随时远程开启日志 | | UDP 转发 | 系统属性 `debug.log.udp` 指定目标 IP,日志经 UDP 实时发往 PC;内部有界队列(512 条,满时丢最旧),长日志按 UTF-8 字符边界分片(默认 1400B/包,防 IP 分片丢包) | | 无额外依赖 | LogUDP 异步发送仅用 JDK 原生队列与线程(ArrayBlockingQueue + 单发送线程)实现,消费方无需额外引入 kotlinx-coroutines 等第三方库 | | JitPack 直连 | 已发布 JitPack:插件 id 即仓库坐标 `com.gitee.ym521.mlog`,接入无需 resolutionStrategy 映射 | | 方法栈 | 提供 `MLog.stackTrace()` 直接打印调用栈 | | 混淆友好 | 库自带 consumer rules 保留 `MLog` API,app 开启 R8 无影响 | #### 软件架构 ##### 模块划分 ``` MLog ├── app # Demo 示例:Application 初始化 + Splash/Main 生命周期日志演示 ├── mlog_plugin # 主:字节码插桩 Gradle 插件(独立构建,includeBuild 接入) │ # LogPlugin -> 注册 mlogPlugin 扩展 + transformClassesWith(InstrumentationScope.ALL) │ # LogRedirectFactory / LogRedirectMethodVisitor -> ASM 指令级替换规则 └── mlog # 辅助:运行时支持库(Android Library),服务 MLogPlugin 插桩后的字节码 # MLog —— hook 目标类:统一开关、转发入口、静态 API(v/d/i/w/e/wtf/stackTrace) # LogUDP —— UDP 发送器:有界队列 + 单发送线程(JDK 原生,无第三方依赖)、拆包、close 释放 ``` ##### 插桩原理 ``` 业务代码 Log.d(TAG, "...") │ 编译期(mlog_plugin,ASM 指令替换 visitMethodInsn) ▼ com.android.mlog.MLog.d(TAG, "...") // MLog 来自 mlog 库(辅助),签名与 Log 完全一致 ├── enable 开启? → android.util.Log.d(...) // 正常输出 logcat └── logUDP 已配置 → LogUDP 异步转发至 PC(UDP :10086) ``` - 替换目标:`android/util/Log` 中 `d/v/i/w/e/wtf` 且描述符为 `(String,String)` 或 `(String,String,Throwable)` 的静态调用,其余重载保持原样; - 替换范围:`InstrumentationScope.ALL`(应用及其依赖的库模块都会被处理),过滤规则由 `mlogPlugin` 扩展控制,`com.android.mlog.*` 始终排除; - 主辅关系:MLogPlugin 只负责编译期“改调用目标”,运行期的日志逻辑(开关/转发)由辅助库 mlog 的 `MLog` 承担,因此被插桩的应用必须依赖 mlog 库; - 环境要求:Gradle 8.7 / AGP 8.6+ / JDK 17(插件本体按 Java 11 编译)。 #### 安装教程 ##### 1. 源码方式接入(同 Demo 工程) 插件作为独立构建引入(`mlog_plugin` 自带独立 `settings.gradle`),并在应用模块**显式应用**才会注册插桩: **写在:项目根目录的 `settings.gradle`(在 `include ':app'` 等模块声明之外)** ```groovy pluginManagement { includeBuild("mlog_plugin") // 引入本地插件构建 repositories { google() mavenCentral() gradlePluginPortal() } } dependencyResolutionManagement { repositories { google() mavenCentral() } } include ':app' include ':mlog' // 运行时支持库(辅助 mlog_plugin,提供 hook 类 MLog) ``` **写在:应用模块的 `app/build.gradle`(插件声明进 `plugins {}` 块,依赖声明进 `dependencies {}` 块,均在 `android {}` 块外)** ```groovy plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'com.gitee.ym521.mlog' // 主:日志重定向插桩插件(编译期替换 Log -> MLog) } // ... android {} 块 ... dependencies { implementation project(':mlog') // 辅助:运行时支持库(插桩后的 MLog 调用依赖它) } ``` ##### 2. 远程依赖方式接入(JitPack 已发布) 已发布至 JitPack(最新版本 **1.1.3**,见顶部徽章)。插件 id 与 JitPack 仓库坐标完全对齐(`com.gitee.ym521.mlog`),**无需 resolutionStrategy 映射**: **写在:项目根目录的 `settings.gradle`** ```groovy pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() maven { url 'https://jitpack.io' } } // 插件 id 即仓库坐标 com.gitee.ym521.mlog,marker 恰好落在 JitPack 仓库坐标上,无需 resolutionStrategy 映射 } dependencyResolutionManagement { repositories { google() mavenCentral() maven { url 'https://jitpack.io' } } } ``` **写在:应用模块的 `app/build.gradle`(`plugins {}` 与 `dependencies {}` 块,均在 `android {}` 块外)** ```groovy plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'com.gitee.ym521.mlog' version '1.1.3' // 主:日志重定向插桩插件(id 即仓库坐标,免映射) } // ... android {} 块 ... dependencies { implementation 'com.gitee.ym521.mlog:mlog:1.1.3' // 辅助:运行时支持库 } ``` > 提示:若项目采用“根 `build.gradle` 声明版本 + 子模块应用”的多模块写法,则根 `plugins {}` 中写 `id 'com.gitee.ym521.mlog' version '1.1.3' apply false`(本插件强依赖 Application 模块,绝不能不带 `apply false` 应用在根工程),子模块 `plugins {}` 里省略 `version` 即可;`settings.gradle` 无需任何额外配置。 #### 使用说明 各步骤涉及的改动位置一览: | 要做什么 | 写在哪个文件 | 文件中的位置 | | --- | --- | --- | | 引入插件/仓库 | 项目根 `settings.gradle` | `pluginManagement {}` / `dependencyResolutionManagement {}` | | 应用插件、加库依赖 | 应用模块 `app/build.gradle` | `plugins {}` / `dependencies {}` 块内 | | 配置插桩范围(可选) | 应用模块 `app/build.gradle` | 与 `android {}` 块平级(块外) | | 初始化 MLog | 应用模块自定义 `Application` 类 + `AndroidManifest.xml` | 类的 `onCreate()` 中调用;并在 `` 标签注册 `android:name` | | 动态开关 / UDP 转发 | 无需改工程代码 | PC 终端执行 `adb shell setprop ...` | ##### 1. 初始化(必须) **代码位置:新建一个继承 `Application` 的类(Demo 工程为 `app/src/main/java/com/android/os/App.kt`),在它的 `onCreate()` 中调用 `MLog.init(...)`**。插桩已把业务代码的 `Log` 调用换成 mlog 库的 `MLog`,因此运行期必须依赖 mlog 库并在此完成初始化: ```kotlin class App : Application() { override fun onCreate() { super.onCreate() // debug:debug 构建直接开启日志;udpEnable 传 true:允许初始化 UDP 转发 MLog.init(this, BuildConfig.DEBUG, true) } } ``` **并且必须在 `AndroidManifest.xml` 的 `` 标签上注册该类,否则初始化不会执行**: ```xml ``` `debug = true` 时日志直接开启;`debug = false` 时是否输出由系统属性 `debug.log.enable` 决定(见第 3 步)。`udpEnable = true` 时 debug/release 均由 `debug.log.udp` 属性控制是否转发;若需 release 包彻底关闭转发通道可改传 `BuildConfig.DEBUG`。 ##### 2. 配置插桩(可选) 应用模块默认不做任何配置即全局替换。需要限定范围时: **代码位置:应用模块的 `app/build.gradle`,写在 `android {}` 块之外、与它平级**(放在 `plugins {}` 之后即可): ```groovy plugins { id 'com.android.application' id 'com.gitee.ym521.mlog' } android { // ... 原有 android 配置,本块不动 ... } // ↓ mlogPlugin 块:android {} 之外、与它平级 mlogPlugin { enabled = true // 总开关,默认 true projectPackages = ['com.example.'] // 仅插桩匹配该包前缀的类;不配置 = 全局替换 } ``` ##### 3. 动态开关与 UDP 转发(关键玩法) **执行位置:不修改任何工程文件,直接在 PC 的命令行终端执行**(先 `adb devices` 确认设备/模拟器已连接)。`MLog.init` 时读取一次系统属性,运行期用 `adb shell setprop` 注入即可(release 包也有效): ```bash # 开启日志:属性值等于应用包名时日志输出生效 adb shell setprop debug.log.enable 你的包名 # 开启 UDP 转发:IP 为 PC 接收端局域网地址(仅做格式校验,不做 DNS 解析) adb shell setprop debug.log.udp 192.168.1.100 # 关闭 UDP 转发(置空) adb shell setprop debug.log.udp "" ``` PC 端监听 **UDP 10086** 端口(可用 LogViewer / 网络调试助手等工具)即可实时接收设备日志。发送内容格式为 `[TAG] message`,带异常时追加堆栈文本。 ##### 4. 直接调用 MLog API(可选) **代码位置:任意业务类中**(需 `import com.android.mlog.MLog;`)。业务代码也可以不经过插桩、直接调用,插桩替换的语义与此完全一致: ```java MLog.d("TAG", "hello"); // v / d / i / w / e / wtf MLog.e("TAG", "failed", new Throwable()); // 带异常重载 MLog.stackTrace(); // 打印调用方法栈 ``` ##### 5. 混淆说明 - `mlog` 库自带 `consumer-rules.pro`,已保留 `MLog` 类全部 public 方法,下游 app 开启 R8 无额外配置; - 库模块自身不做预混淆(`minifyEnabled` 仅为发布校验),收缩统一交由 app 的 R8 处理。 ##### 6. Demo 验证 运行 `app`(applicationId `com.android.os`),依次执行上述 `setprop` 命令,可观察到: - `debug` 构建默认输出生命周期日志; - `debug.log.enable` 未命中且非 debug 构建时日志被整体吞掉(开关生效); - 设置 `debug.log.udp` 后,PC 端 UDP 接收器出现同样内容(转发生效)。 #### 附:发布到 JitPack 仓库已预置发布配置:主插件通过 `java-gradle-plugin` 自动生成插件 marker(id 与 group 均为 `com.gitee.ym521.mlog`,消费端免映射),辅助库 `mlog` 模块配置了 `maven-publish`;`mlog_plugin` 由根 `settings.gradle` 的 `includeBuild` 纳入复合构建,`jitpack.yml` 在 install 阶段以一条命令同时执行 `:mlog` 与 `mlog_plugin` 的 `publishToMavenLocal`。发布流程:打 tag(如 `1.1.3`)并推送 → 在 JitPack 页面 Look up 本仓库、选择该 tag 构建,产出构件 `com.gitee.ym521.mlog:mlog_plugin:1.1.3`(插件)与 `com.gitee.ym521.mlog:mlog:1.1.3`(库)。当前最新已发布版本:**1.1.3**。