From 343ded16a84dc797b8f034c367d54fae3711a72a Mon Sep 17 00:00:00 2001 From: Khushboo Verma Date: Wed, 20 Aug 2025 17:28:35 +0530 Subject: [PATCH] Truncate logs to a limit --- app/controllers/general.php | 14 ++++++++- app/init/constants.php | 1 + .../Functions/Http/Executions/Create.php | 12 +++++++- src/Appwrite/Platform/Workers/Functions.php | 12 +++++++- .../Functions/FunctionsCustomServerTest.php | 30 +++++++++++++++++++ .../functions/log-truncation/index.js | 13 ++++++++ 6 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 tests/resources/functions/log-truncation/index.js diff --git a/app/controllers/general.php b/app/controllers/general.php index 40f861ad8c..88895fcf5a 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -618,7 +618,19 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw /** Update execution status */ $status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed'; $execution->setAttribute('status', $status); - $execution->setAttribute('logs', $executionResponse['logs']); + + // Truncate logs if they exceed the limit + $maxLogLength = APP_FUNCTION_LOG_LENGTH_LIMIT; + $logs = $executionResponse['logs'] ?? ''; + + if (\is_string($logs) && \strlen($logs) > $maxLogLength) { + $warningMessage = "\n[WARNING] Logs truncated. The output exceeded {$maxLogLength} characters."; + $warningLength = \strlen($warningMessage); + $maxContentLength = $maxLogLength - $warningLength; + $logs = \substr($logs, 0, $maxContentLength) . $warningMessage; + } + + $execution->setAttribute('logs', $logs); $execution->setAttribute('errors', $executionResponse['errors']); $execution->setAttribute('responseStatusCode', $executionResponse['statusCode']); $execution->setAttribute('responseHeaders', $headersFiltered); diff --git a/app/init/constants.php b/app/init/constants.php index 62d9f16853..40a23c8fef 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -143,6 +143,7 @@ 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; // Function headers const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host']; const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length']; diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php index ef31d5a79c..9a475bb257 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php @@ -423,12 +423,22 @@ class Create extends Base } } + $maxLogLength = APP_FUNCTION_LOG_LENGTH_LIMIT; + $logs = $executionResponse['logs'] ?? ''; + + if (\is_string($logs) && \strlen($logs) > $maxLogLength) { + $warningMessage = "\n[WARNING] Logs truncated. The output exceeded {$maxLogLength} characters."; + $warningLength = \strlen($warningMessage); + $maxContentLength = $maxLogLength - $warningLength; + $logs = \substr($logs, 0, $maxContentLength) . $warningMessage; + } + /** 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('logs', $logs); $execution->setAttribute('errors', $executionResponse['errors']); $execution->setAttribute('duration', $executionResponse['duration']); } catch (\Throwable $th) { diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php index 2e25248d9a..08fc9d36c4 100644 --- a/src/Appwrite/Platform/Workers/Functions.php +++ b/src/Appwrite/Platform/Workers/Functions.php @@ -551,11 +551,21 @@ class Functions extends Action } /** Update execution status */ + $maxLogLength = APP_FUNCTION_LOG_LENGTH_LIMIT; + $logs = $executionResponse['logs'] ?? ''; + + if (\is_string($logs) && \strlen($logs) > $maxLogLength) { + $warningMessage = "\n[WARNING] Logs truncated. The output exceeded {$maxLogLength} characters."; + $warningLength = \strlen($warningMessage); + $maxContentLength = $maxLogLength - $warningLength; + $logs = \substr($logs, 0, $maxContentLength) . $warningMessage; + } + $execution ->setAttribute('status', $status) ->setAttribute('responseStatusCode', $executionResponse['statusCode']) ->setAttribute('responseHeaders', $headersFiltered) - ->setAttribute('logs', $executionResponse['logs']) + ->setAttribute('logs', $logs) ->setAttribute('errors', $executionResponse['errors']) ->setAttribute('duration', $executionResponse['duration']); } catch (\Throwable $th) { diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index ff99033fdf..955635118b 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -2282,4 +2282,34 @@ class FunctionsCustomServerTest extends Scope $this->cleanupFunction($functionId); } + + public function testLogTruncation(): 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-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 + $logs = $execution['body']['logs']; + $this->assertLessThanOrEqual(APP_FUNCTION_LOG_LENGTH_LIMIT, strlen($logs)); + $this->assertStringContainsString('[WARNING] Logs truncated', $logs); + + $this->cleanupFunction($functionId); + } } diff --git a/tests/resources/functions/log-truncation/index.js b/tests/resources/functions/log-truncation/index.js new file mode 100644 index 0000000000..54bfa5165d --- /dev/null +++ b/tests/resources/functions/log-truncation/index.js @@ -0,0 +1,13 @@ +module.exports = async(context) => { + // Create a string that is 1000001 characters long (exceeds the 1000000 limit) + const longString = 'a'.repeat(1000001); + + context.log(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', + }); +};