//
// Copyright (c) 2013-2025 The SRS Authors
//
// SPDX-License-Identifier: MIT
//

#include <srs_app_tencentcloud.hpp>
#ifdef SRS_APM

#include <srs_kernel_error.hpp>
#include <srs_kernel_buffer.hpp>
#include <srs_app_config.hpp>
#include <srs_protocol_http_client.hpp>
#include <srs_protocol_http_stack.hpp>
#include <srs_kernel_utility.hpp>
#include <srs_protocol_utility.hpp>
#include <srs_core_autofree.hpp>
#include <srs_protocol_json.hpp>
#include <srs_app_statistic.hpp>
#include <srs_kernel_utility.hpp>
#include <srs_app_utility.hpp>
#include <srs_protocol_protobuf.hpp>
#include <srs_protocol_amf0.hpp>

#include <string>
#include <map>
#include <algorithm>
#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <unistd.h>
using namespace std;

// See https://cloud.tencent.com/document/product/614/12445
namespace tencentcloud_api_sign {
    std::string sha1(const void *data, size_t len) {
        unsigned char digest[SHA_DIGEST_LENGTH];
        SHA_CTX ctx;
        SHA1_Init(&ctx);
        SHA1_Update(&ctx, data, len);
        SHA1_Final(digest, &ctx);
        char c_sha1[SHA_DIGEST_LENGTH*2+1];
        for (unsigned i = 0; i < SHA_DIGEST_LENGTH; ++i) {
            snprintf(&c_sha1[i*2], 3, "%02x", (unsigned int)digest[i]);
        }
        return c_sha1;
    }

    std::string hmac_sha1(const char *key, const void *data, size_t len) {
        unsigned char digest[EVP_MAX_MD_SIZE];
        unsigned digest_len;
        char c_hmacsha1[EVP_MAX_MD_SIZE*2+1];
#if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x10100000L
        HMAC_CTX ctx;
        HMAC_CTX_init(&ctx);
        HMAC_Init_ex(&ctx, key, strlen(key), EVP_sha1(), NULL);
        HMAC_Update(&ctx, (unsigned char*)data, len);
        HMAC_Final(&ctx, digest, &digest_len);
        HMAC_CTX_cleanup(&ctx);
#else
        HMAC_CTX *ctx = HMAC_CTX_new();
        HMAC_CTX_reset(ctx);
        HMAC_Init_ex(ctx, key, strlen(key), EVP_sha1(), NULL);
        HMAC_Update(ctx, (unsigned char *)data, len);
        HMAC_Final(ctx, digest, &digest_len);
        HMAC_CTX_free(ctx);
#endif
        for (unsigned i = 0; i != digest_len; ++i) {
            snprintf(&c_hmacsha1[i*2], 3, "%02x", (unsigned int)digest[i]);
        }
        return c_hmacsha1;
    }

    std::string urlencode(const char *s) {
        static unsigned char hexchars[] = "0123456789ABCDEF";
        size_t length = strlen(s), pos = 0;
        unsigned char c_url[length*3+1];
        const unsigned char *p = (const unsigned char *)s;
        for (; *p; ++p) {
            if (isalnum((unsigned char)*p) || (*p == '-') ||
                (*p == '_') || (*p == '.') || (*p == '~')) {
                c_url[pos++] = *p;
            } else {
                c_url[pos++] = '%';
                c_url[pos++] = hexchars[(*p)>>4];
                c_url[pos++] = hexchars[(*p)&15U];
            }
        }
        c_url[pos] = 0;
        return (char*)c_url;
    }

    std::string signature(const std::string &secret_id,
                          const std::string &secret_key,
                          std::string method,
                          const std::string &path,
                          const std::map<std::string, std::string> &params,
                          const std::map<std::string, std::string> &headers,
                          long expire) {

        const size_t SIGNLEN = 1024;
        std::string http_request_info, uri_parm_list,
                header_list, str_to_sign, sign_key;
        transform(method.begin(), method.end(), method.begin(), ::tolower);
        http_request_info.reserve(SIGNLEN);
        http_request_info.append(method).append("\n").append(path).append("\n");
        uri_parm_list.reserve(SIGNLEN);
        std::map<std::string, std::string>::const_iterator iter;
        for (iter = params.begin();
             iter != params.end(); ) {
            uri_parm_list.append(iter->first);
            http_request_info.append(iter->first).append("=")
                    .append(urlencode(iter->second.c_str()));
            if (++iter != params.end()) {
                uri_parm_list.append(";");
                http_request_info.append("&");
            }
        }
        http_request_info.append("\n");
        header_list.reserve(SIGNLEN);
        for (iter = headers.begin();
             iter != headers.end(); ++iter) {
            sign_key = iter->first;
            transform(sign_key.begin(), sign_key.end(), sign_key.begin(), ::tolower);
            if (sign_key == "content-type" || sign_key == "content-md5"
                || sign_key == "host" || sign_key[0] == 'x') {
                header_list.append(sign_key);
                http_request_info.append(sign_key).append("=")
                        .append(urlencode(iter->second.c_str()));
                header_list.append(";");
                http_request_info.append("&");
            }
        }
        if (!header_list.empty()) {
            header_list[header_list.size() - 1] = 0;
            http_request_info[http_request_info.size() - 1] = '\n';
        }
        //printf("%s\nEOF\n", http_request_info.c_str());
        char signed_time[SIGNLEN] = {0};
        int signed_time_len = snprintf(signed_time, SIGNLEN,
                                       "%lu;%lu", time(0) - 60, time(0) + expire);
        //snprintf(signed_time, SIGNLEN, "1510109254;1510109314");
        std::string signkey = hmac_sha1(secret_key.c_str(),
                                        signed_time, signed_time_len);
        str_to_sign.reserve(SIGNLEN);
        str_to_sign.append("sha1").append("\n")
                .append(signed_time).append("\n")
                .append(sha1(http_request_info.c_str(), http_request_info.size()))
                .append("\n");
        //printf("%s\nEOF\n", str_to_sign.c_str());
        std::stringstream c_signature;
        c_signature << "q-sign-algorithm=sha1&q-ak=" << secret_id.c_str()
                    << "&q-sign-time=" << signed_time
                    << "&q-key-time=" << signed_time
                    << "&q-header-list=" << header_list.c_str()
                    << "&q-url-param-list=" << uri_parm_list.c_str()
                    << "&q-signature=" << hmac_sha1(signkey.c_str(), str_to_sign.c_str(), str_to_sign.size()).c_str();
        return c_signature.str();
    }
}

// See https://cloud.tencent.com/document/api/614/16873
class SrsClsLogContent : public ISrsEncoder
{
private:
    // required string key = 1;
    std::string key_;
    // required string value = 2;
    std::string value_;
public:
    SrsClsLogContent();
    virtual ~SrsClsLogContent();
public:
    SrsClsLogContent* set_key(std::string v);
    SrsClsLogContent* set_value(std::string v);
public:
    virtual uint64_t nb_bytes();
    srs_error_t encode(SrsBuffer* b);
};

// See https://cloud.tencent.com/document/api/614/16873
class SrsClsLog : public ISrsEncoder
{
private:
    // required int64 time = 1;
    int64_t time_;
    // repeated Content contents= 2;
    std::vector<SrsClsLogContent*> contents_;
public:
    SrsClsLog();
    virtual ~SrsClsLog();
public:
    SrsClsLogContent* add_content();
    SrsClsLog* set_time(int64_t v);
public:
    virtual uint64_t nb_bytes();
    srs_error_t encode(SrsBuffer* b);
};

// See https://cloud.tencent.com/document/api/614/16873
class SrsClsLogGroup : public ISrsEncoder
{
private:
    // repeated Log logs= 1;
    std::vector<SrsClsLog*> logs_;
    // optional string source = 4;
    std::string source_;
public:
    SrsClsLogGroup();
    virtual ~SrsClsLogGroup();
public:
    SrsClsLogGroup* set_source(std::string v);
    SrsClsLog* add_log();
public:
    virtual uint64_t nb_bytes();
    srs_error_t encode(SrsBuffer* b);
};

// See https://cloud.tencent.com/document/api/614/16873
class SrsClsLogGroupList
{
private:
    // repeated LogGroup logGroupList = 1;
    std::vector<SrsClsLogGroup*> groups_;
public:
    SrsClsLogGroupList();
    virtual ~SrsClsLogGroupList();
public:
    bool empty();
    SrsClsLogGroup* add_log_group();
public:
    virtual uint64_t nb_bytes();
    srs_error_t encode(SrsBuffer* b);
};

