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:
BobSongandJarek Potiuk authored and GitHub committed 2026-11-01 14:12:00 +00:00
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',
+2 -2
View File
@@ -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&#160;taskinstances</text><text class="terminal-944627566-r2" x="378.2" y="20" textLength="24.4" clip-path="url(#terminal-944627566-line-0)">&#160;[</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)">]&#160;</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&#160;TaskInstances&#160;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&#160;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&#160;a&#160;task&#160;instance&#160;for&#160;a&#160;given&#160;Dag&#160;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&#160;unmet&#160;scheduler&#160;dependencies&#160;for&#160;a&#160;task&#160;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&#160;all&#160;task&#160;instances&#160;for&#160;a&#160;given&#160;Dag&#160;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&#160;the&#160;state&#160;store&#160;of&#160;a&#160;task&#160;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)">,&#160;</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&#160;this&#160;help&#160;message&#160;and&#160;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&#160;taskinstances</text><text class="terminal-57093932-r2" x="378.2" y="20" textLength="24.4" clip-path="url(#terminal-57093932-line-0)">&#160;[</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)">]&#160;</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&#160;TaskInstances&#160;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&#160;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&#160;a&#160;task&#160;instance&#160;for&#160;a&#160;given&#160;Dag&#160;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&#160;unmet&#160;scheduler&#160;dependencies&#160;for&#160;a&#160;task&#160;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&#160;all&#160;task&#160;instances&#160;for&#160;a&#160;given&#160;Dag&#160;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&#160;the&#160;logs&#160;of&#160;a&#160;task&#160;instance&#160;for&#160;a&#160;given&#160;try&#160;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&#160;the&#160;state&#160;store&#160;of&#160;a&#160;task&#160;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)">,&#160;</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&#160;this&#160;help&#160;message&#160;and&#160;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

+49 -45
View File
@@ -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&#160;tasks</text><text class="terminal-4044056635-r2" x="280.6" y="20" textLength="24.4" clip-path="url(#terminal-4044056635-line-0)">&#160;[</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)">]&#160;</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&#160;Tasks&#160;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&#160;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&#160;task&#160;instances&#160;of&#160;a&#160;Dag&#160;by&#160;its&#160;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&#160;the&#160;unmet&#160;dependencies&#160;for&#160;a&#160;task&#160;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&#160;a&#160;task&#160;for&#160;a&#160;Dag&#160;by&#160;its&#160;ID&#160;and&#160;task&#160;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&#160;tasks&#160;for&#160;a&#160;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&#160;the&#160;state&#160;of&#160;a&#160;task&#160;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&#160;the&#160;status&#160;of&#160;all&#160;task&#160;instances&#160;in&#160;a&#160;Dag&#160;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)">,&#160;</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&#160;this&#160;help&#160;message&#160;and&#160;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&#160;tasks</text><text class="terminal-986165217-r2" x="280.6" y="20" textLength="24.4" clip-path="url(#terminal-986165217-line-0)">&#160;[</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)">]&#160;</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&#160;Tasks&#160;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&#160;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&#160;task&#160;instances&#160;of&#160;a&#160;Dag&#160;by&#160;its&#160;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&#160;the&#160;unmet&#160;dependencies&#160;for&#160;a&#160;task&#160;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&#160;a&#160;task&#160;for&#160;a&#160;Dag&#160;by&#160;its&#160;ID&#160;and&#160;task&#160;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&#160;tasks&#160;for&#160;a&#160;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&#160;the&#160;logs&#160;of&#160;a&#160;task&#160;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&#160;the&#160;state&#160;of&#160;a&#160;task&#160;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&#160;the&#160;status&#160;of&#160;all&#160;task&#160;instances&#160;in&#160;a&#160;Dag&#160;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)">,&#160;</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&#160;this&#160;help&#160;message&#160;and&#160;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,
)