aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--includes/api/ApiUpload.php148
-rw-r--r--includes/upload/UploadBase.php34
-rw-r--r--includes/upload/UploadFromChunks.php56
3 files changed, 229 insertions, 9 deletions
diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php
index 06d517670e67..53fb33a424e7 100644
--- a/includes/api/ApiUpload.php
+++ b/includes/api/ApiUpload.php
@@ -21,6 +21,7 @@
*/
use MediaWiki\Config\Config;
+use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MainConfigNames;
use MediaWiki\MediaWikiServices;
use MediaWiki\Status\Status;
@@ -28,6 +29,7 @@ use MediaWiki\Title\Title;
use MediaWiki\User\Options\UserOptionsLookup;
use MediaWiki\User\User;
use MediaWiki\Watchlist\WatchlistManager;
+use Psr\Log\LoggerInterface;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\ParamValidator\TypeDef\IntegerDef;
@@ -45,6 +47,8 @@ class ApiUpload extends ApiBase {
private JobQueueGroup $jobQueueGroup;
+ private LoggerInterface $log;
+
/**
* @param ApiMain $mainModule
* @param string $moduleName
@@ -68,6 +72,7 @@ class ApiUpload extends ApiBase {
$this->getConfig()->get( MainConfigNames::WatchlistExpiryMaxDuration );
$this->watchlistManager = $watchlistManager;
$this->userOptionsLookup = $userOptionsLookup;
+ $this->log = LoggerFactory::getInstance( 'upload' );
}
public function execute() {
@@ -111,6 +116,13 @@ class ApiUpload extends ApiBase {
/** @var Status $status */
$status = $this->mUpload->fetchFile();
if ( !$status->isGood() ) {
+ $this->log->info( "Unable to fetch file {filename} for {user} because {status}",
+ [
+ 'user' => $this->getUser()->getName(),
+ 'status' => (string)$status,
+ 'filename' => $this->mParams['filename'] ?? '-',
+ ]
+ );
$this->dieStatus( $status );
}
@@ -313,6 +325,14 @@ class ApiUpload extends ApiBase {
}
if ( $this->mParams['offset'] == 0 ) {
+ $this->log->debug( "Started first chunk of chunked upload of {filename} for {user}",
+ [
+ 'user' => $this->getUser()->getName(),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'filesize' => $this->mParams['filesize'],
+ 'chunkSize' => $chunkSize
+ ]
+ );
$filekey = $this->performStash( 'critical' );
} else {
$filekey = $this->mParams['filekey'];
@@ -321,6 +341,15 @@ class ApiUpload extends ApiBase {
$progress = UploadBase::getSessionStatus( $this->getUser(), $filekey );
if ( !$progress ) {
// Probably can't get here, but check anyway just in case
+ $this->log->info( "Stash failed due to no session for {user}",
+ [
+ 'user' => $this->getUser()->getName(),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'filekey' => $this->mParams['filekey'] ?? '-',
+ 'filesize' => $this->mParams['filesize'],
+ 'chunkSize' => $chunkSize
+ ]
+ );
$this->dieWithError( 'apierror-stashfailed-nosession', 'stashfailed' );
} elseif ( $progress['result'] !== 'Continue' || $progress['stage'] !== 'uploading' ) {
$this->dieWithError( 'apierror-stashfailed-complete', 'stashfailed' );
@@ -332,8 +361,29 @@ class ApiUpload extends ApiBase {
$extradata = [
'offset' => $this->mUpload->getOffset(),
];
-
+ $this->log->info( "Chunked upload stash failure {status} for {user}",
+ [
+ 'status' => (string)$status,
+ 'user' => $this->getUser()->getName(),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'filekey' => $this->mParams['filekey'] ?? '-',
+ 'filesize' => $this->mParams['filesize'],
+ 'chunkSize' => $chunkSize,
+ 'offset' => $this->mUpload->getOffset()
+ ]
+ );
$this->dieStatusWithCode( $status, 'stashfailed', $extradata );
+ } else {
+ $this->log->debug( "Got chunk for {filename} with offset {offset} for {user}",
+ [
+ 'user' => $this->getUser()->getName(),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'filekey' => $this->mParams['filekey'] ?? '-',
+ 'filesize' => $this->mParams['filesize'],
+ 'chunkSize' => $chunkSize,
+ 'offset' => $this->mUpload->getOffset()
+ ]
+ );
}
}
@@ -354,9 +404,28 @@ class ApiUpload extends ApiBase {
'session' => $this->getContext()->exportSession()
]
) );
+ $this->log->info( "Received final chunk of {filename} for {user}, queuing assemble job",
+ [
+ 'user' => $this->getUser()->getName(),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'filekey' => $this->mParams['filekey'] ?? '-',
+ 'filesize' => $this->mParams['filesize'],
+ 'chunkSize' => $chunkSize,
+ ]
+ );
$result['result'] = 'Poll';
$result['stage'] = 'queued';
} else {
+ $this->log->info( "Received final chunk of {filename} for {user}, assembling immediately",
+ [
+ 'user' => $this->getUser()->getName(),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'filekey' => $this->mParams['filekey'] ?? '-',
+ 'filesize' => $this->mParams['filesize'],
+ 'chunkSize' => $chunkSize,
+ ]
+ );
+
$status = $this->mUpload->concatenateChunks();
if ( !$status->isGood() ) {
UploadBase::setSessionStatus(
@@ -364,6 +433,15 @@ class ApiUpload extends ApiBase {
$filekey,
[ 'result' => 'Failure', 'stage' => 'assembling', 'status' => $status ]
);
+ $this->log->info( "Non jobqueue assembly of {filename} failed because {status}",
+ [
+ 'user' => $this->getUser()->getName(),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'filekey' => $this->mParams['filekey'] ?? '-',
+ 'filesize' => $this->mParams['filesize'],
+ 'chunkSize' => $chunkSize,
+ ]
+ );
$this->dieStatusWithCode( $status, 'stashfailed' );
}
@@ -424,7 +502,14 @@ class ApiUpload extends ApiBase {
}
} catch ( Exception $e ) {
$debugMessage = 'Stashing temporary file failed: ' . get_class( $e ) . ' ' . $e->getMessage();
- wfDebug( __METHOD__ . ' ' . $debugMessage );
+ $this->log->info( $debugMessage,
+ [
+ 'user' => $this->getUser()->getName(),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'filekey' => $this->mParams['filekey'] ?? '-'
+ ]
+ );
+
$status = Status::newFatal( $this->getErrorFormatter()->getMessageFromException(
$e, [ 'wrap' => new ApiMessage( 'apierror-stashexception', 'stashfailed' ) ]
) );
@@ -443,9 +528,23 @@ class ApiUpload extends ApiBase {
// Statuses for it. Just extract the exception details and parse them ourselves.
[ $exceptionType, $message ] = $status->getMessage()->getParams();
$debugMessage = 'Stashing temporary file failed: ' . $exceptionType . ' ' . $message;
- wfDebug( __METHOD__ . ' ' . $debugMessage );
+ $this->log->info( $debugMessage,
+ [
+ 'user' => $this->getUser()->getName(),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'filekey' => $this->mParams['filekey'] ?? '-'
+ ]
+ );
}
+ $this->log->info( "Stash upload failure {status}",
+ [
+ 'status' => (string)$status,
+ 'user' => $this->getUser()->getName(),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'filekey' => $this->mParams['filekey'] ?? '-'
+ ]
+ );
// Bad status
if ( $failureMode !== 'optional' ) {
$this->dieStatus( $status );
@@ -524,6 +623,13 @@ class ApiUpload extends ApiBase {
if ( $this->mParams['filekey'] && $this->mParams['checkstatus'] ) {
$progress = UploadBase::getSessionStatus( $this->getUser(), $this->mParams['filekey'] );
if ( !$progress ) {
+ $this->log->info( "Cannot check upload status due to missing upload session for {user}",
+ [
+ 'user' => $this->getUser()->getName(),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'filekey' => $this->mParams['filekey'] ?? '-'
+ ]
+ );
$this->dieWithError( 'apierror-upload-missingresult', 'missingresult' );
} elseif ( !$progress['status']->isGood() ) {
$this->dieStatusWithCode( $progress['status'], 'stashfailed' );
@@ -685,8 +791,19 @@ class ApiUpload extends ApiBase {
wfDebug( __METHOD__ . " about to verify" );
$verification = $this->mUpload->verifyUpload();
+
if ( $verification['status'] === UploadBase::OK ) {
return;
+ } else {
+ $this->log->info( "File verification of {filename} failed for {user} because {result}",
+ [
+ 'user' => $this->getUser()->getName(),
+ 'resultCode' => $verification['status'],
+ 'result' => $this->mUpload->getVerificationErrorCode( $verification['status'] ),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'details' => $verification['details'] ?? ''
+ ]
+ );
}
}
@@ -847,6 +964,15 @@ class ApiUpload extends ApiBase {
* @return StatusValue
*/
protected function handleStashException( $e ) {
+ $this->log->info( "Upload stashing of {filename} failed for {user} because {error}",
+ [
+ 'user' => $this->getUser()->getName(),
+ 'status' => get_class( $e ),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'filekey' => $this->mParams['filekey'] ?? '-'
+ ]
+ );
+
switch ( get_class( $e ) ) {
case UploadStashFileNotFoundException::class:
$wrap = 'apierror-stashedfilenotfound';
@@ -948,6 +1074,14 @@ class ApiUpload extends ApiBase {
'session' => $this->getContext()->exportSession()
]
) );
+ $this->log->info( "Sending publish job of {filename} for {user}",
+ [
+ 'user' => $this->getUser()->getName(),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'filekey' => $this->mParams['filekey'] ?? '-'
+ ]
+
+ );
$result['result'] = 'Poll';
$result['stage'] = 'queued';
} else {
@@ -962,6 +1096,14 @@ class ApiUpload extends ApiBase {
);
if ( !$status->isGood() ) {
+ $this->log->info( "Non-async API upload publish failed for {user} because {status}",
+ [
+ 'user' => $this->getUser()->getName(),
+ 'filename' => $this->mParams['filename'] ?? '-',
+ 'filekey' => $this->mParams['filekey'] ?? '-',
+ 'status' => (string)$status
+ ]
+ );
$this->dieRecoverableError( $status->getErrors() );
}
$result['result'] = 'Success';
diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php
index ab8df0602a96..392a60391c31 100644
--- a/includes/upload/UploadBase.php
+++ b/includes/upload/UploadBase.php
@@ -24,6 +24,7 @@
use MediaWiki\Context\RequestContext;
use MediaWiki\HookContainer\HookRunner;
use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
+use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MainConfigNames;
use MediaWiki\MediaWikiServices;
use MediaWiki\Parser\Sanitizer;
@@ -2250,6 +2251,39 @@ abstract class UploadBase {
public static function setSessionStatus( UserIdentity $user, $statusKey, $value ) {
$store = self::getUploadSessionStore();
$key = self::getUploadSessionKey( $store, $user, $statusKey );
+ $logger = LoggerFactory::getInstance( 'upload' );
+
+ if ( is_array( $value ) && ( $value['result'] ?? '' ) === 'Failure' ) {
+ $logger->info( 'Upload session {key} for {user} set to failure {status} at {stage}',
+ [
+ 'result' => $value['result'] ?? '',
+ 'stage' => $value['stage'] ?? 'unknown',
+ 'user' => $user->getName(),
+ 'status' => (string)( $value['status'] ?? '-' ),
+ 'filekey' => $value['filekey'] ?? '',
+ 'key' => $statusKey
+ ]
+ );
+ } elseif ( is_array( $value ) ) {
+ $logger->debug( 'Upload session {key} for {user} changed {status} at {stage}',
+ [
+ 'result' => $value['result'] ?? '',
+ 'stage' => $value['stage'] ?? 'unknown',
+ 'user' => $user->getName(),
+ 'status' => (string)( $value['status'] ?? '-' ),
+ 'filekey' => $value['filekey'] ?? '',
+ 'key' => $statusKey
+ ]
+ );
+ } else {
+ $logger->debug( "Upload session {key} deleted for {user}",
+ [
+ 'value' => $value,
+ 'key' => $statusKey,
+ 'user' => $user->getName()
+ ]
+ );
+ }
if ( $value === false ) {
$store->delete( $key );
diff --git a/includes/upload/UploadFromChunks.php b/includes/upload/UploadFromChunks.php
index 452951e66269..2906a565435c 100644
--- a/includes/upload/UploadFromChunks.php
+++ b/includes/upload/UploadFromChunks.php
@@ -5,6 +5,7 @@ use MediaWiki\MediaWikiServices;
use MediaWiki\Request\WebRequestUpload;
use MediaWiki\Status\Status;
use MediaWiki\User\User;
+use Psr\Log\LoggerInterface;
/**
* Backend for uploading files from chunks.
@@ -47,6 +48,8 @@ class UploadFromChunks extends UploadFromFile {
protected $mFileKey;
protected $mVirtualTempPath;
+ private LoggerInterface $logger;
+
/** @noinspection PhpMissingParentConstructorInspection */
/**
@@ -71,6 +74,8 @@ class UploadFromChunks extends UploadFromFile {
wfDebug( __METHOD__ . " creating new UploadFromChunks instance for " . $user->getId() );
$this->stash = new UploadStash( $this->repo, $this->user );
}
+
+ $this->logger = LoggerFactory::getInstance( 'upload' );
}
/**
@@ -139,8 +144,14 @@ class UploadFromChunks extends UploadFromFile {
*/
public function concatenateChunks() {
$chunkIndex = $this->getChunkIndex();
- wfDebug( __METHOD__ . " concatenate {$this->mChunkIndex} chunks:" .
- $this->getOffset() . ' inx:' . $chunkIndex );
+ $this->logger->debug(
+ __METHOD__ . ' concatenate {totalChunks} chunks: {offset} inx: {curIndex}',
+ [
+ 'offset' => $this->getOffset(),
+ 'totalChunks' => $this->mChunkIndex,
+ 'curIndex' => $chunkIndex,
+ ]
+ );
// Concatenate all the chunks to mVirtualTempPath
$fileList = [];
@@ -158,6 +169,8 @@ class UploadFromChunks extends UploadFromFile {
if ( $tmpFile ) {
// keep alive with $this
$tmpPath = $tmpFile->bind( $this )->getPath();
+ } else {
+ $this->logger->warning( "Error getting tmp file" );
}
// Concatenate the chunks at the temp file
@@ -182,7 +195,7 @@ class UploadFromChunks extends UploadFromFile {
$ret = $this->verifyUpload();
if ( $ret['status'] !== UploadBase::OK ) {
- wfDebugLog( 'fileconcatenate', "Verification failed for chunked upload" );
+ $this->logger->info( "Verification failed for chunked upload", [ 'user' => $this->user->getName() ] );
$status->fatal( $this->getVerificationErrorCode( $ret['status'] ) );
return $status;
@@ -196,11 +209,26 @@ class UploadFromChunks extends UploadFromFile {
$error = $this->runUploadStashFileHook( $this->user );
if ( $error ) {
$status->fatal( ...$error );
+ $this->logger->info( "Aborting stash upload due to hook - {status}",
+ [
+ 'status' => (string)$status,
+ 'user' => $this->user->getName(),
+ 'filekey' => $this->mFileKey
+ ]
+ );
return $status;
}
try {
$this->mStashFile = parent::doStashFile( $this->user );
} catch ( UploadStashException $e ) {
+ $this->logger->warning( "Could not stash file for {user} because {error} {msg}",
+ [
+ 'user' => $this->user->getName(),
+ 'error' => get_class( $e ),
+ 'msg' => $e->getMessage(),
+ 'filekey' => $this->mFileKey
+ ]
+ );
$status->fatal( 'uploadstash-exception', get_class( $e ), $e->getMessage() );
return $status;
}
@@ -253,6 +281,15 @@ class UploadFromChunks extends UploadFromFile {
$this->verifyChunk();
$this->mTempPath = $oldTemp;
} catch ( UploadChunkVerificationException $e ) {
+ $this->logger->info( "Error verifying upload chunk {msg}",
+ [
+ 'user' => $this->user->getName(),
+ 'msg' => $e->getMessage(),
+ 'chunkIndex' => $this->mChunkIndex,
+ 'filekey' => $this->mFileKey
+ ]
+ );
+
return Status::newFatal( $e->msg );
}
$status = $this->outputChunk( $chunkPath );
@@ -274,8 +311,14 @@ class UploadFromChunks extends UploadFromFile {
* Update the chunk db table with the current status:
*/
private function updateChunkStatus() {
- wfDebug( __METHOD__ . " update chunk status for {$this->mFileKey} offset:" .
- $this->getOffset() . ' inx:' . $this->getChunkIndex() );
+ $this->logger->info( "update chunk status for {filekey} offset: {offset} inx: {inx}",
+ [
+ 'offset' => $this->getOffset(),
+ 'inx' => $this->getChunkIndex(),
+ 'filekey' => $this->mFileKey,
+ 'user' => $this->user->getName()
+ ]
+ );
$dbw = $this->repo->getPrimaryDB();
$dbw->newUpdateQueryBuilder()
@@ -395,7 +438,7 @@ class UploadFromChunks extends UploadFromFile {
* @return array
*/
private function logFileBackendStatus( Status $status, string $logMessage, array $context = [] ): array {
- $logger = LoggerFactory::getInstance( 'upload' );
+ $logger = $this->logger;
$errorToThrow = null;
$warningToThrow = null;
@@ -407,6 +450,7 @@ class UploadFromChunks extends UploadFromFile {
// The message arguments often contains the name of the failing datacenter or file names
// and should not show up in aggregated stats, add to context
$context['details'] = implode( '; ', $errorItem['params'] );
+ $context['user'] = $this->user->getName();
if ( $errorItem['type'] === 'error' ) {
// Use the first error of the list for the exception text