SrsClsLogContent::SrsClsLogContent()
{
}

SrsClsLogContent::~SrsClsLogContent()
{
}

SrsClsLogContent* SrsClsLogContent::set_key(std::string v)
{
    key_ = v;
    return this;
}

SrsClsLogContent* SrsClsLogContent::set_value(std::string v)
{
    value_ = v;
    return this;
}

uint64_t SrsClsLogContent::nb_bytes()
{
    uint64_t  nn = SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(key_);
    nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(value_);
    return nn;
}

srs_error_t SrsClsLogContent::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    // Encode the key.
    if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldString)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufString::encode(b, key_)) != srs_success) {
        return srs_error_wrap(err, "encode key=%s", key_.c_str());
    }

    // Encode the value.
    if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldString)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufString::encode(b, value_)) != srs_success) {
        return srs_error_wrap(err, "encode value=%s", value_.c_str());
    }

    return err;
}

SrsClsLog::SrsClsLog()
{
}

SrsClsLog::~SrsClsLog()
{
    for (std::vector<SrsClsLogContent*>::iterator it = contents_.begin(); it != contents_.end(); ++it) {
        SrsClsLogContent* content = *it;
        srs_freep(content);
    }
}

SrsClsLogContent* SrsClsLog::add_content()
{
    SrsClsLogContent* content = new SrsClsLogContent();
    contents_.push_back(content);
    return content;
}

SrsClsLog* SrsClsLog::set_time(int64_t v)
{
    time_ = v;
    return this;
}

uint64_t SrsClsLog::nb_bytes()
{
    uint64_t nn = SrsProtobufKey::sizeof_key() + SrsProtobufVarints::sizeof_varint(time_);

    for (std::vector<SrsClsLogContent*>::iterator it = contents_.begin(); it != contents_.end(); ++it) {
        SrsClsLogContent* content = *it;
        nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(content);
    }

    return nn;
}

srs_error_t SrsClsLog::encode(SrsBuffer* b)
{
    srs_error_t  err = srs_success;

    // Encode the time.
    if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldVarint)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufVarints::encode(b, time_)) != srs_success) {
        return srs_error_wrap(err, "encode time");
    }

    // Encode each content.
    for (std::vector<SrsClsLogContent*>::iterator it = contents_.begin(); it != contents_.end(); ++it) {
        SrsClsLogContent* content = *it;

        if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldObject)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufObject::encode(b, content)) != srs_success) {
            return srs_error_wrap(err, "encode content");
        }
    }

    return err;
}

SrsClsLogGroup::SrsClsLogGroup()
{
}

SrsClsLogGroup::~SrsClsLogGroup()
{
    for (std::vector<SrsClsLog*>::iterator it = logs_.begin(); it != logs_.end(); ++it) {
        SrsClsLog* log = *it;
        srs_freep(log);
    }
}

SrsClsLogGroup* SrsClsLogGroup::set_source(std::string v)
{
    source_ = v;
    return this;
}

SrsClsLog* SrsClsLogGroup::add_log()
{
    SrsClsLog* log = new SrsClsLog();
    logs_.push_back(log);
    return log;
}

uint64_t SrsClsLogGroup::nb_bytes()
{
    uint64_t nn = 0;
    for (std::vector<SrsClsLog*>::iterator it = logs_.begin(); it != logs_.end(); ++it) {
        SrsClsLog* log = *it;
        nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(log);
    }

    nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(source_);
    return nn;
}

srs_error_t SrsClsLogGroup::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    // Encode each log.
    for (std::vector<SrsClsLog*>::iterator it = logs_.begin(); it != logs_.end(); ++it) {
        SrsClsLog* log = *it;

        if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldObject)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufObject::encode(b, log)) != srs_success) {
            return srs_error_wrap(err, "encode log");
        }
    }

    // Encode the optional source.
    if ((err = SrsProtobufKey::encode(b, 4, SrsProtobufFieldString)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufString::encode(b, source_)) != srs_success) {
        return srs_error_wrap(err, "encode source=%s", source_.c_str());
    }

    return err;
}

SrsClsLogGroupList::SrsClsLogGroupList()
{
}

SrsClsLogGroupList::~SrsClsLogGroupList()
{
    for (std::vector<SrsClsLogGroup*>::iterator it = groups_.begin(); it != groups_.end(); ++it) {
        SrsClsLogGroup* group = *it;
        srs_freep(group);
    }
}

bool SrsClsLogGroupList::empty()
{
    return groups_.empty();
}

SrsClsLogGroup* SrsClsLogGroupList::add_log_group()
{
    SrsClsLogGroup* group = new SrsClsLogGroup();
    groups_.push_back(group);
    return group;
}

uint64_t SrsClsLogGroupList::nb_bytes()
{
    uint64_t nn = 0;
    for (std::vector<SrsClsLogGroup*>::iterator it = groups_.begin(); it != groups_.end(); ++it) {
        SrsClsLogGroup* group = *it;
        nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(group);
    }
    return nn;
}

srs_error_t SrsClsLogGroupList::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    // Encode each group.
    for (std::vector<SrsClsLogGroup*>::iterator it = groups_.begin(); it != groups_.end(); ++it) {
        SrsClsLogGroup* group = *it;

        if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldObject)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufObject::encode(b, group)) != srs_success) {
            return srs_error_wrap(err, "encode group");
        }
    }

    return err;
}

SrsClsSugar::SrsClsSugar()
{
    log_groups_ = new SrsClsLogGroupList();
    log_group_ = log_groups_->add_log_group();
    log_ = log_group_->add_log();

    log_group_->set_source(srs_get_public_internet_address(true));
    log_->set_time(srs_get_system_time() / SRS_UTIME_MILLISECONDS);
    kv("agent", RTMP_SIG_SRS_SERVER);

    string label = _srs_cls->label();
    if (!label.empty()) {
        kv("label", label);
    }

    string tag = _srs_cls->tag();
    if (!tag.empty()) {
        kv("tag", tag);
    }

    string server_id = SrsStatistic::instance()->server_id();
    if (!server_id.empty()) {
        kv("id", server_id);
    }
}

SrsClsSugar::~SrsClsSugar()
{
    srs_freep(log_groups_);
}

uint64_t SrsClsSugar::nb_bytes()
{
    return log_groups_->nb_bytes();
}

srs_error_t SrsClsSugar::encode(SrsBuffer* b)
{
    return log_groups_->encode(b);
}

bool SrsClsSugar::empty()
{
    return log_groups_->empty();
}

SrsClsSugar* SrsClsSugar::kv(std::string k, std::string v)
{
    log_->add_content()->set_key(k)->set_value(v);
    return this;
}

SrsClsSugars::SrsClsSugars()
{
}

SrsClsSugars::~SrsClsSugars()
{
    for (vector<SrsClsSugar*>::iterator it = sugars.begin(); it != sugars.end(); ++it) {
        SrsClsSugar* sugar = *it;
        srs_freep(sugar);
    }
}

uint64_t SrsClsSugars::nb_bytes()
{
    uint64_t size = 0;
    for (vector<SrsClsSugar*>::iterator it = sugars.begin(); it != sugars.end(); ++it) {
        SrsClsSugar* sugar = *it;
        size += sugar->nb_bytes();
    }
    return size;
}

srs_error_t SrsClsSugars::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    for (vector<SrsClsSugar*>::iterator it = sugars.begin(); it != sugars.end(); ++it) {
        SrsClsSugar* sugar = *it;
        if ((err = sugar->encode(b)) != srs_success) {
            return srs_error_wrap(err, "encode %d sugars", (int)sugars.size());
        }
    }

    return err;
}

SrsClsSugar* SrsClsSugars::create()
{
    SrsClsSugar* sugar = new SrsClsSugar();
    sugars.push_back(sugar);
    return sugar;
}

SrsClsSugars* SrsClsSugars::slice(int max_size)
{
    SrsClsSugars* v = new SrsClsSugars();

    uint64_t v_size = 0;
    for (vector<SrsClsSugar*>::iterator it = sugars.begin(); it != sugars.end();) {
        SrsClsSugar* sugar = *it;

        // Always consume it.
        it = sugars.erase(it);

        // If empty, ignore it.
        if (sugar->empty()) {
            srs_freep(sugar);
            continue;
        }

        // Not empty, append it, to make sure at least one elem.
        v->sugars.push_back(sugar);

        // Util exceed the max size.
        v_size += sugar->nb_bytes();
        if ((int)v_size > max_size) {
            break;
        }
    }

    return v;
}

