1. 前言
安装使用C++ SDK可以帮助开发者快速接入使用天翼云的日志服务相关功能。
2. 使用条件
2.1. 先决条件
用户需要具备以下条件才能够使用LTS SDK C++版本:
1、购买并订阅了天翼云的云日志服务,并创建了日志项目和日志单元,获取到相应编码(logProject、logUnit)。
2、已获取AccessKey 和 SecretKey。
3、已安装c++ 运行环境,推荐安装c++11或以上版本。
2.2. 下载及安装
从官方渠道下载ctyun_lts_cpp_sdk.zip压缩包,放到相应位置后并解压。“ctyun_lts_cpp_sdk”目录中sample_putlogs.cpp为SDK的使用示例代码。
2.1.1. 依赖
cmake2.8或更新版本,GCC 4.9或更新版本。以下库及其相关头文件,可在对应Linux发行版的包管理器中安装,括号中给出的是经过验证的版本。
libcurl-devel   (7.6.1)
openssl-devel   (1.1.1k)
protobuf        (3.5.0)
lz4             (v1.8.3)
boost          (1.66.0)
对于 CentOS 安装,可用如下命令搭建依赖环境
yum install gcc gcc-c++    
yum install cmake
yum install libcurl-devel openssl-devel libuuid-devel 
yum install lz4-devel.x86_64
yum install protobuf-devel.x86_64
yum install boost-devel.x86_64
2.1.2. 编译使用
1、进入ctyun_lts_cpp_sdk目录下,里面有CMakelists.txt文件。
2、执行以下命令:
mkdir build
cd build
cmake .. -DCMAKE_INSTALL_PREFIX=..
make
make install
其中make 主要做的工作有:为.proto文件生成对应的.pb.cc、.pb.h,以及编译出静态库文件libltssdk.a。
3、将ctyun_lts_cpp_sdk的include目录下的头文件,全部移动到您项目的include目录下,(如果直接使用SDK 则可以不用移动)。
4、之后就可以在您的项目内使用SDK了,只需要引入对应的头文件即可。
5、编译您的程序,在您目录下内编译您的程序,构建出可执行文件。 如sample_putlogs.cpp:
g++ sample_putlogs.cpp -I./include -L./lib/ -lcurl -lcrypto -llz4 -lprotobuf -lltssdk
或者 根据主目录中的Makefile 文件。执行以下命令,也能实现上面的构建出可执行文件。
make sample_putlogs
6、执行可执行文件。
./sample_putlogs
3. SDK使用设置
3.1. 基本使用
使用 SDK访问 LTS 的服务,需要设置正确的 AccessKey、SecretKey 和服务端 Endpoint,所有的服务可以使用同一 key 凭证来进行访问,但不同的服务需要使用不同的 endpoint 进行访问,详情参考天翼云官网-SDK接入概述。在调用前SDK,需要已知以下参数:
1、云日志服务访问地址。详情请查看访问地址(Endpoint)。
2、key凭证:accessKey和secretKey 。详情请查看如何获取访问密钥(AK/SK)。
3、日志项目编码:logProject,在使用SDK前,需要确保您有至少一个已经存在的日志项目,日志项目就是您要将日志上传到的地方。
4、日志单元编码:logUnit,在使用SDK前,需要确保日志项目中有至少一个已经存在的日志单元。
| 参数 | 参数类型 | 描述 | 是否必须 | 
|---|---|---|---|
| endpoint | string | 域名 | 是 | 
| accessKey | string | AccessKey,简称ak | 是 | 
| secretKey | string | SecretKey ,简称sk | 是 | 
| logProject | string | 日志项目编码 | 是 | 
| logUnit | string | 日志单元编码 | 是 | 
目前通过SDK将日志上传到云日志服务有两种上传形式:同步上传和异步批量上传。
同步上传:当调用日志上传接口时,sdk会立即进行http请求调用,并返回发送结果。这种方式结构简单,可用于发送频率不高的场景。
异步批量上传:当调用日志上传接口时,后台线程会将日志进行累积,当达到发送条件时,会进行一次合并发送。对于需要频繁调用发送接口的场景,这种方式性能更卓越,更高效。
示例代码:同步上传
int main(int argc, char **argv) {
    string ak = "your accessKey";
    string sk = "your secretKey";
    string logProject = "log project Code";
    string logUnit = "log unit Code";
string endpoint = "endpoint";
vector<LogItem> logItems;
     int64_t logTimestamp = GetCurrentTimeStamp(3);
     string oriMessage = "c++ sdk test oriMessage";
     map<std::string, boost::any> contents;
     map<std::string, boost::any> labels;
     contents["level"] = string("error");
     contents["unit_id"] = 3.1415926;
     labels["user_tag"] = string("string");
     LogItem logItem;
     logItem.logTimestamp = logTimestamp;
     logItem.oriMessage = oriMessage;
     logItem.contents = contents;
     logItem.labels = labels;
     logItems.push_back(logItem);
  
    try {
        Client *client = new Client(endpoint, ak, sk,logProject);
        for (int i = 0; i < 100; i++) {    //发送了100次
            LogResponse logResponse = client->PutLogs(logProject, logUnit, logItems);
            cout << logResponse.statusCode << ":" << logResponse.message << ":" << logResponse.error << endl;
        }
    } catch (LOGException &e) {
        cout << e.GetExceptionErrorCode() << ":" << e.GetExceptionMessage() << endl;
}
}
示例代码:异步批量上传
int main(int argc, char **argv) {
    string ak = "your accessKey";
    string sk = "your secretKey";
    string logProject = "log project Code";
    string logUnit = "log unit Code";
string endpoint = "endpoint";
vector<LogItem> logItems;
     int64_t logTimestamp = GetCurrentTimeStamp(3);
     string oriMessage = "c++ sdk test oriMessage";
     map<std::string, boost::any> contents;
     map<std::string, boost::any> labels;
     contents["level"] = string("error");
     contents["unit_id"] = 3.1415926;
     labels["user_tag"] = string("string");
     LogItem logItem;
     logItem.logTimestamp = logTimestamp;
     logItem.oriMessage = oriMessage;
     logItem.contents = contents;
     logItem.labels = labels;
     logItems.push_back(logItem);
  
   try{
        ProducerConfig producerConfig = ProducerConfig();  //使用默认producer配置
        ClientConfig clientConfig(endpoint, ak, sk, logProject);
        Client *client =  createClient(clientConfig); 
        map<string, Client *> clientPool;    //可使用不同client配置,生成多个client
        clientPool.insert(make_pair(client->GetLogProject(), client));
        IoWorker ioWorker(clientPool, producerConfig.getMaxWorkerCount());    
        LogAccumulator logAccumulator(producerConfig, ioWorker);             
        Mover mover(logAccumulator, ioWorker, producerConfig.getLingerMs());  
        Producer producer(producerConfig, ioWorker, logAccumulator, mover);   
        producer.start();   
        for (int i=0;i<1000;i++){
            producer.sendLogsCallback(logProject,logUnit,logItems);
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(3000));
        producer.safeClose();
}catch (exception &e){
        cout<<e.what()<<endl;
   }}
