本文介绍如何实现设备日志上云(阿里云物联网平台)能力的开发。

背景描述

物联网平台提供设备日志上云能力, 用于将设备端的日志通过MQTT协议发送物联网平台, 并在控制台日志服务页面呈现给用户。

  • 设备上云的上报通道默认为关闭状态, 用户可以通过控制台打开上报通道。
  • 设备每次上线后都会自动获取上报通道的开关配置, 在完成配置同步前上报通道处于关闭状态。
  • 日志上云模块依赖MQTT通道, 设备调用aiot_logpost_send向云端上报消息太过频繁时, 会被云端限流, 云端限流发生时, 设备端不会收到任何来自网络的通知, 只能人工在控制台页面查看。
说明 请查看云平台产品限制页面, 实时了解平台对日志上云当前限制的具体数值。

数据结构

SDK中的logpost功能将日志信息以结构体描述。

  • 所有日志消息的传递, 都以用户调用aiot_logpost_send()接口, 从设备发往云平台。
  • 所有日志消息的内容, 都以 aiot_logpost_msg_t 结构体存放, 它是aiot_logpost_send()接口的入参。
  • 所有日志配置的传递, 都以用户调用aiot_mqtt_recv()接口, 进而触发用户注册的 aiot_logpost_event_handler_t 回调, 到达用户侧。
  • 日志配置信息的内容, 都以 aiot_logpost_event_t 结构体存放, 它是上述回调函数的入参, 目前只包含日志开关状态。

SDK中的设备日志上云模块不维护网络会话, 网络连接和数据收发由它所依赖MQTT模块提供。

因此, 用户在创建logpost实例后, 应正确地为它配置MQTT实例句柄, 在进行日志消息发送前, 也应确保MQTT已经建连成功。

API列表

调用时请包含文件components/logpost/aiot_logpost_api.h。

接口名 说明
aiot_logpost_init 初始化logpost会话实例。
aiot_logpost_setopt 设置logpost会话参数, 点击日志上云详细设置参数了解所有参数。
aiot_logpost_send 发送一条logpost消息到物联网平台, 消息内容由msg参数决定。
aiot_logpost_deinit 销毁logpost实例, 释放资源。

API使用概述

  • 在使用日志上云模块前, 用户应首先创建好一个MQTT实例。
  • 调用aiot_logpost_init创建一个日志上云实例, 保存实例句柄。
  • 调用aiot_logpost_setopt配置AIOT_LOGPOSTOPT_MQTT_HANDLE选项以设置MQTT句柄, 此选项为强制配置选项。
  • 调用aiot_logpost_setopt配置AIOT_LOGPOSTOPT_EVENT_HANDLERAIOT_LOGPOSTOPT_USER_DATA选项以注册事件接收回调函数和用户上下文数据指针。
  • 在使用aiot_logpost_send发送日志消息前, 应先完成MQTT实例的建连。

日志信息发送

以下代码演示了如何用aiot_logpost_send发送日志信息到云端, 其中msg是包含多个字段的日志消息结构体。

/* 上报日志到云端 */
void demo_send_log(void *handle, char *log)
{
    int32_t res = 0;
    aiot_logpost_msg_t msg;
    memset(&msg, 0, sizeof(aiot_logpost_msg_t));
    msg.timestamp = 0;                          /* 单位为ms的时间戳, 填写0则SDK将使用当前的时间戳 */
    msg.loglevel = AIOT_LOGPOST_LEVEL_DEBUG;    /* 日志级别 */
    msg.module_name = "APP";                    /* 日志对应的模块 */
    msg.code = 200;                             /* 状态码 */
    msg.msg_id = 0;                             /* 云端下行报文的消息标示符, 若无对应消息可直接填0 */
    msg.content = log;                          /* 日志内容 */
    res = aiot_logpost_send(handle, &msg);
    if (res < 0) {
        printf("aiot_logpost_send failed: -0x%04X\r\n", -res);
    }
}
/* 调用demo函数完成上报 */
demo_send_log(logpost_handle, "log in while(1)");

日志配置信息事件处理

当设备在线时,若用户在控制台对应的设备页面打开或关闭日志开关按钮, 设备会接受此事件;或者设备上线功能后日志模块主动进行配置同步时也会触发此事件。

/* 事件处理回调, 用户可通过此回调获取日志上报通道的开关状态 */
void demo_logpost_event_handler(void *handle, const aiot_logpost_event_t *event, void *userdata)
{
    switch (event->type) {
        /* 日志配置事件, 当设备连云成功或者用户在控制台页面控制日志开关时会收到此事件 */
        case AIOT_LOGPOSTEVT_CONFIG_DATA: {
            printf("log switch state is: %d\r\n", event->data.config_data.on_off);
        }
        default:
            break;
    }
}

例程讲解

现对照demos/logpost_basic_demo.c例程, 分步骤讲解如何使用API。

这个例程适用于Linux这类支持pthread的POSIX设备, 它演示了用SDK配置MQTT参数并建立连接, 之后创建2个线程