bool SrsClsSugars::empty()
{
    return sugars.empty();
}

int SrsClsSugars::size()
{
    return (int)sugars.size();
}

SrsClsClient* _srs_cls = NULL;

SrsClsClient::SrsClsClient()
{
    enabled_ = false;
    stat_heartbeat_ = false;
    stat_streams_ = false;
    debug_logging_ = false;
    heartbeat_ratio_ = 0;
    streams_ratio_ = 0;
    nn_logs_ = 0;
    sugars_ = new SrsClsSugars();
}

SrsClsClient::~SrsClsClient()
{
    srs_freep(sugars_);
}

bool SrsClsClient::enabled()
{
    return enabled_;
}

string SrsClsClient::label()
{
    return label_;
}

string SrsClsClient::tag()
{
    return tag_;
}

uint64_t SrsClsClient::nn_logs()
{
    return nn_logs_;
}

srs_error_t SrsClsClient::initialize()
{
    srs_error_t err = srs_success;

    enabled_ = _srs_config->get_tencentcloud_cls_enabled();
    if (!enabled_) {
        srs_trace("TencentCloud CLS is disabled");
        return err;
    }

    label_ = _srs_config->get_tencentcloud_cls_label();
    tag_ = _srs_config->get_tencentcloud_cls_tag();
    stat_heartbeat_ = _srs_config->get_tencentcloud_cls_stat_heartbeat();
    stat_streams_ = _srs_config->get_tencentcloud_cls_stat_streams();
    debug_logging_ = _srs_config->get_tencentcloud_cls_debug_logging();
    heartbeat_ratio_ = srs_max(1, _srs_config->get_tencentcloud_cls_heartbeat_ratio());
    streams_ratio_ = srs_max(1, _srs_config->get_tencentcloud_cls_streams_ratio());

    secret_id_ = _srs_config->get_tencentcloud_cls_secret_id();
    if (secret_id_.empty()) {
        return srs_error_new(ERROR_CLS_INVALID_CONFIG, "CLS no config for secret_id");
    }

    string secret_key = _srs_config->get_tencentcloud_cls_secret_key();
    if (secret_key.empty()) {
        return srs_error_new(ERROR_CLS_INVALID_CONFIG, "CLS no config for secret_key");
    }

    endpoint_ = _srs_config->get_tencentcloud_cls_endpoint();
    if (endpoint_.empty()) {
        return srs_error_new(ERROR_CLS_INVALID_CONFIG, "CLS no config for endpoint");
    }

    topic_ = _srs_config->get_tencentcloud_cls_topic_id();
    if (topic_.empty()) {
        return srs_error_new(ERROR_CLS_INVALID_CONFIG, "CLS no config for topic_id");
    }

    srs_trace("Initialize TencentCloud CLS label=%s, tag=%s, secret_id=%dB, secret_key=%dB, endpoint=%s, topic=%s, heartbeat=%d/%d, streams=%d/%d debug_logging=%d",
        label_.c_str(), tag_.c_str(), secret_id_.length(), secret_key.length(), endpoint_.c_str(), topic_.c_str(), stat_heartbeat_, heartbeat_ratio_, stat_streams_, streams_ratio_, debug_logging_);

    return err;
}

srs_error_t SrsClsClient::report()
{
    srs_error_t err = srs_success;

    if ((err = dump_summaries(sugars_)) != srs_success) {
        return srs_error_wrap(err, "dump summary");
    }

    if ((err = dump_streams(sugars_)) != srs_success) {
        return srs_error_wrap(err, "dump streams");
    }

    if (sugars_->empty()) {
        return err;
    }

    SrsUniquePtr<SrsClsSugars> sugars(sugars_);
    sugars_ = new SrsClsSugars();

    if ((err = send_logs(sugars.get())) != srs_success) {
        return srs_error_wrap(err, "cls");
    }

    return err;
}

srs_error_t SrsClsClient::do_send_logs(ISrsEncoder* sugar, int count, int total)
{
    srs_error_t err = srs_success;

    uint64_t size = sugar->nb_bytes();
    // Max size is 5MB, error is 403:LogSizeExceed, see https://cloud.tencent.com/document/api/614/12402
    if (size >= 5 * 1024 * 1024) {
        return srs_error_new(ERROR_CLS_EXCEED_SIZE, "exceed 5MB actual %d", size);
    }

    SrsUniquePtr<char[]> buf(new char[size]);

    memset(buf.get(), 0, size);
    SrsBuffer b(buf.get(), size);
    if ((err = sugar->encode(&b)) != srs_success) {
        return srs_error_wrap(err, "encode log");
    }

    string body(buf.get(), size);

    // Write a CLS log to service specified by url.
    string url = "http://" + endpoint_ + ":80/structuredlog?topic_id=" + topic_;

    SrsHttpUri uri;
    if ((err = uri.initialize(url)) != srs_success) {
        return srs_error_wrap(err, "http: post failed. url=%s", url.c_str());
    }

    SrsHttpClient http;
    if ((err = http.initialize(uri.get_schema(), uri.get_host(), uri.get_port())) != srs_success) {
        return srs_error_wrap(err, "http: init client");
    }

    // Sign the request, see https://cloud.tencent.com/document/product/614/56475
    if (true) {
        map<string, string> params;
        params["topic_id"] = topic_;

        map<string, string> headers;
        headers["Host"] = uri.get_host();
        headers["Content-Type"] = "application/x-protobuf";
        http.set_header("Content-Type", "application/x-protobuf");

        string method = "POST";
        string secret_key = _srs_config->get_tencentcloud_cls_secret_key();
        std::string signature = tencentcloud_api_sign::signature(
            secret_id_, secret_key, method, uri.get_path(), params, headers, 300 // Expire in seconds
        );
        headers["Authorization"] = signature;
        http.set_header("Authorization", signature);
    }

    string path = uri.get_path();
    if (!uri.get_query().empty()) {
        path += "?" + uri.get_query();
    }

    // Start request and parse response.
    ISrsHttpMessage* msg_raw = NULL;
    if ((err = http.post(path, body, &msg_raw)) != srs_success) {
        return srs_error_wrap(err, "http: client post");
    }
    SrsUniquePtr<ISrsHttpMessage> msg(msg_raw);

    string res;
    uint16_t code = msg->status_code();
    if ((err = msg->body_read_all(res)) != srs_success) {
        return srs_error_wrap(err, "http: body read");
    }

    // ensure the http status is ok.
    if (code != SRS_CONSTS_HTTP_OK && code != SRS_CONSTS_HTTP_Created) {
        return srs_error_new(ERROR_HTTP_STATUS_INVALID, "http: status %d, body is %s", code, res.c_str());
    }

    string request_id = msg->header()->get("X-Cls-Requestid");
    if (request_id.empty() && !debug_logging_) {
        srs_warn("no CLS requestId for log %dB", body.length());
    }

    if (debug_logging_) {
        string server_id = SrsStatistic::instance()->server_id();
        srs_trace("CLS write logs=%d/%d, size=%dB, server_id=%s, request_id=%s", count, total, body.length(), server_id.c_str(), request_id.c_str());
    }

    return err;
}

// For each upload, never exceed 2MB, to avoid burst of CPU or network usage.
#define SRS_CLS_BATCH_MAX_LOG_SIZE 2 * 1024 * 1024

srs_error_t SrsClsClient::send_logs(SrsClsSugars* sugars)
{
    srs_error_t err = srs_success;

    // Record the total logs sent out.
    int total = sugars->size();
    nn_logs_ += total;

    // Never do infinite loop, limit to a max loop and drop logs if exceed.
    for (int i = 0; i < 128 && !sugars->empty(); ++i) {
        SrsUniquePtr<SrsClsSugars> v(sugars->slice(SRS_CLS_BATCH_MAX_LOG_SIZE));

        if ((err = do_send_logs((ISrsEncoder*)v.get(), v->size(), total)) != srs_success) {
            return srs_error_wrap(err, "send %d/%d/%d logs", v->size(), i, total);
        }
    }

    return err;
}

