Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/azure-cli/azure/cli/command_modules/appservice/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -2365,6 +2365,39 @@
text: az webapp log deployment list --name MyWebApp --resource-group MyResourceGroup
"""

helps['webapp log startup'] = """
type: group
short-summary: View web app container startup logs.
long-summary: >
View startup logs written during container initialization for Linux web apps.
Use when a container fails to start, crashes on cold start, times out waiting
for a port, or returns HTTP 502/503 errors after deployment. These logs contain
platform lifecycle events and container stdout/stderr output.
"""

helps['webapp log startup list'] = """
type: command
short-summary: List all container startup log files for a web app.
examples:
- name: List all startup log files
text: az webapp log startup list --name MyWebApp --resource-group MyResourceGroup
- name: List only failure logs
text: az webapp log startup list --name MyWebApp --resource-group MyResourceGroup --outcome failure
"""

helps['webapp log startup show'] = """
type: command
short-summary: Show the content of a container startup log.
long-summary: By default, shows the most recent startup log, preferring failure logs. Use --filename to view a specific log file, or --instance to scope to a specific worker.
examples:
- name: Show the latest startup log (prefers failures)
text: az webapp log startup show --name MyWebApp --resource-group MyResourceGroup
- name: Show a specific startup log file
text: az webapp log startup show --name MyWebApp --resource-group MyResourceGroup --filename 2026_04_13_lw0sdlwk000002_failure.log
- name: Show the latest startup log for a specific worker instance
text: az webapp log startup show --name MyWebApp --resource-group MyResourceGroup --instance lw0sdlwk000002
"""

helps['functionapp log'] = """
type: group
short-summary: Manage function app logs.
Expand Down
13 changes: 13 additions & 0 deletions src/azure-cli/azure/cli/command_modules/appservice/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,19 @@ def load_arguments(self, _):
c.argument('resource_group', arg_type=resource_group_name_type)
c.argument('slot', options_list=['--slot', '-s'], help="the name of the slot. Default to the productions slot if not specified")

with self.argument_context('webapp log startup') as c:
c.argument('name', arg_type=webapp_name_arg_type, id_part=None)
c.argument('resource_group', arg_type=resource_group_name_type)
c.argument('slot', options_list=['--slot', '-s'], help="the name of the slot. Default to the production slot if not specified")
c.argument('instance', options_list=['--instance'], help='Filter by worker instance name.')

with self.argument_context('webapp log startup list') as c:
c.argument('outcome', options_list=['--outcome'], help='Filter by startup outcome.',
arg_type=get_enum_type(['success', 'failure']))

with self.argument_context('webapp log startup show') as c:
Copy link
Copy Markdown

@aamos-company aamos-company Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the show also have an instance param? Currently, if a customer is running on 3+ worker, what is considered the "lastest log"? Sometimes the application fails on one specific worker, so if I were a customer I would want to see the latest on the failing worker v. the successful worker for comparison

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great suggestion. Added --instance to the show command. The KuduLite StartupLogsController already supports ?latest=true&instance=X (the instance param is accepted independently alongside latest), so /api/startuplogs?latest=true&instance=lw0sdlwk000002 returns the most recent log for that specific worker. Also added a unit test and a help example for it.

c.argument('filename', options_list=['--filename', '-f'], help='Name of a specific startup log file to display. If not specified, shows the latest log (preferring failures).')