4. 服务代码示例-同步上传
4.1. 关于Client的操作
4.1.1. New Client()
此操作是初始化Client。用户需要配置至少3个关键的参数才够初始化Client。初始化Client之后,其包含的配置信息如下:
| 参数 | 参数类型 | 描述 | 是否必须 | 
|---|---|---|---|
| endpoint | string | 域名 | 是 | 
| accessKey | string | AccessKey,简称ak | 是 | 
| secretKey | string | SecretKey ,简称sk | 是 | 
| logProject | string | 日志单元编码 | 是 | 
| userAgent | string | 使用的SDK信息标识 | 否 | 
| requestTimeout | int | http请求超时时间,默认30s | 否 | 
| compressType | int | 日志压缩算法,默认“lz4” | 否 | 
| securityToken | TokenInfo* | 获取的token信息 | 否 | 
| httpCurl | CURL* | 定义的CURL ,用于http请求 | 否 | 
示例代码:初始化Client配置
Client *client = new Client(endpoint, ak, sk,logProject);
//或者
ClientConfig clientConfig(endpoint, ak, sk, logProject);
Client *client =  createClient(clientConfig); 
4.2. 关于临时凭证Token的操作
4.2.1. SetTokenByApi()
此操作是为client注入token信息,这一步需要使用ak和sk信息换取临时凭证TokenInfo,其中包含了token随机串和过期时间两个参数。这一步需要去访问CTIAM的api接口,调用api接口,传入ak/sk/endpoint信息,返回token信息。
TokenInfo信息如下:
| 参数 | 类型 | 描述 | 
|---|---|---|
| token | string | token 随机串 | 
| expireTime | int64 | 过期时间,默认30分钟 | 
获取TokenInfo这一步在Client 初始化时会自动调用。用户默认可以不用进行这一步操作。
示例代码:为client注入Token信息
this->SetTokenByApi();
4.3. 关于Log的操作
4.3.1. logItems.push_back(logItem)
此操作用于生成待上传的日志,日志上传只能上传LogItem格式的日志,logItems是一个vector类型,里面包含若干条LogItem日志,格式如下:
| 参数 | 类型 | 描述 | 是否必须 | 
|---|---|---|---|
| logItems | vector | LogItem格式的vector数组,将多份日志组合起来发送 | 是 | 
LogItem类型的日志格式如下如下。
| 参数 | 类型 | 描述 | 是否必须 | 
|---|---|---|---|
| logTimestamp | int | 时间戳,单位纳秒 | 是 | 
| originMesssage | string | 原始日志内容 | 是 | 
| contents | map<string,any> | 日志内容,分词后的内容,可用于索引 | 否 | 
| labels | map<string,any> | 自定义标签 | 否 | 
注意:其中Contents和Labels的key的长度不超过64字符,仅支持数字、字母、下划线、连字符(-)、点(.),且必须以字母开头。value类型最好使用字符串(string)和数字类型(int,double),其他类型建议先转为字符串类型,并且value值不能为空或空字符串。
示例代码:组装生成10条日志
vector<LogItem> logItems;
for (int k = 0; k < 10; k++) {
     int64_t logTimestamp = GetCurrentTimeStamp(3);
     string oriMessage = "c++ sdk test oriMessage";
     map<std::string, boost::any> contents;
     map<std::string, boost::any> labels;
     contents["level"] = string("error");
     contents["unit_id"] = 3.1415926;
     labels["user_tag"] = string("string");
     LogItem logItem;
     
     logItem.logTimestamp = logTimestamp;
     logItem.oriMessage = oriMessage;
     logItem.contents = contents;
     logItem.labels = labels;
     logItems.push_back(logItem);
}
4.4. 关于日志上传的操作
4.4.1. PutLogs()
此操作用于日志上传服务,需要传入的参数有三个,分别是logProject(日志项目编码),logUnit(日志单元编码),logItems(要上传的日志)。
| 参数 | 类型 | 描述 | 是否必须 | 
|---|---|---|---|
| logProject | string | 日志项目编码 | 是 | 
| logUnit | string | 日志单元编码 | 是 | 
| logItems | vector | 日志信息 | 是 | 
示例代码:上传日志
LogResponse logResponse = client->PutLogs(logProject, logUnit, logItems);
cout<<logResponse.statusCaode<<":"<<logResponse.message<<":"<<logResponse.error<<endl;
logResponse 是LogResponse 格式的返回响应体,如下表格式:
| 参数 | 类型 | 描述 | 示例 | 
|---|---|---|---|
| statusCode | int | 返回码,取值范围:0:-正常、-1:严重错误,其他自定义 | |
| message | string | 状态描述 | SUCCESS | 
| error | string | 参考错误编码列表 | 
日志服务相关错误编码(部分):
| statusCode | error | message | 
|---|---|---|
| -1 | LTS_8000 | 请求失败,请稍候重试,或提交工单反馈 | 
| -1 | LTS_8001 | 内容不合法,无法解析 | 
| -1 | LTS_8004 | 日志内容包含的日志必须小于[x] MB和[y]条 | 
| -1 | LTS_8006 | 日志内容解压失败 | 
| -1 | LTS_8007 | Token失效,请重新获取 | 
| -1 | LTS_8009 | 无云日志服务产品实例,请先开通云日志服务 | 
| -1 | LTS_8010 | 日志项目不存在 | 
| -1 | LTS_8011 | 日志单元不存在 | 
| -1 | LTS_8013 | 在1个日志项目下,写入流量最大限制:200MB/s | 
| -1 | LTS_8014 | 在1个日志项目下,写入次数最大限制:1000次/s | 
| -1 | LTS_8015 | 在1个日志单元下,写入流量最大限制:100MB/s | 
| -1 | LTS_8016 | 在1个日志单元下,写入次数最大限制:500次/s | 
| -1 | LTS_18000 | 调用ITIAM的接口失败 | 
5. 服务代码-异步批量上传
异步批量上传是为了解决同步上传无法高频异步发送等问题所增加的模块。原理是会开启多个线程,当调用日志发送接口后,会立刻返回,而内部的线程会将日志数据缓存合并,最后进行批量发送。
5.1. 关于Producer操作
5.1.1. ProducerConfig()
此操作是初始化producer配置,producer可以看作是一个启动器,内部封装了异步线程的初始化、启动和关闭等功能,只需要对producer进行操作,即可安全便捷地控制这些异步的线程。使用这份producerConfig配置去初始化一个producer。
//使用默认参数
ProducerConfig producerConfig = ProducerConfig();
//自定义参数
producerConfig.setLingerMs(2000);
producerConfig.setMaxBatchCount(4096);
...
producerConfig内的属性是异步操作中的线程所需要的参数,如果不设置参数,则初始化的时候会使用默认的参数,默认参数如下所示:
| 参数 | 类型 | 描述 | 默认值 | 
|---|---|---|---|
| max_worker_count_ | int | 线程数 | 4 | 
| max_batch_size_ | int | 每批日志大小,最大值5MB | 512KB | 
| max_batch_count_ | int | 每批日志条数,最大值40960 | 4096 | 
| linger_ms_ | int | 定时器等待时间 | 2000 | 
5.2. 关于异步批量日志上传操作
5.2.1. SendLogs()
此操作是将日志发送到后台的日志累加器队列中,然后立刻返回。累加器的状态达到可发送条件时(日志量达到阈值或者等待时间达到阈值),后台任务的线程将里面的日志进行打包批量发送,参数如下:
| 参数 | 类型 | 描述 | 是否必须 | 
|---|---|---|---|
| logProject | int | 日志项目编码 | 是 | 
| logUnit | int | 日志单元编码 | 是 | 
| logItem logItems | LogItem vector | 待上传的日志,可以是单条,也可以是多条(支持重载) | 是 | 
producer.sendLogs(logProject, logUnit, logItem);
sendLogs()方法有很多重载方法,可以满足多种类型的发送,既可以发送单条日志,也可以发送多条日志,同时也可以根据需求是否需要结果返回值。类型如下:
sendLogs(string logProject,string logUnit,LogItem &logItems);
sendLogs(string logProject,string logUnit,vector<LogItem> &logItems);
sendLogsCallback(string logProject,string logUnit,LogItem &logItems);
sendLogsCallback(string logProject,string logUnit,vector<LogItem> &logItems);
5.2.2. onCallback()
此操作是为线程注册回调函数,异步非阻塞获取返回结果。如果您想自定义回调函数,可以修改onCallback()函数。onCallback() 函数位于ioWorker.cpp 内。
 void onCallback(LogResponse &response, int logsize){
      std::cout << "statusCode:" << response.statusCode 
                << ", message:" << response.message 
                << ", error:" << response.error 
                << ", count:" << logsize << std::endl;
}
5.2.3. safeClose()
此操作是用于关闭producer。当不再需要发送数据或当前进程即将终止时,关闭producer是必要的步骤,以确保producer中缓存的所有数据都能得到妥善处理。当调用时,异步线程会停止接收数据,然后把所有缓存数据放进线程发送上传,等待所有线程任务结束后停止任务。
producer.safeClose();