srs_error_t SrsClsClient::dump_summaries(SrsClsSugars* sugars)
{
    srs_error_t err = srs_success;

    // Ignore if disabled.
    if (!enabled_ || !stat_heartbeat_) {
        return err;
    }

    // Whether it's time to report heartbeat.
    static int nn_heartbeat = -1;
    bool interval_ok = nn_heartbeat == -1 || ++nn_heartbeat >= heartbeat_ratio_;
    if (interval_ok) {
        nn_heartbeat = 0;
    }
    if (!interval_ok) {
        return err;
    }

    SrsClsSugar* sugar = sugars->create();
    sugar->kv("hint", "summary");
    sugar->kv("version", RTMP_SIG_SRS_VERSION);
    sugar->kv("pid", srs_fmt("%d", getpid()));

    // Server ID to identify logs from a set of servers' logs.
    SrsStatistic::instance()->dumps_cls_summaries(sugar);

    SrsProcSelfStat* u = srs_get_self_proc_stat();
    if (u->ok) {
        // The cpu usage of SRS, 1 means 1/1000
        if (u->percent > 0) {
            sugar->kv("cpu", srs_fmt("%d", (int)(u->percent * 1000)));
        }
    }

    SrsPlatformInfo* p = srs_get_platform_info();
    if (p->ok) {
        // The uptime of SRS, in seconds.
        if (p->srs_startup_time > 0) {
            sugar->kv("uptime", srs_fmt("%d", (int)((srs_get_system_time() - p->srs_startup_time) / SRS_UTIME_SECONDS)));
        }
        // The load of system, load every 1 minute, 1 means 1/1000.
        if (p->load_one_minutes > 0) {
            sugar->kv("load", srs_fmt("%d", (int)(p->load_one_minutes * 1000)));
        }
    }

    SrsRusage* r = srs_get_system_rusage();
    SrsMemInfo* m = srs_get_meminfo();
    if (r->ok && m->ok) {
        float self_mem_percent = 0;
        if (m->MemTotal > 0) {
            self_mem_percent = (float)(r->r.ru_maxrss / (double)m->MemTotal);
        }

        // The memory of SRS, 1 means 1/1000
        if (self_mem_percent > 0) {
            sugar->kv("mem", srs_fmt("%d", (int)(self_mem_percent * 1000)));
        }
    }

    SrsProcSystemStat* s = srs_get_system_proc_stat();
    if (s->ok) {
        // The cpu usage of system, 1 means 1/1000
        if (s->percent > 0) {
            sugar->kv("cpu2", srs_fmt("%d", (int)(s->percent * 1000)));
        }
    }

    SrsNetworkRtmpServer* nrs = srs_get_network_rtmp_server();
    if (nrs->ok) {
        // The number of connections of SRS.
        if (nrs->nb_conn_srs > 0) {
            sugar->kv("conn", srs_fmt("%d", nrs->nb_conn_srs));
        }
        // The number of connections of system.
        if (nrs->nb_conn_sys > 0) {
            sugar->kv("conn2", srs_fmt("%d", nrs->nb_conn_sys));
        }
        // The received kbps in 30s of SRS.
        if (nrs->rkbps_30s > 0) {
            sugar->kv("recv", srs_fmt("%d", nrs->rkbps_30s));
        }
        // The sending out kbps in 30s of SRS.
        if (nrs->skbps_30s > 0) {
            sugar->kv("send", srs_fmt("%d", nrs->skbps_30s));
        }
    }

    return err;
}

srs_error_t SrsClsClient::dump_streams(SrsClsSugars* sugars)
{
    srs_error_t err = srs_success;

    // Ignore if disabled.
    if (!enabled_ || !stat_streams_) {
        return err;
    }

    // Whether it's time to report streams.
    static int nn_streams = -1;
    bool interval_ok = nn_streams == -1 || ++nn_streams >= streams_ratio_;
    if (interval_ok) {
        nn_streams = 0;
    }
    if (!interval_ok) {
        return err;
    }

    // Dumps all streams as sugars.
    SrsStatistic::instance()->dumps_cls_streams(sugars);

    return err;
}

SrsOtelExportTraceServiceRequest::SrsOtelExportTraceServiceRequest()
{
}

SrsOtelExportTraceServiceRequest::~SrsOtelExportTraceServiceRequest()
{
    for (vector<SrsOtelResourceSpans*>::iterator it = spans_.begin(); it != spans_.end(); ++it) {
        SrsOtelResourceSpans* span = *it;
        srs_freep(span);
    }
}

SrsOtelResourceSpans* SrsOtelExportTraceServiceRequest::append()
{
    SrsOtelResourceSpans* v = new SrsOtelResourceSpans();
    spans_.push_back(v);
    return v;
}

uint64_t SrsOtelExportTraceServiceRequest::nb_bytes()
{
    uint64_t nn = 0;
    for (vector<SrsOtelResourceSpans*>::iterator it = spans_.begin(); it != spans_.end(); ++it) {
        SrsOtelResourceSpans* span = *it;
        nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(span);
    }
    return nn;
}

srs_error_t SrsOtelExportTraceServiceRequest::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    // Encode each span.
    for (vector<SrsOtelResourceSpans*>::iterator it = spans_.begin(); it != spans_.end(); ++it) {
        SrsOtelResourceSpans* span = *it;

        if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldObject)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufObject::encode(b, span)) != srs_success) {
            return srs_error_wrap(err, "encode span");
        }
    }

    return err;
}

SrsOtelResourceSpans::SrsOtelResourceSpans()
{
    resource_ = new SrsOtelResource();
}

SrsOtelResourceSpans::~SrsOtelResourceSpans()
{
    srs_freep(resource_);
    
    for (std::vector<SrsOtelScopeSpans*>::iterator it = spans_.begin(); it != spans_.end(); ++it) {
        SrsOtelScopeSpans* span = *it;
        srs_freep(span);
    }
}

SrsOtelResource* SrsOtelResourceSpans::resource()
{
    return resource_;
}

SrsOtelScopeSpans* SrsOtelResourceSpans::append()
{
    SrsOtelScopeSpans* v = new SrsOtelScopeSpans();
    spans_.push_back(v);
    return v;
}

uint64_t SrsOtelResourceSpans::nb_bytes()
{
    uint64_t nn = SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(resource_);
    for (std::vector<SrsOtelScopeSpans*>::iterator it = spans_.begin(); it != spans_.end(); ++it) {
        SrsOtelScopeSpans* span = *it;
        nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(span);
    }
    return nn;
}

srs_error_t SrsOtelResourceSpans::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    // Encode the resource.
    if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldObject)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufObject::encode(b, resource_)) != srs_success) {
        return srs_error_wrap(err, "encode resource");
    }

    // Encode scope spans.

    // Encode each group.
    for (std::vector<SrsOtelScopeSpans*>::iterator it = spans_.begin(); it != spans_.end(); ++it) {
        SrsOtelScopeSpans* span = *it;

        if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldObject)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufObject::encode(b, span)) != srs_success) {
            return srs_error_wrap(err, "encode span");
        }
    }

    return err;
}

SrsOtelResource::SrsOtelResource()
{
}

SrsOtelResource::~SrsOtelResource()
{
    for (vector<SrsOtelAttribute*>::iterator it = attributes_.begin(); it != attributes_.end(); ++it) {
        SrsOtelAttribute* attribute = *it;
        srs_freep(attribute);
    }
}

SrsOtelResource* SrsOtelResource::add_addr(SrsOtelAttribute* v)
{
    attributes_.push_back(v);
    return this;
}

uint64_t SrsOtelResource::nb_bytes()
{
    uint64_t nn = 0;
    for (vector<SrsOtelAttribute*>::iterator it = attributes_.begin(); it != attributes_.end(); ++it) {
        SrsOtelAttribute* attribute = *it;
        nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(attribute);
    }
    return nn;
}

srs_error_t SrsOtelResource::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    // Encode attributes.
    for (vector<SrsOtelAttribute*>::iterator it = attributes_.begin(); it != attributes_.end(); ++it) {
        SrsOtelAttribute* attribute = *it;

        if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldObject)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufObject::encode(b, attribute)) != srs_success) {
            return srs_error_wrap(err, "encode attribute");
        }
    }

    return err;
}

SrsOtelAttribute::SrsOtelAttribute()
{
    value_ = new SrsOtelAnyValue();
}

SrsOtelAttribute::~SrsOtelAttribute()
{
    srs_freep(value_);
}

