prod/native/extension/code/ModuleFunctions.cpp (236 lines of code) (raw):

/* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include "ModuleFunctions.h" #include "ConfigurationStorage.h" #include "LoggerInterface.h" #include "LogFeature.h" #include "RequestScope.h" #include "ModuleGlobals.h" #include "ModuleFunctionsImpl.h" #include "InternalFunctionInstrumentation.h" #include "transport/HttpTransportAsync.h" #include "PhpBridge.h" #include "OtlpExporter/LogsConverter.h" #include "OtlpExporter/MetricConverter.h" #include "OtlpExporter/SpanConverter.h" #include <main/php.h> #include <Zend/zend_API.h> #include <Zend/zend_closures.h> #include <Zend/zend_exceptions.h> // bool elastic_otel_is_enabled() PHP_FUNCTION(elastic_otel_is_enabled) { RETVAL_BOOL(false); ZEND_PARSE_PARAMETERS_NONE(); RETVAL_BOOL(EAPM_CFG(enabled)); } ZEND_BEGIN_ARG_INFO_EX(elastic_otel_get_config_option_by_name_arginfo, 0, 0, 1) ZEND_ARG_TYPE_INFO(/* pass_by_ref: */ 0, optionName, IS_STRING, /* allow_null: */ 0) ZEND_END_ARG_INFO() /* elastic_otel_get_config_option_by_name( string $optionName ): mixed */ PHP_FUNCTION(elastic_otel_get_config_option_by_name) { ZVAL_NULL(return_value); char *optionName = nullptr; size_t optionNameLength = 0; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(optionName, optionNameLength) ZEND_PARSE_PARAMETERS_END(); elasticApmGetConfigOption({optionName, optionNameLength}, /* out */ return_value); } ZEND_BEGIN_ARG_INFO_EX(elastic_otel_log_feature_arginfo, /* _unused: */ 0, /* return_reference: */ 0, /* required_num_args: */ 7) ZEND_ARG_TYPE_INFO(/* pass_by_ref: */ 0, isForced, IS_LONG, /* allow_null: */ 0) ZEND_ARG_TYPE_INFO(/* pass_by_ref: */ 0, level, IS_LONG, /* allow_null: */ 0) ZEND_ARG_TYPE_INFO(/* pass_by_ref: */ 0, feature, IS_LONG, /* allow_null: */ 0) ZEND_ARG_TYPE_INFO(/* pass_by_ref: */ 0, file, IS_STRING, /* allow_null: */ 0) ZEND_ARG_TYPE_INFO(/* pass_by_ref: */ 0, line, IS_LONG, /* allow_null: */ 1) ZEND_ARG_TYPE_INFO(/* pass_by_ref: */ 0, func, IS_STRING, /* allow_null: */ 0) ZEND_ARG_TYPE_INFO(/* pass_by_ref: */ 0, message, IS_STRING, /* allow_null: */ 0) ZEND_END_ARG_INFO() /* {{{ elastic_otel_log_feature( * int $isForced, * int $level, * int $feature, * string $file, * ?int $line, * string $func, * string $message * ): void */ PHP_FUNCTION(elastic_otel_log_feature) { zend_long isForced = 0; zend_long level = 0; zend_long feature = 0; char *file = nullptr; size_t fileLength = 0; zend_long line = 0; bool lineNull = true; char *func = nullptr; size_t funcLength = 0; char *message = nullptr; size_t messageLength = 0; ZEND_PARSE_PARAMETERS_START(/* min_num_args: */ 7, /* max_num_args: */ 7) Z_PARAM_LONG(isForced) Z_PARAM_LONG(level) Z_PARAM_LONG(feature) Z_PARAM_STRING(file, fileLength) Z_PARAM_LONG_OR_NULL(line, lineNull) Z_PARAM_STRING(func, funcLength) Z_PARAM_STRING(message, messageLength) ZEND_PARSE_PARAMETERS_END(); if (isForced || ELASTICAPM_G(globals)->logger_->doesFeatureMeetsLevelCondition(static_cast<LogLevel>(level), static_cast<elasticapm::php::LogFeature>(feature))) { if (lineNull) { ELASTICAPM_G(globals)->logger_->printf(static_cast<LogLevel>(level), "[" PRsv "] [" PRsv "] [" PRsv "] " PRsv, PRsvArg(elasticapm::php::getLogFeatureName(static_cast<elasticapm::php::LogFeature>(feature))), PRcsvArg(file, fileLength), PRcsvArg(func, funcLength), PRcsvArg(message, messageLength)); return; } ELASTICAPM_G(globals)->logger_->printf(static_cast<LogLevel>(level), "[" PRsv "] [" PRsv ":%d] [" PRsv "] " PRsv, PRsvArg(elasticapm::php::getLogFeatureName(static_cast<elasticapm::php::LogFeature>(feature))), PRcsvArg(file, fileLength), line, PRcsvArg(func, funcLength), PRcsvArg(message, messageLength)); } } /* }}} */ ZEND_BEGIN_ARG_INFO_EX(elastic_otel_get_last_thrown_arginfo, /* _unused */ 0, /* return_reference: */ 0, /* required_num_args: */ 0) ZEND_END_ARG_INFO() /* {{{ elastic_otel_get_last_thrown(): mixed */ PHP_FUNCTION(elastic_otel_get_last_thrown) { ZVAL_NULL(/* out */ return_value); //TODO elasticApmGetLastThrown(/* out */ return_value); } /* }}} */ ZEND_BEGIN_ARG_INFO_EX(elastic_otel_get_last_php_error_arginfo, /* _unused */ 0, /* return_reference: */ 0, /* required_num_args: */ 0) ZEND_END_ARG_INFO() /* {{{ elastic_otel_get_last_error(): array */ PHP_FUNCTION(elastic_otel_get_last_php_error) { ZVAL_NULL(/* out */ return_value); //TODO elasticApmGetLastPhpError(/* out */ return_value); } /* }}} */ ZEND_BEGIN_ARG_INFO(elastic_otel_no_paramters_arginfo, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(elastic_otel_hook_arginfo, 0, 2, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, class, IS_STRING, 1) ZEND_ARG_TYPE_INFO(0, function, IS_STRING, 0) ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, pre, Closure, 1, "null") ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, post, Closure, 1, "null") ZEND_END_ARG_INFO() PHP_FUNCTION(elastic_otel_hook) { zend_string *class_name = nullptr; zend_string *function_name = nullptr; zval *pre = NULL; zval *post = NULL; ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STR_OR_NULL(class_name) Z_PARAM_STR(function_name) Z_PARAM_OPTIONAL Z_PARAM_OBJECT_OF_CLASS_OR_NULL(pre, zend_ce_closure) Z_PARAM_OBJECT_OF_CLASS_OR_NULL(post, zend_ce_closure) ZEND_PARSE_PARAMETERS_END(); std::string_view className = class_name ? std::string_view{ZSTR_VAL(class_name), ZSTR_LEN(class_name)} : std::string_view{}; std::string_view functionName = function_name ? std::string_view{ZSTR_VAL(function_name), ZSTR_LEN(function_name)} : std::string_view{}; // if (!EAPM_GL(requestScope_)->isFunctional()) { // ELOGF_DEBUG(EAPM_GL(logger_), MODULE, "elastic_otel_hook. Can't instrument " PRsv "::" PRsv " beacuse agent is not functional.", PRsvArg(className), PRsvArg(functionName)); // RETURN_BOOL(false); // return; // } RETURN_BOOL(elasticapm::php::instrumentFunction(EAPM_GL(logger_).get(), className, functionName, pre, post)); } ZEND_BEGIN_ARG_INFO_EX(ArgInfoInitialize, 0, 0, 3) ZEND_ARG_TYPE_INFO(0, endpoint, IS_STRING, 1) ZEND_ARG_TYPE_INFO(0, contentType, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, headers, IS_ARRAY, 0) ZEND_END_ARG_INFO() PHP_FUNCTION(initialize) { zend_string *endpoint; zend_string *contentType; zval *headers; double timeout = 0.0; // s long retryDelay = 0; // ms long maxRetries = 0; ZEND_PARSE_PARAMETERS_START(6, 6) Z_PARAM_STR(endpoint) Z_PARAM_STR(contentType) Z_PARAM_ARRAY(headers) Z_PARAM_DOUBLE(timeout) Z_PARAM_LONG(retryDelay) Z_PARAM_LONG(maxRetries) ZEND_PARSE_PARAMETERS_END(); HashTable *ht = Z_ARRVAL_P(headers); zval *value = nullptr; zend_string *arrkey = nullptr; std::vector<std::pair<std::string_view, std::string_view>> endpointHeaders; ZEND_HASH_FOREACH_STR_KEY_VAL(ht, arrkey, value) { if (value && Z_TYPE_P(value) == IS_STRING) { endpointHeaders.emplace_back(std::make_pair(std::string_view(ZSTR_VAL(arrkey), ZSTR_LEN(arrkey)), std::string_view(Z_STRVAL_P(value), Z_STRLEN_P(value)))); } } ZEND_HASH_FOREACH_END(); EAPM_GL(httpTransportAsync_)->initializeConnection(std::string(ZSTR_VAL(endpoint), ZSTR_LEN(endpoint)), ZSTR_HASH(endpoint), std::string(ZSTR_VAL(contentType), ZSTR_LEN(contentType)), endpointHeaders, std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::duration<double>(timeout)), static_cast<std::size_t>(maxRetries), std::chrono::milliseconds(retryDelay)); } ZEND_BEGIN_ARG_INFO_EX(ArgInfoSend, 0, 0, 2) ZEND_ARG_TYPE_INFO(0, endpoint, IS_STRING, 1) ZEND_ARG_TYPE_INFO(0, payload, IS_STRING, 1) ZEND_END_ARG_INFO() PHP_FUNCTION(enqueue) { zend_string *payload = nullptr; zend_string *endpoint = nullptr; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STR(endpoint) Z_PARAM_STR(payload) ZEND_PARSE_PARAMETERS_END(); EAPM_GL(httpTransportAsync_)->enqueue(ZSTR_HASH(endpoint), std::span<std::byte>(reinterpret_cast<std::byte *>(ZSTR_VAL(payload)), ZSTR_LEN(payload))); } ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(elastic_otel_force_set_object_property_value_arginfo, 0, 3, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) ZEND_ARG_TYPE_INFO(0, property_name, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) ZEND_END_ARG_INFO() PHP_FUNCTION(force_set_object_property_value) { zend_object *object = nullptr; zend_string *property_name = nullptr; zval *value = nullptr; ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_OBJ(object) Z_PARAM_STR(property_name) Z_PARAM_ZVAL(value) ZEND_PARSE_PARAMETERS_END(); RETURN_BOOL(elasticapm::php::forceSetObjectPropertyValue(object, property_name, value)); } ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_elastic_convert_spans, 0, 1, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, batch, IS_ITERABLE, 0) ZEND_END_ARG_INFO() PHP_FUNCTION(convert_spans) { zval *batch; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(batch) ZEND_PARSE_PARAMETERS_END(); try { elasticapm::php::SpanConverter converter; auto res = converter.getStringSerialized(elasticapm::php::AutoZval(batch)); RETURN_STRINGL(res.c_str(), res.length()); } catch (std::exception const &e) { ELOGF_WARNING(EAPM_GL(logger_).get(), OTLPEXPORT, "Failed to serialize spans batch: '%s'", e.what()); zend_throw_exception_ex(NULL, 0, "Failed to serialize spans batch: '%s'", e.what()); RETURN_THROWS(); } } ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_elastic_convert_logs, 0, 1, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, batch, IS_ITERABLE, 0) ZEND_END_ARG_INFO() PHP_FUNCTION(convert_logs) { zval *batch; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(batch) ZEND_PARSE_PARAMETERS_END(); try { elasticapm::php::LogsConverter converter; auto res = converter.getStringSerialized(elasticapm::php::AutoZval(batch)); RETURN_STRINGL(res.c_str(), res.length()); } catch (std::exception const &e) { ELOGF_WARNING(EAPM_GL(logger_).get(), OTLPEXPORT, "Failed to serialize logs batch: '%s'", e.what()); zend_throw_exception_ex(NULL, 0, "Failed to serialize logs batch: '%s'", e.what()); RETURN_THROWS(); } } ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_elastic_convert_metrics, 0, 1, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, batch, IS_ITERABLE, 0) ZEND_END_ARG_INFO() PHP_FUNCTION(convert_metrics) { zval *batch; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(batch) ZEND_PARSE_PARAMETERS_END(); try { elasticapm::php::MetricConverter converter; auto res = converter.getStringSerialized(elasticapm::php::AutoZval(batch)); RETURN_STRINGL(res.c_str(), res.length()); } catch (std::exception const &e) { ELOGF_WARNING(EAPM_GL(logger_).get(), OTLPEXPORT, "Failed to serialize metrics batch: '%s'", e.what()); zend_throw_exception_ex(NULL, 0, "Failed to serialize metrics batch: '%s'", e.what()); RETURN_THROWS(); } } // clang-format off const zend_function_entry elastic_otel_functions[] = { PHP_FE( elastic_otel_is_enabled, elastic_otel_no_paramters_arginfo ) PHP_FE( elastic_otel_get_config_option_by_name, elastic_otel_get_config_option_by_name_arginfo ) PHP_FE( elastic_otel_log_feature, elastic_otel_log_feature_arginfo ) PHP_FE( elastic_otel_get_last_thrown, elastic_otel_get_last_thrown_arginfo ) PHP_FE( elastic_otel_get_last_php_error, elastic_otel_get_last_php_error_arginfo ) PHP_FE( elastic_otel_hook, elastic_otel_hook_arginfo ) ZEND_NS_FE( "Elastic\\OTel\\HttpTransport", initialize, ArgInfoInitialize) ZEND_NS_FE( "Elastic\\OTel\\HttpTransport", enqueue, elastic_otel_no_paramters_arginfo) ZEND_NS_FE( "Elastic\\OTel\\InferredSpans", force_set_object_property_value, elastic_otel_force_set_object_property_value_arginfo) ZEND_NS_FE( "Elastic\\OTel\\OtlpExporters", convert_spans, arginfo_elastic_convert_spans) ZEND_NS_FE( "Elastic\\OTel\\OtlpExporters", convert_logs, arginfo_elastic_convert_logs) ZEND_NS_FE( "Elastic\\OTel\\OtlpExporters", convert_metrics, arginfo_elastic_convert_metrics) PHP_FE_END }; // clang-format on