with self.argument_context('functionapp log deployment show') as c:
c.argument('name', arg_type=functionapp_name_arg_type, id_part=None)
c.argument('resource_group', arg_type=resource_group_name_type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ def load_command_table(self, _):
g.custom_show_command('show', 'show_deployment_log')
g.custom_command('list', 'list_deployment_logs')

with self.command_group('webapp log startup', is_preview=True) as g:
g.custom_command('list', 'list_startup_logs')
g.custom_show_command('show', 'show_startup_log')

with self.command_group('functionapp log deployment') as g:
g.custom_show_command('show', 'show_deployment_log')
g.custom_command('list', 'list_deployment_logs')
Expand Down
82 changes: 82 additions & 0 deletions src/azure-cli/azure/cli/command_modules/appservice/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -5597,6 +5597,78 @@ def list_deployment_logs(cmd, resource_group, name, slot=None):
return response.json() or []


def list_startup_logs(cmd, resource_group, name, slot=None, outcome=None, instance=None):
import requests

scm_url = _get_scm_url(cmd, resource_group, name, slot)
headers = get_scm_site_headers(cmd.cli_ctx, name, resource_group, slot)

params = {}
if outcome:
params['type'] = outcome
if instance:
params['instance'] = instance

url = '{}/api/startuplogs'.format(scm_url)
response = requests.get(url, headers=headers, params=params)

if response.status_code == 404:
logger.warning(
'Startup logs are not available for this app. '
'This feature requires a platform version that may not have rolled out to your app\'s region yet.')
return []
if response.status_code != 200:
raise CLIError("Failed to retrieve startup logs from '{}' with status code '{}' and reason '{}'".format(
url, response.status_code, response.reason))

result = response.json()
return result.get('files', result) if isinstance(result, dict) else result


def show_startup_log(cmd, resource_group, name, slot=None, filename=None, instance=None):
import requests

scm_url = _get_scm_url(cmd, resource_group, name, slot)
headers = get_scm_site_headers(cmd.cli_ctx, name, resource_group, slot)

if filename:
url = '{}/api/startuplogs/{}'.format(scm_url, quote(filename, safe=''))
else:
url = '{}/api/startuplogs?latest=true'.format(scm_url)
if instance:
url += '&instance={}'.format(quote(instance, safe=''))

response = requests.get(url, headers=headers)

if response.status_code == 404:
if filename:
logger.warning('Startup log file \'%s\' was not found.', filename)
else:
logger.warning(
'Startup logs are not available for this app. '
'This feature requires a platform version that may not have rolled out to your app\'s region yet.')
return None
if response.status_code != 200:
raise CLIError("Failed to retrieve startup log from '{}' with status code '{}' and reason '{}'".format(
url, response.status_code, response.reason))

content_type = response.headers.get('Content-Type', '')
if 'text/plain' in content_type:
# Raw log content — return metadata from headers along with content
log_content = response.text
metadata = {}
for header_name in ['X-StartupLog-Filename', 'X-StartupLog-Date', 'X-StartupLog-Instance',
'X-StartupLog-Outcome']:
value = response.headers.get(header_name)
if value:
key = header_name.replace('X-StartupLog-', '').lower()
metadata[key] = value
metadata['content'] = log_content
return metadata

return response.json()


def config_slot_auto_swap(cmd, resource_group_name, webapp, slot, auto_swap_slot=None, disable=None):
client = web_client_factory(cmd.cli_ctx)
site_config = client.web_apps.get_configuration_slot(resource_group_name, webapp, slot)
Expand Down Expand Up @@ -8521,6 +8593,11 @@ def _poll_deployment_runtime_status(cmd, resource_group_name, webapp_name, slot,
if failure_logs is not None and len(failure_logs) > 0:
failure_logs = failure_logs[0]
error_text += "Please check the runtime logs for more info: {}\n".format(failure_logs)
tip_cmd = "az webapp log startup show -n {} -g {}".format(webapp_name, resource_group_name)
if slot:
tip_cmd += " --slot {}".format(slot)
error_text += ("TIP: Run '{}' "
"to view container startup logs.\n").format(tip_cmd)
if site_started_partially:
logger.warning(error_text)
break
Expand Down Expand Up @@ -8565,6 +8642,11 @@ def _poll_deployment_runtime_status(cmd, resource_group_name, webapp_name, slot,
deployment_properties.get('numberOfInstancesInProgress'),
deployment_properties.get('numberOfInstancesSuccessful'),
deployment_properties.get('numberOfInstancesFailed'))
tip_cmd = "az webapp log startup show -n {} -g {}".format(webapp_name, resource_group_name)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this being recommended for all app types? This is currently only available for Linux so want to make sure it's not also being presented for Windows

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call out!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. This is already scoped to Linux only. The _poll_deployment_runtime_status function is only reachable from the Linux code path. The caller gates on is_linux_webapp(app) at line ~8510, so Windows apps and slot deployments go through _check_zip_deployment_status instead, which doesn't include the hint.

if slot:
tip_cmd += " --slot {}".format(slot)
error_text += ("\nTIP: Run '{}' "
"to view container startup logs.").format(tip_cmd)
raise CLIError(error_text)
return response_body

Expand Down
Loading
Loading