public async Task <List <SearchBuildLogsResult> > SearchBuildLogsAsync( IEnumerable <BuildResultInfo> builds, SearchBuildLogsRequest request, Action <Exception>?onError = null) { if (request.Text is null) { throw new ArgumentException("Need text to search for", nameof(request)); } var nameRegex = CreateSearchRegex(request.LogName); var textRegex = CreateSearchRegex(request.Text); var list = new List <(BuildResultInfo BuildInfo, TimelineTree Tree, TimelineRecord TimelineRecord, BuildLogReference BuildLogReference)>(); foreach (var buildInfo in builds) { try { var timeline = await Server.GetTimelineAsync(buildInfo.Project, buildInfo.Number).ConfigureAwait(false); if (timeline is object) { var tree = TimelineTree.Create(timeline); var records = timeline.Records.Where(r => nameRegex is null || nameRegex.IsMatch(r.Name)); foreach (var record in records) { if (record.Log is { } log) { list.Add((buildInfo, tree, record, log)); } } } } catch (Exception ex) { onError?.Invoke(ex); } } if (list.Count > request.Limit) { onError?.Invoke(new Exception($"Limiting the {list.Count} logs to first {request.Limit}")); list = list.Take(request.Limit).ToList(); } var resultTasks = list .AsParallel() .Select(async x => { var match = await SearchFileForFirstMatchAsync(x.BuildLogReference.Url, textRegex, onError).ConfigureAwait(false); var line = match is object && match.Success ? match.Value : null; return(Query: x, Line: line); }); var results = new List <SearchBuildLogsResult>(); foreach (var task in resultTasks) { try { var result = await task.ConfigureAwait(false); if (result.Line is object) { string jobName = ""; if (result.Query.Tree.TryGetJob(result.Query.TimelineRecord, out var jobRecord)) { jobName = jobRecord.Name; } results.Add(new SearchBuildLogsResult(result.Query.BuildInfo, jobName, result.Query.TimelineRecord, result.Query.BuildLogReference, result.Line)); } } catch (Exception ex) { onError?.Invoke(ex); } } return(results); }
public async Task OnGet() { if (User.GetVsoIdentity() is { } identity) { AzureDevOpsEmail = identity.FindFirst(ClaimTypes.Email)?.Value; } if (string.IsNullOrEmpty(BuildQuery)) { BuildQuery = new SearchBuildsRequest() { Definition = "roslyn-ci", Started = new DateRequestValue(dayQuery: 3), }.GetQueryString(); return; } if (!SearchBuildsRequest.TryCreate(BuildQuery, out var buildsRequest, out var errorMessage) || !SearchBuildLogsRequest.TryCreate(LogQuery ?? "", out var logsRequest, out errorMessage)) { ErrorMessage = errorMessage; return; } if (string.IsNullOrEmpty(logsRequest.Text)) { ErrorMessage = @"Must specify text to search for 'text: ""StackOverflowException""'"; return; } ErrorMessage = null; List <BuildResultInfo> buildInfos; try { buildInfos = await buildsRequest .Filter(TriageContextUtil.Context.ModelBuilds) .OrderByDescending(x => x.BuildNumber) .ToBuildResultInfoListAsync(); } catch (SqlException ex) when(ex.IsTimeoutViolation()) { ErrorMessage = "Timeout fetching data from server"; return; } BuildCount = buildInfos.Count; var queryUtil = await DotNetQueryUtilFactory.CreateDotNetQueryUtilForUserAsync(); var errorBuilder = new StringBuilder(); var results = await queryUtil.SearchBuildLogsAsync(buildInfos, logsRequest, ex => errorBuilder.AppendLine(ex.Message)); foreach (var result in results) { BuildLogs.Add(new BuildLogData() { BuildNumber = result.BuildInfo.Number, Line = result.Line, JobName = result.JobName, BuildLogUri = result.BuildLogReference.Url, }); } if (errorBuilder.Length > 0) { ErrorMessage = errorBuilder.ToString(); } }