mirror of
https://github.com/apache/airflow.git
synced 2026-11-04 07:12:48 +00:00
Add airflowctl tasks logs command for retrieving task instance logs (#71763)
* Add airflowctl tasks logs command
Without direct DB or webserver access, operators can list failed task instances, check their state and clear them for retry, but retrieving the logs needed to diagnose the failure still forced a fallback to the webserver UI or a hand-rolled REST client. This closes that gap by fetching task instance logs for a given try through the existing /dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/logs/{try_number} endpoint, defaulting to the latest attempt.
* Make --try-number required for airflowctl tasks logs command
The /logs/{try_number} endpoint types the path parameter as
NonNegativeInt and has no server-side latest-attempt fallback, so
the client-side -1 default cannot work. Require --try-number
explicitly, per review feedback, and update the tests that relied
on the default.
* Fetch the full log in airflowctl tasks logs
Without full_content the endpoint returns one chunk and a continuation token, so a running or remotely stored log printed only partially. Also give the auto-generated taskinstances logs command a help text and an integration test, as main now requires for every operations method.
Generated-by: Claude Opus 5
* Regenerate airflowctl help images for the tasks logs commands
Generated-by: Claude Opus 5
---------
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
This commit is contained in:
1 parent
77609e83c2
commit
c82f0ee58a
10 files changed
+321
-89
No files matched your search
@@ -113,10 +113,12 @@ TEST_COMMANDS = [
|
||||
'tasks states-for-dag-run example_bash_operator --logical-date "{date_param}"',
|
||||
'tasks state example_bash_operator runme_0 "manual__{date_param}"',
|
||||
'tasks state example_bash_operator runme_0 --logical-date "{date_param}"',
|
||||
'tasks logs example_bash_operator "manual__{date_param}" runme_0 --try-number 1',
|
||||
'tasks clear example_bash_operator --dag-run-id "manual__{date_param}" --task-ids runme_0 -o json',
|
||||
# Task Instances commands
|
||||
'taskinstances get example_bash_operator "manual__{date_param}" runme_0',
|
||||
'taskinstances get-dependencies example_bash_operator "manual__{date_param}" runme_0',
|
||||
'taskinstances logs example_bash_operator "manual__{date_param}" runme_0 1',
|
||||
'taskinstances list example_bash_operator "manual__{date_param}"',
|
||||
'taskinstances state-store set example_bash_operator "manual__{date_param}" runme_0 test_key test_value',
|
||||
'taskinstances state-store get example_bash_operator "manual__{date_param}" runme_0 test_key',
|
||||
|
||||
@@ -9,8 +9,8 @@ dagrun:07035226eaaff0a557d3ad9aab4b41b5
|
||||
jobs:d4af478f28dae48ee18d43b998edc345
|
||||
pools:19efe105b9515ab1926ebcaf0e028d71
|
||||
providers:826e68c9de8254293ba810a5c67fa8c7
|
||||
taskinstances:af95eda24a2bc4c6bd87c9f1324dbc3b
|
||||
tasks:56fa16e067fea389a11727b21985ec2f
|
||||
taskinstances:ef73172ce155dcad1f4ee38c1050db8f
|
||||
tasks:79059c3c8de9114e73895d4b8e86ed00
|
||||
variables:f8fc76d3d398b2780f4e97f7cd816646
|
||||
version:31f4efdf8de0dbaaa4fac71ff7efecc3
|
||||
plugins:4864fd8f356704bd2b3cd1aec3567e35
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg class="rich-terminal" viewBox="0 0 933 416.0" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="rich-terminal" viewBox="0 0 933 440.4" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Generated with Rich https://www.textualize.io -->
|
||||
<style>
|
||||
|
||||
@@ -19,98 +19,102 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.terminal-944627566-matrix {
|
||||
.terminal-57093932-matrix {
|
||||
font-family: Fira Code, monospace;
|
||||
font-size: 20px;
|
||||
line-height: 24.4px;
|
||||
font-variant-east-asian: full-width;
|
||||
}
|
||||
|
||||
.terminal-944627566-title {
|
||||
.terminal-57093932-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
font-family: arial;
|
||||
}
|
||||
|
||||
.terminal-944627566-r1 { fill: #ff8700 }
|
||||
.terminal-944627566-r2 { fill: #c5c8c6 }
|
||||
.terminal-944627566-r3 { fill: #808080 }
|
||||
.terminal-944627566-r4 { fill: #68a0b3 }
|
||||
.terminal-57093932-r1 { fill: #ff8700 }
|
||||
.terminal-57093932-r2 { fill: #c5c8c6 }
|
||||
.terminal-57093932-r3 { fill: #808080 }
|
||||
.terminal-57093932-r4 { fill: #68a0b3 }
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
<clipPath id="terminal-944627566-clip-terminal">
|
||||
<rect x="0" y="0" width="914.0" height="365.0" />
|
||||
<clipPath id="terminal-57093932-clip-terminal">
|
||||
<rect x="0" y="0" width="914.0" height="389.4" />
|
||||
</clipPath>
|
||||
<clipPath id="terminal-944627566-line-0">
|
||||
<clipPath id="terminal-57093932-line-0">
|
||||
<rect x="0" y="1.5" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-944627566-line-1">
|
||||
<clipPath id="terminal-57093932-line-1">
|
||||
<rect x="0" y="25.9" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-944627566-line-2">
|
||||
<clipPath id="terminal-57093932-line-2">
|
||||
<rect x="0" y="50.3" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-944627566-line-3">
|
||||
<clipPath id="terminal-57093932-line-3">
|
||||
<rect x="0" y="74.7" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-944627566-line-4">
|
||||
<clipPath id="terminal-57093932-line-4">
|
||||
<rect x="0" y="99.1" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-944627566-line-5">
|
||||
<clipPath id="terminal-57093932-line-5">
|
||||
<rect x="0" y="123.5" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-944627566-line-6">
|
||||
<clipPath id="terminal-57093932-line-6">
|
||||
<rect x="0" y="147.9" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-944627566-line-7">
|
||||
<clipPath id="terminal-57093932-line-7">
|
||||
<rect x="0" y="172.3" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-944627566-line-8">
|
||||
<clipPath id="terminal-57093932-line-8">
|
||||
<rect x="0" y="196.7" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-944627566-line-9">
|
||||
<clipPath id="terminal-57093932-line-9">
|
||||
<rect x="0" y="221.1" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-944627566-line-10">
|
||||
<clipPath id="terminal-57093932-line-10">
|
||||
<rect x="0" y="245.5" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-944627566-line-11">
|
||||
<clipPath id="terminal-57093932-line-11">
|
||||
<rect x="0" y="269.9" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-944627566-line-12">
|
||||
<clipPath id="terminal-57093932-line-12">
|
||||
<rect x="0" y="294.3" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-944627566-line-13">
|
||||
<clipPath id="terminal-57093932-line-13">
|
||||
<rect x="0" y="318.7" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-57093932-line-14">
|
||||
<rect x="0" y="343.1" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="931" height="414" rx="8"/>
|
||||
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="931" height="438.4" rx="8"/>
|
||||
<g transform="translate(26,22)">
|
||||
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
|
||||
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
|
||||
<circle cx="44" cy="0" r="7" fill="#28c840"/>
|
||||
</g>
|
||||
|
||||
<g transform="translate(9, 41)" clip-path="url(#terminal-944627566-clip-terminal)">
|
||||
<g transform="translate(9, 41)" clip-path="url(#terminal-57093932-clip-terminal)">
|
||||
|
||||
<g class="terminal-944627566-matrix">
|
||||
<text class="terminal-944627566-r1" x="0" y="20" textLength="73.2" clip-path="url(#terminal-944627566-line-0)">Usage:</text><text class="terminal-944627566-r3" x="85.4" y="20" textLength="292.8" clip-path="url(#terminal-944627566-line-0)">airflowctl taskinstances</text><text class="terminal-944627566-r2" x="378.2" y="20" textLength="24.4" clip-path="url(#terminal-944627566-line-0)"> [</text><text class="terminal-944627566-r4" x="402.6" y="20" textLength="24.4" clip-path="url(#terminal-944627566-line-0)">-h</text><text class="terminal-944627566-r2" x="427" y="20" textLength="24.4" clip-path="url(#terminal-944627566-line-0)">] </text><text class="terminal-944627566-r4" x="451.4" y="20" textLength="85.4" clip-path="url(#terminal-944627566-line-0)">COMMAND</text><text class="terminal-944627566-r4" x="549" y="20" textLength="36.6" clip-path="url(#terminal-944627566-line-0)">...</text><text class="terminal-944627566-r2" x="915" y="20" textLength="12.2" clip-path="url(#terminal-944627566-line-0)">
|
||||
</text><text class="terminal-944627566-r2" x="915" y="44.4" textLength="12.2" clip-path="url(#terminal-944627566-line-1)">
|
||||
</text><text class="terminal-944627566-r2" x="0" y="68.8" textLength="390.4" clip-path="url(#terminal-944627566-line-2)">Perform TaskInstances operations</text><text class="terminal-944627566-r2" x="915" y="68.8" textLength="12.2" clip-path="url(#terminal-944627566-line-2)">
|
||||
</text><text class="terminal-944627566-r2" x="915" y="93.2" textLength="12.2" clip-path="url(#terminal-944627566-line-3)">
|
||||
</text><text class="terminal-944627566-r1" x="0" y="117.6" textLength="256.2" clip-path="url(#terminal-944627566-line-4)">Positional Arguments:</text><text class="terminal-944627566-r2" x="915" y="117.6" textLength="12.2" clip-path="url(#terminal-944627566-line-4)">
|
||||
</text><text class="terminal-944627566-r4" x="24.4" y="142" textLength="85.4" clip-path="url(#terminal-944627566-line-5)">COMMAND</text><text class="terminal-944627566-r2" x="915" y="142" textLength="12.2" clip-path="url(#terminal-944627566-line-5)">
|
||||
</text><text class="terminal-944627566-r4" x="48.8" y="166.4" textLength="36.6" clip-path="url(#terminal-944627566-line-6)">get</text><text class="terminal-944627566-r2" x="244" y="166.4" textLength="475.8" clip-path="url(#terminal-944627566-line-6)">Get a task instance for a given Dag run</text><text class="terminal-944627566-r2" x="915" y="166.4" textLength="12.2" clip-path="url(#terminal-944627566-line-6)">
|
||||
</text><text class="terminal-944627566-r4" x="48.8" y="190.8" textLength="195.2" clip-path="url(#terminal-944627566-line-7)">get-dependencies</text><text class="terminal-944627566-r2" x="915" y="190.8" textLength="12.2" clip-path="url(#terminal-944627566-line-7)">
|
||||
</text><text class="terminal-944627566-r2" x="244" y="215.2" textLength="634.4" clip-path="url(#terminal-944627566-line-8)">Get unmet scheduler dependencies for a task instance</text><text class="terminal-944627566-r2" x="915" y="215.2" textLength="12.2" clip-path="url(#terminal-944627566-line-8)">
|
||||
</text><text class="terminal-944627566-r4" x="48.8" y="239.6" textLength="48.8" clip-path="url(#terminal-944627566-line-9)">list</text><text class="terminal-944627566-r2" x="244" y="239.6" textLength="524.6" clip-path="url(#terminal-944627566-line-9)">List all task instances for a given Dag run</text><text class="terminal-944627566-r2" x="915" y="239.6" textLength="12.2" clip-path="url(#terminal-944627566-line-9)">
|
||||
</text><text class="terminal-944627566-r4" x="48.8" y="264" textLength="134.2" clip-path="url(#terminal-944627566-line-10)">state-store</text><text class="terminal-944627566-r2" x="244" y="264" textLength="500.2" clip-path="url(#terminal-944627566-line-10)">Manage the state store of a task instance</text><text class="terminal-944627566-r2" x="915" y="264" textLength="12.2" clip-path="url(#terminal-944627566-line-10)">
|
||||
</text><text class="terminal-944627566-r2" x="915" y="288.4" textLength="12.2" clip-path="url(#terminal-944627566-line-11)">
|
||||
</text><text class="terminal-944627566-r1" x="0" y="312.8" textLength="97.6" clip-path="url(#terminal-944627566-line-12)">Options:</text><text class="terminal-944627566-r2" x="915" y="312.8" textLength="12.2" clip-path="url(#terminal-944627566-line-12)">
|
||||
</text><text class="terminal-944627566-r4" x="24.4" y="337.2" textLength="24.4" clip-path="url(#terminal-944627566-line-13)">-h</text><text class="terminal-944627566-r2" x="48.8" y="337.2" textLength="24.4" clip-path="url(#terminal-944627566-line-13)">, </text><text class="terminal-944627566-r4" x="73.2" y="337.2" textLength="73.2" clip-path="url(#terminal-944627566-line-13)">--help</text><text class="terminal-944627566-r2" x="244" y="337.2" textLength="378.2" clip-path="url(#terminal-944627566-line-13)">show this help message and exit</text><text class="terminal-944627566-r2" x="915" y="337.2" textLength="12.2" clip-path="url(#terminal-944627566-line-13)">
|
||||
</text><text class="terminal-944627566-r2" x="915" y="361.6" textLength="12.2" clip-path="url(#terminal-944627566-line-14)">
|
||||
<g class="terminal-57093932-matrix">
|
||||
<text class="terminal-57093932-r1" x="0" y="20" textLength="73.2" clip-path="url(#terminal-57093932-line-0)">Usage:</text><text class="terminal-57093932-r3" x="85.4" y="20" textLength="292.8" clip-path="url(#terminal-57093932-line-0)">airflowctl taskinstances</text><text class="terminal-57093932-r2" x="378.2" y="20" textLength="24.4" clip-path="url(#terminal-57093932-line-0)"> [</text><text class="terminal-57093932-r4" x="402.6" y="20" textLength="24.4" clip-path="url(#terminal-57093932-line-0)">-h</text><text class="terminal-57093932-r2" x="427" y="20" textLength="24.4" clip-path="url(#terminal-57093932-line-0)">] </text><text class="terminal-57093932-r4" x="451.4" y="20" textLength="85.4" clip-path="url(#terminal-57093932-line-0)">COMMAND</text><text class="terminal-57093932-r4" x="549" y="20" textLength="36.6" clip-path="url(#terminal-57093932-line-0)">...</text><text class="terminal-57093932-r2" x="915" y="20" textLength="12.2" clip-path="url(#terminal-57093932-line-0)">
|
||||
</text><text class="terminal-57093932-r2" x="915" y="44.4" textLength="12.2" clip-path="url(#terminal-57093932-line-1)">
|
||||
</text><text class="terminal-57093932-r2" x="0" y="68.8" textLength="390.4" clip-path="url(#terminal-57093932-line-2)">Perform TaskInstances operations</text><text class="terminal-57093932-r2" x="915" y="68.8" textLength="12.2" clip-path="url(#terminal-57093932-line-2)">
|
||||
</text><text class="terminal-57093932-r2" x="915" y="93.2" textLength="12.2" clip-path="url(#terminal-57093932-line-3)">
|
||||
</text><text class="terminal-57093932-r1" x="0" y="117.6" textLength="256.2" clip-path="url(#terminal-57093932-line-4)">Positional Arguments:</text><text class="terminal-57093932-r2" x="915" y="117.6" textLength="12.2" clip-path="url(#terminal-57093932-line-4)">
|
||||
</text><text class="terminal-57093932-r4" x="24.4" y="142" textLength="85.4" clip-path="url(#terminal-57093932-line-5)">COMMAND</text><text class="terminal-57093932-r2" x="915" y="142" textLength="12.2" clip-path="url(#terminal-57093932-line-5)">
|
||||
</text><text class="terminal-57093932-r4" x="48.8" y="166.4" textLength="36.6" clip-path="url(#terminal-57093932-line-6)">get</text><text class="terminal-57093932-r2" x="244" y="166.4" textLength="475.8" clip-path="url(#terminal-57093932-line-6)">Get a task instance for a given Dag run</text><text class="terminal-57093932-r2" x="915" y="166.4" textLength="12.2" clip-path="url(#terminal-57093932-line-6)">
|
||||
</text><text class="terminal-57093932-r4" x="48.8" y="190.8" textLength="195.2" clip-path="url(#terminal-57093932-line-7)">get-dependencies</text><text class="terminal-57093932-r2" x="915" y="190.8" textLength="12.2" clip-path="url(#terminal-57093932-line-7)">
|
||||
</text><text class="terminal-57093932-r2" x="244" y="215.2" textLength="634.4" clip-path="url(#terminal-57093932-line-8)">Get unmet scheduler dependencies for a task instance</text><text class="terminal-57093932-r2" x="915" y="215.2" textLength="12.2" clip-path="url(#terminal-57093932-line-8)">
|
||||
</text><text class="terminal-57093932-r4" x="48.8" y="239.6" textLength="48.8" clip-path="url(#terminal-57093932-line-9)">list</text><text class="terminal-57093932-r2" x="244" y="239.6" textLength="524.6" clip-path="url(#terminal-57093932-line-9)">List all task instances for a given Dag run</text><text class="terminal-57093932-r2" x="915" y="239.6" textLength="12.2" clip-path="url(#terminal-57093932-line-9)">
|
||||
</text><text class="terminal-57093932-r4" x="48.8" y="264" textLength="48.8" clip-path="url(#terminal-57093932-line-10)">logs</text><text class="terminal-57093932-r2" x="244" y="264" textLength="658.8" clip-path="url(#terminal-57093932-line-10)">Get the logs of a task instance for a given try number</text><text class="terminal-57093932-r2" x="915" y="264" textLength="12.2" clip-path="url(#terminal-57093932-line-10)">
|
||||
</text><text class="terminal-57093932-r4" x="48.8" y="288.4" textLength="134.2" clip-path="url(#terminal-57093932-line-11)">state-store</text><text class="terminal-57093932-r2" x="244" y="288.4" textLength="500.2" clip-path="url(#terminal-57093932-line-11)">Manage the state store of a task instance</text><text class="terminal-57093932-r2" x="915" y="288.4" textLength="12.2" clip-path="url(#terminal-57093932-line-11)">
|
||||
</text><text class="terminal-57093932-r2" x="915" y="312.8" textLength="12.2" clip-path="url(#terminal-57093932-line-12)">
|
||||
</text><text class="terminal-57093932-r1" x="0" y="337.2" textLength="97.6" clip-path="url(#terminal-57093932-line-13)">Options:</text><text class="terminal-57093932-r2" x="915" y="337.2" textLength="12.2" clip-path="url(#terminal-57093932-line-13)">
|
||||
</text><text class="terminal-57093932-r4" x="24.4" y="361.6" textLength="24.4" clip-path="url(#terminal-57093932-line-14)">-h</text><text class="terminal-57093932-r2" x="48.8" y="361.6" textLength="24.4" clip-path="url(#terminal-57093932-line-14)">, </text><text class="terminal-57093932-r4" x="73.2" y="361.6" textLength="73.2" clip-path="url(#terminal-57093932-line-14)">--help</text><text class="terminal-57093932-r2" x="244" y="361.6" textLength="378.2" clip-path="url(#terminal-57093932-line-14)">show this help message and exit</text><text class="terminal-57093932-r2" x="915" y="361.6" textLength="12.2" clip-path="url(#terminal-57093932-line-14)">
|
||||
</text><text class="terminal-57093932-r2" x="915" y="386" textLength="12.2" clip-path="url(#terminal-57093932-line-15)">
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 9.3 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg class="rich-terminal" viewBox="0 0 933 464.79999999999995" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="rich-terminal" viewBox="0 0 933 489.2" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Generated with Rich https://www.textualize.io -->
|
||||
<style>
|
||||
|
||||
@@ -19,106 +19,110 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.terminal-4044056635-matrix {
|
||||
.terminal-986165217-matrix {
|
||||
font-family: Fira Code, monospace;
|
||||
font-size: 20px;
|
||||
line-height: 24.4px;
|
||||
font-variant-east-asian: full-width;
|
||||
}
|
||||
|
||||
.terminal-4044056635-title {
|
||||
.terminal-986165217-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
font-family: arial;
|
||||
}
|
||||
|
||||
.terminal-4044056635-r1 { fill: #ff8700 }
|
||||
.terminal-4044056635-r2 { fill: #c5c8c6 }
|
||||
.terminal-4044056635-r3 { fill: #808080 }
|
||||
.terminal-4044056635-r4 { fill: #68a0b3 }
|
||||
.terminal-986165217-r1 { fill: #ff8700 }
|
||||
.terminal-986165217-r2 { fill: #c5c8c6 }
|
||||
.terminal-986165217-r3 { fill: #808080 }
|
||||
.terminal-986165217-r4 { fill: #68a0b3 }
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
<clipPath id="terminal-4044056635-clip-terminal">
|
||||
<rect x="0" y="0" width="914.0" height="413.79999999999995" />
|
||||
<clipPath id="terminal-986165217-clip-terminal">
|
||||
<rect x="0" y="0" width="914.0" height="438.2" />
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-0">
|
||||
<clipPath id="terminal-986165217-line-0">
|
||||
<rect x="0" y="1.5" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-1">
|
||||
<clipPath id="terminal-986165217-line-1">
|
||||
<rect x="0" y="25.9" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-2">
|
||||
<clipPath id="terminal-986165217-line-2">
|
||||
<rect x="0" y="50.3" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-3">
|
||||
<clipPath id="terminal-986165217-line-3">
|
||||
<rect x="0" y="74.7" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-4">
|
||||
<clipPath id="terminal-986165217-line-4">
|
||||
<rect x="0" y="99.1" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-5">
|
||||
<clipPath id="terminal-986165217-line-5">
|
||||
<rect x="0" y="123.5" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-6">
|
||||
<clipPath id="terminal-986165217-line-6">
|
||||
<rect x="0" y="147.9" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-7">
|
||||
<clipPath id="terminal-986165217-line-7">
|
||||
<rect x="0" y="172.3" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-8">
|
||||
<clipPath id="terminal-986165217-line-8">
|
||||
<rect x="0" y="196.7" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-9">
|
||||
<clipPath id="terminal-986165217-line-9">
|
||||
<rect x="0" y="221.1" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-10">
|
||||
<clipPath id="terminal-986165217-line-10">
|
||||
<rect x="0" y="245.5" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-11">
|
||||
<clipPath id="terminal-986165217-line-11">
|
||||
<rect x="0" y="269.9" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-12">
|
||||
<clipPath id="terminal-986165217-line-12">
|
||||
<rect x="0" y="294.3" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-13">
|
||||
<clipPath id="terminal-986165217-line-13">
|
||||
<rect x="0" y="318.7" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-14">
|
||||
<clipPath id="terminal-986165217-line-14">
|
||||
<rect x="0" y="343.1" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-4044056635-line-15">
|
||||
<clipPath id="terminal-986165217-line-15">
|
||||
<rect x="0" y="367.5" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-986165217-line-16">
|
||||
<rect x="0" y="391.9" width="915" height="24.65"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="931" height="462.8" rx="8"/>
|
||||
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="931" height="487.2" rx="8"/>
|
||||
<g transform="translate(26,22)">
|
||||
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
|
||||
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
|
||||
<circle cx="44" cy="0" r="7" fill="#28c840"/>
|
||||
</g>
|
||||
|
||||
<g transform="translate(9, 41)" clip-path="url(#terminal-4044056635-clip-terminal)">
|
||||
<g transform="translate(9, 41)" clip-path="url(#terminal-986165217-clip-terminal)">
|
||||
|
||||
<g class="terminal-4044056635-matrix">
|
||||
<text class="terminal-4044056635-r1" x="0" y="20" textLength="73.2" clip-path="url(#terminal-4044056635-line-0)">Usage:</text><text class="terminal-4044056635-r3" x="85.4" y="20" textLength="195.2" clip-path="url(#terminal-4044056635-line-0)">airflowctl tasks</text><text class="terminal-4044056635-r2" x="280.6" y="20" textLength="24.4" clip-path="url(#terminal-4044056635-line-0)"> [</text><text class="terminal-4044056635-r4" x="305" y="20" textLength="24.4" clip-path="url(#terminal-4044056635-line-0)">-h</text><text class="terminal-4044056635-r2" x="329.4" y="20" textLength="24.4" clip-path="url(#terminal-4044056635-line-0)">] </text><text class="terminal-4044056635-r4" x="353.8" y="20" textLength="85.4" clip-path="url(#terminal-4044056635-line-0)">COMMAND</text><text class="terminal-4044056635-r4" x="451.4" y="20" textLength="36.6" clip-path="url(#terminal-4044056635-line-0)">...</text><text class="terminal-4044056635-r2" x="915" y="20" textLength="12.2" clip-path="url(#terminal-4044056635-line-0)">
|
||||
</text><text class="terminal-4044056635-r2" x="915" y="44.4" textLength="12.2" clip-path="url(#terminal-4044056635-line-1)">
|
||||
</text><text class="terminal-4044056635-r2" x="0" y="68.8" textLength="292.8" clip-path="url(#terminal-4044056635-line-2)">Perform Tasks operations</text><text class="terminal-4044056635-r2" x="915" y="68.8" textLength="12.2" clip-path="url(#terminal-4044056635-line-2)">
|
||||
</text><text class="terminal-4044056635-r2" x="915" y="93.2" textLength="12.2" clip-path="url(#terminal-4044056635-line-3)">
|
||||
</text><text class="terminal-4044056635-r1" x="0" y="117.6" textLength="256.2" clip-path="url(#terminal-4044056635-line-4)">Positional Arguments:</text><text class="terminal-4044056635-r2" x="915" y="117.6" textLength="12.2" clip-path="url(#terminal-4044056635-line-4)">
|
||||
</text><text class="terminal-4044056635-r4" x="24.4" y="142" textLength="85.4" clip-path="url(#terminal-4044056635-line-5)">COMMAND</text><text class="terminal-4044056635-r2" x="915" y="142" textLength="12.2" clip-path="url(#terminal-4044056635-line-5)">
|
||||
</text><text class="terminal-4044056635-r4" x="48.8" y="166.4" textLength="61" clip-path="url(#terminal-4044056635-line-6)">clear</text><text class="terminal-4044056635-r2" x="268.4" y="166.4" textLength="475.8" clip-path="url(#terminal-4044056635-line-6)">Clear task instances of a Dag by its ID</text><text class="terminal-4044056635-r2" x="915" y="166.4" textLength="12.2" clip-path="url(#terminal-4044056635-line-6)">
|
||||
</text><text class="terminal-4044056635-r4" x="48.8" y="190.8" textLength="134.2" clip-path="url(#terminal-4044056635-line-7)">failed-deps</text><text class="terminal-4044056635-r2" x="268.4" y="190.8" textLength="610" clip-path="url(#terminal-4044056635-line-7)">Returns the unmet dependencies for a task instance</text><text class="terminal-4044056635-r2" x="915" y="190.8" textLength="12.2" clip-path="url(#terminal-4044056635-line-7)">
|
||||
</text><text class="terminal-4044056635-r4" x="48.8" y="215.2" textLength="36.6" clip-path="url(#terminal-4044056635-line-8)">get</text><text class="terminal-4044056635-r2" x="268.4" y="215.2" textLength="573.4" clip-path="url(#terminal-4044056635-line-8)">Retrieve a task for a Dag by its ID and task ID</text><text class="terminal-4044056635-r2" x="915" y="215.2" textLength="12.2" clip-path="url(#terminal-4044056635-line-8)">
|
||||
</text><text class="terminal-4044056635-r4" x="48.8" y="239.6" textLength="48.8" clip-path="url(#terminal-4044056635-line-9)">list</text><text class="terminal-4044056635-r2" x="268.4" y="239.6" textLength="244" clip-path="url(#terminal-4044056635-line-9)">List tasks for a Dag</text><text class="terminal-4044056635-r2" x="915" y="239.6" textLength="12.2" clip-path="url(#terminal-4044056635-line-9)">
|
||||
</text><text class="terminal-4044056635-r4" x="48.8" y="264" textLength="61" clip-path="url(#terminal-4044056635-line-10)">state</text><text class="terminal-4044056635-r2" x="268.4" y="264" textLength="390.4" clip-path="url(#terminal-4044056635-line-10)">Get the state of a task instance</text><text class="terminal-4044056635-r2" x="915" y="264" textLength="12.2" clip-path="url(#terminal-4044056635-line-10)">
|
||||
</text><text class="terminal-4044056635-r4" x="48.8" y="288.4" textLength="219.6" clip-path="url(#terminal-4044056635-line-11)">states-for-dag-run</text><text class="terminal-4044056635-r2" x="915" y="288.4" textLength="12.2" clip-path="url(#terminal-4044056635-line-11)">
|
||||
</text><text class="terminal-4044056635-r2" x="268.4" y="312.8" textLength="597.8" clip-path="url(#terminal-4044056635-line-12)">Get the status of all task instances in a Dag run</text><text class="terminal-4044056635-r2" x="915" y="312.8" textLength="12.2" clip-path="url(#terminal-4044056635-line-12)">
|
||||
</text><text class="terminal-4044056635-r2" x="915" y="337.2" textLength="12.2" clip-path="url(#terminal-4044056635-line-13)">
|
||||
</text><text class="terminal-4044056635-r1" x="0" y="361.6" textLength="97.6" clip-path="url(#terminal-4044056635-line-14)">Options:</text><text class="terminal-4044056635-r2" x="915" y="361.6" textLength="12.2" clip-path="url(#terminal-4044056635-line-14)">
|
||||
</text><text class="terminal-4044056635-r4" x="24.4" y="386" textLength="24.4" clip-path="url(#terminal-4044056635-line-15)">-h</text><text class="terminal-4044056635-r2" x="48.8" y="386" textLength="24.4" clip-path="url(#terminal-4044056635-line-15)">, </text><text class="terminal-4044056635-r4" x="73.2" y="386" textLength="73.2" clip-path="url(#terminal-4044056635-line-15)">--help</text><text class="terminal-4044056635-r2" x="268.4" y="386" textLength="378.2" clip-path="url(#terminal-4044056635-line-15)">show this help message and exit</text><text class="terminal-4044056635-r2" x="915" y="386" textLength="12.2" clip-path="url(#terminal-4044056635-line-15)">
|
||||
</text><text class="terminal-4044056635-r2" x="915" y="410.4" textLength="12.2" clip-path="url(#terminal-4044056635-line-16)">
|
||||
<g class="terminal-986165217-matrix">
|
||||
<text class="terminal-986165217-r1" x="0" y="20" textLength="73.2" clip-path="url(#terminal-986165217-line-0)">Usage:</text><text class="terminal-986165217-r3" x="85.4" y="20" textLength="195.2" clip-path="url(#terminal-986165217-line-0)">airflowctl tasks</text><text class="terminal-986165217-r2" x="280.6" y="20" textLength="24.4" clip-path="url(#terminal-986165217-line-0)"> [</text><text class="terminal-986165217-r4" x="305" y="20" textLength="24.4" clip-path="url(#terminal-986165217-line-0)">-h</text><text class="terminal-986165217-r2" x="329.4" y="20" textLength="24.4" clip-path="url(#terminal-986165217-line-0)">] </text><text class="terminal-986165217-r4" x="353.8" y="20" textLength="85.4" clip-path="url(#terminal-986165217-line-0)">COMMAND</text><text class="terminal-986165217-r4" x="451.4" y="20" textLength="36.6" clip-path="url(#terminal-986165217-line-0)">...</text><text class="terminal-986165217-r2" x="915" y="20" textLength="12.2" clip-path="url(#terminal-986165217-line-0)">
|
||||
</text><text class="terminal-986165217-r2" x="915" y="44.4" textLength="12.2" clip-path="url(#terminal-986165217-line-1)">
|
||||
</text><text class="terminal-986165217-r2" x="0" y="68.8" textLength="292.8" clip-path="url(#terminal-986165217-line-2)">Perform Tasks operations</text><text class="terminal-986165217-r2" x="915" y="68.8" textLength="12.2" clip-path="url(#terminal-986165217-line-2)">
|
||||
</text><text class="terminal-986165217-r2" x="915" y="93.2" textLength="12.2" clip-path="url(#terminal-986165217-line-3)">
|
||||
</text><text class="terminal-986165217-r1" x="0" y="117.6" textLength="256.2" clip-path="url(#terminal-986165217-line-4)">Positional Arguments:</text><text class="terminal-986165217-r2" x="915" y="117.6" textLength="12.2" clip-path="url(#terminal-986165217-line-4)">
|
||||
</text><text class="terminal-986165217-r4" x="24.4" y="142" textLength="85.4" clip-path="url(#terminal-986165217-line-5)">COMMAND</text><text class="terminal-986165217-r2" x="915" y="142" textLength="12.2" clip-path="url(#terminal-986165217-line-5)">
|
||||
</text><text class="terminal-986165217-r4" x="48.8" y="166.4" textLength="61" clip-path="url(#terminal-986165217-line-6)">clear</text><text class="terminal-986165217-r2" x="268.4" y="166.4" textLength="475.8" clip-path="url(#terminal-986165217-line-6)">Clear task instances of a Dag by its ID</text><text class="terminal-986165217-r2" x="915" y="166.4" textLength="12.2" clip-path="url(#terminal-986165217-line-6)">
|
||||
</text><text class="terminal-986165217-r4" x="48.8" y="190.8" textLength="134.2" clip-path="url(#terminal-986165217-line-7)">failed-deps</text><text class="terminal-986165217-r2" x="268.4" y="190.8" textLength="610" clip-path="url(#terminal-986165217-line-7)">Returns the unmet dependencies for a task instance</text><text class="terminal-986165217-r2" x="915" y="190.8" textLength="12.2" clip-path="url(#terminal-986165217-line-7)">
|
||||
</text><text class="terminal-986165217-r4" x="48.8" y="215.2" textLength="36.6" clip-path="url(#terminal-986165217-line-8)">get</text><text class="terminal-986165217-r2" x="268.4" y="215.2" textLength="573.4" clip-path="url(#terminal-986165217-line-8)">Retrieve a task for a Dag by its ID and task ID</text><text class="terminal-986165217-r2" x="915" y="215.2" textLength="12.2" clip-path="url(#terminal-986165217-line-8)">
|
||||
</text><text class="terminal-986165217-r4" x="48.8" y="239.6" textLength="48.8" clip-path="url(#terminal-986165217-line-9)">list</text><text class="terminal-986165217-r2" x="268.4" y="239.6" textLength="244" clip-path="url(#terminal-986165217-line-9)">List tasks for a Dag</text><text class="terminal-986165217-r2" x="915" y="239.6" textLength="12.2" clip-path="url(#terminal-986165217-line-9)">
|
||||
</text><text class="terminal-986165217-r4" x="48.8" y="264" textLength="48.8" clip-path="url(#terminal-986165217-line-10)">logs</text><text class="terminal-986165217-r2" x="268.4" y="264" textLength="378.2" clip-path="url(#terminal-986165217-line-10)">Get the logs of a task instance</text><text class="terminal-986165217-r2" x="915" y="264" textLength="12.2" clip-path="url(#terminal-986165217-line-10)">
|
||||
</text><text class="terminal-986165217-r4" x="48.8" y="288.4" textLength="61" clip-path="url(#terminal-986165217-line-11)">state</text><text class="terminal-986165217-r2" x="268.4" y="288.4" textLength="390.4" clip-path="url(#terminal-986165217-line-11)">Get the state of a task instance</text><text class="terminal-986165217-r2" x="915" y="288.4" textLength="12.2" clip-path="url(#terminal-986165217-line-11)">
|
||||
</text><text class="terminal-986165217-r4" x="48.8" y="312.8" textLength="219.6" clip-path="url(#terminal-986165217-line-12)">states-for-dag-run</text><text class="terminal-986165217-r2" x="915" y="312.8" textLength="12.2" clip-path="url(#terminal-986165217-line-12)">
|
||||
</text><text class="terminal-986165217-r2" x="268.4" y="337.2" textLength="597.8" clip-path="url(#terminal-986165217-line-13)">Get the status of all task instances in a Dag run</text><text class="terminal-986165217-r2" x="915" y="337.2" textLength="12.2" clip-path="url(#terminal-986165217-line-13)">
|
||||
</text><text class="terminal-986165217-r2" x="915" y="361.6" textLength="12.2" clip-path="url(#terminal-986165217-line-14)">
|
||||
</text><text class="terminal-986165217-r1" x="0" y="386" textLength="97.6" clip-path="url(#terminal-986165217-line-15)">Options:</text><text class="terminal-986165217-r2" x="915" y="386" textLength="12.2" clip-path="url(#terminal-986165217-line-15)">
|
||||
</text><text class="terminal-986165217-r4" x="24.4" y="410.4" textLength="24.4" clip-path="url(#terminal-986165217-line-16)">-h</text><text class="terminal-986165217-r2" x="48.8" y="410.4" textLength="24.4" clip-path="url(#terminal-986165217-line-16)">, </text><text class="terminal-986165217-r4" x="73.2" y="410.4" textLength="73.2" clip-path="url(#terminal-986165217-line-16)">--help</text><text class="terminal-986165217-r2" x="268.4" y="410.4" textLength="378.2" clip-path="url(#terminal-986165217-line-16)">show this help message and exit</text><text class="terminal-986165217-r2" x="915" y="410.4" textLength="12.2" clip-path="url(#terminal-986165217-line-16)">
|
||||
</text><text class="terminal-986165217-r2" x="915" y="434.8" textLength="12.2" clip-path="url(#terminal-986165217-line-17)">
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@@ -83,6 +83,7 @@ from airflowctl.api.datamodels.generated import (
|
||||
TaskDependencyCollectionResponse,
|
||||
TaskInstanceCollectionResponse,
|
||||
TaskInstanceResponse,
|
||||
TaskInstancesLogResponse,
|
||||
TaskResponse,
|
||||
TaskStateStoreBody,
|
||||
TaskStateStoreCollectionResponse,
|
||||
@@ -879,6 +880,32 @@ class TaskInstancesOperations(BaseOperations):
|
||||
)
|
||||
return validate_response(self.response.content, TaskDependencyCollectionResponse)
|
||||
|
||||
def logs(
|
||||
self,
|
||||
dag_id: str,
|
||||
dag_run_id: str,
|
||||
task_id: str,
|
||||
try_number: int,
|
||||
map_index: int | None = None,
|
||||
*,
|
||||
suppress_error_log: bool = False,
|
||||
) -> TaskInstancesLogResponse | ServerResponseError:
|
||||
"""Get the logs of a task instance for a given try number."""
|
||||
path = _build_task_instance_path(
|
||||
dag_id=dag_id, dag_run_id=dag_run_id, task_id=task_id, map_index=None
|
||||
)
|
||||
# Without full_content the endpoint returns one chunk and a continuation token, so a running or
|
||||
# remotely stored log would print only partially.
|
||||
params: dict[str, Any] = {"full_content": True}
|
||||
if map_index is not None and map_index >= 0:
|
||||
params["map_index"] = map_index
|
||||
self.response = self.client.get(
|
||||
f"{path}/logs/{try_number}",
|
||||
params=params,
|
||||
extensions={"airflowctl_suppress_error_log": suppress_error_log},
|
||||
)
|
||||
return TaskInstancesLogResponse.model_validate_json(self.response.content)
|
||||
|
||||
def list(self, dag_id: str, dag_run_id: str) -> TaskInstanceCollectionResponse | ServerResponseError:
|
||||
"""List task instances for a Dag run."""
|
||||
return super().execute_list(
|
||||
|
||||
@@ -415,6 +415,20 @@ ARG_MAP_INDEX = Arg(
|
||||
help="Mapped task index",
|
||||
)
|
||||
|
||||
# Task logs command args. Required primitive parameters stay positional per the
|
||||
# airflowctl parameter style consensus (#66768).
|
||||
ARG_TASKS_LOGS_DAG_RUN_ID = Arg(
|
||||
flags=("dag_run_id",),
|
||||
type=str,
|
||||
help="The run ID of the Dag run",
|
||||
)
|
||||
ARG_TRY_NUMBER = Arg(
|
||||
flags=("--try-number",),
|
||||
type=int,
|
||||
required=True,
|
||||
help="The try number of the task instance logs to fetch",
|
||||
)
|
||||
|
||||
ARG_ACTION_ON_EXISTING_KEY = Arg(
|
||||
flags=("-a", "--action-on-existing-key"),
|
||||
type=str,
|
||||
@@ -1430,6 +1444,19 @@ TASK_COMMANDS = (
|
||||
ARG_OUTPUT,
|
||||
),
|
||||
),
|
||||
ActionCommand(
|
||||
name="logs",
|
||||
help="Get the logs of a task instance",
|
||||
description="Get the logs of a task instance for a given try number.",
|
||||
func=lazy_load_command("airflowctl.ctl.commands.task_command.logs"),
|
||||
args=(
|
||||
ARG_DAG_ID,
|
||||
ARG_TASKS_LOGS_DAG_RUN_ID,
|
||||
ARG_TASK_ID,
|
||||
ARG_MAP_INDEX,
|
||||
ARG_TRY_NUMBER,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
VARIABLE_COMMANDS = (
|
||||
|
||||
@@ -23,7 +23,7 @@ from typing import TYPE_CHECKING
|
||||
import rich
|
||||
|
||||
from airflowctl.api.client import NEW_API_CLIENT, ClientKind, ServerResponseError, provide_api_client
|
||||
from airflowctl.api.datamodels.generated import TaskInstanceState
|
||||
from airflowctl.api.datamodels.generated import StructuredLogMessage, TaskInstanceState
|
||||
from airflowctl.ctl.console_formatting import AirflowConsole
|
||||
from airflowctl.ctl.utils.dag_run import resolve_dag_run_id
|
||||
|
||||
@@ -158,3 +158,27 @@ def state(args, api_client=NEW_API_CLIENT) -> None:
|
||||
# Unset states print as None to stay drop-in compatible with the deprecated
|
||||
# `airflow tasks state`, which prints its nullable `ti.state` column directly.
|
||||
print(task_instance.state.value if task_instance.state else None)
|
||||
|
||||
|
||||
@provide_api_client(kind=ClientKind.CLI)
|
||||
def logs(args, api_client=NEW_API_CLIENT) -> None:
|
||||
"""Get the logs of a task instance for a given try."""
|
||||
try:
|
||||
response = api_client.task_instances.logs(
|
||||
dag_id=args.dag_id,
|
||||
dag_run_id=args.dag_run_id,
|
||||
task_id=args.task_id,
|
||||
try_number=args.try_number,
|
||||
map_index=args.map_index,
|
||||
suppress_error_log=True,
|
||||
)
|
||||
except ServerResponseError as e:
|
||||
if e.response.status_code == 404:
|
||||
rich.print(
|
||||
f"[red]{_task_instance_not_found_message(args.dag_id, args.dag_run_id, args.task_id, args.map_index, e)}[/red]"
|
||||
)
|
||||
sys.exit(1)
|
||||
raise
|
||||
|
||||
for entry in response.content:
|
||||
print(entry.event if isinstance(entry, StructuredLogMessage) else entry)
|
||||
@@ -96,6 +96,7 @@ taskinstances:
|
||||
list: "List all task instances for a given Dag run"
|
||||
get: "Get a task instance for a given Dag run"
|
||||
get-dependencies: "Get unmet scheduler dependencies for a task instance"
|
||||
logs: "Get the logs of a task instance for a given try number"
|
||||
# state-store is the nested subcommand group. The *-state-store keys stay because
|
||||
# help is still looked up by the operation method name, not the CLI path.
|
||||
state-store: "Manage the state store of a task instance"
|
||||
|
||||
@@ -105,6 +105,7 @@ from airflowctl.api.datamodels.generated import (
|
||||
TaskInletAssetReference,
|
||||
TaskInstanceCollectionResponse,
|
||||
TaskInstanceResponse,
|
||||
TaskInstancesLogResponse,
|
||||
TaskInstanceState,
|
||||
TaskOutletAssetReference,
|
||||
TaskResponse,
|
||||
@@ -2450,6 +2451,27 @@ class TestTaskInstancesOperations:
|
||||
)
|
||||
assert response == self.task_dependency_collection_response
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("map_index", "expected_params"),
|
||||
[
|
||||
pytest.param(None, {"full_content": "true"}, id="unmapped"),
|
||||
pytest.param(2, {"full_content": "true", "map_index": "2"}, id="mapped"),
|
||||
],
|
||||
)
|
||||
def test_logs_requests_full_content(self, map_index, expected_params):
|
||||
log_response = TaskInstancesLogResponse(content=["line"], continuation_token=None)
|
||||
|
||||
def handle_request(request: httpx.Request) -> httpx.Response:
|
||||
assert request.url.path == "/api/v2/dags/dag_id/dagRuns/dag_run_id/taskInstances/task_id/logs/1"
|
||||
assert dict(request.url.params) == expected_params
|
||||
return httpx.Response(200, json=json.loads(log_response.model_dump_json()))
|
||||
|
||||
client = make_api_client(transport=httpx.MockTransport(handle_request))
|
||||
response = client.task_instances.logs(
|
||||
dag_id="dag_id", dag_run_id="dag_run_id", task_id="task_id", try_number=1, map_index=map_index
|
||||
)
|
||||
assert response == log_response
|
||||
|
||||
def test_get_dependencies_with_map_index(self):
|
||||
client = self._make_client_asserting_path(
|
||||
"/api/v2/dags/dag_id/dagRuns/dag_run_id/taskInstances/task_id/3/dependencies",
|
||||
|
||||
@@ -24,10 +24,12 @@ import httpx
|
||||
import pytest
|
||||
|
||||
from airflowctl.api.datamodels.generated import (
|
||||
StructuredLogMessage,
|
||||
TaskDependencyCollectionResponse,
|
||||
TaskDependencyResponse,
|
||||
TaskInstanceCollectionResponse,
|
||||
TaskInstanceResponse,
|
||||
TaskInstancesLogResponse,
|
||||
TaskInstanceState,
|
||||
)
|
||||
from airflowctl.api.operations import ServerResponseError
|
||||
@@ -909,3 +911,122 @@ class TestState:
|
||||
self.parser.parse_args(["tasks", "state", self.dag_id, self.task_id, self.run_id]),
|
||||
api_client=api_client,
|
||||
)
|
||||
|
||||
|
||||
class TestLogs:
|
||||
parser = cli_parser.get_parser()
|
||||
dag_id = "test_dag"
|
||||
run_id = "test_run"
|
||||
task_id = "test_task"
|
||||
|
||||
def _make_log_response(self, content: list[StructuredLogMessage] | list[str]) -> TaskInstancesLogResponse:
|
||||
return TaskInstancesLogResponse(content=content, continuation_token=None)
|
||||
|
||||
def _make_structured_content(self) -> list[StructuredLogMessage]:
|
||||
return [
|
||||
StructuredLogMessage(
|
||||
timestamp=datetime.datetime(2025, 1, 1, tzinfo=datetime.UTC),
|
||||
event="INFO - hello",
|
||||
),
|
||||
StructuredLogMessage(
|
||||
timestamp=datetime.datetime(2025, 1, 1, tzinfo=datetime.UTC),
|
||||
event="plain line",
|
||||
),
|
||||
]
|
||||
|
||||
def test_logs_with_try_number(self, capsys):
|
||||
api_client = mock.MagicMock()
|
||||
api_client.task_instances.logs.return_value = self._make_log_response(self._make_structured_content())
|
||||
|
||||
task_command.logs(
|
||||
self.parser.parse_args(
|
||||
["tasks", "logs", self.dag_id, self.run_id, self.task_id, "--try-number", "1"]
|
||||
),
|
||||
api_client=api_client,
|
||||
)
|
||||
|
||||
api_client.task_instances.logs.assert_called_once_with(
|
||||
dag_id=self.dag_id,
|
||||
dag_run_id=self.run_id,
|
||||
task_id=self.task_id,
|
||||
try_number=1,
|
||||
map_index=-1,
|
||||
suppress_error_log=True,
|
||||
)
|
||||
assert capsys.readouterr().out == "INFO - hello\nplain line\n"
|
||||
|
||||
def test_logs_requires_try_number(self, capsys):
|
||||
with pytest.raises(SystemExit, match="2"):
|
||||
self.parser.parse_args(["tasks", "logs", self.dag_id, self.run_id, self.task_id])
|
||||
assert "--try-number" in capsys.readouterr().err
|
||||
|
||||
def test_logs_prints_plain_string_entries(self, capsys):
|
||||
api_client = mock.MagicMock()
|
||||
api_client.task_instances.logs.return_value = self._make_log_response(["plain line"])
|
||||
|
||||
task_command.logs(
|
||||
self.parser.parse_args(
|
||||
["tasks", "logs", self.dag_id, self.run_id, self.task_id, "--try-number", "1"]
|
||||
),
|
||||
api_client=api_client,
|
||||
)
|
||||
|
||||
assert capsys.readouterr().out == "plain line\n"
|
||||
|
||||
def test_logs_with_explicit_try_number_and_map_index(self, capsys):
|
||||
api_client = mock.MagicMock()
|
||||
api_client.task_instances.logs.return_value = self._make_log_response(self._make_structured_content())
|
||||
|
||||
task_command.logs(
|
||||
self.parser.parse_args(
|
||||
[
|
||||
"tasks",
|
||||
"logs",
|
||||
self.dag_id,
|
||||
self.run_id,
|
||||
self.task_id,
|
||||
"--try-number",
|
||||
"2",
|
||||
"--map-index",
|
||||
"3",
|
||||
]
|
||||
),
|
||||
api_client=api_client,
|
||||
)
|
||||
|
||||
api_client.task_instances.logs.assert_called_once_with(
|
||||
dag_id=self.dag_id,
|
||||
dag_run_id=self.run_id,
|
||||
task_id=self.task_id,
|
||||
try_number=2,
|
||||
map_index=3,
|
||||
suppress_error_log=True,
|
||||
)
|
||||
|
||||
def test_logs_task_instance_not_found(self, capsys):
|
||||
api_client = mock.MagicMock()
|
||||
api_client.task_instances.logs.side_effect = _make_server_error(404)
|
||||
|
||||
with pytest.raises(SystemExit, match="1"):
|
||||
task_command.logs(
|
||||
self.parser.parse_args(
|
||||
["tasks", "logs", self.dag_id, self.run_id, self.task_id, "--try-number", "1"]
|
||||
),
|
||||
api_client=api_client,
|
||||
)
|
||||
|
||||
assert _normalize_rich_output(capsys.readouterr().out) == (
|
||||
"Task instance for task 'test_task' in Dag run 'test_run' of Dag 'test_dag' not found"
|
||||
)
|
||||
|
||||
def test_logs_reraises_non_404_error(self):
|
||||
api_client = mock.MagicMock()
|
||||
api_client.task_instances.logs.side_effect = _make_server_error(500)
|
||||
|
||||
with pytest.raises(ServerResponseError):
|
||||
task_command.logs(
|
||||
self.parser.parse_args(
|
||||
["tasks", "logs", self.dag_id, self.run_id, self.task_id, "--try-number", "1"]
|
||||
),
|
||||
api_client=api_client,
|
||||
)
|
||||
Reference in new issue
Block a user