Merge branch 'MDL-87045-main-minimal' of https://github.com/andrewnicols/moodle
This commit is contained in:
@@ -2211,6 +2211,8 @@ $string['targetrole'] = 'Target role';
|
||||
$string['teacheronly'] = 'for the {$a} only';
|
||||
$string['teacherroles'] = '{$a} roles';
|
||||
$string['teachers'] = 'Teachers';
|
||||
$string['telemetrytraceidfooterlink'] = 'Telemetry trace';
|
||||
$string['telemetrytraceidfooterlinkcopied'] = 'The telemetry trace ID has been copied to your clipboard. You can now paste it in the support form or anywhere else you need.';
|
||||
$string['textcopiedtoclipboard'] = 'Text copied to clipboard';
|
||||
$string['textediting'] = 'Text editor';
|
||||
$string['textediting_help'] = 'If an HTML editor such as Atto or TinyMCE is selected, text input areas will have a toolbar with buttons for easily adding content.
|
||||
|
||||
Vendored
+1
-1
@@ -8,6 +8,6 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 2.9
|
||||
*/
|
||||
define("core/ajax",["jquery","core/config","core/log","core/url"],(function($,config,Log,URL){var unloading=!1,requestSuccess=function(responses){var request,response,nosessionupdate,exception=null,i=0;if(responses.error)for(;i<this.length;i++)(request=this[i]).deferred.reject(responses);else{for(i=0;i<this.length;i++){if(request=this[i],void 0===(response=responses[i])){exception=new Error("missing response");break}if(!1!==response.error){exception=response.exception,nosessionupdate=this[i].nosessionupdate;break}request.deferred.resolve(response.data)}null!==exception&&("servicerequireslogin"!==exception.errorcode||nosessionupdate?this.forEach((function(request){request.deferred.reject(exception)})):window.location=URL.relativeUrl("/login/index.php"))}},requestFail=function(jqXHR,textStatus,exception){var i=0;for(i=0;i<this.length;i++){var request=this[i];unloading?(Log.error("Page unloaded."),Log.error(exception)):request.deferred.reject(exception)}};return{call:function(requests,async,loginrequired,nosessionupdate,timeout,cachekey){$(window).bind("beforeunload",(function(){unloading=!0}));var i,ajaxRequestData=[],promises=[],methodInfo=[],requestInfo="";for(void 0===loginrequired&&(loginrequired=!0),void 0===async&&(async=!0),void 0===timeout&&(timeout=0),void 0===cachekey||(cachekey=parseInt(cachekey))<=0?cachekey=null:cachekey||(cachekey=null),void 0===nosessionupdate&&(nosessionupdate=!1),i=0;i<requests.length;i++){var request=requests[i];ajaxRequestData.push({index:i,methodname:request.methodname,args:request.args}),request.nosessionupdate=nosessionupdate,request.deferred=$.Deferred(),promises.push(request.deferred.promise()),void 0!==request.done&&request.deferred.done(request.done),void 0!==request.fail&&request.deferred.fail(request.fail),request.index=i,methodInfo.push(request.methodname)}requestInfo=methodInfo.length<=5?methodInfo.sort().join():methodInfo.length+"-method-calls",ajaxRequestData=JSON.stringify(ajaxRequestData);var settings={type:"POST",context:requests,dataType:"json",processData:!1,async:async,contentType:"application/json",timeout:timeout},script="service.php",url=config.wwwroot+"/lib/ajax/";if(loginrequired?url+=script+"?sesskey="+config.sesskey+"&info="+requestInfo:(url+=(script="service-nologin.php")+"?info="+requestInfo,cachekey&&(url+="&cachekey="+cachekey,settings.type="GET")),nosessionupdate&&(url+="&nosessionupdate=true"),"POST"===settings.type)settings.data=ajaxRequestData;else{var urlUseGet=url+"&args="+encodeURIComponent(ajaxRequestData);urlUseGet.length>2e3?(settings.type="POST",settings.data=ajaxRequestData):url=urlUseGet}return async?$.ajax(url,settings).done(requestSuccess).fail(requestFail):(settings.success=requestSuccess,settings.error=requestFail,$.ajax(url,settings)),promises}}}));
|
||||
define("core/ajax",["jquery","core/config","core/log","core/url"],(function($,config,Log,URL){var unloading=!1,requestSuccess=function(responses){var request,response,nosessionupdate,exception=null,i=0;if(responses.error)for(;i<this.length;i++)(request=this[i]).deferred.reject(responses);else{for(i=0;i<this.length;i++){if(request=this[i],void 0===(response=responses[i])){exception=new Error("missing response");break}if(!1!==response.error){exception=response.exception,nosessionupdate=this[i].nosessionupdate;break}request.deferred.resolve(response.data)}null!==exception&&("servicerequireslogin"!==exception.errorcode||nosessionupdate?this.forEach((function(request){request.deferred.reject(exception)})):window.location=URL.relativeUrl("/login/index.php"))}},requestFail=function(jqXHR,textStatus,exception){var i=0;for(i=0;i<this.length;i++){var request=this[i];unloading?(Log.error("Page unloaded."),Log.error(exception)):request.deferred.reject(exception)}};return{call:function(requests,async,loginrequired,nosessionupdate,timeout,cachekey){$(window).bind("beforeunload",(function(){unloading=!0}));var i,ajaxRequestData=[],promises=[],methodInfo=[],requestInfo="";for(void 0===loginrequired&&(loginrequired=!0),void 0===async&&(async=!0),void 0===timeout&&(timeout=0),void 0===cachekey||(cachekey=parseInt(cachekey))<=0?cachekey=null:cachekey||(cachekey=null),void 0===nosessionupdate&&(nosessionupdate=!1),i=0;i<requests.length;i++){var request=requests[i];ajaxRequestData.push({index:i,methodname:request.methodname,args:request.args}),request.nosessionupdate=nosessionupdate,request.deferred=$.Deferred(),promises.push(request.deferred.promise()),void 0!==request.done&&request.deferred.done(request.done),void 0!==request.fail&&request.deferred.fail(request.fail),request.index=i,methodInfo.push(request.methodname)}requestInfo=methodInfo.length<=5?methodInfo.sort().join():methodInfo.length+"-method-calls",ajaxRequestData=JSON.stringify(ajaxRequestData);var settings={type:"POST",context:requests,dataType:"json",processData:!1,async:async,contentType:"application/json",timeout:timeout,headers:{pageparent:config.traceId||""}},script="service.php",url=config.wwwroot+"/lib/ajax/";if(loginrequired?url+=script+"?sesskey="+config.sesskey+"&info="+requestInfo:(url+=(script="service-nologin.php")+"?info="+requestInfo,cachekey&&(url+="&cachekey="+cachekey,settings.type="GET")),nosessionupdate&&(url+="&nosessionupdate=true"),"POST"===settings.type)settings.data=ajaxRequestData;else{var urlUseGet=url+"&args="+encodeURIComponent(ajaxRequestData);urlUseGet.length>2e3?(settings.type="POST",settings.data=ajaxRequestData):url=urlUseGet}return async?$.ajax(url,settings).done(requestSuccess).fail(requestFail):(settings.success=requestSuccess,settings.error=requestFail,$.ajax(url,settings)),promises}}}));
|
||||
|
||||
//# sourceMappingURL=ajax.min.js.map
|
||||
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -261,7 +261,10 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
|
||||
processData: false,
|
||||
async: async,
|
||||
contentType: "application/json",
|
||||
timeout: timeout
|
||||
timeout: timeout,
|
||||
headers: {
|
||||
pageparent: config.traceId || '',
|
||||
},
|
||||
};
|
||||
|
||||
var script = 'service.php';
|
||||
|
||||
@@ -323,6 +323,9 @@ export default class Fetch {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
// Note: Do not use traceparent as this is used by the telemetry system to link requests from the
|
||||
// same request together.
|
||||
'pageparent': Cfg.traceId || '',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -574,7 +574,7 @@ class component {
|
||||
// Always keep moodle_exception in place.
|
||||
$keyclasses = [
|
||||
\core\exception\moodle_exception::class,
|
||||
\core\router\middleware\api_validation_middleware::class,
|
||||
\core\telemetry::class,
|
||||
];
|
||||
foreach ($keyclasses as $classname) {
|
||||
if (!array_key_exists($classname, $cache['classmap'])) {
|
||||
|
||||
@@ -4001,6 +4001,21 @@ EOD;
|
||||
return !empty($this->communication_link());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the telemetry trace id for the current page, if available.
|
||||
*
|
||||
* This can be used to correlate logs and telemetry data with specific page views.
|
||||
* It is typically presented in the footer or somewhere inconspicious so that user's experiencing difficulties
|
||||
* may be asked for it.
|
||||
*
|
||||
* The value is also passed in the Response header if required.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function telemetry_traceid(): ?string {
|
||||
return \core\telemetry::get_page_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the communication link, complete with html.
|
||||
*
|
||||
|
||||
@@ -338,6 +338,7 @@ class page_requirements_manager {
|
||||
'siteId' => (int) SITEID,
|
||||
'userId' => (int) $USER->id,
|
||||
'deprecationignorelist' => !empty($CFG->jsdeprecationignorelist) ? $CFG->jsdeprecationignorelist : [],
|
||||
'traceId' => \core\telemetry::get_trace_parent_id(),
|
||||
];
|
||||
if ($CFG->debugdeveloper) {
|
||||
$this->M_cfg['developerdebug'] = true;
|
||||
|
||||
@@ -43,7 +43,7 @@ class shutdown_manager {
|
||||
*/
|
||||
public static function initialize(): void {
|
||||
if (self::$registered) {
|
||||
debugging('Shutdown manager is already initialised!');
|
||||
self::log('Shutdown manager is already initialised!');
|
||||
return;
|
||||
}
|
||||
self::$registered = true;
|
||||
@@ -76,6 +76,15 @@ class shutdown_manager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the shutdown manager initialized.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_initialized(): bool {
|
||||
return self::$registered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal handler for SIGINT, and SIGTERM.
|
||||
*
|
||||
@@ -169,12 +178,17 @@ class shutdown_manager {
|
||||
public static function shutdown_handler(): void {
|
||||
global $DB;
|
||||
|
||||
// In case we caught an out of memory shutdown we increase memory limit to unlimited, so we can gracefully shut down.
|
||||
raise_memory_limit(MEMORY_UNLIMITED);
|
||||
if (function_exists('raise_memory_limit')) {
|
||||
// In case we caught an out of memory shutdown we increase memory limit to unlimited,
|
||||
// so we can gracefully shut down.
|
||||
raise_memory_limit(MEMORY_UNLIMITED);
|
||||
}
|
||||
|
||||
// Always ensure we know who the user is in access logs even if they
|
||||
// were logged in a weird way midway through the request.
|
||||
set_access_log_user();
|
||||
if (function_exists('set_access_log_user')) {
|
||||
// Always ensure we know who the user is in access logs even if they
|
||||
// were logged in a weird way midway through the request.
|
||||
set_access_log_user();
|
||||
}
|
||||
|
||||
// Custom stuff first.
|
||||
foreach (self::$callbacks as $data) {
|
||||
@@ -189,7 +203,7 @@ class shutdown_manager {
|
||||
|
||||
// Handle DB transactions, session need to be written afterwards
|
||||
// in order to maintain consistency in all session handlers.
|
||||
if ($DB->is_transaction_started()) {
|
||||
if ($DB && $DB->is_transaction_started()) {
|
||||
if (!defined('PHPUNIT_TEST') || !PHPUNIT_TEST) {
|
||||
// This should not happen, it usually indicates wrong catching of exceptions,
|
||||
// because all transactions should be finished manually or in default exception handler.
|
||||
@@ -242,7 +256,12 @@ class shutdown_manager {
|
||||
// phpcs:ignore moodle.PHP.ForbiddenFunctions.FoundWithAlternative
|
||||
error_log('Mem usage over ' . $apachereleasemem . ': marking Apache child for reaping.');
|
||||
}
|
||||
if (MDL_PERFTOLOG) {
|
||||
|
||||
$logperformance = MDL_PERFTOLOG;
|
||||
$logperformance = $logperformance || !empty($PERF->perfdebugdeferred);
|
||||
$logperformance = $logperformance && function_exists('get_performance_info');
|
||||
|
||||
if ($logperformance) {
|
||||
$perf = get_performance_info();
|
||||
// phpcs:ignore moodle.PHP.ForbiddenFunctions.FoundWithAlternative
|
||||
error_log("PERF: " . $perf['txt']);
|
||||
@@ -251,6 +270,7 @@ class shutdown_manager {
|
||||
$perf = get_performance_info();
|
||||
echo $OUTPUT->select_element_for_replace('#perfdebugfooter', $perf['html']);
|
||||
}
|
||||
|
||||
if (MDL_PERFINC) {
|
||||
$inc = get_included_files();
|
||||
$ts = 0;
|
||||
@@ -274,9 +294,11 @@ class shutdown_manager {
|
||||
}
|
||||
}
|
||||
|
||||
// Close the current streaming element if any.
|
||||
if ($OUTPUT->has_started()) {
|
||||
echo $OUTPUT->close_element_for_append();
|
||||
if ($OUTPUT) {
|
||||
// Close the current streaming element if any.
|
||||
if ($OUTPUT->has_started()) {
|
||||
echo $OUTPUT->close_element_for_append();
|
||||
}
|
||||
}
|
||||
|
||||
// Print any closing buffered tags.
|
||||
@@ -284,6 +306,22 @@ class shutdown_manager {
|
||||
echo $CFG->closingtags;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logging for the shutdown manager.
|
||||
*
|
||||
* @param string $value
|
||||
*/
|
||||
protected static function log(string $value): void {
|
||||
if (function_exists('debugging')) {
|
||||
// Use Moodle's debugging function if available.
|
||||
debugging($value, DEBUG_DEVELOPER);
|
||||
} else {
|
||||
// Fallback to error_log if debugging is not available.
|
||||
// This is useful for older PHP versions or when debugging is not set up.
|
||||
error_log($value); // phpcs:ignore moodle.PHP.ForbiddenFunctions.FoundWithAlternative
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core;
|
||||
|
||||
use OpenTelemetry\API\Globals;
|
||||
use OpenTelemetry\API\Trace\LocalRootSpan;
|
||||
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
|
||||
use OpenTelemetry\API\Trace\Span;
|
||||
use OpenTelemetry\Context\Context;
|
||||
|
||||
/**
|
||||
* OpenTelemetry Telemetry manager class for Moodle.
|
||||
*
|
||||
* This class acts as a central point for managing telemetry in Moodle,
|
||||
* providing methods to initialize the telemetry system, get the current page ID, and record exceptions.
|
||||
*
|
||||
* @package core
|
||||
* @copyright Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class telemetry {
|
||||
/**
|
||||
* Get the page ID for the current request.
|
||||
*
|
||||
* If OpenTelemetry is not configured, or there is no current root span, this will return null.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function get_page_id(): ?string {
|
||||
if (!static::is_available()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fetch the current root span, and return the trace ID as the page ID.
|
||||
$rootspan = LocalRootSpan::current();
|
||||
$rootcontext = $rootspan->getContext();
|
||||
if ($rootcontext->isValid() === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $rootcontext->getTraceId();
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper method to get the trace id for the current request.
|
||||
*
|
||||
* @return string|null Null if OpenTelemetry is not configured
|
||||
*/
|
||||
public static function get_trace_parent_id(): ?string {
|
||||
if (!static::is_available()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$headers = [];
|
||||
Globals::propagator()->inject($headers);
|
||||
|
||||
return $headers[TraceContextPropagator::TRACEPARENT] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether Telemetry is available and configured.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_available(): bool {
|
||||
if (!class_exists(Globals::class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (extension_loaded('opentelemetry') === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record an error, or exception to the current span.
|
||||
*
|
||||
* @param \Throwable $ex The exception to record.
|
||||
*/
|
||||
public static function record_throwable(\Throwable $ex): void {
|
||||
if (!static::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$span = Span::fromContext(Context::getCurrent());
|
||||
$span->recordException($ex);
|
||||
}
|
||||
}
|
||||
@@ -592,7 +592,12 @@ ini_set('include_path', $CFG->libdir . '/pear' . PATH_SEPARATOR . ini_get('inclu
|
||||
// Register our classloader.
|
||||
\core\component::register_autoloader();
|
||||
|
||||
// Early profiling start, based exclusively on config.php $CFG settings
|
||||
// Register our shutdown manager, do NOT use register_shutdown_function().
|
||||
if (\core\shutdown_manager::is_initialized() === false) {
|
||||
\core\shutdown_manager::initialize();
|
||||
}
|
||||
|
||||
// Early profiling start, based exclusively on config.php $CFG settings.
|
||||
if (!empty($CFG->earlyprofilingenabled) && !defined('ABORT_AFTER_CONFIG_CANCEL')) {
|
||||
require_once($CFG->libdir . '/xhprof/xhprof_moodle.php');
|
||||
profiling_start();
|
||||
@@ -778,10 +783,7 @@ if (!isset($CFG->debugdisplay)) {
|
||||
ini_set('display_errors', '1');
|
||||
}
|
||||
|
||||
// Register our shutdown manager, do NOT use register_shutdown_function().
|
||||
\core\shutdown_manager::initialize();
|
||||
|
||||
// Verify upgrade is not running unless we are in a script that needs to execute in any case
|
||||
// Verify upgrade is not running unless we are in a script that needs to execute in any case.
|
||||
if (!defined('NO_UPGRADE_CHECK') and isset($CFG->upgraderunning)) {
|
||||
if ($CFG->upgraderunning < time()) {
|
||||
unset_config('upgraderunning');
|
||||
|
||||
@@ -136,7 +136,10 @@ function get_whoops(): ?\Whoops\Run {
|
||||
function default_exception_handler(Throwable $ex): void {
|
||||
global $CFG, $DB, $OUTPUT, $USER, $FULLME, $SESSION, $PAGE;
|
||||
|
||||
// detect active db transactions, rollback and log as error
|
||||
// Record the throwable in OpenTelemetry if it's available.
|
||||
\core\telemetry::record_throwable($ex);
|
||||
|
||||
// Detect active db transactions, rollback and log as error.
|
||||
abort_all_db_transactions();
|
||||
|
||||
if (($ex instanceof required_capability_exception) && !CLI_SCRIPT && !AJAX_SCRIPT && !empty($CFG->autologinguests) && !empty($USER->autologinguest)) {
|
||||
|
||||
@@ -73,7 +73,17 @@
|
||||
</div>
|
||||
<div class="tool_usertours-resettourcontainer">
|
||||
</div>
|
||||
|
||||
{{# output.telemetry_traceid }}
|
||||
<div>
|
||||
<span id="telemetry_traceid" class="d-none">
|
||||
{{{ output.telemetry_traceid }}}
|
||||
</span>
|
||||
<a href="#" data-action="copytoclipboard" data-clipboard-target="#telemetry_traceid"
|
||||
data-clipboard-success-message="{{#str}}telemetrytraceidfooterlinkcopied, core{{/str}}">
|
||||
{{#str}}telemetrytraceidfooterlink, core{{/str}}
|
||||
</a>
|
||||
</div>
|
||||
{{/ output.telemetry_traceid }}
|
||||
{{{ output.standard_footer_html }}}
|
||||
{{{ output.standard_end_of_body_html }}}
|
||||
</div>
|
||||
@@ -94,7 +104,10 @@
|
||||
</div>
|
||||
</footer>
|
||||
{{#js}}
|
||||
require(['theme_boost/footer-popover'], function(FooterPopover) {
|
||||
require([
|
||||
'theme_boost/footer-popover',
|
||||
'core/copy_to_clipboard',
|
||||
], function(FooterPopover) {
|
||||
FooterPopover.init();
|
||||
});
|
||||
{{/js}}
|
||||
|
||||
Reference in New Issue
Block a user