const std::string& SrsOtelAttribute::key()
{
    return key_;
}

SrsOtelAttribute* SrsOtelAttribute::kv(std::string k, std::string v)
{
    SrsOtelAttribute* attr = new SrsOtelAttribute();
    attr->key_ = k;
    attr->value_->set_string(v);
    return attr;
}

SrsOtelAttribute* SrsOtelAttribute::kvi(std::string k, int64_t v)
{
    SrsOtelAttribute* attr = new SrsOtelAttribute();
    attr->key_ = k;
    attr->value_->set_int(v);
    return attr;
}

uint64_t SrsOtelAttribute::nb_bytes()
{
    uint64_t  nn = SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(key_);
    nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(value_);
    return nn;
}

srs_error_t SrsOtelAttribute::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    // Encode the key.
    if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldString)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufString::encode(b, key_)) != srs_success) {
        return srs_error_wrap(err, "encode key=%s", key_.c_str());
    }

    // Encode the value.
    if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldObject)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufObject::encode(b, value_)) != srs_success) {
        return srs_error_wrap(err, "encode value");
    }

    return err;
}

SrsOtelAnyValue::SrsOtelAnyValue()
{
    used_field_id_ = 1;
    int_value_ = 0;
}

SrsOtelAnyValue::~SrsOtelAnyValue()
{
}

SrsOtelAnyValue* SrsOtelAnyValue::set_string(const std::string& v)
{
    string_value_ = v;
    used_field_id_ = 1;
    return this;
}

SrsOtelAnyValue* SrsOtelAnyValue::set_int(int64_t v)
{
    int_value_ = v;
    used_field_id_ = 3;
    return this;
}

uint64_t SrsOtelAnyValue::nb_bytes()
{
    uint64_t  nn = 0;
    if (used_field_id_ == 1) nn = SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(string_value_);
    if (used_field_id_ == 3) nn = SrsProtobufKey::sizeof_key() + SrsProtobufVarints::sizeof_varint(int_value_);
    return nn;
}

srs_error_t SrsOtelAnyValue::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    if (used_field_id_ == 1) {
        // Encode the string value.
        if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldString)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufString::encode(b, string_value_)) != srs_success) {
            return srs_error_wrap(err, "encode value=%s", string_value_.c_str());
        }
    } else if (used_field_id_ == 3) {
        // Encode the int value.
        if ((err = SrsProtobufKey::encode(b, 3, SrsProtobufFieldVarint)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufVarints::encode(b, int_value_)) != srs_success) {
            return srs_error_wrap(err, "encode value=%" PRId64, int_value_);
        }
    }

    return err;
}

SrsOtelScopeSpans::SrsOtelScopeSpans()
{
    scope_ = new SrsOtelScope();
}

SrsOtelScopeSpans::~SrsOtelScopeSpans()
{
    srs_freep(scope_);
    for (std::vector<SrsOtelSpan*>::iterator it = spans_.begin(); it != spans_.end(); ++it) {
        SrsOtelSpan* span = *it;
        srs_freep(span);
    }
}

SrsOtelScope* SrsOtelScopeSpans::scope()
{
    return scope_;
}

SrsOtelScopeSpans* SrsOtelScopeSpans::swap(std::vector<SrsOtelSpan*>& spans)
{
    spans_.swap(spans);
    return this;
}

int SrsOtelScopeSpans::size()
{
    return (int)spans_.size();
}

uint64_t SrsOtelScopeSpans::nb_bytes()
{
    uint64_t nn = SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(scope_);
    for (std::vector<SrsOtelSpan*>::iterator it = spans_.begin(); it != spans_.end(); ++it) {
        SrsOtelSpan* span = *it;
        nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(span);
    }
    return nn;
}

srs_error_t SrsOtelScopeSpans::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    // Encode the scope.
    if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldObject)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufObject::encode(b, scope_)) != srs_success) {
        return srs_error_wrap(err, "encode scope");
    }

    // Encode each span.
    for (std::vector<SrsOtelSpan*>::iterator it = spans_.begin(); it != spans_.end(); ++it) {
        SrsOtelSpan* span = *it;

        if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldObject)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufObject::encode(b, span)) != srs_success) {
            return srs_error_wrap(err, "encode span");
        }
    }
    return err;
}

SrsOtelScope::SrsOtelScope()
{
}

SrsOtelScope::~SrsOtelScope()
{
}

uint64_t SrsOtelScope::nb_bytes()
{
    uint64_t nn = 0;
    nn = SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(name_);
    return nn;
}

srs_error_t SrsOtelScope::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    // Encode the name.
    if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldString)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufString::encode(b, name_)) != srs_success) {
        return srs_error_wrap(err, "encode name=%s", name_.c_str());
    }

    return err;
}

SrsOtelSpan::SrsOtelSpan()
{
    start_time_unix_nano_ = 0;
    end_time_unix_nano_ = 0;
    status_ = new SrsOtelStatus();
}

SrsOtelSpan::~SrsOtelSpan()
{
    for (vector<SrsOtelAttribute*>::iterator it = attributes_.begin(); it != attributes_.end(); ++it) {
        SrsOtelAttribute* attribute = *it;
        srs_freep(attribute);
    }

    for (vector<SrsOtelEvent*>::iterator it = events_.begin(); it != events_.end(); ++it) {
        SrsOtelEvent* event = *it;
        srs_freep(event);
    }

    for (vector<SrsOtelLink*>::iterator it = links_.begin(); it != links_.end(); ++it) {
        SrsOtelLink* link = *it;
        srs_freep(link);
    }

    srs_freep(status_);
}

SrsOtelAttribute* SrsOtelSpan::attr(const std::string k)
{
    for (vector<SrsOtelAttribute*>::iterator it = attributes_.begin(); it != attributes_.end(); ++it) {
        SrsOtelAttribute* attribute = *it;
        if (attribute->key() == k) {
            return attribute;
        }
    }
    return NULL;
}

uint64_t SrsOtelSpan::nb_bytes()
{
    uint64_t nn = 0;

    nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(trace_id_);
    nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(span_id_);
    if (!parent_span_id_.empty()) {
        nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(parent_span_id_);
    }
    nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(name_);
    nn += SrsProtobufKey::sizeof_key() + SrsProtobufVarints::sizeof_varint(kind_);
    nn += SrsProtobufKey::sizeof_key() + SrsProtobufFixed64::sizeof_int(start_time_unix_nano_);
    nn += SrsProtobufKey::sizeof_key() + SrsProtobufFixed64::sizeof_int(end_time_unix_nano_);

    for (vector<SrsOtelAttribute*>::iterator it = attributes_.begin(); it != attributes_.end(); ++it) {
        SrsOtelAttribute* attribute = *it;
        nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(attribute);
    }

    for (vector<SrsOtelEvent*>::iterator it = events_.begin(); it != events_.end(); ++it) {
        SrsOtelEvent* event = *it;
        nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(event);
    }

    for (vector<SrsOtelLink*>::iterator it = links_.begin(); it != links_.end(); ++it) {
        SrsOtelLink* link = *it;
        nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(link);
    }

    nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(status_);

    return nn;
}

