cpp_client_telemetry/lib/api/capi.cpp

424 строки
13 KiB
C++

/* Copyright (c) Microsoft. All rights reserved. */
#include "mat/config.h"
#ifdef _WIN32
#define MATSDK_DECLSPEC __declspec(dllexport)
#endif
#ifndef ANDROID
#include "http/HttpClient_CAPI.hpp"
#endif
#include "LogManagerProvider.hpp"
#include "mat.h"
#include "pal/TaskDispatcher_CAPI.hpp"
#include "utils/Utils.hpp"
#include "pal/PAL.hpp"
#include "CommonFields.h"
#include <mutex>
#include <map>
#include <cstdint>
static const char * libSemver = TELEMETRY_EVENTS_VERSION;
using namespace MAT;
static std::mutex mtx;
static std::map<evt_handle_t, capi_client> clients;
/// <summary>
/// Convert from C API handle to internal C API client struct.
///
/// This method may be used for C API flow debugging, i.e. to obtain the
/// underlying instance of ILogManager and attach a debug event callback.
///
/// NOTE: method is not guaranteed to be ABI-stable and should not be used
/// across dynamic / shared library boundary. Underlying ILogManager /
/// ILogConfiguration are implemented in C++ and do not provide ABI-stable
/// guarantee from one SDK version to another.
/// </summary>
capi_client * MAT::capi_get_client(evt_handle_t handle)
{
LOCKGUARD(mtx);
const auto it = clients.find(handle);
return (it != clients.cend()) ? &(it->second) : nullptr;
}
/// <summary>
/// Remove C API handle from active client tracking struct.
/// </summary>
void remove_client(evt_handle_t handle)
{
LOCKGUARD(mtx);
clients.erase(handle);
}
#define VERIFY_CLIENT_HANDLE(client, ctx) \
if (ctx==nullptr) \
{ \
return EFAULT; /* bad address */ \
}; \
auto client = MAT::capi_get_client(ctx->handle); \
if ((client == nullptr) || (client->logmanager == nullptr)) \
{ \
return ENOENT; \
};
evt_status_t mat_open_core(
evt_context_t *ctx,
const char* config,
http_send_fn_t httpSendFn,
http_cancel_fn_t httpCancelFn,
task_dispatcher_queue_fn_t taskDispatcherQueueFn,
task_dispatcher_cancel_fn_t taskDispatcherCancelFn,
task_dispatcher_join_fn_t taskDispatcherJoinFn)
{
if ((config == nullptr) || (config[0] == 0))
{
// Invalid configuration
return EFAULT;
}
evt_handle_t code = static_cast<evt_handle_t>(hashCode(config));
bool isHashFound = false;
// Find the next available spare hashcode
do
{
auto client = MAT::capi_get_client(code);
if (client != nullptr)
{
if (client->ctx_data == config)
{
// Guest instance with the same config is already open
return EALREADY;
}
// hash code is assigned to another client, increment and retry
// with the next empty slot
code++;
continue;
};
isHashFound = true;
} while (!isHashFound);
// JSON configuration must start with {
if (config[0] == '{')
{
// Create new configuration object from JSON
clients[code].config = MAT::FromJSON(config);
}
else
{
// Assume that the config string is a token, not JSON.
// That approach allows to consume the lightweght C API without JSON parser compiled in.
std::string moduleName = "CAPI-Client-";
moduleName += std::to_string(code);
clients[code].config =
{
{ CFG_STR_FACTORY_NAME, moduleName },
{ "version", "1.0.0" },
{ CFG_MAP_FACTORY_CONFIG,
{
{ CFG_STR_FACTORY_HOST, "*" },
{ CFG_STR_CONTEXT_SCOPE, CONTEXT_SCOPE_NONE }
}
},
{ CFG_STR_PRIMARY_TOKEN, config }
};
}
// Remember the original config string. Needed to avoid hash code collisions
clients[code].ctx_data = config;
#ifndef ANDROID
// Create custom HttpClient
if (httpSendFn != nullptr && httpCancelFn != nullptr)
{
try
{
auto http = std::make_shared<HttpClient_CAPI>(httpSendFn, httpCancelFn);
clients[code].http = http;
clients[code].config.AddModule(CFG_MODULE_HTTP_CLIENT, http);
}
catch (...)
{
return EFAULT;
}
}
#endif
// Create custom worker thread
if (taskDispatcherQueueFn != nullptr && taskDispatcherCancelFn != nullptr && taskDispatcherJoinFn != nullptr)
{
try
{
auto taskDispatcher = std::make_shared<PAL::TaskDispatcher_CAPI>(taskDispatcherQueueFn, taskDispatcherCancelFn, taskDispatcherJoinFn);
clients[code].taskDispatcher = taskDispatcher;
clients[code].config.AddModule(CFG_MODULE_TASK_DISPATCHER, taskDispatcher);
}
catch (...)
{
return EFAULT;
}
}
status_t status = static_cast<status_t>(EFAULT);
clients[code].logmanager = LogManagerProvider::CreateLogManager(clients[code].config, status);
// Verify that the instance pointer is valid
if (clients[code].logmanager == nullptr)
{
status = static_cast<status_t>(EFAULT);
}
ctx->result = static_cast<evt_status_t>(status);
ctx->handle = code;
return ctx->result;
}
evt_status_t mat_open(evt_context_t *ctx)
{
if (ctx == nullptr)
{
return EFAULT; /* bad address */
};
char* config = static_cast<char *>(ctx->data);
return mat_open_core(ctx, config, nullptr, nullptr, nullptr, nullptr, nullptr);
}
evt_status_t mat_open_with_params(evt_context_t *ctx)
{
if (ctx == nullptr)
{
return EFAULT; /* bad address */
};
evt_open_with_params_data_t* data = static_cast<evt_open_with_params_data_t*>(ctx->data);
if ((data == nullptr) || (data->params == nullptr))
{
// Invalid param data
return EFAULT;
}
http_send_fn_t httpSendFn = nullptr;
http_cancel_fn_t httpCancelFn = nullptr;
task_dispatcher_queue_fn_t taskDispatcherQueueFn = nullptr;
task_dispatcher_cancel_fn_t taskDispatcherCancelFn = nullptr;
task_dispatcher_join_fn_t taskDispatcherJoinFn = nullptr;
for (int32_t i = 0; i < data->paramsCount; ++i) {
const evt_open_param_t& param = data->params[i];
switch (param.type) {
case OPEN_PARAM_TYPE_HTTP_HANDLER_SEND:
httpSendFn = reinterpret_cast<http_send_fn_t>(param.data);
break;
case OPEN_PARAM_TYPE_HTTP_HANDLER_CANCEL:
httpCancelFn = reinterpret_cast<http_cancel_fn_t>(param.data);
break;
case OPEN_PARAM_TYPE_TASK_DISPATCHER_QUEUE:
taskDispatcherQueueFn = reinterpret_cast<task_dispatcher_queue_fn_t>(param.data);
break;
case OPEN_PARAM_TYPE_TASK_DISPATCHER_CANCEL:
taskDispatcherCancelFn = reinterpret_cast<task_dispatcher_cancel_fn_t>(param.data);
break;
case OPEN_PARAM_TYPE_TASK_DISPATCHER_JOIN:
taskDispatcherJoinFn = reinterpret_cast<task_dispatcher_join_fn_t>(param.data);
break;
}
}
return mat_open_core(ctx, data->config, httpSendFn, httpCancelFn, taskDispatcherQueueFn, taskDispatcherCancelFn, taskDispatcherJoinFn);
}
/**
* Marashal C struct to C++ API
*/
evt_status_t mat_log(evt_context_t *ctx)
{
VERIFY_CLIENT_HANDLE(client, ctx);
ILogConfiguration & config = client->config;
evt_prop *evt = static_cast<evt_prop*>(ctx->data);
EventProperties props;
props.unpack(evt, ctx->size);
auto m = props.GetProperties();
EventProperty &prop = m[COMMONFIELDS_IKEY];
std::string token = prop.as_string;
props.erase(COMMONFIELDS_IKEY);
// Privacy feature for OTEL C API client:
//
// C API customer that does not explicitly pass down JSON
// config["config]["scope"] = COMMONFIELDS_SCOPE_ALL;
//
// should not be able to capture the host's context vars.
std::string scope = CONTEXT_SCOPE_NONE;
{
MAT::VariantMap &config_map = config[CFG_MAP_FACTORY_CONFIG];
const auto & it = config_map.find(CFG_STR_CONTEXT_SCOPE);
if (it != config_map.cend())
{
scope = static_cast<const char *>(it->second);
// Specifying "*" in JSON config allows Guest C API logger to capture Host context variables
if (scope == CONTEXT_SCOPE_ALL)
{
scope = CONTEXT_SCOPE_EMPTY;
}
}
}
const auto & it = m.find(COMMONFIELDS_EVENT_SOURCE);
std::string source = ((it != m.cend()) && (it->second.type == EventProperty::TYPE_STRING)) ? it->second.as_string : "";
ILogger *logger = client->logmanager->GetLogger(token, source, scope);
if (logger == nullptr)
{
ctx->result = EFAULT; /* invalid address */
}
else
{
// TODO: [MG] - verify guest configuration and decide if we need to overwrite
logger->SetParentContext(nullptr);
logger->LogEvent(props);
ctx->result = EOK;
}
return ctx->result;
}
evt_status_t mat_close(evt_context_t *ctx)
{
VERIFY_CLIENT_HANDLE(client, ctx);
const auto result = static_cast<evt_status_t>(LogManagerProvider::Release(client->logmanager->GetLogConfiguration()));
if (client->http != nullptr)
{
client->http = nullptr;
}
if (client->taskDispatcher != nullptr)
{
client->taskDispatcher = nullptr;
}
remove_client(ctx->handle);
ctx->result = result;
return result;
}
evt_status_t mat_pause(evt_context_t *ctx)
{
VERIFY_CLIENT_HANDLE(client, ctx);
const auto result = static_cast<evt_status_t>(client->logmanager->PauseTransmission());
ctx->result = result;
return result;
}
evt_status_t mat_resume(evt_context_t *ctx)
{
VERIFY_CLIENT_HANDLE(client, ctx);
const auto result = static_cast<evt_status_t>(client->logmanager->ResumeTransmission());
ctx->result = result;
return result;
}
evt_status_t mat_upload(evt_context_t *ctx)
{
VERIFY_CLIENT_HANDLE(client, ctx);
const auto result = static_cast<evt_status_t>(client->logmanager->UploadNow());
ctx->result = result;
return result;
}
evt_status_t mat_flush(evt_context_t *ctx)
{
VERIFY_CLIENT_HANDLE(client, ctx);
const auto result = static_cast<evt_status_t>(client->logmanager->Flush());
ctx->result = result;
return result;
}
extern "C" {
/**
* Simple stable backwards- / forward- compatible ABI interface
*/
evt_status_t EVTSDK_LIBABI_CDECL evt_api_call_default(evt_context_t *ctx)
{
evt_status_t result = EFAIL;
if (ctx != nullptr)
{
switch (ctx->call)
{
case EVT_OP_LOAD:
result = ENOTSUP;
break;
case EVT_OP_UNLOAD:
result = ENOTSUP;
break;
case EVT_OP_OPEN:
result = mat_open(ctx);
break;
case EVT_OP_OPEN_WITH_PARAMS:
result = mat_open_with_params(ctx);
break;
case EVT_OP_CLOSE:
result = mat_close(ctx);
break;
case EVT_OP_CONFIG:
result = ENOTSUP;
break;
case EVT_OP_LOG:
result = mat_log(ctx);
break;
case EVT_OP_PAUSE:
result = mat_pause(ctx);
break;
case EVT_OP_RESUME:
result = mat_resume(ctx);
break;
case EVT_OP_UPLOAD:
result = mat_upload(ctx);
break;
case EVT_OP_FLUSH:
result = mat_flush(ctx);
break;
case EVT_OP_VERSION:
// TODO: [MG] - add handling of ctx->data passed by caller inline stub.
// If there is API version mismatch between the stub and lib impl, then
// depending on version passed down to SDK - lib may need to figure out
// how to handle the mismatch. For now the onus of verifying for SDK
// compatibility is on API caller.
// Inlined stub version passed by the caller - compiled in executable.
LOG_TRACE("header version: %s", ctx->data);
// Actual SDK library implementation version - compiled in the library.
ctx->data = (void*)libSemver;
LOG_TRACE("library version: %s", ctx->data);
result = STATUS_SUCCESS;
break;
// Add more OPs here
default:
result = ENOTSUP;
break;
}
}
return result;
}
}