prod/native/libcommon/code/RequestScope.h (143 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. */ #pragma once #include "ConfigurationStorage.h" #include "CommonUtils.h" #include "DependencyAutoLoaderGuard.h" #include "Diagnostics.h" #include "InferredSpans.h" #include "LoggerInterface.h" #include "PeriodicTaskExecutor.h" #include "PhpBridgeInterface.h" #include "PhpSapi.h" #include "SharedMemoryState.h" #include <memory> #include <string_view> namespace elasticapm::php { class RequestScope { public: using clearHooks_t = std::function<void()>; using getPeriodicTaskExecutor_t = std::function<std::shared_ptr<PeriodicTaskExecutor>()>; RequestScope(std::shared_ptr<LoggerInterface> log, std::shared_ptr<PhpBridgeInterface> bridge, std::shared_ptr<PhpSapi> sapi, std::shared_ptr<SharedMemoryState> sharedMemory, std::shared_ptr<DependencyAutoLoaderGuard> dependencyGuard, std::shared_ptr<InferredSpans> inferredSpans, std::shared_ptr<ConfigurationStorage> config, clearHooks_t clearHooks, getPeriodicTaskExecutor_t getPeriodicTaskExecutor) : log_(log), bridge_(std::move(bridge)), sapi_(std::move(sapi)), sharedMemory_(sharedMemory), dependencyGuard_(dependencyGuard), inferredSpans_(std::move(inferredSpans)), config_(config), clearHooks_(std::move(clearHooks)), getPeriodicTaskExecutor_(std::move(getPeriodicTaskExecutor)) { } void onRequestInit() { ELOGF_DEBUG(log_, REQUEST, "%s", __FUNCTION__); resetRequest(); if (!sapi_->isSupported()) { ELOGF_DEBUG(log_, REQUEST, "SAPI '%s' not supported", sapi_->getName().data()); return; } config_->update(); if (!(*config_)->enabled) { ELOGF_DEBUG(log_, REQUEST, "Global instrumentation not enabled"); return; } requestCounter_++; if (requestCounter_ == 1) { dependencyGuard_->setBootstrapPath((*config_)->bootstrap_php_part_file); } auto requestStartTime = std::chrono::system_clock::now(); bridge_->enableAccessToServerGlobal(); preloadDetected_ = requestCounter_ == 1 ? bridge_->detectOpcachePreload() : false; dependencyGuard_->onRequestInit(); if (requestCounter_ == 1 && preloadDetected_) { ELOGF_DEBUG(log_, REQUEST, "opcache.preload request detected on init"); return; } else if (!preloadDetected_ && requestCounter_ <= 2) { auto const &diagnosticFile = (*config_)->debug_diagnostic_file; if (!diagnosticFile.empty()) { if (sharedMemory_->shouldExecuteOneTimeTaskAmongWorkers()) { try { // TODO log supportability info elasticapm::utils::storeDiagnosticInformation(elasticapm::utils::getParameterizedString(diagnosticFile), *(bridge_)); } catch (std::exception const &e) { ELOGF_WARNING(log_, REQUEST, "Unable to write agent diagnostics: %s", e.what()); } } } } if (!bridge_->isScriptRestricedByOpcacheAPI() && bridge_->detectOpcacheRestartPending()) { ELOGF_WARNING(log_, REQUEST, "Detected that opcache reset is in a pending state. Instrumentation has been disabled for this request. There may be warnings or errors logged for this request."); return; } bootstrapSuccessfull_ = bootstrapPHPSideInstrumentation(requestStartTime); if (bootstrapSuccessfull_ && (*config_)->inferred_spans_enabled) { auto periodicTaskExecutor = getPeriodicTaskExecutor_(); auto interval = (*config_)->inferred_spans_sampling_interval; if (interval.count() == 0) { interval = std::chrono::milliseconds{ConfigurationSnapshot().ELASTIC_OTEL_CFG_OPT_NAME_INFERRED_SPANS_SAMPLING_INTERVAL}; ELOGF_DEBUG(log_, REQUEST, "inferred spans thread interval too low, forced to default %zums", interval.count()); } ELOGF_DEBUG(log_, REQUEST, "resuming inferred spans thread with sampling interval %zums", interval.count()); inferredSpans_->setInterval(interval); inferredSpans_->reset(); periodicTaskExecutor->setInterval(interval); periodicTaskExecutor->resumePeriodicTasks(); } } void onRequestShutdown() { ELOGF_DEBUG(log_, REQUEST, "%s", __FUNCTION__); if (preloadDetected_) { ELOGF_DEBUG(log_, REQUEST, "opcache.preload request detected on shutdown"); return; } if (!bootstrapSuccessfull_) { ELOGF_DEBUG(log_, REQUEST, "onRequestShutdown bootstrap not successfull"); return; } if ((*config_)->inferred_spans_enabled) { ELOGF_DEBUG(log_, REQUEST, "pausing inferred spans thread"); getPeriodicTaskExecutor_()->suspendPeriodicTasks(); } if (!bridge_->callPHPSideExitPoint()) { ELOGF_ERROR(log_, REQUEST, "callPHPSideExitPoint failed"); } } void onRequestPostDeactivate() { ELOGF_DEBUG(log_, REQUEST, "%s", __FUNCTION__); dependencyGuard_->onRequestShutdown(); resetRequest(); if (!bootstrapSuccessfull_) { return; } } void handleError(int type, std::string_view errorFilename, uint32_t errorLineno, std::string_view message, bool callPHPHandler) { ELOGF_DEBUG(log_, REQUEST, "RequestScope::handleError calling handler: %d, type: %d fn: %s:%d msg: %s", callPHPHandler, type, errorFilename.data(), errorLineno, message.data()); if (callPHPHandler) { bridge_->callPHPSideErrorHandler(type, errorFilename, errorLineno, message); } } bool isFunctional() { return bootstrapSuccessfull_; } protected: bool bootstrapPHPSideInstrumentation(std::chrono::system_clock::time_point requestStartTime) { using namespace std::string_view_literals; try { ELOGF_DEBUG(log_, REQUEST, "Loading bootstrap_php_part_file: '%s' ...", (*config_)->bootstrap_php_part_file.c_str()); bridge_->compileAndExecuteFile((*config_)->bootstrap_php_part_file); ELOGF_DEBUG(log_, REQUEST, "Executing entry point in bootstrap_php_part_file: '%s' ...", (*config_)->bootstrap_php_part_file.c_str()); bridge_->callPHPSideEntryPoint(log_->getMaxLogLevel(), requestStartTime); } catch (std::exception const &e) { ELOGF_CRITICAL(log_, REQUEST, "Unable to bootstrap PHP-side instrumentation '%s'", e.what()); return false; } ELOGF_DEBUG(log_, REQUEST, "Executed entry point in bootstrap_php_part_file: %s", (*config_)->bootstrap_php_part_file.data()); return true; } void resetRequest() { bootstrapSuccessfull_ = false; clearHooks_(); } private: std::shared_ptr<LoggerInterface> log_; std::shared_ptr<PhpBridgeInterface> bridge_; std::shared_ptr<PhpSapi> sapi_; std::shared_ptr<SharedMemoryState> sharedMemory_; std::shared_ptr<DependencyAutoLoaderGuard> dependencyGuard_; std::shared_ptr<InferredSpans> inferredSpans_; std::shared_ptr<ConfigurationStorage> config_; clearHooks_t clearHooks_; getPeriodicTaskExecutor_t getPeriodicTaskExecutor_; size_t requestCounter_ = 0; bool bootstrapSuccessfull_ = false; bool preloadDetected_ = false; }; } // namespace elasticapm::php