blob: 8464ae4341c8b7a3014920f57fe26afe72632502 [file] [log] [blame]
Struan Shrimptona64cb192024-03-05 02:22:001# Copyright 2024 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Struan Shrimptond98829f2024-03-12 19:41:384"""Adapter for printing legacy recipe output
Struan Shrimptona64cb192024-03-05 02:22:005
Struan Shrimptond98829f2024-03-12 19:41:386TODO(https://crbug.com/326904531): This file is intended to be a temporary
7workaround and should be replaced once this bug is resolved"""
8
9import json
Struan Shrimptona64cb192024-03-05 02:22:0010import logging
Struan Shrimpton33065b92024-11-15 23:00:1511import os
Struan Shrimptona64cb192024-03-05 02:22:0012import re
13import sys
14
Struan Shrimpton3b28ff02024-04-03 22:46:0515# Create a logger that avoids rich formatting as we can't control recipe
16# formatting from here
17basic_logger = logging.getLogger('basic_logger')
18basic_logger.addHandler(logging.StreamHandler(sys.stdout))
19basic_logger.propagate = False
Struan Shrimptona64cb192024-03-05 02:22:0020
Ben Pasteneca5ad4b2024-03-05 19:02:4721class PassthroughAdapter:
22 """Doesn't filter anything, just logs everything from the recipe run."""
23
24 def ProcessLine(self, line):
Struan Shrimpton3b28ff02024-04-03 22:46:0525 basic_logger.log(logging.DEBUG, line)
Ben Pasteneca5ad4b2024-03-05 19:02:4726
27
Struan Shrimptona64cb192024-03-05 02:22:0028class LegacyOutputAdapter:
Struan Shrimptond98829f2024-03-12 19:41:3829 """Interprets the legacy recipe run mode output to logging
30
31 This will filter, route and in some cases reformat the output to trace levels
32 of logging. This will cause specific output (e.g. unfiltered step names) to
33 always print to std out or when -v is passed, the stdout will additionally
34 be passed to the logging stdout. Note -vv will cause PassthroughAdapter to
35 interpret results"""
36
37 SEED_STEP_TEXT = '@@@SEED_STEP@'
38 STEP_CLOSED_TEXT = '@@@STEP_CLOSED@@@'
39 ANNOTATOR_PREFIX_SUFIX = '@@@'
40 TRIGGER_STEP_PREFIX = 'test_pre_run.[trigger] '
41 TRIGGER_LINK_TEXT = '@@@STEP_LINK@task UI:'
Ben Pastene30f4b3c2024-11-22 22:57:5042 # Special sub-log names added by the UTR recipe to surface to users.
43 UTR_LOG_NAME = 'utr_log'
Struan Shrimptona64cb192024-03-05 02:22:0044
45 def __init__(self):
Struan Shrimptond98829f2024-03-12 19:41:3846 self._trigger_link_re = re.compile(r'.+@(https://.+)@@@$')
Struan Shrimptona64cb192024-03-05 02:22:0047 self._ninja_status_re = re.compile(r'\[(\d+)\/(\d+)\]')
Struan Shrimptond98829f2024-03-12 19:41:3848 self._collect_wait_re = re.compile(
49 r'.+prpc call (.+) swarming.v2.Tasks.ListTaskStates, stdin: '
50 r'(\{"task_id": .+\})$'
51 )
52 self._result_links_re = re.compile(
53 r'@@@STEP_LINK@shard (#\d+) test results@(https://[^@]+)@@@')
Ben Pastene37651fc502024-03-19 22:32:4154
Struan Shrimptond98829f2024-03-12 19:41:3855 self._current_proccess_fn = self._StepNameProcessLine
Struan Shrimpton5267e0f2024-03-28 22:22:2656 # The first match is used. This allows us to filter parent steps while still
57 # printing child steps by adding the child step name first. By default
58 # _StepNameProcessLine will be used which prints the step name and it's
59 # stdout
Struan Shrimptona64cb192024-03-05 02:22:0060 self._step_to_processors = {
Ben Pasteneca5ad4b2024-03-05 19:02:4761 'compile': self._ProcessCompileLine,
62 'reclient compile': self._ProcessCompileLine,
Struan Shrimptond98829f2024-03-12 19:41:3863 'test_pre_run.[trigger] ': self._ProcessTriggerLine,
64 'collect tasks.wait for tasks': self._ProcessCollectLine,
Ben Pastene70570982024-11-19 22:08:4765 'download compilation outputs': self._PrintOnlyStepName,
Struan Shrimptona64cb192024-03-05 02:22:0066 }
Struan Shrimpton5267e0f2024-03-28 22:22:2667 # The first match is used. This allows us to filter parent steps while still
68 # printing child steps by adding the child step name first. By default INFO
69 # will be used which prints in non-verbose mode (i.e. no -v flag)
Struan Shrimptona64cb192024-03-05 02:22:0070 self._step_to_log_level = {
Struan Shrimpton5267e0f2024-03-28 22:22:2671 'lookup_builder_gn_args': logging.DEBUG,
72 'git rev-parse': logging.DEBUG,
73 'git diff to instrument': logging.DEBUG,
74 'save paths of affected files': logging.DEBUG,
75 'preprocess for reclient.start reproxy via bootstrap': logging.INFO,
76 'preprocess for reclient': logging.DEBUG,
77 'process clang crashes': logging.DEBUG,
78 'compile confirm no-op': logging.DEBUG,
79 'postprocess for reclient': logging.DEBUG,
Struan Shrimptond98829f2024-03-12 19:41:3880 'setup_build': logging.DEBUG,
81 'get compile targets for scripts': logging.DEBUG,
82 'lookup GN args': logging.DEBUG,
83 'install infra/tools/luci/isolate': logging.DEBUG,
84 'find command lines': logging.DEBUG,
85 'test_pre_run.install infra/tools/luci/swarming': logging.DEBUG,
86 'isolate tests': logging.DEBUG,
87 'read GN args': logging.DEBUG,
88 'test_pre_run.[trigger] ': logging.INFO,
89 'test_pre_run.': logging.DEBUG,
90 'collect tasks.wait for tasks': logging.INFO,
91 'collect tasks': logging.DEBUG,
92 '$debug - all results': logging.DEBUG,
93 'Test statistics': logging.DEBUG,
94 'read gclient': logging.DEBUG,
95 'write output_properties_file': logging.DEBUG,
Struan Shrimptonca55bdad2025-01-28 21:16:4996 'prepare skylab tests.': logging.DEBUG,
Struan Shrimpton15aa41e2025-05-07 21:51:1797 'update invocation instructions': logging.DEBUG,
Struan Shrimptona64cb192024-03-05 02:22:0098 }
Struan Shrimptona64cb192024-03-05 02:22:0099 # Setup logger for printing to the same line
100 logger = logging.getLogger('single_line_logger')
101 handler = logging.StreamHandler(sys.stdout)
102 handler.terminator = ''
103 logger.addHandler(handler)
104 logger.propagate = False
Ben Pastene37651fc502024-03-19 22:32:41105
106 self._last_line = ''
Struan Shrimpton33065b92024-11-15 23:00:15107 self._last_line_teriminal_lines = 0
Ben Pastene37651fc502024-03-19 22:32:41108 self._current_log_level = logging.DEBUG
Struan Shrimptona64cb192024-03-05 02:22:00109 self._single_line_logger = logger
Struan Shrimpton33065b92024-11-15 23:00:15110 self._terminal_columns, _ = os.get_terminal_size()
Struan Shrimptond98829f2024-03-12 19:41:38111 self._current_step_name = ''
112 self._dot_count = 0
Struan Shrimptona64cb192024-03-05 02:22:00113
Ben Pastene70570982024-11-19 22:08:47114 def _PrintCurrentStepName(self, log_level):
115 logging.log(log_level, '\n[cyan]Running: %s[/]', self._current_step_name)
116
Struan Shrimptond98829f2024-03-12 19:41:38117 def _StdoutProcessLine(self, line):
Ben Pastene30f4b3c2024-11-22 22:57:50118 # Pass through any non-engine or utr-log text.
119 if line.startswith(f'@@@STEP_LOG_LINE@{self.UTR_LOG_NAME}@'):
120 # '-3' corresponds to the trailing @@@ on every sub-log line.
121 line = line[len(f'@@@STEP_LOG_LINE@{self.UTR_LOG_NAME}@'):-3]
122 if line.startswith(self.ANNOTATOR_PREFIX_SUFIX):
123 return
124 is_urlish = re.match(r'^http[s]?://\S+$', line)
125 if is_urlish:
126 logging.log(self._current_log_level, line)
127 else:
128 basic_logger.log(self._current_log_level, line)
Struan Shrimptona64cb192024-03-05 02:22:00129
Struan Shrimptond98829f2024-03-12 19:41:38130 def _StepNameProcessLine(self, line):
131 if line.startswith(self.SEED_STEP_TEXT):
132 # Always print the step name to info
Ben Pastene70570982024-11-19 22:08:47133 self._PrintCurrentStepName(self._current_log_level)
Struan Shrimptond98829f2024-03-12 19:41:38134 return
Ben Pastene2446d70a2024-07-15 21:44:16135 self._StdoutProcessLine(line)
Struan Shrimptond98829f2024-03-12 19:41:38136
Ben Pastene70570982024-11-19 22:08:47137 def _PrintOnlyStepName(self, line):
138 if line.startswith(self.SEED_STEP_TEXT):
139 self._PrintCurrentStepName(logging.INFO)
140
Struan Shrimptond98829f2024-03-12 19:41:38141 def _ProcessTriggerLine(self, line):
142 if line.startswith(self.SEED_STEP_TEXT + self.TRIGGER_STEP_PREFIX):
143 # The step names for tests don't have any identifying keywords so the
144 # result step parsers need to be installed at trigger time
145 test_name = line[len(self.SEED_STEP_TEXT +
146 self.TRIGGER_STEP_PREFIX):line.index(' (') if ' (' in
147 line else -len(self.ANNOTATOR_PREFIX_SUFIX)]
148 self._step_to_processors[test_name] = self._ProcessResult
149 self._step_to_log_level[test_name] = logging.DEBUG
150 elif line.startswith(self.TRIGGER_LINK_TEXT):
151 matches = self._trigger_link_re.match(line)
152 if matches:
153 task_name = self._current_step_name[len(self.TRIGGER_STEP_PREFIX):]
Struan Shrimpton3b28ff02024-04-03 22:46:05154 basic_logger.log(self._current_log_level,
155 f'Triggered {task_name}: ' + matches[1])
Struan Shrimptond98829f2024-03-12 19:41:38156 else:
157 self._StdoutProcessLine(line)
158
Ben Pasteneca5ad4b2024-03-05 19:02:47159 def _ProcessCompileLine(self, line):
Struan Shrimptond98829f2024-03-12 19:41:38160 if line.startswith(self.SEED_STEP_TEXT):
Ben Pastene70570982024-11-19 22:08:47161 self._PrintCurrentStepName(logging.INFO)
Struan Shrimptond98829f2024-03-12 19:41:38162 return
Struan Shrimptona64cb192024-03-05 02:22:00163 matches = self._ninja_status_re.match(line)
164 if matches:
Struan Shrimpton33065b92024-11-15 23:00:15165 # Remove the last line which might be multiple on the terminal
166 self._single_line_logger.log(self._current_log_level, '\33[2K')
167 if self._last_line_teriminal_lines > 1:
168 for _ in range(self._last_line_teriminal_lines - 1):
169 self._single_line_logger.log(self._current_log_level, '\33[A\33[2K')
170 self._single_line_logger.log(self._current_log_level, '\r' + line)
171 self._single_line_logger.handlers[0].flush()
Struan Shrimptona64cb192024-03-05 02:22:00172 return
Ben Pasteneff8e1f122024-03-22 17:03:16173 if self._last_line.startswith('['):
Struan Shrimpton3b28ff02024-04-03 22:46:05174 basic_logger.log(self._current_log_level, '')
Struan Shrimptond98829f2024-03-12 19:41:38175 self._StdoutProcessLine(line)
176
177 def _ProcessCollectLine(self, line):
178 if line.startswith(self.SEED_STEP_TEXT):
Ben Pastene70570982024-11-19 22:08:47179 self._PrintCurrentStepName(logging.INFO)
Struan Shrimptond98829f2024-03-12 19:41:38180 matches = self._collect_wait_re.match(line)
181 if matches:
182 task_ids = json.loads(matches[2])['task_id']
183 self._dot_count = (self._dot_count % 5) + 1
184 self._single_line_logger.log(
185 self._current_log_level,
186 f'\33[2K\rStill waiting on: {len(task_ids)} shard(s)' +
187 '.' * self._dot_count)
188 return
Ben Pasteneff8e1f122024-03-22 17:03:16189 if line == self.STEP_CLOSED_TEXT:
Struan Shrimptond98829f2024-03-12 19:41:38190 self._single_line_logger.log(self._current_log_level,
191 '\33[2K\rStill waiting on: 0 shard(s)...')
Struan Shrimpton3b28ff02024-04-03 22:46:05192 basic_logger.log(self._current_log_level, '')
Struan Shrimptond98829f2024-03-12 19:41:38193
194 def _ProcessResult(self, line):
195 matches = self._result_links_re.match(line)
196 if matches:
Struan Shrimpton3b28ff02024-04-03 22:46:05197 basic_logger.log(self._current_log_level,
198 'Test results for %s shard %s: %s',
199 self._current_step_name, matches[1], matches[2])
Struan Shrimptona64cb192024-03-05 02:22:00200
201 def ProcessLine(self, line):
202 # If we're in a new step see if it needs to be parsed differently
Struan Shrimptond98829f2024-03-12 19:41:38203 if line.startswith(self.SEED_STEP_TEXT):
204 self._current_step_name = line[len(self.SEED_STEP_TEXT
205 ):-len(self.ANNOTATOR_PREFIX_SUFIX)]
206 self._current_proccess_fn = self._get_processor(self._current_step_name)
207 self._current_log_level = self._get_log_level(self._current_step_name)
Struan Shrimptona64cb192024-03-05 02:22:00208 self._current_proccess_fn(line)
209 self._last_line = line
Struan Shrimpton33065b92024-11-15 23:00:15210 self._last_line_teriminal_lines = int(
211 (len(line) - 1) / self._terminal_columns) + 1
Struan Shrimpton5267e0f2024-03-28 22:22:26212 if line.startswith(self.STEP_CLOSED_TEXT):
213 # Text outside of steps will use the last processor otherwise
214 self._current_log_level = logging.DEBUG
215 _current_proccess_fn = self._StepNameProcessLine
Struan Shrimptond98829f2024-03-12 19:41:38216
217 def _get_processor(self, step_name):
218 if step_name in self._step_to_processors:
219 return self._step_to_processors[step_name]
Ben Pasteneff8e1f122024-03-22 17:03:16220 for match_name in self._step_to_processors:
221 if step_name.startswith(match_name):
222 return self._step_to_processors[match_name]
Struan Shrimptond98829f2024-03-12 19:41:38223 return self._StepNameProcessLine
224
225 def _get_log_level(self, step_name):
226 if step_name in self._step_to_log_level:
227 return self._step_to_log_level[step_name]
Ben Pasteneff8e1f122024-03-22 17:03:16228 for match_name in self._step_to_log_level:
229 if step_name.startswith(match_name):
230 return self._step_to_log_level[match_name]
Struan Shrimptond98829f2024-03-12 19:41:38231 return logging.INFO