说明
  • 一个线程用于保活长连接。
  • 一个线程用于接收消息, 并在有消息到达时进入默认的数据回调, 在连接状态变化时进入事件回调。

接着演示了在MQTT连接上进行日志上云更新, 删除, 拉取等操作, 取消这些代码段落的注释即可观察运行效果。

需要用户关注或修改的部分, 已经用 TODO 在注释中标明。

  1. 设置设备证书

    例程使用的设备证书是公用的, 所以应用于实际业务时, 请替换如下的TODO部分, 传入用户自己真实的设备证书。

    /* TODO: 替换为自己设备的设备证书 */
        char *product_key       = "a18wPXXXXXX";
        char *device_name       = "logpost_basic_demo";
        char *device_secret     = "yPzQqkybjFHOx76tUlfDYJSZ0NXXXXXX";
  2. 进入程序入口, 给SDK配置全局的底层依赖和内部日志回调

    底层依赖描述了硬件平台的资源使用方式, 比如怎样获取时钟, 分配内存等, 日志回调是用户的函数, SDK有log输出的时候会进入这个函数。

    int main(int argc, char *argv[])
    {
        ...
        ...
        /* 配置SDK的底层依赖 */
        aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
        /* 配置SDK的日志输出 */
        aiot_state_set_logcb(demo_state_logcb);
  3. 给MQTT会话配置参数

    这些连接参数包括如何建立TLS连接、服务器地址、连接后的数据及事件回调函数, 然后用 aiot_mqtt_connect() 建立连接。

    /* 创建1个MQTT客户端实例并内部初始化默认参数 */
        mqtt_handle = aiot_mqtt_init();
        if (mqtt_handle == NULL) {
            printf("aiot_mqtt_init failed\n");
            return -1;
        }
        snprintf(host, 100, "%s.%s", product_key, url);
        /* 配置MQTT服务器地址 */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)host);
        /* 配置MQTT服务器端口 */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
        /* 配置设备productKey */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
        /* 配置设备deviceName */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
        /* 配置设备deviceSecret */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
        /* 配置网络连接的安全凭据, 上面已经创建好了 */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
        /* 配置MQTT事件回调函数 */
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
  4. 创建Logpost实例并建立MQTT连接

    日志上云会话创建后, 可通过其会话句柄组装和解析标准的logpost报文, 这些报文依赖MQTT模块发送和接收, 因此要关联日志上云的会话和MQTT的会话, 并建立MQTT连接。

    /* 创建日志上云实例 */
        void *logpost_handle = aiot_logpost_init();
        if (logpost_handle == NULL) {
            printf("aiot_logpost_init failed");
            return -1;
        }
        /* 配置MQTT实例句柄 */
        aiot_logpost_setopt(logpost_handle, AIOT_LOGPOSTOPT_MQTT_HANDLE, mqtt_handle);
        /* 配置事件处理回调函数 */
        aiot_logpost_setopt(logpost_handle, AIOT_LOGPOSTOPT_EVENT_HANDLER, (void *)aiot_logpost_event_handler);
        /* 与服务器建立MQTT连接 */
        res = aiot_mqtt_connect(mqtt_handle);
  5. 日志发送

    请查看API使用概述部分了解如何发送日志。

    在运行例程前, 用于应到物联网平台的设备管理>设备详情页面打开日志开关, 详情请参见下图:

    服务开关
  6. 全链路日志

    SDK支持在上行消息中加入用于唯一标识每条消息的request_id, 用户可以很方便的通过本地日志获取到request_id, 并通过云端控制的日志服务查询到request_id对应的云端日志TraceId, 从而实现本地日志与云端日志的串联。在MQTT配置阶段增加以下代码即可以使能消息携带request_id功能。

    uint8_t append_reqid = 1;
        aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_APPEND_REQUESTID, (void *)&append_reqid);

    使能功能后, 我们可以在本地日志看到在上行消息的topic后面附加了?_rid=15875244498803775192393字符串。

    [1587524449.880][LK-0309] pub: /sys/a18wPXXXXXX/logpost_basic_demo/thing/config/log/get?_rid=15875244498803775192393
    [LK-030A] > 7B 22 69 64 22 3A 22 31  22 2C 22 76 65 72 73 69 | {"id":"1","versi
    [LK-030A] > 6F 6E 22 3A 22 31 2E 30  22 2C 22 70 61 72 61 6D | on":"1.0","param
    [LK-030A] > 73 22 3A 7B 22 67 65 74  54 79 70 65 22 3A 22 63 | s":{"getType":"c
    [LK-030A] > 6F 6E 74 65 6E 74 22 2C  22 63 6F 6E 66 69 67 53 | ontent","configS
    [LK-030A] > 63 6F 70 65 22 3A 22 64  65 76 69 63 65 22 7D 7D | cope":"device"}}

    如下图所示, 假设本地日志上行消息中附加?_rid的值为1234, 那么通过查询1234就能找到对应消息在云端日志中的TraceId,详情请参见下图:

    日志服务记录
  7. 观察本地日志

    以上各种和云端的交互代码都被注释掉, 用户取消注释后, 重新编译运行例程, 即可看到对日志上云的上报过程。

    [1584338567.757][LK-0313] MQTT user calls aiot_mqtt_connect api, connect
    [1584338567.757][LK-0317] aiot_logpost_test&a14QGrpsFto
    [1584338567.757][LK-0318] 12356738FF106B214FEDC32ED4D1EB1BEA6113C8494111FAB2E8BD60E39F8408
    [1584338567.757][LK-0319] a14QGrpsFto.aiot_logpost_test|timestamp=2524608000000,_ss=1,_v=sdk-c-4.0.0,securemode=2,signmethod=hmacsha256,ext=1,|
    establish mbedtls connection with server(host='iot-test-daily.iot-as-mqtt.unify.aliyuncs.com', port=[1883])
    success to establish tcp, fd=3
    success to establish mbedtls connection, fd = 3(cost 20187 bytes in total, max used 23099 bytes)
    [1584338568.319][LK-0313] MQTT connect success in 562 ms
    AIOT_MQTTEVT_CONNECT
    [1584338568.320][LK-0309] pub: /sys/a14QGrpsFto/aiot_logpost_test/thing/config/log/get
    [LK-030A] > 7B 22 69 64 22 3A 22 31  22 2C 22 76 65 72 73 69 | {"id":"1","versi
    [LK-030A] > 6F 6E 22 3A 22 31 2E 30  22 2C 22 70 61 72 61 6D | on":"1.0","param
    [LK-030A] > 73 22 3A 7B 22 67 65 74  54 79 70 65 22 3A 22 63 | s":{"getType":"c
    [LK-030A] > 6F 6E 74 65 6E 74 22 2C  22 63 6F 6E 66 69 67 53 | ontent","configS
    [LK-030A] > 63 6F 70 65 22 3A 22 64  65 76 69 63 65 22 7D 7D | cope":"device"}}
    aiot_logpost_send failed: -0x1502
    heartbeat response
    [1584338568.787][LK-0309] pub: /sys/a14QGrpsFto/aiot_logpost_test/thing/config/log/get_reply
    [LK-030A] < 7B 22 63 6F 64 65 22 3A  32 30 30 2C 22 64 61 74 | {"code":200,"dat
    [LK-030A] < 61 22 3A 7B 22 63 6F 6E  74 65 6E 74 22 3A 7B 22 | a":{"content":{"
    [LK-030A] < 6D 6F 64 65 22 3A 31 7D  2C 22 67 65 74 54 79 70 | mode":1},"getTyp
    [LK-030A] < 65 22 3A 22 63 6F 6E 74  65 6E 74 22 7D 2C 22 69 | e":"content"},"i
    [LK-030A] < 64 22 3A 22 31 22 2C 22  6D 65 74 68 6F 64 22 3A | d":"1","method":
    [LK-030A] < 22 74 68 69 6E 67 2E 63  6F 6E 66 69 67 2E 6C 6F | "thing.config.lo
    [LK-030A] < 67 2E 67 65 74 22 2C 22  76 65 72 73 69 6F 6E 22 | g.get","version"
    [LK-030A] < 3A 22 31 2E 30 22 7D                             | :"1.0"}
    [1584338568.787][LK-1503] LOGPOST log config arrived
    [1584338578.324][LK-0309] pub: /sys/a14QGrpsFto/aiot_logpost_test/thing/log/post
    [LK-030A] > 7B 22 69 64 22 3A 22 32  22 2C 22 76 65 72 73 69 | {"id":"2","versi
    [LK-030A] > 6F 6E 22 3A 22 31 2E 30  22 2C 22 70 61 72 61 6D | on":"1.0","param
    [LK-030A] > 73 22 3A 5B 7B 22 75 74  63 54 69 6D 65 22 3A 22 | s":[{"utcTime":"
    [LK-030A] > 31 32 33 34 35 36 37 38  39 22 2C 22 6C 6F 67 4C | 123456789","logL
    [LK-030A] > 65 76 65 6C 22 3A 22 44  45 42 55 47 22 2C 22 6D | evel":"DEBUG","m
    [LK-030A] > 6F 64 75 6C 65 22 3A 22  41 50 50 22 2C 22 63 6F | odule":"APP","co
    [LK-030A] > 64 65 22 3A 22 2D 32 30  34 38 22 2C 22 74 72 61 | de":"-2048","tra
    [LK-030A] > 63 65 43 6F 6E 74 65 78  74 22 3A 22 39 38 37 36 | ceContext":"9876
    [LK-030A] > 35 34 33 32 31 22 2C 22  6C 6F 67 43 6F 6E 74 65 | 54321","logConte
    [LK-030A] > 6E 74 22 3A 22 74 68 69  73 20 69 73 20 61 20 6C | nt":"this is a l
    [LK-030A] > 6F 67 20 66 72 6F 6D 20  64 65 6D 6F 21 22 7D 5D | og from demo!"}]
    [LK-030A] > 7D                                               | }

    可以看到设备通过MQTT通道上报日志到云端,打开控制台的日志查询页面的设备本地日志选项卡,可以查看到设备上报的对应日志信息。

    设备本地日志