srs_error_t SrsOtelSpan::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    // Encode the trace id.
    if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldString)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufString::encode(b, trace_id_)) != srs_success) {
        return srs_error_wrap(err, "encode trace_id=%s", trace_id_.c_str());
    }

    // Encode the span id.
    if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldString)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufString::encode(b, span_id_)) != srs_success) {
        return srs_error_wrap(err, "encode span_id=%s", span_id_.c_str());
    }

    // Encode the parent span id.
    if (!parent_span_id_.empty()) {
        if ((err = SrsProtobufKey::encode(b, 4, SrsProtobufFieldString)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufString::encode(b, parent_span_id_)) != srs_success) {
            return srs_error_wrap(err, "encode parent_span_id=%s", parent_span_id_.c_str());
        }
    }

    // Encode the name.
    if ((err = SrsProtobufKey::encode(b, 5, SrsProtobufFieldString)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufString::encode(b, name_)) != srs_success) {
        return srs_error_wrap(err, "encode name=%s", name_.c_str());
    }

    // Encode the kind.
    if ((err = SrsProtobufKey::encode(b, 6, SrsProtobufFieldEnum)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufVarints::encode(b, kind_)) != srs_success) {
        return srs_error_wrap(err, "encode kind=%d", (int)kind_);
    }

    // Encode the start time.
    if ((err = SrsProtobufKey::encode(b, 7, SrsProtobufField64bit)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufFixed64::encode(b, start_time_unix_nano_)) != srs_success) {
        return srs_error_wrap(err, "encode start_time=%" PRId64, start_time_unix_nano_);
    }

    // Encode the end time.
    if ((err = SrsProtobufKey::encode(b, 8, SrsProtobufField64bit)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufFixed64::encode(b, end_time_unix_nano_)) != srs_success) {
        return srs_error_wrap(err, "encode end_time=%" PRId64, end_time_unix_nano_);
    }

    // Encode attribute if not empty.
    for (vector<SrsOtelAttribute*>::iterator it = attributes_.begin(); it != attributes_.end(); ++it) {
        SrsOtelAttribute* attribute = *it;

        if ((err = SrsProtobufKey::encode(b, 9, SrsProtobufFieldObject)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufObject::encode(b, attribute)) != srs_success) {
            return srs_error_wrap(err, "encode attribute");
        }
    }

    // Encode the events if not empty.
    for (vector<SrsOtelEvent*>::iterator it = events_.begin(); it != events_.end(); ++it) {
        SrsOtelEvent* event = *it;

        if ((err = SrsProtobufKey::encode(b, 11, SrsProtobufFieldObject)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufObject::encode(b, event)) != srs_success) {
            return srs_error_wrap(err, "encode event");
        }
    }

    // Encode the links if not empty.
    for (vector<SrsOtelLink*>::iterator it = links_.begin(); it != links_.end(); ++it) {
        SrsOtelLink* link = *it;

        if ((err = SrsProtobufKey::encode(b, 13, SrsProtobufFieldObject)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufObject::encode(b, link)) != srs_success) {
            return srs_error_wrap(err, "encode link");
        }
    }

    // Encode the status.
    if ((err = SrsProtobufKey::encode(b, 15, SrsProtobufFieldObject)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufObject::encode(b, status_)) != srs_success) {
        return srs_error_wrap(err, "encode status");
    }

    return err;
}

SrsOtelEvent::SrsOtelEvent()
{
    time_ = srs_update_system_time();
}

SrsOtelEvent::~SrsOtelEvent()
{
    for (vector<SrsOtelAttribute*>::iterator it = attributes_.begin(); it != attributes_.end(); ++it) {
        SrsOtelAttribute* attribute = *it;
        srs_freep(attribute);
    }
}

SrsOtelEvent* SrsOtelEvent::create(std::string v)
{
    SrsOtelEvent* e = new SrsOtelEvent();
    e->name_ = v;
    return e;
}

SrsOtelEvent* SrsOtelEvent::add_attr(SrsOtelAttribute* v)
{
    attributes_.push_back(v);
    return this;
}

uint64_t SrsOtelEvent::nb_bytes()
{
    uint64_t nn = 0;
    nn += SrsProtobufKey::sizeof_key() + SrsProtobufFixed64::sizeof_int(time_);
    nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(name_);
    for (vector<SrsOtelAttribute*>::iterator it = attributes_.begin(); it != attributes_.end(); ++it) {
        SrsOtelAttribute* attribute = *it;
        nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(attribute);
    }
    return nn;
}

srs_error_t SrsOtelEvent::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    // Encode the time.
    if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufField64bit)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufFixed64::encode(b, time_)) != srs_success) {
        return srs_error_wrap(err, "encode time=%" PRId64, time_);
    }

    // Encode the name.
    if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldString)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufString::encode(b, name_)) != srs_success) {
        return srs_error_wrap(err, "encode key=%s", name_.c_str());
    }

    // Encode attributes.
    for (vector<SrsOtelAttribute*>::iterator it = attributes_.begin(); it != attributes_.end(); ++it) {
        SrsOtelAttribute* attribute = *it;

        if ((err = SrsProtobufKey::encode(b, 3, SrsProtobufFieldObject)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufObject::encode(b, attribute)) != srs_success) {
            return srs_error_wrap(err, "encode attribute");
        }
    }

    return err;
}

SrsOtelLink::SrsOtelLink()
{
}

SrsOtelLink::~SrsOtelLink()
{
}

SrsOtelLink* SrsOtelLink::create()
{
    return new SrsOtelLink();
}

SrsOtelLink* SrsOtelLink::set_id(const std::string& trace_id, const std::string& span_id)
{
    trace_id_ = trace_id;
    span_id_ = span_id;
    return this;
}

uint64_t SrsOtelLink::nb_bytes()
{
    uint64_t nn = 0;
    nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(trace_id_);
    nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(span_id_);
    return nn;
}

srs_error_t SrsOtelLink::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    // Encode the trace id.
    if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldString)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufString::encode(b, trace_id_)) != srs_success) {
        return srs_error_wrap(err, "encode trace_id=%s", trace_id_.c_str());
    }

    // Encode the span id.
    if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldString)) != srs_success) {
        return srs_error_wrap(err, "key");
    }

    if ((err = SrsProtobufString::encode(b, span_id_)) != srs_success) {
        return srs_error_wrap(err, "encode span_id=%s", span_id_.c_str());
    }

    return err;
}

SrsOtelStatus::SrsOtelStatus()
{
    code_ = SrsApmStatusUnset;
}

SrsOtelStatus::~SrsOtelStatus()
{
}

uint64_t SrsOtelStatus::nb_bytes()
{
    uint64_t nn = 0;
    if (!message_.empty()) {
        nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(message_);
    }
    if (code_ != SrsApmStatusUnset) {
        nn += SrsProtobufKey::sizeof_key() + SrsProtobufVarints::sizeof_varint(code_);
    }
    return nn;
}

srs_error_t SrsOtelStatus::encode(SrsBuffer* b)
{
    srs_error_t err = srs_success;

    // Encode the message.
    if (!message_.empty()) {
        if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldString)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufString::encode(b, message_)) != srs_success) {
            return srs_error_wrap(err, "encode message=%s", message_.c_str());
        }
    }

    // Encode the status.
    if (code_ != SrsApmStatusUnset) {
        if ((err = SrsProtobufKey::encode(b, 3, SrsProtobufFieldEnum)) != srs_success) {
            return srs_error_wrap(err, "key");
        }

        if ((err = SrsProtobufVarints::encode(b, code_)) != srs_success) {
            return srs_error_wrap(err, "encode kind=%d", (int) code_);
        }
    }

    return err;
}

SrsApmClient* _srs_apm = NULL;

SrsApmContext::SrsApmContext(const std::string& name)
{
    name_ = name;
    kind_ = SrsApmKindUnspecified;

    set_trace_id(srs_random_str(16));
    set_span_id(srs_random_str(8));
    // We must not use time cache, or span render might fail.
    start_time_ = srs_update_system_time();
    ended_ = false;
    status_ = SrsApmStatusUnset;
    err_ = srs_success;

    span_ = NULL;
    parent_ = NULL;
}

SrsApmContext::~SrsApmContext()
{
    srs_freep(err_);

    // Span is not created by context, so should never free it here.
    if (span_) {
        span_->ctx_ = NULL;
        span_ = NULL;
    }

    // Free all child context.
    for (vector<SrsApmContext*>::iterator it = childs_.begin(); it != childs_.end(); ++it) {
        SrsApmContext* ctx = *it;
        srs_freep(ctx);
    }

    for (vector<SrsOtelAttribute*>::iterator it = attributes_.begin(); it != attributes_.end(); ++it) {
        SrsOtelAttribute* attribute = *it;
        srs_freep(attribute);
    }

    for (vector<SrsOtelLink*>::iterator it = links_.begin(); it != links_.end(); ++it) {
        SrsOtelLink* link = *it;
        srs_freep(link);
    }
}

void SrsApmContext::set_trace_id(std::string v)
{
    trace_id_ = v;
    str_trace_id_ = srs_string_dumps_hex(trace_id_.data(), trace_id_.length(), INT_MAX, 0, INT_MAX, 0);
}

void SrsApmContext::set_span_id(std::string v)
{
    span_id_ = v;
    str_span_id_ = srs_string_dumps_hex(span_id_.data(), span_id_.length(), INT_MAX, 0, INT_MAX, 0);
}

const char* SrsApmContext::format_trace_id()
{
    return str_trace_id_.c_str();
}

const char* SrsApmContext::format_span_id()
{
    return str_span_id_.c_str();
}

