Skip to content

Commit ff5cfd4

Browse files
committed
fix(api): normalize log endpoint query handling
Clamp log line counts, parse timestamp flags consistently, and filter service subcontainers by Coolify labels. Document log endpoint timestamp parameters and database/service log routes in OpenAPI.
1 parent adc4b30 commit ff5cfd4

7 files changed

Lines changed: 417 additions & 19 deletions

File tree

app/Http/Controllers/Api/ApplicationsController.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2087,8 +2087,8 @@ public function logs_by_uuid(Request $request)
20872087
], 400);
20882088
}
20892089

2090-
$lines = $request->query->get('lines', 100);
2091-
$showTimestamps = $request->query->get('show_timestamps', false);
2090+
$lines = normalizeLogLines($request->query('lines'));
2091+
$showTimestamps = parseLogTimestampFlag($request->query('show_timestamps'));
20922092
$logs = getContainerLogs($application->destination->server, $container['ID'], $lines, $showTimestamps);
20932093

20942094
return response()->json([

app/Http/Controllers/Api/DatabasesController.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2246,6 +2246,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
22462246

22472247
return response()->json(['message' => 'Invalid database type requested.'], 400);
22482248
}
2249+
22492250
#[OA\Get(
22502251
summary: 'Get database logs.',
22512252
description: 'Get database logs by UUID.',
@@ -2331,7 +2332,7 @@ public function logs_by_uuid(Request $request)
23312332
}
23322333

23332334
$containers = getCurrentDatabaseContainerStatus($database->destination->server, $database->id);
2334-
2335+
23352336
if ($containers->count() == 0) {
23362337
return response()->json([
23372338
'message' => 'Database is not running.',
@@ -2347,8 +2348,8 @@ public function logs_by_uuid(Request $request)
23472348
], 400);
23482349
}
23492350

2350-
$lines = $request->query->get('lines', 100);
2351-
$showTimestamps = $request->query->get('show_timestamps', false);
2351+
$lines = normalizeLogLines($request->query('lines'));
2352+
$showTimestamps = parseLogTimestampFlag($request->query('show_timestamps'));
23522353
$logs = getContainerLogs($database->destination->server, $container['ID'], $lines, $showTimestamps);
23532354

