如何获取应用打印的日志并写入文件
================

#### 问题现象

使用HiLog打印默认是打印在控制台上，如何获取应用打印的日志并写入文件？

#### 背景知识

*   [使用HiLog打印日志](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hilog-guidelines-ndk)：HiLog中定义了DEBUG、INFO、WARN、ERROR、FATAL五种日志级别，并提供了对应的方法输出不同级别的日志，具体说明可查阅[API参考文档](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-log-h)。
*   [使用Node-API实现跨语言交互开发流程](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/use-napi-process)：
    *   ArkTS/JS侧：实现C++方法的调用。import一个对应的so库后，即可调用C++方法。
    *   Native侧：.cpp文件，实现模块的注册。需要提供注册lib库的名称，并在注册回调方法中定义接口的映射关系，即Native方法及对应的JS/ArkTS接口名称等。
*   [OH\_LOG\_SetCallback](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-log-h#oh_log_setcallback)：调用此函数后，用户实现的回调函数可以接收当前进程的所有HiLog日志。回调函数里不允许再调用HiLog接口打印日志会死循环。

#### 解决方案

在ArkTS侧将沙箱中的路径传递给Native侧，通过OpenFile方法打开文件，然后调用MyHiLog方法把日志写入到文件中。

*   index.ets：在ArkTS侧获取到沙箱路径，调用testNapi.init方法将沙箱中的路径传递给Native侧。
    
    import { hilog } from '@kit.PerformanceAnalysisKit';
    import { common } from '@kit.AbilityKit';
    import testNapi from 'libentry.so';
    
    const DOMAIN \= 0x0000;
    
    @Entry
    @Component
    struct Index {
    
      aboutToAppear(): void {
        let context \= this.getUIContext().getHostContext() as common.UIAbilityContext;
        let path: string \= context.filesDir;
        testNapi.init(path);
      }
    
      build() {
        Row() {
          Column() {
            Button('test')
              .fontSize($r('app.float.page\_text\_font\_size'))
              .fontWeight(FontWeight.Bold)
              .onClick(() \=\> {
                hilog.info(DOMAIN, 'testTag', 'Test NAPI 2 + 3 = %{public}d', testNapi.add(2, 3));
              })
          }
          .width('100%')
        }
        .height('100%')
      }
    }
    
*   Index.d.ts：init接口声明。
    
    export const add: (a: number, b: number) \=\> number;
    export const init: (path: string) \=\> number;
    
*   CMakeLists.txt：链接libhilog\_ndk.z.so依赖。
    
    _\# the minimum version of CMake._
    cmake\_minimum\_required(VERSION 3.5.0)
    project(HilogToFile)
    set(NATIVERENDER\_ROOT\_PATH ${CMAKE\_CURRENT\_SOURCE\_DIR})
    if(DEFINED PACKAGE\_FIND\_FILE)
        include(${PACKAGE\_FIND\_FILE})
    endif()
    include\_directories(${NATIVERENDER\_ROOT\_PATH}
                        ${NATIVERENDER\_ROOT\_PATH}/include)
    add\_library(entry SHARED napi\_init.cpp)
    target\_link\_libraries(entry PUBLIC libace\_napi.z.so libhilog\_ndk.z.so )
    
*   napi\_init.cpp：添加init函数Napi框架。
    
    static napi\_value Init(napi\_env env, napi\_value exports)
    {
        napi\_property\_descriptor desc\[\] \= {
            {"add", nullptr, Add, nullptr, nullptr, nullptr, napi\_default, nullptr},
            {"init", nullptr, Init, nullptr, nullptr, nullptr, napi\_default, nullptr}};
        napi\_define\_properties(env, exports, sizeof(desc) / sizeof(desc\[0\]), desc);
        return exports;
    }
    
*   init函数：Native层从ArkTS层获取到的日志路径存放到static变量g\_path中，作为日志全局的路径。设置g\_openState来防止重复初始化，通过函数OpenFile来打开/创建日志文件。如果正常打开文件则使用OH\_LOG\_SetCallback来注册日志落盘函数。否则返回异常，ArkTS通过返回值来判断是否初始化成功。
    
    static napi\_value Init(napi\_env env, napi\_callback\_info info)
    {
        size\_t argc \= 1;
        napi\_value args\[1\] \= {nullptr};
        napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
        char path\[1024 \* 4\] \= {0};
        size\_t pathLen \= 0;
        napi\_get\_value\_string\_utf8(env, args\[0\], path, 1024 \* 4, &pathLen);
        int ret \= 0;
       _ // 1.设置打印文件路径&日志文件_
        std::string tmpPath(path);
        g\_path \= tmpPath;
        OH\_LOG\_INFO(LOG\_APP, "napi sum Init m\_path: %{public}s, %{public}d", g\_path.c\_str(), g\_path.size());
        if (g\_openState \== UNOPENED && g\_path != "" && (g\_file \= OpenFile("ab"))) {
            g\_openState \= OPEN;
         _   // 2.注册回调接口_
            OH\_LOG\_SetCallback(MyHiLog);
        } else {
            ret \= \-1;
            OH\_LOG\_INFO(LOG\_APP, "napi sum Init m\_path fail:%{public}d| %{public}s, %{public}d", g\_openState, g\_path.c\_str(),
                        g\_path.size());
        }
        napi\_value returnValue;
        napi\_create\_int32(env, ret, &returnValue);
        return returnValue;
    }
    
*   OpenFile函数：使用realpath函数来获取文件的绝对路径，并判断路径是否可用，如可用则使用fopen带参数"ab"来打开该文件，并获取到句柄存储到g\_file静态变量中。
    
    FILE \*OpenFile(const char \*pszMode)
    {
        char filePath\[PATH\_MAX\] \= {0};
        char \*ret \= realpath(g\_path.c\_str(), filePath);
        if (ret \== nullptr) {
            OH\_LOG\_INFO(LOG\_APP, "napi sum OpenFile path realpath fail!!, %{public}s, %{public}d", g\_path.c\_str(),
                        g\_path.size());
            return nullptr;
        }
        std::string fullFileName \= std::string(filePath) + "/test.log";
        OH\_LOG\_INFO(LOG\_APP, "napi sum OpenFile path is:%{public}s", fullFileName.c\_str());
        return std::fopen(fullFileName.c\_str(), pszMode);
    }
    
*   回调函数MyHiLog：判断文件句柄g\_file是否正常，正常则可以使用fwrite和fflush来写入到文件中。
    
    void MyHiLog(const LogType type, const LogLevel level, const unsigned int domain, const char \*tag, const char \*msg)
    {
        if (g\_file != nullptr) {
            std::string tmp(msg);
            tmp += '\\n';
            fwrite(tmp.c\_str(), sizeof(char), tmp.size(), g\_file);
            fflush(g\_file);
        }
    }
    
*   Add函数：ArkTS侧调用Add方法，Add方法内输出日志。
    
    static napi\_value Add(napi\_env env, napi\_callback\_info info)
    {
        size\_t argc \= 2;
        napi\_value args\[2\] \= {nullptr};
        napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
        napi\_valuetype valuetype0;
        napi\_typeof(env, args\[0\], &valuetype0);
        napi\_valuetype valuetype1;
        napi\_typeof(env, args\[1\], &valuetype1);
        double value0;
        napi\_get\_value\_double(env, args\[0\], &value0);
        double value1;
        napi\_get\_value\_double(env, args\[1\], &value1);
        napi\_value sum;
        napi\_create\_double(env, value0 + value1, &sum);
        OH\_LOG\_INFO(LOG\_APP, "napi sum is:%{public}d.", (int)(value0 + value1));
        
        return sum;
    }