SrsApmContext* SrsApmContext::root()
{
    // Root is node that has no parent.
    if (!parent_) return this;

    // Use cached root or parent root, literally they should be the same.
    return parent_->root();
}

void SrsApmContext::set_parent(SrsApmContext* parent)
{
    if (ended_) return;

    parent_ = parent;
    if (parent) {
        set_trace_id(parent->trace_id_);
        parent_span_id_ = parent->span_id_;
        parent->childs_.push_back(this);
    }
}

void SrsApmContext::set_status(SrsApmStatus status, const std::string& description)
{
    if (ended_) return;

    status_ = status;
    if (status == SrsApmStatusError) {
        description_ = description;
    }
}

bool SrsApmContext::all_ended()
{
    if (!ended_) return false;

    for (vector<SrsApmContext*>::iterator it = childs_.begin(); it != childs_.end(); ++it) {
        SrsApmContext* ctx = *it;
        if (!ctx->all_ended()) return false;
    }

    return true;
}

int SrsApmContext::count_spans()
{
    int nn = span_ ? 1 : 0;

    for (vector<SrsApmContext*>::iterator it = childs_.begin(); it != childs_.end(); ++it) {
        SrsApmContext* ctx = *it;
        nn += ctx->count_spans();
    }

    return nn;
}

void SrsApmContext::update_trace_id(std::string v)
{
    if (ended_) return;

    set_trace_id(v);

    for (vector<SrsApmContext*>::iterator it = childs_.begin(); it != childs_.end(); ++it) {
        SrsApmContext* ctx = *it;
        ctx->set_trace_id(v);
    }
}

void SrsApmContext::link(SrsApmContext* to)
{
    if (ended_) return;

    links_.push_back(SrsOtelLink::create()->set_id(to->trace_id_, to->span_id_));
    attributes_.push_back(SrsOtelAttribute::kv("link_trace", to->str_trace_id_));
    attributes_.push_back(SrsOtelAttribute::kv("link_span", to->str_span_id_));

    to->links_.push_back(SrsOtelLink::create()->set_id(trace_id_, span_id_));
    to->attributes_.push_back(SrsOtelAttribute::kv("link_trace", str_trace_id_));
    to->attributes_.push_back(SrsOtelAttribute::kv("link_span", str_span_id_));

    srs_trace("APM: Link span %s(trace=%s, span=%s) with %s(trace=%s, span=%s)",
        name_.c_str(), str_trace_id_.c_str(), str_span_id_.c_str(), to->name_.c_str(),
        to->str_trace_id_.c_str(), to->str_span_id_.c_str()
    );
}

void SrsApmContext::end()
{
    if (ended_) return;
    ended_ = true;

    SrsOtelSpan* otel = new SrsOtelSpan();

    otel->name_ = name_;
    otel->trace_id_ = trace_id_;
    otel->span_id_ = span_id_;
    otel->parent_span_id_ = parent_span_id_;
    otel->kind_ = kind_;
    otel->start_time_unix_nano_ = start_time_ * 1000;
    // We must not use time cache, or span render might fail.
    otel->end_time_unix_nano_ = srs_update_system_time() * 1000;

    otel->status_->code_ = status_;
    otel->status_->message_ = description_;

    otel->attributes_.swap(attributes_);
    otel->links_.swap(links_);

    // Insert server ip if not exists.
    if (!otel->attr("ip")) {
        otel->attributes_.push_back(SrsOtelAttribute::kv("ip", srs_get_public_internet_address()));
    }
    if (!otel->attr("component")) {
        otel->attributes_.push_back(SrsOtelAttribute::kv("component", "srs"));
    }

    // See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/exceptions.md
    if (err_ != srs_success) {
        // Set the events for detail about the error.
        otel->events_.push_back(SrsOtelEvent::create("exception")
            ->add_attr(SrsOtelAttribute::kv("exception.type", srs_fmt("code_%d_%s", srs_error_code(err_), srs_error_code_str(err_).c_str())))
            ->add_attr(SrsOtelAttribute::kv("exception.message", srs_error_summary(err_)))
            ->add_attr(SrsOtelAttribute::kv("exception.stacktrace", srs_error_desc(err_)))
        );

        // We also use HTTP status code for APM to class the error. Note that it also works for non standard HTTP status
        // code, for example, SRS error codes.
        otel->attributes_.push_back(SrsOtelAttribute::kv("http.status_code", srs_fmt("%d", srs_error_code(err_))));
    }

    _srs_apm->snapshot(otel);
    srs_info("APM: Snapshot name=%s, trace=%s, span=%s", name_.c_str(), trace_id_.c_str(), span_id_.c_str());
}

SrsApmSpan::SrsApmSpan(const std::string& name)
{
    child_ = false;

    // Create the context for this span.
    ctx_ = new SrsApmContext(name);
    ctx_->span_ = this;

    // Default to internal span.
    ctx_->kind_ = SrsApmKindInternal;
}

SrsApmSpan::~SrsApmSpan()
{
    end();

    // Context might be freed by other span.
    if (!ctx_) return;

    // Span is not available.
    ctx_->span_ = NULL;

    // Dispose the context tree when all spans are ended.
    SrsApmContext* root = ctx_->root();

    // Only free the tree when free the last span, because we might create new span even all spans are ended only if the
    // root span has not been freed, for example, when RTMP client cycle done, we create a final span.
    if (root->count_spans() == 0 && root->all_ended()) {
        srs_freep(root);
    }
}

const char* SrsApmSpan::format_trace_id()
{
    return ctx_ ? ctx_->format_trace_id() : "";
}

const char* SrsApmSpan::format_span_id()
{
    return ctx_ ? ctx_->format_span_id() : "";
}

ISrsApmSpan* SrsApmSpan::set_name(const std::string& name)
{
    if (ctx_) ctx_->name_ = name;
    return this;
}

ISrsApmSpan* SrsApmSpan::set_kind(SrsApmKind kind)
{
    if (ctx_) ctx_->kind_ = kind;
    return this;
}

ISrsApmSpan* SrsApmSpan::as_child(ISrsApmSpan* parent)
{
    // Should not be child of multiple parent spans.
    if (child_) return this;

    // For child, always load parent from context.
    SrsApmSpan* span = dynamic_cast<SrsApmSpan*>(parent);
    if (span) {
        ctx_->set_parent(span->ctx_);
        child_ = true;
    }

    return this;
}

ISrsApmSpan* SrsApmSpan::set_status(SrsApmStatus status, const std::string& description)
{
    if (ctx_) ctx_->set_status(status, description);
    return this;
}

ISrsApmSpan* SrsApmSpan::record_error(srs_error_t err)
{
    if (ctx_) ctx_->err_ = srs_error_copy(err);
    return this;
}

ISrsApmSpan* SrsApmSpan::attr(const std::string& k, const std::string& v)
{
    if (ctx_) ctx_->attributes_.push_back(SrsOtelAttribute::kv(k, v));
    return this;
}

ISrsApmSpan* SrsApmSpan::link(ISrsApmSpan* span)
{
    SrsApmSpan* to = dynamic_cast<SrsApmSpan*>(span);
    if (ctx_ && span) ctx_->link(to->ctx_);
    return this;
}

ISrsApmSpan* SrsApmSpan::end()
{
    if (ctx_) ctx_->end();
    return this;
}

ISrsApmSpan* SrsApmSpan::extract(SrsAmf0Object* h)
{
    if (!ctx_) return this;

    SrsAmf0Any* prop = h->ensure_property_string("Traceparent");
    if (!prop) return this;

    std::string trace_parent = prop->to_str();
    vector<string> vs = srs_string_split(trace_parent, "-");
    if (vs.size() != 4) return this;

    // Update trace id for span and all its child spans.
    ctx_->update_trace_id(vs[1]);

    // Update the parent span id of span, note that the parent is NULL.
    ctx_->parent_span_id_ = vs[2];

    return this;
}

ISrsApmSpan* SrsApmSpan::inject(SrsAmf0Object* h)
{
    if (!ctx_) return this;

    string v = text_propagator();
    if (!v.empty()) h->set("Traceparent", SrsAmf0Any::str(v.c_str()));

    return this;
}

