Merge pull request #9773 from samikshaaagarwal/samiksha/9510

Truncate logs in function worker
This commit is contained in:
Matej Bačo 2025-08-21 12:58:57 +02:00 committed by GitHub
commit 36049cf9aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 130 additions and 8 deletions

View file

@ -1917,7 +1917,7 @@ return [
'$id' => ID::custom('errors'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 1000000,
'size' => APP_FUNCTION_ERROR_LENGTH_LIMIT,
'signed' => true,
'required' => false,
'default' => null,
@ -1928,7 +1928,7 @@ return [
'$id' => ID::custom('logs'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 1000000,
'size' => APP_FUNCTION_LOG_LENGTH_LIMIT,
'signed' => true,
'required' => false,
'default' => null,

View file

@ -615,11 +615,33 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
}
// Truncate logs if they exceed the limit
$maxLogLength = APP_FUNCTION_LOG_LENGTH_LIMIT;
$logs = $executionResponse['logs'] ?? '';
if (\is_string($logs) && \strlen($logs) > $maxLogLength) {
$warningMessage = "[WARNING] Logs truncated. The output exceeded {$maxLogLength} characters.\n";
$warningLength = \strlen($warningMessage);
$maxContentLength = $maxLogLength - $warningLength;
$logs = $warningMessage . \substr($logs, -$maxContentLength);
}
// Truncate errors if they exceed the limit
$maxErrorLength = APP_FUNCTION_ERROR_LENGTH_LIMIT;
$errors = $executionResponse['errors'] ?? '';
if (\is_string($errors) && \strlen($errors) > $maxErrorLength) {
$warningMessage = "[WARNING] Errors truncated. The output exceeded {$maxErrorLength} characters.\n";
$warningLength = \strlen($warningMessage);
$maxContentLength = $maxErrorLength - $warningLength;
$errors = $warningMessage . \substr($errors, -$maxContentLength);
}
/** Update execution status */
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
$execution->setAttribute('status', $status);
$execution->setAttribute('logs', $executionResponse['logs']);
$execution->setAttribute('errors', $executionResponse['errors']);
$execution->setAttribute('logs', $logs);
$execution->setAttribute('errors', $errors);
$execution->setAttribute('responseStatusCode', $executionResponse['statusCode']);
$execution->setAttribute('responseHeaders', $headersFiltered);
$execution->setAttribute('duration', $executionResponse['duration']);

View file

@ -143,6 +143,8 @@ const APP_AUTH_TYPE_KEY = 'Key';
const APP_AUTH_TYPE_ADMIN = 'Admin';
// Response related
const MAX_OUTPUT_CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
const APP_FUNCTION_LOG_LENGTH_LIMIT = 1000000;
const APP_FUNCTION_ERROR_LENGTH_LIMIT = 1000000;
// Function headers
const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host'];
const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];

View file

@ -423,13 +423,34 @@ class Create extends Base
}
}
$maxLogLength = APP_FUNCTION_LOG_LENGTH_LIMIT;
$logs = $executionResponse['logs'] ?? '';
if (\is_string($logs) && \strlen($logs) > $maxLogLength) {
$warningMessage = "[WARNING] Logs truncated. The output exceeded {$maxLogLength} characters.\n";
$warningLength = \strlen($warningMessage);
$maxContentLength = $maxLogLength - $warningLength;
$logs = $warningMessage . \substr($logs, -$maxContentLength);
}
// Truncate errors if they exceed the limit
$maxErrorLength = APP_FUNCTION_ERROR_LENGTH_LIMIT;
$errors = $executionResponse['errors'] ?? '';
if (\is_string($errors) && \strlen($errors) > $maxErrorLength) {
$warningMessage = "[WARNING] Errors truncated. The output exceeded {$maxErrorLength} characters.\n";
$warningLength = \strlen($warningMessage);
$maxContentLength = $maxErrorLength - $warningLength;
$errors = $warningMessage . \substr($errors, -$maxContentLength);
}
/** Update execution status */
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
$execution->setAttribute('status', $status);
$execution->setAttribute('responseStatusCode', $executionResponse['statusCode']);
$execution->setAttribute('responseHeaders', $headersFiltered);
$execution->setAttribute('logs', $executionResponse['logs']);
$execution->setAttribute('errors', $executionResponse['errors']);
$execution->setAttribute('logs', $logs);
$execution->setAttribute('errors', $errors);
$execution->setAttribute('duration', $executionResponse['duration']);
} catch (\Throwable $th) {
$durationEnd = \microtime(true);

View file

@ -550,14 +550,36 @@ class Functions extends Action
}
}
$maxLogLength = APP_FUNCTION_LOG_LENGTH_LIMIT;
$logs = $executionResponse['logs'] ?? '';
if (\is_string($logs) && \strlen($logs) > $maxLogLength) {
$warningMessage = "[WARNING] Logs truncated. The output exceeded {$maxLogLength} characters.\n";
$warningLength = \strlen($warningMessage);
$maxContentLength = $maxLogLength - $warningLength;
$logs = $warningMessage . \substr($logs, -$maxContentLength);
}
// Truncate errors if they exceed the limit
$maxErrorLength = APP_FUNCTION_ERROR_LENGTH_LIMIT;
$errors = $executionResponse['errors'] ?? '';
if (\is_string($errors) && \strlen($errors) > $maxErrorLength) {
$warningMessage = "[WARNING] Errors truncated. The output exceeded {$maxErrorLength} characters.\n";
$warningLength = \strlen($warningMessage);
$maxContentLength = $maxErrorLength - $warningLength;
$errors = $warningMessage . \substr($errors, -$maxContentLength);
}
/** Update execution status */
$execution
->setAttribute('status', $status)
->setAttribute('responseStatusCode', $executionResponse['statusCode'])
->setAttribute('responseHeaders', $headersFiltered)
->setAttribute('logs', $executionResponse['logs'])
->setAttribute('errors', $executionResponse['errors'])
->setAttribute('logs', $logs)
->setAttribute('errors', $errors)
->setAttribute('duration', $executionResponse['duration']);
} catch (\Throwable $th) {
$durationEnd = \microtime(true);
$execution

View file

@ -2282,4 +2282,45 @@ class FunctionsCustomServerTest extends Scope
$this->cleanupFunction($functionId);
}
public function testLogAndErrorTruncation(): void
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test Log Truncation',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 15,
]);
$this->setupDeployment($functionId, [
'code' => $this->packageFunction('log-error-truncation'),
'activate' => true
]);
$execution = $this->createExecution($functionId, [
'async' => 'false'
]);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
// Verify logs are truncated and warning message is present at the beginning
$logs = $execution['body']['logs'];
$this->assertLessThanOrEqual(APP_FUNCTION_LOG_LENGTH_LIMIT, strlen($logs));
$this->assertStringStartsWith('[WARNING] Logs truncated', $logs);
$this->assertStringNotContainsString('z', $logs);
$this->assertStringContainsString('a', $logs);
// Verify errors are truncated and warning message is present at the beginning
$errors = $execution['body']['errors'];
$this->assertLessThanOrEqual(APP_FUNCTION_ERROR_LENGTH_LIMIT, strlen($errors));
$this->assertStringStartsWith('[WARNING] Errors truncated', $errors);
$this->assertStringNotContainsString('z', $errors);
$this->assertStringContainsString('a', $errors);
$this->cleanupFunction($functionId);
}
}

View file

@ -0,0 +1,14 @@
module.exports = async(context) => {
// Create a string that is 1000001 characters long (exceeds the 1000000 limit)
const longString = 'z' + 'a'.repeat(1000000);
context.log(longString);
context.error(longString);
return context.res.json({
motto: 'Build like a team of hundreds_',
learn: 'https://appwrite.io/docs',
connect: 'https://appwrite.io/discord',
getInspired: 'https://builtwith.appwrite.io',
});
};