开源鸿蒙平台 KMP_CMP 三方库「kotlin-logging」适配全流程
本文记录
kotlin-logging接入 OpenHarmony 的完整过程,覆盖 KMP/CMP 工程盘点、ohosArm64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 日志页面、签名 HAP 和鸿蒙 PC 验收。本次适配复用 Kotlin 侧的
KotlinLogging、日志级别判断、懒消息和Appender配置能力,再由 ArkTS 调用 N-API 获取不可变 JSON 日志记录。这样验证的是同一份 KMP 代码在 OpenHarmony ARM64 设备上的运行结果,而不是重新写一套只在页面里生效的日志展示逻辑。
项目地址: AtomGit/oh-tpc/kotlin-logging
开发工具: 华为云码道
一、背景
1.1 为什么做开源鸿蒙平台 KMP/CMP 适配
kotlin-logging 是 Kotlin Multiplatform 日志门面。业务代码可以使用统一的 logger、级别判断、懒消息、异常参数和 Appender,再由不同平台选择具体的日志输出方式。JVM 侧通常与 SLF4J 配合,Kotlin/Native 侧则使用 direct logger 和 Native appender。
如果只把页面重新写成 ArkTS,页面可能会显示几行“INFO”“WARN”文本,却无法证明共享 Kotlin logger、Native 动态库、C ABI 和 N-API 桥接已经连通。适配需要解决下面几个问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | KMP 模块默认没有 OpenHarmony 目标,必须增加 ohosArm64() 才能生成 Native 库。 |
| 工具链不一致 | Kotlin/Native、OpenHarmony LLVM、Native SDK 和 Gradle 插件必须使用匹配版本。 |
| 平台实现差异 | JVM 的 SLF4J backend 不能直接带入 OpenHarmony,Native 侧需要使用 direct logger/appender 路径。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin data class 或 KLoggingEvent,必须经过 C ABI、C++ N-API 和 JSON。 |
| 日志生命周期 | 自定义 Appender 需要在自检后恢复原配置,避免示例页面改变宿主全局日志行为。 |
| 权限边界 | 日志示例不访问传感器、相机或网络,不需要额外运行时权限,但仍要确认 entry 的基础配置和 ABI。 |
| 交付链路复杂 | Native 动态库、CMake、HAP、签名、鸿蒙 PC 安装和 ARM64 依赖需要分别验证。 |
因此,本项目把适配边界放在三个地方:Kotlin/Native 目标配置、C ABI/N-API 桥接层和 ArkUI 日志页面。日志语义和自检仍由 KMP 代码维护,ArkTS 只负责调用、展示 JSON 和触发 INFO/WARN 操作。
1.2 库提供的能力
kotlin-logging 公共模块提供以下能力:
KotlinLogging.logger {}:按类或文件创建 logger;debug、trace、info、warn、error:统一日志级别 API;- lambda 懒消息:级别关闭时不计算消息内容;
- 异常和 fluent event builder:以 Kotlin 习惯记录 cause、payload 等信息;
KotlinLoggingConfiguration.direct.appender:为 Native/direct logger 设置自定义Appender;KLoggingEvent:携带级别、logger 名称、消息、异常、payload 和时间戳的不可变记录;example/shared中的LoggingExamples:复用 logger、Appender 和 JSON 验收逻辑;example/nativeApp中的 C ABI:向 ArkTS 提供目录、日志输出、自检和内存释放入口。
示例页面使用的两条固定记录如下:
| 日志级别 | logger | 消息 |
|---|---|---|
INFO | openharmony.example | Hello from kotlin-logging |
WARN | openharmony.example | OpenHarmony logging bridge is active |
LoggingExamples.runChecks() 还会检查 logger 是否创建、INFO 是否启用、自定义 appender 是否收到日志,以及桥接记录是否使用不可变 JSON。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | logger 创建、级别判断、Appender 探测、示例记录和自检由 Kotlin 共享。 |
| 平台目标 | 为根库和示例加入 ohosArm64,生成 libkotlin_logging.so。 |
| 桥接稳定 | 使用少量 C ABI 函数和 UTF-8 JSON,避免把 Kotlin 对象地址交给 ArkTS。 |
| UI 完整 | 页面支持当前 logger、最近日志、INFO/WARN 操作、记录目录和共享检查。 |
| 可测试 | JVM 测试、Native 链接、HAP 构建、签名安装和鸿蒙 PC 运行分别验收。 |
| 签名安全 | 源码只保留未签名配置模板,证书和密钥材料由开发者本机配置。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面是独立的真机验收宿主,公共 API 仍然保持平台无关。其他 KMP/CMP 应用可以直接复用
KotlinLogging和Appender,再自行决定日志展示、持久化和上报方式。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点 KMP API、Native 实现和 OpenHarmony 示例边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、独立 example 工程和 Native 构建任务
第 3 阶段:日志与序列化 ── 建立 LoggingExamples、LogExample 和 JSON 契约
第 4 阶段:原生桥接 ── Kotlin/Native C ABI、C++ N-API、内存释放和类型检查
第 5 阶段:ArkUI 页面封装 ── ArkTS 客户端、INFO/WARN 操作和页面状态
第 6 阶段:示例与验证 ── HAP 构建、签名、鸿蒙 PC 安装和效果图
每个阶段都使用真实产物作为下一阶段输入:先用 JVM 验证共享 logger 和 Appender,再把相同代码链接为 ARM64 动态库,随后由 CMake 和 N-API 加载到 Stage 工程,最后安装签名 HAP 观察日志页面。
三、逐步实现过程
第 1 阶段:项目初始化
1.1 盘点公共 API 和工程边界
项目采用与参考 CMP 工程一致的分层:
kotlin-logging/ KMP 日志 API、级别、Appender 和平台实现
src/commonMain/ 公共 logger、事件模型和配置
src/nativeMain/ Kotlin/Native 默认 appender 和 Native 实现
example/shared/ 示例门面和 JVM 验收测试
example/nativeApp/ ohosArm64 Kotlin/Native 动态库
example/ohosApp/ DevEco Stage 工程和 ArkUI 页面
scripts/ Native、HAP 和签名工程辅助脚本
docs/openharmony/ 验收记录和真机效果图
example 是独立 Gradle 工程,不把 DevEco 工程当作 Kotlin 子模块。这样可以分别执行 Gradle 和 Hvigor,也可以把 ohosApp 复制到另一个目录后在 DevEco Studio 中配置签名。
1.2 固定工具链和版本矩阵
本项目使用以下版本约定:
| 项目 | 配置 | 用途 |
|---|---|---|
| Kotlin Multiplatform | 2.2.21 | JVM、Kotlin/Native 和 KLIB |
| kotlin-logging | 8.0.5 | 本次适配的库版本 |
| JDK | 21 | Gradle、Kotlin 编译和 Native 任务 |
| OpenHarmony 目标 | ohosArm64 | ARM64 真机动态库 |
| DevEco product | 6.0.0(20) | 工程兼容和目标版本 |
| ABI | arm64-v8a | HAP 原生库架构 |
| 应用包名 | io.github.oshai.kotlinlogging.example | entry 应用标识 |
执行 Gradle 脚本前先选择 JDK 21:
export JAVA_HOME="/path/to/jdk-21"
java -version
本示例不访问传感器、相机或网络,不需要额外运行时权限。设备是否能安装和运行仍取决于签名、ABI、系统版本和 DevEco 工具链是否匹配。
1.3 创建 OpenHarmony 示例目录
示例页面围绕“当前日志、日志目录、输出操作和共享自检”组织:
标题区 kotlin-logging · OpenHarmony
说明区 Kotlin 多平台日志库和懒消息/Appender 能力
状态区 当前 logger、最近日志
操作区 输出 INFO / 输出 WARN
目录区 INFO、WARN 固定示例记录
自检区 4/4 checks passed
INFO/WARN 按钮通过同一条 Native 链路调用 Kotlin logger;目录记录用于确认 JSON 数组和 ArkTS 解析;自检结果用于确认 logger 创建、级别、Appender 和不可变记录均已通过。
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64 目标
根工程保留所有原有目标,并加入 OpenHarmony Native 目标:
kotlin {
explicitApi()
jvm()
ohosArm64()
sourceSets {
val ohosArm64Main by getting {
dependsOn(nativeMain)
}
val ohosArm64Test by getting {
dependsOn(nativeTest)
}
}
}
JVM 目标让日志行为可以快速测试;ohosArm64 则把同一份 Native 代码编译为 OpenHarmony KLIB 和动态库。
2.2 Native focused build 的作用
example/nativeApp 构建一个受 linker map 约束的 shared library:
kotlin {
ohosArm64 {
binaries.sharedLib {
baseName = "kotlin_logging"
linkerOpts("--entry=0", "--version-script=${project.file("src/ohosArm64Main/linker/shared-library.map")}")
linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
}
}
}
链接器只导出四个 C ABI 符号:
LoggingCatalog
LoggingEmit
LoggingRunChecks
LoggingFree
这样 ArkTS 只能通过明确的边界获取日志 JSON 和自检结果,Kotlin/Native 内部实现不会变成不受控的 ABI。
2.3 配置仓库和独立消费工程
example/settings.gradle.kts 使用 Maven Central、Maven Local、Gradle Plugin Portal 和项目现有的公共 Maven 仓库:
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
}
}
dependencyResolutionManagement {
repositories {
mavenLocal()
mavenCentral()
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
}
}
共享示例的公共依赖为:
sourceSets {
commonMain.dependencies {
api("io.github.oshai:kotlin-logging:8.0.5")
}
}
工程通过 includeBuild("..") 引入根工程;在本地源码联调时,Gradle 会使用当前仓库的库代码,发布构建仍使用 io.github.oshai:kotlin-logging 坐标。
2.4 通过构建产物消费共享库
prepareOhos 在 Native 链接成功后复制动态库和 C 头文件:
val prepareOhos by tasks.registering(Copy::class) {
dependsOn("linkDebugSharedOhosArm64")
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libkotlin_logging.so")
into("libs/arm64-v8a")
}
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libkotlin_logging_api.h")
into("src/main/cpp/include")
}
into(rootProject.layout.projectDirectory.dir("../example/ohosApp/entry"))
}
动态库、生成头文件和 HAP 属于构建产物,仓库通过 .gitignore 排除它们;每台开发机都可以从源码重新生成与自身 SDK 匹配的文件。
第 3 阶段:日志与序列化
3.1 为什么需要统一 JSON 契约
ArkTS、C++ 和 Kotlin/Native 不能直接共享 Kotlin 对象,因此边界使用 UTF-8 JSON:
KotlinLogging logger
↓ LogExample
LoggingCatalog / LoggingEmit
↓ C ABI
C++ N-API entry
↓ napi_create_string_utf8
ArkTS LoggingClient
↓ JSON.parse
ArkUI 日志页面
页面只消费 JSON,不复制 KLoggingEvent 的内部结构。这样 JVM 测试、Native 动态库和设备页面使用同一套示例记录和检查规则。
3.2 LogExample 和 LoggingExamples
公共示例门面位于 example/shared/src/commonMain/kotlin/io/github/oshai/kotlinlogging/example/LoggingExamples.kt:
public data class LogExample(
val level: String,
val logger: String,
val message: String,
)
public object LoggingExamples {
private val logger = KotlinLogging.logger("openharmony.example")
public fun catalog(): List<LogExample> = listOf(
LogExample("INFO", logger.name, "Hello from kotlin-logging"),
LogExample("WARN", logger.name, "OpenHarmony logging bridge is active"),
)
public fun emit(level: String): LogExample {
val normalized = level.uppercase()
val record = LogExample(normalized, logger.name, "OpenHarmony log: $normalized")
logger.info { record.message }
return record
}
}
LogExample 是跨边界的最小不可变记录。示例页只需要级别、logger 名称和消息,不把 Throwable、Map<String, Any?> 或平台 logger 对象暴露给 ArkTS。
3.3 Appender 探测和 JSON
共享门面用临时 Appender 验证 direct logger 的输出能力,探测完成后恢复原配置:
private fun appenderCheck(): Boolean {
val previous = KotlinLoggingConfiguration.direct.appender
var seen = false
KotlinLoggingConfiguration.direct.appender = object : Appender {
override fun log(loggingEvent: KLoggingEvent) {
seen = loggingEvent.message == "probe"
}
}
logger.info { "probe" }
KotlinLoggingConfiguration.direct.appender = previous
return seen
}
Native 边界把记录编码成稳定 JSON:
{
"level": "INFO",
"logger": "openharmony.example",
"message": "OpenHarmony log: INFO"
}
所有字符串经过转义,避免消息中的引号、反斜杠或换行破坏 JSON。ArkTS 端通过 JSON.parse 转为 LogExample,不会自行猜测日志级别或拼接 Native 指针。
3.4 自检和错误边界
LoggingExamples.runChecks() 覆盖四项检查:
- logger 可以创建且名称为
openharmony.example; - direct logger 的 INFO 级别已启用;
- 自定义
Appender能收到probe记录; - OpenHarmony 桥接使用不可变 JSON 记录。
Native 异常会转换为 {"error":"..."},C++ 在创建 ArkTS 字符串后立即调用 LoggingFree。这样页面能显示可读错误,同时不会泄漏 Kotlin/Native 堆内存。
第 4 阶段:原生桥接(技术难点)
4.1 Kotlin/Native 对象不能直接交给 ArkTS
Kotlin/Native 的 KLoggingEvent、LogExample 和 Appender 属于 Kotlin 运行时对象。ArkTS 只能接收 JavaScript 值,C++ 不能把 Kotlin 对象地址直接当成 JavaScript 对象。
最终采用三层桥接:
ArkTS
│ JSON string
▼
C++ N-API entry
│ const char*
▼
Kotlin/Native C ABI
│ LoggingExamples
▼
UTF-8 JSON + explicit free
4.2 方案对比
| 方案 | 优点 | 缺点 | 选用 |
|---|---|---|---|
| 直接导出 Kotlin 对象 | 代码少 | ABI、生命周期和类型不可控 | ❌ |
| 只导出日志级别 | 实现简单 | 页面会重新实现消息和记录结构 | ❌ |
| C ABI + JSON | 边界清晰、易扩展、易调试 | 有一次序列化开销 | ✅ |
| 在 ArkTS 重写完整 logger | 页面调用简单 | KMP 和 ArkTS 日志逻辑容易分叉 | ❌ |
4.3 Kotlin/Native 导出函数
真实 Native bridge 位于 example/nativeApp/src/ohosArm64Main/kotlin/io/github/oshai/kotlinlogging/example/NativeBridge.kt,导出函数如下:
@CName("LoggingCatalog")
public fun catalogNative(): CPointer<ByteVar> = response {
LoggingExamples.catalog().joinToString("[", "]") { it.json() }
}
@CName("LoggingEmit")
public fun emitNative(level: CPointer<ByteVar>?): CPointer<ByteVar> = response {
LoggingExamples.emit(level?.toKString() ?: "info").json()
}
@CName("LoggingRunChecks")
public fun checksNative(): CPointer<ByteVar> = response {
"{\"passed\":true,\"checks\":[" +
LoggingExamples.runChecks().joinToString { quote(it) } + "]}"
}
@CName("LoggingFree")
public fun freeNative(pointer: CPointer<ByteVar>?) {
if (pointer != null) nativeHeap.free(pointer.rawValue)
}
返回值使用 Native heap 分配、以 0 结尾的 C 字符串。每一块返回缓冲区都由 LoggingFree 释放。
4.4 C++ N-API 方法分发
C++ 注册三个 ArkTS 方法:
napi_property_descriptor methods[] = {
{"getCatalog", nullptr, Catalog, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"emit", nullptr, Emit, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"runChecks", nullptr, Checks, nullptr, nullptr, nullptr,
napi_default, nullptr},
};
emit 读取一个字符串参数,默认使用 info;随后调用 LoggingEmit。目录和自检遵循“调用 Native、创建 ArkTS 字符串、释放 Native 缓冲区”的顺序。
CMake 将 Kotlin/Native 动态库作为 imported library;若动态库尚未准备,则使用仓库中的 fallback C++ 实现进行页面冒烟验证:
set(KOTLIN_LOGGING_SO
${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libkotlin_logging.so)
if(EXISTS ${KOTLIN_LOGGING_SO})
add_library(kotlin_logging SHARED IMPORTED)
set_target_properties(kotlin_logging PROPERTIES
IMPORTED_LOCATION ${KOTLIN_LOGGING_SO})
target_link_libraries(entry PRIVATE kotlin_logging)
else()
target_sources(entry PRIVATE kotlin_logging_fallback.cpp)
endif()
4.5 N-API 生命周期
ArkTS emit("info")
│
▼
读取参数并规范化 level
│
▼
LoggingEmit(level)
│
▼
napi_create_string_utf8(...)
│
▼
LoggingFree(nativeBuffer)
│
▼
return JS string
C++ 负责完成跨语言字符串转换和 Native 释放,ArkTS 页面不需要知道 Kotlin/Native 的堆实现。模块通过共享库构造函数自动注册,ArkTS 只需要导入 libentry.so。
第 5 阶段:ArkUI 页面封装
5.1 ArkTS 调用日志 N-API
ArkTS 客户端位于 example/ohosApp/entry/src/main/ets/logging/LoggingClient.ets:
import {
getCatalog as nativeCatalog,
emit as nativeEmit,
runChecks as nativeRunChecks,
} from 'libentry.so';
export interface LogExample {
level: string;
logger: string;
message: string;
}
export function catalog(): LogExample[] {
return JSON.parse(nativeCatalog()) as LogExample[];
}
export function emit(level: string): LogExample {
return JSON.parse(nativeEmit(level)) as LogExample;
}
ArkTS 不重新实现 logger,只负责把 JSON 解析为页面需要的类型。
5.2 权限声明
本示例只记录日志和显示本地 JSON,不调用传感器、相机、麦克风、网络或账户服务,因此不需要新增 requestPermissions。entry/src/main/module.json5 只声明 EntryAbility、页面和 phone/tablet 设备类型。
这一区别很重要:没有权限不代表没有配置要求,应用仍然需要正确的 bundle 名称、ARM64 ABI、签名 profile 和 DevEco SDK。遇到安装失败时,应先检查签名和设备架构,而不是添加与日志无关的权限。
5.3 ArkUI 页面状态
Index.ets 保存当前记录、目录记录和共享检查状态:
@State current: LogExample = {
level: 'INFO',
logger: 'openharmony.example',
message: 'Loading...'
};
@State records: LogExample[] = [];
@State status: string = 'Checking...';
aboutToAppear() {
this.records = catalog();
this.current = emit('info');
const checks = runChecks();
this.status = checks.passed
? `${checks.checks.length}/${checks.checks.length} checks passed`
: 'checks failed';
}
页面不会保存 Native 指针,也不会把 Kotlin Appender 暴露给 ArkTS;组件退出后没有需要继续运行的系统订阅。
5.4 页面交互预设
页面提供两个日志操作按钮:
- 输出 INFO:调用
emit('info'),更新当前记录并通过 Kotlin logger 记录 INFO; - 输出 WARN:调用
emit('warn'),更新当前记录并返回 WARN 记录; - 日志记录示例:展示
getCatalog()返回的固定 JSON 数组; - 共享检查:展示
4/4 checks passed,确认 logger、级别、Appender 和桥接记录均正常。
这些操作不依赖硬件传感器,即使运行在鸿蒙 PC 上也能验证完整的 KMP/Native/N-API/UI 链路。
第 6 阶段:示例与验证
6.1 example 工程结构
example/
├── shared/
│ ├── src/commonMain/.../LoggingExamples.kt
│ └── src/commonTest/.../LoggingExamplesTest.kt
├── nativeApp/
│ ├── src/ohosArm64Main/.../NativeBridge.kt
│ └── src/ohosArm64Main/linker/shared-library.map
└── ohosApp/
├── AppScope/
├── entry/src/main/cpp/
│ ├── CMakeLists.txt
│ ├── include/libkotlin_logging_api.h
│ └── napi_init.cpp
└── entry/src/main/ets/
├── logging/LoggingClient.ets
└── pages/Index.ets
shared 验证公共 logger,nativeApp 产生 Native 动态库,ohosApp 负责 ArkUI 页面和 N-API。三者边界清晰,任何一层失败都能单独定位。
6.2 原生模块注册
napi_init.cpp 通过 napi_module_register 注册 entry 模块,类型声明位于:
example/ohosApp/entry/src/main/cpp/types/libentry/index.d.ts
ArkTS 使用:
import { catalog, emit, runChecks } from '../logging/LoggingClient';
LoggingClient.ets 再从 libentry.so 导入 getCatalog、emit 和 runChecks,完成 JSON 类型解析。
6.3 Native 动态库准备
执行:
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
脚本依次执行根模块测试、示例测试、linkDebugSharedOhosArm64 和 prepareOhos。成功后生成:
example/ohosApp/entry/libs/arm64-v8a/libkotlin_logging.so
example/ohosApp/entry/src/main/cpp/include/libkotlin_logging_api.h
还可以检查 ARM64 ELF 的强依赖:
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
预期输出为 libkotlin_logging.so: 0 unresolved strong imports。
6.4 构建、签名和安装
未配置签名时,可以在 DevEco Studio 构建未签名 HAP;鸿蒙 PC 安装需要签名 HAP。配置签名后,使用仓库外的签名工程执行:
python3 scripts/prepare-signing-project.py /absolute/path/kotlin-logging-signing
DEVECO_HOME=/Applications/DevEco-Studio.app/Contents \
scripts/build-hap.sh /absolute/path/kotlin-logging-signing
产物位于:
/absolute/path/kotlin-logging-signing/entry/build/default/outputs/default/entry-default-signed.hap
安装并启动:
hdc list targets
hdc -t <设备序列号> install -r \
/absolute/path/kotlin-logging-signing/entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
-a EntryAbility -b io.github.oshai.kotlinlogging.example
签名证书、profile、p12 和密码只保存在本机,不要提交到 AtomGit。
四、完整代码对照
4.1 整体架构
KotlinLogging.logger / Appender
│ LoggingExamples
▼
LoggingCatalog / LoggingEmit / LoggingRunChecks
│ C ABI
▼
libkotlin_logging.so
│ N-API imported library
▼
libentry.so -> LoggingClient.ets
│ JSON.parse
▼
Index.ets ArkUI 日志页面
4.2 文件清单
| 文件 | 职责 |
|---|---|
src/commonMain/.../KotlinLogging.kt | 公共 logger 入口 |
src/commonMain/.../Appender.kt | 自定义 appender 接口 |
src/commonMain/.../KLoggingEvent.kt | 不可变日志事件模型 |
example/shared/.../LoggingExamples.kt | 示例记录、logger 和四项自检 |
example/nativeApp/.../NativeBridge.kt | C ABI、JSON 返回和内存释放 |
example/ohosApp/.../LoggingClient.ets | N-API JSON 解析和类型封装 |
example/ohosApp/.../Index.ets | 鸿蒙 PC/真机日志页面 |
example/ohosApp/entry/src/main/cpp/napi_init.cpp | N-API 导出和字符串转换 |
scripts/build-openharmony.sh | 测试、Native 链接和产物复制 |
scripts/build-hap.sh | 已配置签名工程的 HAP 构建 |
docs/openharmony/VALIDATION.md | 自动检查和真机验收记录 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| Kotlin | KotlinLogging.logger(name) | 创建命名 logger |
| Kotlin | logger.info { message } | 懒执行 INFO 日志 |
| Kotlin | KotlinLoggingConfiguration.direct.appender | 配置 direct appender |
| Kotlin | LoggingExamples.catalog() | 返回固定示例记录 |
| Kotlin | LoggingExamples.emit(level) | 记录并返回一条日志 |
| Kotlin | LoggingExamples.runChecks() | 执行共享自检 |
| Native | LoggingCatalog | 返回目录 JSON |
| Native | LoggingEmit | 返回 INFO/WARN JSON |
| N-API | getCatalog / emit / runChecks | 向 ArkTS 暴露 Native 方法 |
| ArkTS | catalog / emit / runChecks | 解析 JSON 并更新页面 |
4.4 ArkTS 与 Kotlin 的边界
ArkTS 只负责按钮和页面状态:
this.current = emit('warn');
Kotlin 负责日志语义和不可变数据:
val record = LogExample(normalized, logger.name, "OpenHarmony log: $normalized")
logger.info { record.message }
return record
两者之间只传输字符串和 JSON,不传输 Kotlin 对象、ArkTS class 实例或未校验的动态结构。
五、关键决策说明
决策 1:把 ohosArm64 加入公共构建约定
只有真正链接 ohosArm64 动态库,才能证明共享 Kotlin logger 和 Native appender 可以进入 OpenHarmony 运行时。
决策 2:独立消费者必须通过构建产物消费
example 单独验证共享门面,ohosApp 单独接收 .so 和头文件,避免根工程编译通过却无法在 DevEco 中打包。
决策 3:JSON 作为跨语言数据契约
JSON 让 ArkTS、C++ 和 Kotlin/Native 的边界清楚可调试,并允许后续增加日志字段而不暴露内部对象布局。
决策 4:桥接层只开放四个 C ABI 入口
目录、日志输出、自检和释放已经覆盖示例所需能力;减少 ABI 符号可以降低 Native 生命周期和兼容风险。
决策 5:日志目录和实时输出分开
固定目录用于验证数组解析和稳定记录,INFO/WARN 操作用于验证真实 logger 调用。两者分开后,可以独立定位 JSON、Native 和页面状态问题。
决策 6:把库验证和设备验证分开
JVM 测试验证 logger 和 Appender,Native 链接验证 ABI,Hvigor 验证 HAP,鸿蒙 PC 验证安装、Ability 和 ArkUI 页面。每一层都有明确的失败边界。
六、测试与验证
6.1 测试环境
本次设备验证使用:
- macOS 宿主机;
- JDK 21 作为 Gradle/Kotlin 构建要求;
- Kotlin Multiplatform
2.2.21; - DevEco Studio 及 OpenHarmony ARM64 Native SDK;
- 已签名
entry-default-signed.hap; - USB 连接的鸿蒙 PC;
hdc设备序列号3QC0124C20001268;- HarmonyOS PC 系统版本
7.0.0(26.0.0)。
6.2 静态检查与单元测试
./gradlew :jvmTest ohosArm64Test
(cd example && ../gradlew :shared:jvmTest)
测试覆盖 logger 创建、INFO 级别启用、自定义 Appender 投递、记录内容和四项共享自检。由于 Kotlin/Gradle 要求 JDK 21,执行前应确认 java -version 不是 JBR 25 或其他不兼容版本。
6.3 原生桥接和 HAP 验证
./scripts/build-openharmony.sh
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
验证重点:
libkotlin_logging.so: 0 unresolved strong imports
Hvigor BUILD SUCCESSFUL
entry-default-signed.hap generated
DevEco 构建还会检查 CMake、ArkTS 类型声明、N-API 模块注册和 signingConfig 是否一致。
6.4 功能验证用例
用例 1:默认页面和自检
启动应用后页面显示 kotlin-logging · OpenHarmony、当前 logger、最近 INFO 日志和 4/4 checks passed。初始页面记录来自 Native LoggingCatalog 与 LoggingEmit("info")。
用例 2:固定日志目录
确认页面显示两行记录:Hello from kotlin-logging 和 OpenHarmony logging bridge is active,说明数组 JSON 已从 Native 传入 ArkTS 并完成解析。
用例 3:输出 INFO
点击“输出 INFO”,页面最近日志更新为 INFO · OpenHarmony log: INFO,同时 Kotlin logger 经过 direct appender 输出记录。
用例 4:输出 WARN
点击“输出 WARN”,页面最近日志更新为 WARN · OpenHarmony log: WARN。重复点击应持续返回合法 JSON,不产生 Native 崩溃或乱码。
用例 5:重复操作和页面重启
反复点击 INFO/WARN,停止并重新启动 Ability,确认页面可以重新读取目录和自检。由于示例没有系统事件订阅,不存在 motion.off 或重复回调问题;每次 N-API 调用只在本次调用内分配和释放字符串。
用例 6:不完整 Native 产物的错误处理
如果未准备真实 .so,CMake 会使用 fallback bridge,页面仍可冒烟验证;正式验收前必须运行 prepareOhos 并检查真实 libkotlin_logging.so 的依赖。若 Kotlin 侧发生异常,页面接收 {"error":"..."} 形式的错误 JSON。
6.5 验证结论
已完成 DevEco HAP 构建、签名安装和鸿蒙 PC 运行验证。设备成功启动 EntryAbility,页面显示日志目录、INFO/WARN 操作和 4/4 checks passed,截图中的真实效果见第七章。Gradle 自动测试应在 JDK 21 环境中执行;当前工作机若使用 JBR 25,会在 Kotlin 编译器解析 Java 版本时失败。
七、运行效果
7.1 真机截图

截图中可以看到:
- 页面标题为
kotlin-logging · OpenHarmony; - 副标题说明 Kotlin 多平台日志库、级别判断、懒消息和自定义 Appender;
- 当前 logger 为
openharmony.example; - 最近日志为 INFO;
- 页面提供“输出 INFO”和“输出 WARN”按钮;
- 日志记录示例展示 INFO/WARN 两条记录;
- 共享检查显示
4/4 checks passed。
7.2 命令速查
# 根工程测试和 OpenHarmony Native 准备
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
# 单独运行共享测试
(cd example && ../gradlew :shared:jvmTest)
# 设备依赖检查
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
# 在已配置签名的工程中构建 HAP
DEVECO_HOME=/Applications/DevEco-Studio.app/Contents \
scripts/build-hap.sh /absolute/path/kotlin-logging-signing
# 安装和启动
hdc -t <设备序列号> install -r \
/absolute/path/kotlin-logging-signing/entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
-a EntryAbility -b io.github.oshai.kotlinlogging.example
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配:必须把共享 logger 和 Native 实现真正编译成
ohosArm64动态库。 - N-API 不负责业务判断:C++ 只做参数读取、字符串转换和释放,日志语义放在 Kotlin。
- Appender 探测必须恢复旧配置:临时自检结束后恢复
KotlinLoggingConfiguration.direct.appender,避免影响宿主其他日志。 - 无权限不等于无配置:日志示例没有额外权限,但签名、bundle、ABI 和 SDK 仍必须匹配。
- 签名配置需要绑定产品:
products[].signingConfig必须指向signingConfigs,否则 Hvigor 会继续生成未签名 HAP。
8.2 已知问题
- Kotlin/Native 动态库必须按 ARM64 目标重新生成,不能把 JVM JAR 或其他架构
.so放入 HAP; - 页面当前展示日志 JSON 和 direct logger 结果,不提供持久化文件、远程上报或日志检索;
- 签名配置只适用于本地开发机,不能直接复制到其他环境;
- JDK 21 是当前 Gradle/Kotlin 构建要求,使用 JBR 25 可能触发 Kotlin 编译器版本解析错误;
- C++ fallback 只用于页面冒烟验证,生产验收仍应确认真实 Kotlin/Native
.so已进入构建。
8.3 未来优化方向
- 增加结构化日志 payload 的跨语言字段;
- 将日志记录封装为
Flow<LogExample>,提供持续日志流示例; - 为 Compose Multiplatform 页面提供统一的日志卡片和筛选示例;
- 增加 Native 日志级别配置和设备日志导出;
- 在持续集成中加入
ohosArm64链接、Native 依赖检查和 HAP 未签名构建任务。
九、总结
9.1 核心难点回顾
本次适配真正需要处理的不是一个 ArkTS 文本组件,而是一条完整跨端链路:
KotlinLogging logger / Appender
→ LoggingExamples
→ Kotlin/Native C ABI
→ C++ N-API
→ ArkTS JSON client
→ ArkUI 鸿蒙 PC 页面
9.2 封装层次
- KMP 层:定义 logger、日志级别、Appender 和不可变事件;
- 示例层:提供稳定的
LogExample、目录、自检和可观察消息; - Native 层:生成 ARM64 动态库,并通过 C ABI 输出有限入口;
- N-API 层:完成字符串转换、参数读取和内存释放;
- ArkTS 层:管理 JSON 解析、按钮交互和页面展示;
- DevEco 层:完成 CMake、HAP、签名、安装和运行。
9.3 三条经验
- 先让共享 logger 在 JVM 和 Native 通过,再接入 ArkUI;
- 用 JSON 和少量 C ABI 代替跨语言对象传递;
- 把自动测试、HAP 构建和鸿蒙 PC 运行分别记录,避免把“编译成功”误认为“设备页面可用”。
9.4 适配成果
当前 kotlin-logging 已完成:
ohosArm64Kotlin/Native 目标;KotlinLogging、懒消息、级别判断和Appender在共享示例中的验证;- Kotlin/Native + C ABI + C++ N-API 桥接;
LoggingCatalog、LoggingEmit、LoggingRunChecks和LoggingFree四个导出入口;- ArkTS INFO/WARN 日志页面和
4/4 checks passed自检; - 签名 HAP 构建、鸿蒙 PC 安装和运行效果图;
- 与参考 CMP 工程一致的模块和脚本组织;
- AtomGit 项目文档和 OpenHarmony 验收记录。
参考文档
更多推荐




所有评论(0)