std::string SrsApmSpan::text_propagator()
{
    // FlagsSampled is a bitmask with the sampled bit set. A SpanContext
    // with the sampling bit set means the span is sampled.
    const uint8_t FlagsSampled = 0x01;
    const uint8_t supportedVersion = 0;
    static char buf[256];

    // For text based propagation, for example, HTTP header "Traceparent: 00-bb8dedf16c53ab4b6ceb1f4ca6d985bb-29247096662468ab-01"
    // About the "%.2x", please see https://www.quora.com/What-does-2x-do-in-C-code for detail.
    int nn = snprintf(buf, sizeof(buf), "%.2x-%s-%s-%.2x", supportedVersion, ctx_->trace_id_.c_str(), ctx_->span_id_.c_str(), FlagsSampled);
    if (nn > 0 && nn < (int)sizeof(buf)) {
        return string(buf, nn);
    }

    return "";
}

static int _srs_apm_key = -1;
void _srs_apm_destructor(void* arg)
{
    // We don't free the span, because it's not created by us.
    // Note that it's safe because keys will be reset when coroutine is terminated.
    //SrsApmSpan* span = (SrsApmSpan*)arg;
}

ISrsApmSpan* SrsApmSpan::store()
{
    ISrsApmSpan* span = _srs_apm->load();
    if (span == this) return this;

    int r0 = srs_thread_setspecific(_srs_apm_key, this);
    srs_assert(r0 == 0);
    return this;
}

ISrsApmSpan* SrsApmSpan::load()
{
    if (_srs_apm_key < 0) {
        int r0 = srs_key_create(&_srs_apm_key, _srs_apm_destructor);
        srs_assert(r0 == 0);
    }

    void* span = srs_thread_getspecific(_srs_apm_key);
    return (ISrsApmSpan*)span;
}

SrsApmClient::SrsApmClient()
{
    enabled_ = false;
    nn_spans_ = 0;
}

SrsApmClient::~SrsApmClient()
{
    for (vector<SrsOtelSpan*>::iterator it = spans_.begin(); it != spans_.end(); ++it) {
        SrsOtelSpan* span = *it;
        srs_freep(span);
    }
}

srs_error_t SrsApmClient::initialize()
{
    srs_error_t err = srs_success;

    enabled_ = _srs_config->get_tencentcloud_apm_enabled();
    if (!enabled_) {
        srs_trace("TencentCloud APM is disabled");
        return err;
    }

    team_ = _srs_config->get_tencentcloud_apm_team();
    token_ = _srs_config->get_tencentcloud_apm_token();
    endpoint_ = _srs_config->get_tencentcloud_apm_endpoint();
    service_name_ = _srs_config->get_tencentcloud_apm_service_name();
    debug_logging_ = _srs_config->get_tencentcloud_apm_debug_logging();
    srs_trace("Initialize TencentCloud APM, team=%s, token=%dB, endpoint=%s, service_name=%s, debug_logging=%d", team_.c_str(), token_.length(), endpoint_.c_str(), service_name_.c_str(), debug_logging_);

    // Check authentication, the team or token.
    if (team_.empty()) {
        return srs_error_new(ERROR_APM_AUTH, "No authentication team for APM");
    }
    if (token_.empty()) {
        return srs_error_new(ERROR_APM_AUTH, "No authentication token for APM");
    }

    // Please note that 4317 is for GRPC/HTTP2, while SRS only support HTTP and the port shoule be 55681.
    if (srs_string_contains(endpoint_, ":4317")) {
        return srs_error_new(ERROR_APM_ENDPOINT, "Port 4317 is for GRPC over HTTP2 for APM");
    }

    return err;
}

srs_error_t SrsApmClient::report()
{
    srs_error_t err = do_report();
    if (err != srs_success) {
        return srs_error_wrap(err, "team=%s, token=%dB", team_.c_str(), token_.length());
    }

    return err;
}

srs_error_t SrsApmClient::do_report()
{
    srs_error_t err = srs_success;

    if (spans_.empty()) return err;

    // Update statistaic for APM.
    nn_spans_ += spans_.size();

    SrsUniquePtr<SrsOtelExportTraceServiceRequest> sugar(new SrsOtelExportTraceServiceRequest());

    SrsOtelResourceSpans* rs = sugar->append();
    // See https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/resource/semantic_conventions
    rs->resource()->add_addr(SrsOtelAttribute::kv("service.name", service_name_));
    // For Tencent Cloud APM authentication, see https://console.cloud.tencent.com/apm/monitor/access
    rs->resource()->add_addr(SrsOtelAttribute::kv("token", token_));
    // For Tencent Cloud APM debugging, see https://console.cloud.tencent.com/apm/monitor/team
    rs->resource()->add_addr(SrsOtelAttribute::kv("tapm.team", team_));

    SrsOtelScopeSpans* spans = rs->append();
    spans->scope()->name_ = "srs";
    spans->swap(spans_);

    // Send out over HTTP1.
    uint64_t size = sugar->nb_bytes();
    if (size >= 5 * 1024 * 1024) {
        return srs_error_new(ERROR_APM_EXCEED_SIZE, "exceed 5MB actual %d", size);
    }

    SrsUniquePtr<char[]> buf(new char[size]);

    memset(buf.get(), 0, size);
    SrsBuffer b(buf.get(), size);
    if ((err = sugar->encode(&b)) != srs_success) {
        return srs_error_wrap(err, "encode log");
    }

    string body(buf.get(), size);

    // Write a CLS log to service specified by url.
    string url = "http://" + endpoint_ + "/v1/traces";

    SrsHttpUri uri;
    if ((err = uri.initialize(url)) != srs_success) {
        return srs_error_wrap(err, "http: post failed. url=%s", url.c_str());
    }

    SrsHttpClient http;
    if ((err = http.initialize(uri.get_schema(), uri.get_host(), uri.get_port())) != srs_success) {
        return srs_error_wrap(err, "http: init client");
    }

    // Setup the request.
    if (true) {
        http.set_header("Content-Type", "application/x-protobuf");
    }

    string path = uri.get_path();
    if (!uri.get_query().empty()) {
        path += "?" + uri.get_query();
    }

    // Start request and parse response.
    ISrsHttpMessage* msg_raw = NULL;
    if ((err = http.post(path, body, &msg_raw)) != srs_success) {
        return srs_error_wrap(err, "http: client post");
    }
    SrsUniquePtr<ISrsHttpMessage> msg(msg_raw);

    string res;
    uint16_t code = msg->status_code();
    if ((err = msg->body_read_all(res)) != srs_success) {
        return srs_error_wrap(err, "http: body read");
    }

    // ensure the http status is ok.
    if (code != SRS_CONSTS_HTTP_OK && code != SRS_CONSTS_HTTP_Created) {
        return srs_error_new(ERROR_HTTP_STATUS_INVALID, "http: status %d, body is %s", code, res.c_str());
    }

    if (debug_logging_) {
        string server_id = SrsStatistic::instance()->server_id();
        srs_trace("APM write team=%s, token=%dB, logs=%d, size=%dB, server_id=%s", team_.c_str(), token_.length(), spans->size(), body.length(), server_id.c_str());
    }

    return err;
}

bool SrsApmClient::enabled()
{
    return enabled_;
}

uint64_t SrsApmClient::nn_spans()
{
    return nn_spans_;
}

ISrsApmSpan* SrsApmClient::span(const std::string& name)
{
    if (!enabled_) return new ISrsApmSpan();
    return new SrsApmSpan(name);
}

ISrsApmSpan* SrsApmClient::dummy()
{
    return new ISrsApmSpan();
}

ISrsApmSpan* SrsApmClient::extract(ISrsApmSpan* v, SrsAmf0Object* h)
{
    SrsApmSpan* span = dynamic_cast<SrsApmSpan*>(v);
    if (span) span->extract(h);
    return v;
}

ISrsApmSpan* SrsApmClient::inject(ISrsApmSpan* v, SrsAmf0Object* h)
{
    SrsApmSpan* span = dynamic_cast<SrsApmSpan*>(v);
    if (span) span->inject(h);
    return v;
}

void SrsApmClient::store(ISrsApmSpan* v)
{
    SrsApmSpan* span = dynamic_cast<SrsApmSpan*>(v);
    if (span) span->store();
}

ISrsApmSpan* SrsApmClient::load()
{
    static ISrsApmSpan* dummy = new ISrsApmSpan();
    ISrsApmSpan* span = SrsApmSpan::load();
    return span ? span : dummy;
}

void SrsApmClient::snapshot(SrsOtelSpan* span)
{
    spans_.push_back(span);
}
#endif