23542355
return response()->json([

app/Http/Controllers/Api/ServicesController.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ public function service_by_uuid(Request $request)
733733

734734
#[OA\Get(
735735
summary: 'Get service logs.',
736-
description: 'Get service logs by UUID.',
736+
description: 'Get logs for a specific service sub-resource by service UUID. The `sub_service_name` query parameter must match the `name` field of one of the service applications or databases returned by `GET /services/{uuid}`.',
737737
path: '/services/{uuid}/logs',
738738
operationId: 'get-service-logs-by-uuid',
739739
security: [
@@ -754,9 +754,9 @@ public function service_by_uuid(Request $request)
754754
new OA\Parameter(
755755
name: 'sub_service_name',
756756
in: 'query',
757-
description: 'Sub service name.',
757+
description: 'Sub-service name from `GET /services/{uuid}` under `applications[].name` or `databases[].name`. Do not use `human_name` or the Docker container name with the service UUID suffix.',
758758
required: true,
759-
schema: new OA\Schema(type: 'string'),
759+
schema: new OA\Schema(type: 'string', example: 'appwrite-console'),
760760
),
761761
new OA\Parameter(
762762
name: 'lines',
@@ -841,8 +841,8 @@ public function logs_by_uuid(Request $request)
841841
], 400);
842842
}
843843

844-
$lines = $request->query->get('lines', 100);
845-
$showTimestamps = $request->query->get('show_timestamps', false);
844+
$lines = normalizeLogLines($request->query('lines'));
845+
$showTimestamps = parseLogTimestampFlag($request->query('show_timestamps'));
846846
$logs = getContainerLogs($service->destination->server, $container['ID'], $lines, $showTimestamps);
847847

848848
return response()->json([

bootstrap/helpers/docker.php

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,19 @@ function getCurrentDatabaseContainerStatus(Server $server, int $id): Collection
8787

8888
function getCurrentServiceSubContainerStatus(Server $server, int $id, string $name): Collection
8989
{
90-
$containers = collect([]);
91-
if (! $server->isSwarm()) {
92-
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.serviceId={$id}' --filter='label=coolify.name={$name}' --format '{{json .}}' "], $server);
93-
$containers = format_docker_command_output_to_json($containers);
90+
return filterServiceSubContainersByName(getCurrentServiceContainerStatus($server, $id), $name);
91+
}
9492

95-
return $containers->filter();
96-
}
93+
function filterServiceSubContainersByName(Collection $containers, string $name): Collection
94+
{
95+
return $containers->filter(function ($container) use ($name) {
96+
$labels = data_get($container, 'Labels', []);
97+
if (is_string($labels)) {
98+
$labels = format_docker_labels_to_json($labels);
99+
}
97100

98-
return $containers;
101+
return collect($labels)->get('coolify.name') === $name;
102+
})->values();
99103
}
100104

101105
function format_docker_command_output_to_json($rawOutput): Collection
@@ -1273,7 +1277,22 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable
12731277
}
12741278
}
12751279

1276-
function getContainerLogs(Server $server, string $container_id, int $lines = 100, bool $showTimestamps = false): string
1280+
function normalizeLogLines(mixed $lines, int $default = 100, int $max = 10000): int
1281+
{
1282+
$lines = filter_var($lines, FILTER_VALIDATE_INT);
1283+
if ($lines === false || $lines <= 0) {
1284+
return $default;
1285+
}
1286+
1287+
return min($lines, $max);
1288+
}
1289+
1290+
function parseLogTimestampFlag(mixed $showTimestamps): bool
1291+
{
1292+
return filter_var($showTimestamps, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false;
1293+
}
1294+
1295+
function buildContainerLogsCommand(Server $server, string $container_id, int $lines = 100, bool $showTimestamps = false): string
12771296
{
12781297
$command = "docker logs -n {$lines}";
12791298
if ($server->isSwarm()) {
@@ -1284,7 +1303,12 @@ function getContainerLogs(Server $server, string $container_id, int $lines = 100
12841303
$command .= ' --timestamps';
12851304
}
12861305

1287-
$output = instant_remote_process(["{$command} {$container_id} 2>&1"], $server);
1306+
return "{$command} ".escapeshellarg($container_id).' 2>&1';
1307+
}
1308+
1309+
function getContainerLogs(Server $server, string $container_id, int $lines = 100, bool $showTimestamps = false): string
1310+
{
1311+
$output = instant_remote_process([buildContainerLogsCommand($server, $container_id, $lines, $showTimestamps)], $server);
12881312
$output = removeAnsiColors($output);
12891313

12901314
return $output;

openapi.json

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2675,6 +2675,16 @@
26752675
"format": "int32",
26762676
"default": 100
26772677
}
2678+
},
2679+
{
2680+
"name": "show_timestamps",
2681+
"in": "query",
2682+
"description": "Show timestamps in the logs.",
2683+
"required": false,
2684+
"schema": {
2685+
"type": "boolean",
2686+
"default": false
2687+
}
26782688
}
26792689
],
26802690
"responses": {
@@ -5952,6 +5962,80 @@
59525962
]
59535963
}
59545964
},
5965+
"\/databases\/{uuid}\/logs": {
5966+
"get": {
5967+
"tags": [
5968+
"Databases"
5969+
],
5970+
"summary": "Get database logs.",
5971+
"description": "Get database logs by UUID.",
5972+
"operationId": "get-database-logs-by-uuid",
5973+
"parameters": [
5974+
{
5975+
"name": "uuid",
5976+
"in": "path",
5977+
"description": "UUID of the database.",
5978+
"required": true,
5979+
"schema": {
5980+
"type": "string",
5981+
"format": "uuid"
5982+
}
5983+
},
5984+
{
5985+
"name": "lines",
5986+
"in": "query",
5987+
"description": "Number of lines to show from the end of the logs.",
5988+
"required": false,
5989+
"schema": {
5990+
"type": "integer",
5991+
"format": "int32",
5992+
"default": 100
5993+
}
5994+
},
5995+
{
5996+
"name": "show_timestamps",
5997+
"in": "query",
5998+
"description": "Show timestamps in the logs.",
5999+
"required": false,
6000+
"schema": {
6001+
"type": "boolean",
6002+
"default": false
6003+
}
6004+
}
6005+
],
6006+
"responses": {
6007+
"200": {
6008+
"description": "Get database logs by UUID.",
6009+
"content": {
6010+
"application\/json": {
6011+
"schema": {
6012+
"properties": {
6013+
"logs": {
6014+
"type": "string"
6015+
}
6016+
},
6017+
"type": "object"
6018+
}
6019+
}
6020+
}
6021+
},
6022+
"401": {
6023+
"$ref": "#\/components\/responses\/401"
6024+
},
6025+
"400": {
6026+
"$ref": "#\/components\/responses\/400"
6027+
},
6028+
"404": {
6029+
"$ref": "#\/components\/responses\/404"
6030+
}
6031+
},
6032+
"security": [
6033+
{
6034+
"bearerAuth": []
6035+
}
6036+
]
6037+
}
6038+
},
59556039
"\/databases\/{uuid}\/backups\/{scheduled_backup_uuid}\/executions\/{execution_uuid}": {
59566040
"delete": {
59576041
"tags": [
@@ -11293,6 +11377,90 @@
1129311377
]
1129411378
}
1129511379
},
11380+
"\/services\/{uuid}\/logs": {
11381+
"get": {
11382+
"tags": [
11383+
"Services"
11384+
],
11385+
"summary": "Get service logs.",
11386+
"description": "Get logs for a specific service sub-resource by service UUID. The `sub_service_name` query parameter must match the `name` field of one of the service applications or databases returned by `GET \/services\/{uuid}`.",
11387+
"operationId": "get-service-logs-by-uuid",
11388+
"parameters": [
11389+
{
11390+
"name": "uuid",
11391+
"in": "path",
11392+
"description": "UUID of the service.",
11393+
"required": true,
11394+
"schema": {
11395+
"type": "string",
11396+
"format": "uuid"
11397+
}
11398+
},
11399+
{
11400+
"name": "sub_service_name",
11401+
"in": "query",
11402+
"description": "Sub-service name from `GET \/services\/{uuid}` under `applications[].name` or `databases[].name`. Do not use `human_name` or the Docker container name with the service UUID suffix.",
11403+
"required": true,
11404+
"schema": {
11405+
"type": "string",
11406+
"example": "appwrite-console"
11407+
}
11408+
},
11409+
{
11410+
"name": "lines",
11411+
"in": "query",
11412+
"description": "Number of lines to show from the end of the logs.",
11413+
"required": false,
11414+
"schema": {
11415+
"type": "integer",
11416+
"format": "int32",
11417+
"default": 100
11418+
}
11419+
},
11420+
{
11421+
"name": "show_timestamps",
11422+
"in": "query",
11423+
"description": "Show timestamps in the logs.",
11424+
"required": false,
11425+
"schema": {
11426+
"type": "boolean",
11427+
"default": false
11428+
}
11429+
}
11430+
],
11431+
"responses": {
11432+
"200": {
11433+
"description": "Get service logs by UUID.",
11434+
"content": {
11435+
"application\/json": {
11436+
"schema": {
11437+
"properties": {
11438+
"logs": {
11439+
"type": "string"
11440+
}
11441+
},
11442+
"type": "object"
11443+
}
11444+
}
11445+
}
11446+
},
11447+
"401": {
11448+
"$ref": "#\/components\/responses\/401"
11449+
},
11450+
"400": {
11451+
"$ref": "#\/components\/responses\/400"
11452+
},
11453+
"404": {
11454+
"$ref": "#\/components\/responses\/404"
11455+
}
11456+
},
11457+
"security": [
11458+
{
11459+
"bearerAuth": []
11460+
}
11461+
]
11462+
}
11463+
},
1129611464
"\/services\/{uuid}\/envs": {
1129711465
"get": {
1129811466
"tags": [

0 commit comments

Comments
 (0)