-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Pivot toward immutable DistributedTask.WebApi::Issue instances.
#2378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3902257
87ababb
efc0a92
4eb9adc
14096a7
b70f97f
cb89be7
f3961c4
e3e4288
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ | |
| using GitHub.Runner.Sdk; | ||
| using GitHub.Runner.Worker.Container; | ||
| using GitHub.Runner.Worker.Handlers; | ||
| using GitHub.Services.Common; | ||
| using Newtonsoft.Json; | ||
| using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating; | ||
| using Pipelines = GitHub.DistributedTask.Pipelines; | ||
|
|
@@ -90,7 +91,8 @@ public interface IExecutionContext : IRunnerService | |
| void SetGitHubContext(string name, string value); | ||
| void SetOutput(string name, string value, out string reference); | ||
| void SetTimeout(TimeSpan? timeout); | ||
| void AddIssue(Issue issue, string message = null); | ||
| IReadOnlyIssue CreateIssue(IssueType issueType, string rawMessage, IssueMetadata metadata, bool writeToLog); | ||
| void AddIssue(IReadOnlyIssue issue); | ||
| void Progress(int percentage, string currentOperation = null); | ||
| void UpdateDetailTimelineRecord(TimelineRecord record); | ||
|
|
||
|
|
@@ -124,7 +126,7 @@ public sealed class ExecutionContext : RunnerService, IExecutionContext | |
|
|
||
| private readonly TimelineRecord _record = new(); | ||
| private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new(); | ||
| private readonly List<Issue> _embeddedIssueCollector; | ||
| private readonly List<IReadOnlyIssue> _embeddedIssueCollector; | ||
| private readonly object _loggerLock = new(); | ||
| private readonly object _matchersLock = new(); | ||
| private readonly ExecutionContext _parentExecutionContext; | ||
|
|
@@ -577,74 +579,77 @@ public void Progress(int percentage, string currentOperation = null) | |
| _jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record); | ||
| } | ||
|
|
||
| // This is not thread safe, the caller need to take lock before calling issue() | ||
| public void AddIssue(Issue issue, string logMessage = null) | ||
| // This is not thread safe, the caller needs to take lock before calling issue() | ||
| public IReadOnlyIssue CreateIssue(IssueType issueType, string rawMessage, IssueMetadata metadata, bool writeToLog) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This Side-effect or otherwise, the usage pattern is consistent, so there doesn't appear to be a compelling case to address this (e.g. split this functionality out into its own function). |
||
| { | ||
| ArgUtil.NotNull(issue, nameof(issue)); | ||
| string refinedMessage = PrimitiveExtensions.TrimExcess(HostContext.SecretMasker.MaskSecrets(rawMessage), _maxIssueMessageLength); | ||
|
|
||
| if (string.IsNullOrEmpty(logMessage)) | ||
| { | ||
| logMessage = issue.Message; | ||
| } | ||
| var result = new Issue() { | ||
| Type = issueType, | ||
| Message = refinedMessage, | ||
| }; | ||
|
|
||
| issue.Message = HostContext.SecretMasker.MaskSecrets(issue.Message); | ||
| if (issue.Message.Length > _maxIssueMessageLength) | ||
| if (metadata != null) | ||
| { | ||
| issue.Message = issue.Message[.._maxIssueMessageLength]; | ||
| result.Category = metadata.Category; | ||
| result.IsInfrastructureIssue = metadata.IsInfrastructureIssue; | ||
| foreach (var kvp in metadata.Data) | ||
| { | ||
| result[kvp.Key] = kvp.Value; | ||
| } | ||
| } | ||
|
|
||
| // Tracking the line number (logFileLineNumber) and step number (stepNumber) for each issue that gets created | ||
| // Actions UI from the run summary page use both values to easily link to an exact locations in logs where annotations originate from | ||
| // It's important to keep track of the step number (key:stepNumber) and the line number (key:logFileLineNumber) of every issue that gets logged. | ||
| // Actions UI from the run summary page use both values to easily link to an exact locations in logs where annotations originate from. | ||
| if (_record.Order != null) | ||
| { | ||
| issue.Data["stepNumber"] = _record.Order.ToString(); | ||
| result["stepNumber"] = _record.Order.ToString(); | ||
| } | ||
|
|
||
| if (issue.Type == IssueType.Error) | ||
| string wellKnownTag = null; | ||
| Int32? previousCountForIssueType = null; | ||
| switch (issueType) | ||
| { | ||
| if (!string.IsNullOrEmpty(logMessage)) | ||
| { | ||
| long logLineNumber = Write(WellKnownTags.Error, logMessage); | ||
| issue.Data["logFileLineNumber"] = logLineNumber.ToString(); | ||
| } | ||
|
|
||
| if (_record.ErrorCount < _maxIssueCount) | ||
| { | ||
| _record.Issues.Add(issue); | ||
| } | ||
|
|
||
| _record.ErrorCount++; | ||
| case IssueType.Error: | ||
| wellKnownTag = WellKnownTags.Error; | ||
| previousCountForIssueType = _record.ErrorCount++; | ||
| break; | ||
| case IssueType.Warning: | ||
| wellKnownTag = WellKnownTags.Warning; | ||
| previousCountForIssueType = _record.WarningCount++; | ||
| break; | ||
| case IssueType.Notice: | ||
| wellKnownTag = WellKnownTags.Notice; | ||
| previousCountForIssueType = _record.NoticeCount++; | ||
| break; | ||
| } | ||
| else if (issue.Type == IssueType.Warning) | ||
|
|
||
| if (!string.IsNullOrEmpty(wellKnownTag)) | ||
| { | ||
| if (!string.IsNullOrEmpty(logMessage)) | ||
| if (writeToLog) | ||
| { | ||
| long logLineNumber = Write(WellKnownTags.Warning, logMessage); | ||
| issue.Data["logFileLineNumber"] = logLineNumber.ToString(); | ||
| //Note that ::Write() has it's own secret masking logic | ||
| string logText = metadata?.LogMessageOverride ?? result.Message; | ||
| if (!string.IsNullOrEmpty(logText)) | ||
| { | ||
| long logLineNumber = Write(wellKnownTag, logText); | ||
| result["logFileLineNumber"] = logLineNumber.ToString(); | ||
| } | ||
| } | ||
|
|
||
| if (_record.WarningCount < _maxIssueCount) | ||
| if (previousCountForIssueType.GetValueOrDefault(0) < _maxIssueCount) | ||
| { | ||
| _record.Issues.Add(issue); | ||
| _record.Issues.Add(result); | ||
| } | ||
|
|
||
| _record.WarningCount++; | ||
| } | ||
| else if (issue.Type == IssueType.Notice) | ||
| { | ||
| if (!string.IsNullOrEmpty(logMessage)) | ||
| { | ||
| long logLineNumber = Write(WellKnownTags.Notice, logMessage); | ||
| issue.Data["logFileLineNumber"] = logLineNumber.ToString(); | ||
| } | ||
|
|
||
| if (_record.NoticeCount < _maxIssueCount) | ||
| { | ||
| _record.Issues.Add(issue); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| _record.NoticeCount++; | ||
| } | ||
|
|
||
| // This is not thread safe, the caller needs to take lock before calling issue() | ||
| public void AddIssue(IReadOnlyIssue issue) | ||
| { | ||
| ArgUtil.NotNull(issue, nameof(issue)); | ||
|
|
||
| // Embedded ExecutionContexts (a.k.a. Composite actions) should never upload a timeline record to the server. | ||
| // Instead, we store processed issues on a shared (psuedo-inherited) list (belonging to the closest | ||
|
|
@@ -1017,16 +1022,7 @@ public void PublishStepTelemetry() | |
| if ((issue.Type == IssueType.Error || issue.Type == IssueType.Warning) && | ||
| !string.IsNullOrEmpty(issue.Message)) | ||
| { | ||
| string issueTelemetry; | ||
| if (issue.Message.Length > _maxIssueMessageLengthInTelemetry) | ||
| { | ||
| issueTelemetry = $"{issue.Message[.._maxIssueMessageLengthInTelemetry]}"; | ||
| } | ||
| else | ||
| { | ||
| issueTelemetry = issue.Message; | ||
| } | ||
|
|
||
| string issueTelemetry = PrimitiveExtensions.TrimExcess(issue.Message, _maxIssueMessageLengthInTelemetry); | ||
| StepTelemetry.ErrorMessages.Add(issueTelemetry); | ||
|
|
||
| // Only send over the first 3 issues to avoid sending too much data. | ||
|
|
@@ -1200,19 +1196,23 @@ public static void Error(this IExecutionContext context, Exception ex) | |
| // Do not add a format string overload. See comment on ExecutionContext.Write(). | ||
| public static void Error(this IExecutionContext context, string message) | ||
| { | ||
| context.AddIssue(new Issue() { Type = IssueType.Error, Message = message }); | ||
| var issue = context.CreateIssue(IssueType.Error, message, null, true); | ||
| context.AddIssue(issue); | ||
| } | ||
|
|
||
| // Do not add a format string overload. See comment on ExecutionContext.Write(). | ||
| public static void InfrastructureError(this IExecutionContext context, string message) | ||
| { | ||
| context.AddIssue(new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true }); | ||
| var metadata = new IssueMetadata(null, true, null, Enumerable.Empty<KeyValuePair<string, string>>()); | ||
| var issue = context.CreateIssue(IssueType.Error, message, metadata, true); | ||
| context.AddIssue(issue); | ||
| } | ||
|
|
||
| // Do not add a format string overload. See comment on ExecutionContext.Write(). | ||
| public static void Warning(this IExecutionContext context, string message) | ||
| { | ||
| context.AddIssue(new Issue() { Type = IssueType.Warning, Message = message }); | ||
| var issue = context.CreateIssue(IssueType.Warning, message, null, true); | ||
| context.AddIssue(issue); | ||
| } | ||
|
|
||
| // Do not add a format string overload. See comment on ExecutionContext.Write(). | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I defined
IssueMetadataas a data bucket to pass toCreateIssueIt holds the seldom-used fields of anIssue. It was easier than polluting theCreateIssuefunction signature with those seldom-used fields.You can pass a
nullIssueMetadatatoCreateIssueif you don't have any of those seldom-used fields to send.